| 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 | ||
| d5c694 | 60 | def sid_compare(sid_no, sid_mask): |
| SP | 61 | for s,es in zip(sid_mask,sid_no): |
| 62 | if s!='x' and s!=es: | |
| 63 | return False | |
| 64 | return True | |
| 65 | ||
| 66 | ||
| 9efc18 | 67 | |
| 762a5e | 68 | def segment_by_contours(image, sorted_ctrs, classifier): |
| SP | 69 | sid_no = "" |
| 70 | for i, ctr in enumerate(sorted_ctrs): | |
| 71 | # Get bounding box | |
| 72 | x, y, w, h = cv2.boundingRect(ctr) | |
| 73 | # Getting ROI | |
| 74 | if w < h / 2: | |
| 75 | sid_no = sid_no + "1" | |
| 76 | continue | |
| 77 | roi = image[y : y + h, x : x + w] | |
| 78 | roi = img_as_ubyte(roi < 128) | |
| 79 | roi = cv2.resize(roi, (32, 32)) | |
| 80 | ||
| 81 | # cv2.rectangle(image,(x,y),( x + w, y + h ),(0,255,0),2) | |
| 82 | cv2.imwrite("sid_no_{}.png".format(i), roi) | |
| 83 | sid_no = sid_no + str(classifier.predict(roi.reshape(1, -1) / 255.0)[0]) | |
| 84 | return sid_no | |
| 85 | ||
| 86 | ||
| d5c694 | 87 | def segment_by_sid_len(image, original_image, sid_mask, classifier): |
| 5cb7c1 | 88 | sid_no = "" |
| SP | 89 | sid_len = len(sid_mask) |
| 90 | if sid_mask[0] == "1": | |
| 91 | move_left = 45 | |
| 92 | elif sid_mask[0] == "x": | |
| 93 | move_left = 55 | |
| 94 | else: | |
| 95 | move_left = 0 | |
| d5c694 | 96 | # Remove noise |
| SP | 97 | image2 = cv2.morphologyEx(original_image, cv2.MORPH_OPEN, kernel(2, 2), iterations=7) |
| 5cb7c1 | 98 | # find biggest block of pixels |
| d5c694 | 99 | image1 = cv2.morphologyEx(image2, cv2.MORPH_DILATE, kernel(5, 25), iterations=4) |
| SP | 100 | image1=img_as_ubyte(image1>50) |
| 5cb7c1 | 101 | cv2.imwrite("sidblock1.png", image1) |
| ac766e | 102 | im2, ctrs, hier = cv2.findContours( |
| SP | 103 | image1.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE |
| 104 | ) | |
| 5cb7c1 | 105 | sorted_ctrs = sorted( |
| SP | 106 | ctrs, key=lambda ctr: cv2.contourArea(ctr) |
| 107 | ) # get bigges contour | |
| ac766e | 108 | x, y, w, h = cv2.boundingRect(sorted_ctrs[-1]) |
| d5c694 | 109 | image = image[y : y + h, x + 25 - move_left : x + w - 40] #+25,-25 |
| 5cb7c1 | 110 | cv2.imwrite("sidblock2.png", image) |
| ac766e | 111 | imgHeight, imgWidth = image.shape[0:2] |
| 5cb7c1 | 112 | numWidth = int(imgWidth / (sid_len)) |
| SP | 113 | for i in range(0, sid_len): |
| 114 | num = image[:, i * numWidth : (i + 1) * numWidth] | |
| ac766e | 115 | num = img_as_ubyte(num < 128) |
| SP | 116 | num = cv2.resize(num, (32, 32)) |
| 117 | ||
| 118 | # cv2.rectangle(image,(x,y),( x + w, y + h ),(0,255,0),2) | |
| 119 | cv2.imwrite("sid_no_{}.png".format(i), num) | |
| 120 | sid_no = sid_no + str(classifier.predict(num.reshape(1, -1) / 255.0)[0]) | |
| 121 | return sid_no | |
| 122 | ||
| d5c694 | 123 | def segment_by_7segments(image,original_image,sid_mask,classifier): |
| SP | 124 | block_image = cv2.morphologyEx(original_image, cv2.MORPH_CLOSE, kernel(2, 2), iterations=10) |
| 125 | block_image =img_as_ubyte(block_image<50) | |
| 126 | cv2.imwrite("sid_3rd1.png", block_image) | |
| 127 | template = cv2.imread("template-8.png", 0) | |
| 128 | w, h = template.shape[::-1] | |
| 129 | res = cv2.matchTemplate(block_image, template, cv2.TM_CCOEFF_NORMED) | |
| 130 | loc = np.where(res >= 0.75) | |
| 131 | cimg = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR) | |
| 132 | loc_filtered_x=[] | |
| 133 | loc_filtered_y=[] | |
| 134 | for pt in zip(*loc[::-1]): | |
| 135 | pt=(pt[0]-10,pt[1]-10) | |
| 136 | loc_filtered_y.append(pt[1]) | |
| 137 | loc_filtered_x.append(pt[0]) | |
| 138 | # points.append(pt) | |
| 139 | #filter points | |
| 140 | if(len(loc_filtered_x)==0): | |
| 141 | return "" | |
| 142 | loc_filtered_x, loc_filtered_y = zip( | |
| 143 | *sorted(zip(loc_filtered_x, loc_filtered_y)) | |
| 144 | ) | |
| 145 | a = np.diff(loc_filtered_x) > int(w/2) | |
| 146 | a = np.append(a, True) | |
| 147 | loc_filtered_x = np.array(loc_filtered_x) | |
| 148 | loc_filtered_y = np.array(loc_filtered_y) | |
| 149 | points = [loc_filtered_y[a], loc_filtered_x[a]] | |
| 150 | for pt in zip(*points[::-1]): | |
| 151 | cv2.rectangle(cimg, pt, (pt[0] + w, pt[1] + h), (0, 255, 255), 2) | |
| 152 | cv2.imwrite("sid_3rd2.png", cimg) | |
| 153 | ||
| 154 | sid_no="" | |
| 155 | for i,pt in enumerate(zip(*points[::-1])): | |
| 156 | num=image[pt[1]:pt[1] + h, pt[0]:pt[0]+w] | |
| 157 | #cv2.imwrite("sid_3no_{}.png".format(i), num) | |
| 158 | num = img_as_ubyte(num < 128) | |
| 159 | try: | |
| 160 | num = cv2.resize(num, (32, 32)) | |
| 161 | except: | |
| 162 | return "" | |
| 163 | cv2.imwrite("sid_3no_{}.png".format(i), num) | |
| 164 | sid_no = sid_no + str(classifier.predict(num.reshape(1, -1) / 255.0)[0]) | |
| 165 | ||
| 166 | return sid_no | |
| ac766e | 167 | |
| 762a5e | 168 | def getSID(image, classifier, sid_mask): |
| 5cb7c1 | 169 | sid_warn = [] |
| d5c694 | 170 | sid_err=[] |
| 762a5e | 171 | image = 255 - image |
| d5c694 | 172 | image_original=image.copy() |
| 762a5e | 173 | image = img_as_ubyte(image > 100) |
| 9efc18 | 174 | cv2.imwrite("enSID0.png", image) |
| SP | 175 | # Remove noise |
| d5c694 | 176 | image = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel(2, 2), iterations=3) |
| 9efc18 | 177 | # Closing. Connect non connected parts |
| 02e0f7 | 178 | image = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel(5, 3), iterations=4) |
| 9efc18 | 179 | # Again noise removal after closing |
| 02e0f7 | 180 | |
| 5cb7c1 | 181 | # image = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel(8, 8), iterations=1) |
| SP | 182 | # don't do too much noise removal. |
| ac766e | 183 | image = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel(3, 3), iterations=1) |
| SP | 184 | |
| 9efc18 | 185 | # Skeletonization |
| 762a5e | 186 | image = img_as_ubyte(morphology.thin(image > 128)) |
| SP | 187 | cv2.imwrite("enSID1.png", image) |
| 9efc18 | 188 | # Stub removal (might not be necessary if thinning instead of skeletonize is used above |
| SP | 189 | # Making lines stronger |
| 190 | image = cv2.morphologyEx(image, cv2.MORPH_DILATE, kernel(5, 5), iterations=1) | |
| 191 | ||
| 192 | image = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel(10, 10)) | |
| 193 | # Thining again | |
| 762a5e | 194 | image = img_as_ubyte(morphology.skeletonize(image > 0.5)) |
| 9efc18 | 195 | image = cv2.morphologyEx(image, cv2.MORPH_DILATE, kernel(10, 10)) |
| 5cb7c1 | 196 | cv2.imwrite("enhancedSID.png", image) |
| 762a5e | 197 | im2, ctrs, hier = cv2.findContours( |
| SP | 198 | image.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE |
| 199 | ) | |
| 02e0f7 | 200 | sorted_ctrs = sorted(ctrs, key=lambda ctr: cv2.boundingRect(ctr)[0]) |
| SP | 201 | |
| 5cb7c1 | 202 | print(len(sid_mask), len(sorted_ctrs)) |
| SP | 203 | sid_no = segment_by_contours( |
| 204 | image, sorted_ctrs[1:], classifier | |
| 205 | ) # we remove largest contour that surrounds whole image | |
| 02e0f7 | 206 | print(sid_no) |
| d5c694 | 207 | if len(sid_no) != len(sid_mask) or not sid_compare(sid_no,sid_mask): |
| 5cb7c1 | 208 | sid_warn.append("Trying second SID algorithm.") |
| d5c694 | 209 | sid_no = segment_by_7segments(image, image_original, sid_mask, classifier) |
| SP | 210 | print(sid_no) |
| 211 | if(len(sid_no))!=len(sid_mask): | |
| 212 | sid_no = segment_by_sid_len(image, image_original, sid_mask, classifier) | |
| 213 | sid_warn.append("Trying third SID algorithm.") | |
| 214 | ||
| 215 | ||
| 216 | if not sid_compare(sid_no, sid_mask): | |
| 217 | sid_err=['Wrong SID!'] | |
| 218 | ||
| 219 | return (sid_no, sid_err, sid_warn) | |