| commit | author | age | ||
| 9efc18 | 1 | import cv2 |
| SP | 2 | import numpy as np |
| 762a5e | 3 | from skimage import morphology, img_as_ubyte |
| 02e0f7 | 4 | |
| 9efc18 | 5 | |
| SP | 6 | """ |
| 7 | (1) The text is an array of chars (in row-major order) where | |
| 8 | * each char can be one of the following: | |
| 9 | * 'x': hit | |
| 10 | * 'o': miss | |
| 11 | * ' ': don't-care | |
| 12 | * (2) When the origin falls on a hit or miss, use an upper case | |
| 13 | * char (e.g., 'X' or 'O') to indicate it. When the origin | |
| 14 | * falls on a don't-care, indicate this with a 'C'. | |
| 15 | * The string must have exactly one origin specified. | |
| 16 | * (3) The advantage of this method is that the text can be input | |
| 17 | * in a format that shows the 2D layout of the Sel; e.g., | |
| 18 | ||
| 19 | ||
| 20 | :::: AND :::: | |
| 21 | ||
| 22 | ||
| 23 | (10) The sequence string is formatted as follows: | |
| 24 | * ~ An arbitrary number of operations, each separated | |
| 25 | * by a '+' character. White space is ignored. | |
| 26 | * ~ Each operation begins with a case-independent character | |
| 27 | * specifying the operation: | |
| 28 | * d or D (dilation) | |
| 29 | * e or E (erosion) | |
| 30 | * o or O (opening) | |
| 31 | * c or C (closing) | |
| 32 | * r or R (rank binary reduction) | |
| 33 | * x or X (replicative binary expansion) | |
| 34 | * b or B (add a border of 0 pixels of this size) | |
| 35 | * ~ The args to the morphological operations are bricks of hits, | |
| 36 | * and are formatted as a.b, where a and b are horizontal and | |
| 37 | * vertical dimensions, rsp. | |
| 38 | * ~ The args to the reduction are a sequence of up to 4 integers, | |
| 39 | * each from 1 to 4. | |
| 40 | * ~ The arg to the expansion is a power of two, in the set | |
| 41 | * {2, 4, 8, 16}. | |
| 42 | * (11) An example valid sequence is: | |
| 43 | * "b32 + o1.3 + C3.1 + r23 + e2.2 + D3.2 + X4" | |
| 44 | * In this example, the following operation sequence is carried out: | |
| 45 | * * b32: Add a 32 pixel border around the input image | |
| 46 | * * o1.3: Opening with vert sel of length 3 (e.g., 1 x 3) | |
| 47 | * * C3.1: Closing with horiz sel of length 3 (e.g., 3 x 1) | |
| 48 | * * r23: Two successive 2x2 reductions with rank 2 in the first | |
| 49 | * and rank 3 in the second. The result is a 4x reduced pix. | |
| 50 | * * e2.2: Erosion with a 2x2 sel (origin will be at x,y: 0,0) | |
| 51 | * * d3.2: Dilation with a 3x2 sel (origin will be at x,y: 1,0) | |
| 52 | * * X4: 4x replicative expansion, back to original resolution | |
| 53 | ||
| 54 | """ | |
| 55 | ||
| 56 | ||
| 57 | def kernel(x, y): | |
| 58 | return np.ones((x, y), np.uint8) | |
| 59 | ||
| 60 | ||
| 762a5e | 61 | def segment_by_contours(image, sorted_ctrs, classifier): |
| SP | 62 | sid_no = "" |
| 63 | for i, ctr in enumerate(sorted_ctrs): | |
| 64 | # Get bounding box | |
| 65 | x, y, w, h = cv2.boundingRect(ctr) | |
| 66 | # Getting ROI | |
| 67 | if w < h / 2: | |
| 68 | sid_no = sid_no + "1" | |
| 69 | continue | |
| 70 | roi = image[y : y + h, x : x + w] | |
| 71 | roi = img_as_ubyte(roi < 128) | |
| 72 | roi = cv2.resize(roi, (32, 32)) | |
| 73 | ||
| 74 | # cv2.rectangle(image,(x,y),( x + w, y + h ),(0,255,0),2) | |
| 75 | cv2.imwrite("sid_no_{}.png".format(i), roi) | |
| 76 | sid_no = sid_no + str(classifier.predict(roi.reshape(1, -1) / 255.0)[0]) | |
| 77 | return sid_no | |
| 78 | ||
| 79 | ||
| 5cb7c1 | 80 | def segment_by_sid_len(image, sid_mask, classifier): |
| SP | 81 | sid_no = "" |
| 82 | sid_len = len(sid_mask) | |
| 83 | if sid_mask[0] == "1": | |
| 84 | move_left = 45 | |
| 85 | elif sid_mask[0] == "x": | |
| 86 | move_left = 55 | |
| 87 | else: | |
| 88 | move_left = 0 | |
| 89 | # find biggest block of pixels | |
| ac766e | 90 | |
| e2fa6a | 91 | image1 = cv2.morphologyEx(image, cv2.MORPH_DILATE, kernel(5, 25), iterations=4) |
| 5cb7c1 | 92 | cv2.imwrite("sidblock1.png", image1) |
| ac766e | 93 | im2, ctrs, hier = cv2.findContours( |
| SP | 94 | image1.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE |
| 95 | ) | |
| 5cb7c1 | 96 | sorted_ctrs = sorted( |
| SP | 97 | ctrs, key=lambda ctr: cv2.contourArea(ctr) |
| 98 | ) # get bigges contour | |
| ac766e | 99 | x, y, w, h = cv2.boundingRect(sorted_ctrs[-1]) |
| 5cb7c1 | 100 | image = image[y : y + h, x + 25 - move_left : x + w - 25] |
| SP | 101 | cv2.imwrite("sidblock2.png", image) |
| ac766e | 102 | imgHeight, imgWidth = image.shape[0:2] |
| 5cb7c1 | 103 | numWidth = int(imgWidth / (sid_len)) |
| SP | 104 | for i in range(0, sid_len): |
| 105 | num = image[:, i * numWidth : (i + 1) * numWidth] | |
| ac766e | 106 | num = img_as_ubyte(num < 128) |
| SP | 107 | num = cv2.resize(num, (32, 32)) |
| 108 | ||
| 109 | # cv2.rectangle(image,(x,y),( x + w, y + h ),(0,255,0),2) | |
| 110 | cv2.imwrite("sid_no_{}.png".format(i), num) | |
| 111 | sid_no = sid_no + str(classifier.predict(num.reshape(1, -1) / 255.0)[0]) | |
| 112 | return sid_no | |
| 113 | ||
| 114 | ||
| 762a5e | 115 | def getSID(image, classifier, sid_mask): |
| 5cb7c1 | 116 | sid_warn = [] |
| 762a5e | 117 | image = 255 - image |
| SP | 118 | image = img_as_ubyte(image > 100) |
| 9efc18 | 119 | cv2.imwrite("enSID0.png", image) |
| SP | 120 | # Remove noise |
| 762a5e | 121 | image = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel(2, 2), iterations=1) |
| 9efc18 | 122 | # Closing. Connect non connected parts |
| 02e0f7 | 123 | image = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel(5, 3), iterations=4) |
| 9efc18 | 124 | # Again noise removal after closing |
| 02e0f7 | 125 | |
| 5cb7c1 | 126 | # image = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel(8, 8), iterations=1) |
| SP | 127 | # don't do too much noise removal. |
| ac766e | 128 | image = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel(3, 3), iterations=1) |
| SP | 129 | |
| 9efc18 | 130 | # Skeletonization |
| 762a5e | 131 | image = img_as_ubyte(morphology.thin(image > 128)) |
| SP | 132 | cv2.imwrite("enSID1.png", image) |
| 9efc18 | 133 | # Stub removal (might not be necessary if thinning instead of skeletonize is used above |
| SP | 134 | # Making lines stronger |
| 135 | image = cv2.morphologyEx(image, cv2.MORPH_DILATE, kernel(5, 5), iterations=1) | |
| 136 | ||
| 137 | image = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel(10, 10)) | |
| 138 | # Thining again | |
| 762a5e | 139 | image = img_as_ubyte(morphology.skeletonize(image > 0.5)) |
| 9efc18 | 140 | image = cv2.morphologyEx(image, cv2.MORPH_DILATE, kernel(10, 10)) |
| 5cb7c1 | 141 | cv2.imwrite("enhancedSID.png", image) |
| 762a5e | 142 | im2, ctrs, hier = cv2.findContours( |
| SP | 143 | image.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE |
| 144 | ) | |
| 02e0f7 | 145 | sorted_ctrs = sorted(ctrs, key=lambda ctr: cv2.boundingRect(ctr)[0]) |
| SP | 146 | |
| 762a5e | 147 | sid_no = "" |
| 5cb7c1 | 148 | print(len(sid_mask), len(sorted_ctrs)) |
| SP | 149 | sid_no = segment_by_contours( |
| 150 | image, sorted_ctrs[1:], classifier | |
| 151 | ) # we remove largest contour that surrounds whole image | |
| 02e0f7 | 152 | print(sid_no) |
| 5cb7c1 | 153 | if len(sid_no) != len(sid_mask): |
| SP | 154 | #print("Ooops have to find another way") |
| 155 | sid_warn.append("Trying second SID algorithm.") | |
| 156 | sid_no = segment_by_sid_len(image, sid_mask, classifier) | |
| 157 | return (sid_no, [], sid_warn) | |