From fe2c1025b102bbf20c9afbc29eedf7a5f069410c Mon Sep 17 00:00:00 2001 From: Samo Penic <samo.penic@gmail.com> Date: Sat, 17 Nov 2018 14:11:17 +0000 Subject: [PATCH] Readme requirements. --- sid_process.py | 230 +++++++++++++++++++++++++++++---------------------------- 1 files changed, 118 insertions(+), 112 deletions(-) diff --git a/sid_process.py b/sid_process.py index 1f93d3c..f21fafb 100644 --- a/sid_process.py +++ b/sid_process.py @@ -3,70 +3,68 @@ from skimage import morphology, img_as_ubyte -""" - (1) The text is an array of chars (in row-major order) where - * each char can be one of the following: - * 'x': hit - * 'o': miss - * ' ': don't-care - * (2) When the origin falls on a hit or miss, use an upper case - * char (e.g., 'X' or 'O') to indicate it. When the origin - * falls on a don't-care, indicate this with a 'C'. - * The string must have exactly one origin specified. - * (3) The advantage of this method is that the text can be input - * in a format that shows the 2D layout of the Sel; e.g., - - - :::: AND :::: - - - (10) The sequence string is formatted as follows: - * ~ An arbitrary number of operations, each separated - * by a '+' character. White space is ignored. - * ~ Each operation begins with a case-independent character - * specifying the operation: - * d or D (dilation) - * e or E (erosion) - * o or O (opening) - * c or C (closing) - * r or R (rank binary reduction) - * x or X (replicative binary expansion) - * b or B (add a border of 0 pixels of this size) - * ~ The args to the morphological operations are bricks of hits, - * and are formatted as a.b, where a and b are horizontal and - * vertical dimensions, rsp. - * ~ The args to the reduction are a sequence of up to 4 integers, - * each from 1 to 4. - * ~ The arg to the expansion is a power of two, in the set - * {2, 4, 8, 16}. - * (11) An example valid sequence is: - * "b32 + o1.3 + C3.1 + r23 + e2.2 + D3.2 + X4" - * In this example, the following operation sequence is carried out: - * * b32: Add a 32 pixel border around the input image - * * o1.3: Opening with vert sel of length 3 (e.g., 1 x 3) - * * C3.1: Closing with horiz sel of length 3 (e.g., 3 x 1) - * * r23: Two successive 2x2 reductions with rank 2 in the first - * and rank 3 in the second. The result is a 4x reduced pix. - * * e2.2: Erosion with a 2x2 sel (origin will be at x,y: 0,0) - * * d3.2: Dilation with a 3x2 sel (origin will be at x,y: 1,0) - * * X4: 4x replicative expansion, back to original resolution - -""" - - def kernel(x, y): + """ + Function greates square kernel of size x and y + """ return np.ones((x, y), np.uint8) + +def find_biggest_blob(image, original_image,sid_mask): + if sid_mask[0] == "1": + move_left = 45 + elif sid_mask[0] == "x": + move_left = 55 + else: + move_left = 0 + # Remove noise + image2 = cv2.morphologyEx( + original_image, cv2.MORPH_OPEN, kernel(2, 2), iterations=7 + ) + # find biggest block of pixels + image1 = cv2.morphologyEx(image2, cv2.MORPH_DILATE, kernel(5, 25), iterations=4) + image1 = img_as_ubyte(image1 > 50) + cv2.imwrite("sidblock1.png", image1) + im2, ctrs, hier = cv2.findContours( + image1.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE + ) + sorted_ctrs = sorted( + ctrs, key=lambda ctr: cv2.contourArea(ctr) + ) # get bigges contour + x, y, w, h = cv2.boundingRect(sorted_ctrs[-1]) + image = image[y : y + h, x + 25 - move_left : x + w - 40] # +25,-25 + return image + def sid_compare(sid_no, sid_mask): - for s,es in zip(sid_mask,sid_no): - if s!='x' and s!=es: + """ + Function compares student id number with student id mask if the recognised number is valid according to the mask + :param sid_no: + :param sid_mask: + :return: True if they match, else False + """ + for s, es in zip(sid_mask, sid_no): + if s != "x" and s != es: return False return True +def segment_by_contours(image, original_image, classifier,sid_mask): + """ + First algorithm. it segments numerals with contours. It works with numbers where individual numerals does not touch. + :param image: + :param original_image: + :param classifier: + :return: student id as a string + """ -def segment_by_contours(image, sorted_ctrs, classifier): sid_no = "" + image=find_biggest_blob(image,original_image,sid_mask) + cv2.imwrite("sid_contour1.png",image) + im2, ctrs, hier = cv2.findContours( + image.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE + ) + sorted_ctrs = sorted(ctrs, key=lambda ctr: cv2.boundingRect(ctr)[0]) + for i, ctr in enumerate(sorted_ctrs): # Get bounding box x, y, w, h = cv2.boundingRect(ctr) @@ -85,28 +83,19 @@ def segment_by_sid_len(image, original_image, sid_mask, classifier): + """ + Third algorithm. It trys to get biggest "blob" in the image and then it cuts it into individual numbers by force. + It has some problems with finding individual numbers, so some tweaking must be done! + + :param image: + :param original_image: + :param sid_mask: + :param classifier: + :return: student id as a string + """ sid_no = "" sid_len = len(sid_mask) - if sid_mask[0] == "1": - move_left = 45 - elif sid_mask[0] == "x": - move_left = 55 - else: - move_left = 0 - # Remove noise - image2 = cv2.morphologyEx(original_image, cv2.MORPH_OPEN, kernel(2, 2), iterations=7) - # find biggest block of pixels - image1 = cv2.morphologyEx(image2, cv2.MORPH_DILATE, kernel(5, 25), iterations=4) - image1=img_as_ubyte(image1>50) - cv2.imwrite("sidblock1.png", image1) - im2, ctrs, hier = cv2.findContours( - image1.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE - ) - sorted_ctrs = sorted( - ctrs, key=lambda ctr: cv2.contourArea(ctr) - ) # get bigges contour - x, y, w, h = cv2.boundingRect(sorted_ctrs[-1]) - image = image[y : y + h, x + 25 - move_left : x + w - 40] #+25,-25 + image=find_biggest_blob(image,original_image,sid_mask) cv2.imwrite("sidblock2.png", image) imgHeight, imgWidth = image.shape[0:2] numWidth = int(imgWidth / (sid_len)) @@ -114,35 +103,44 @@ num = image[:, i * numWidth : (i + 1) * numWidth] num = img_as_ubyte(num < 128) num = cv2.resize(num, (32, 32)) - - # cv2.rectangle(image,(x,y),( x + w, y + h ),(0,255,0),2) cv2.imwrite("sid_no_{}.png".format(i), num) sid_no = sid_no + str(classifier.predict(num.reshape(1, -1) / 255.0)[0]) return sid_no -def segment_by_7segments(image,original_image,sid_mask,classifier): - block_image = cv2.morphologyEx(original_image, cv2.MORPH_CLOSE, kernel(2, 2), iterations=10) - block_image =img_as_ubyte(block_image<50) + +def segment_by_7segments(image, original_image, sid_mask, classifier): + """ + Second attempt. It dilates the image to get all 7 segments wisible as 8888888 then it does pattern matching of 8 with + pattern image. It works if the scaned gray level is high enough. + + :param image: + :param original_image: + :param sid_mask: + :param classifier: + :return: student id number as a string + """ + block_image = cv2.morphologyEx( + original_image, cv2.MORPH_CLOSE, kernel(2, 2), iterations=10 + ) + block_image = img_as_ubyte(block_image < 50) cv2.imwrite("sid_3rd1.png", block_image) template = cv2.imread("template-8.png", 0) w, h = template.shape[::-1] res = cv2.matchTemplate(block_image, template, cv2.TM_CCOEFF_NORMED) loc = np.where(res >= 0.75) cimg = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR) - loc_filtered_x=[] - loc_filtered_y=[] + loc_filtered_x = [] + loc_filtered_y = [] for pt in zip(*loc[::-1]): - pt=(pt[0]-10,pt[1]-10) + pt = (pt[0] - 10, pt[1] - 10) loc_filtered_y.append(pt[1]) loc_filtered_x.append(pt[0]) -# points.append(pt) - #filter points - if(len(loc_filtered_x)==0): + # points.append(pt) + # filter points + if len(loc_filtered_x) == 0: return "" - loc_filtered_x, loc_filtered_y = zip( - *sorted(zip(loc_filtered_x, loc_filtered_y)) - ) - a = np.diff(loc_filtered_x) > int(w/2) + loc_filtered_x, loc_filtered_y = zip(*sorted(zip(loc_filtered_x, loc_filtered_y))) + a = np.diff(loc_filtered_x) > int(w / 2) a = np.append(a, True) loc_filtered_x = np.array(loc_filtered_x) loc_filtered_y = np.array(loc_filtered_y) @@ -151,10 +149,10 @@ cv2.rectangle(cimg, pt, (pt[0] + w, pt[1] + h), (0, 255, 255), 2) cv2.imwrite("sid_3rd2.png", cimg) - sid_no="" - for i,pt in enumerate(zip(*points[::-1])): - num=image[pt[1]:pt[1] + h, pt[0]:pt[0]+w] - #cv2.imwrite("sid_3no_{}.png".format(i), num) + sid_no = "" + for i, pt in enumerate(zip(*points[::-1])): + num = image[pt[1] : pt[1] + h, pt[0] : pt[0] + w] + # cv2.imwrite("sid_3no_{}.png".format(i), num) num = img_as_ubyte(num < 128) try: num = cv2.resize(num, (32, 32)) @@ -165,19 +163,34 @@ return sid_no + def getSID(image, classifier, sid_mask): + """ + Tries different approaches on image to get student id number. Firstly clears image of noise and then skeletonizes + numbers and thickens it until it gets normalized image. It sends it to the segmentation and recognition functions. + + Tweak both MORPH_OPEN lines.... + + :param image: + :param classifier: + :param sid_mask: + :return: (student_id, error, warning) student id as a string, list of errors and list of warnings during the recognition + + """ sid_warn = [] - sid_err=[] + sid_err = [] image = 255 - image - image_original=image.copy() + image_original = image.copy() image = img_as_ubyte(image > 100) cv2.imwrite("enSID0.png", image) + # Remove noise image = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel(2, 2), iterations=3) + # Closing. Connect non connected parts image = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel(5, 3), iterations=4) - # Again noise removal after closing + # Again noise removal after closing # image = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel(8, 8), iterations=1) # don't do too much noise removal. image = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel(3, 3), iterations=1) @@ -185,35 +198,28 @@ # Skeletonization image = img_as_ubyte(morphology.thin(image > 128)) cv2.imwrite("enSID1.png", image) + # Stub removal (might not be necessary if thinning instead of skeletonize is used above # Making lines stronger image = cv2.morphologyEx(image, cv2.MORPH_DILATE, kernel(5, 5), iterations=1) - image = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel(10, 10)) + # Thining again image = img_as_ubyte(morphology.skeletonize(image > 0.5)) image = cv2.morphologyEx(image, cv2.MORPH_DILATE, kernel(10, 10)) cv2.imwrite("enhancedSID.png", image) - im2, ctrs, hier = cv2.findContours( - image.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE - ) - sorted_ctrs = sorted(ctrs, key=lambda ctr: cv2.boundingRect(ctr)[0]) - print(len(sid_mask), len(sorted_ctrs)) - sid_no = segment_by_contours( - image, sorted_ctrs[1:], classifier - ) # we remove largest contour that surrounds whole image - print(sid_no) - if len(sid_no) != len(sid_mask) or not sid_compare(sid_no,sid_mask): + sid_no = segment_by_contours(image, image_original, classifier, sid_mask) + + if len(sid_no) != len(sid_mask) or not sid_compare(sid_no, sid_mask): sid_warn.append("Trying second SID algorithm.") sid_no = segment_by_7segments(image, image_original, sid_mask, classifier) - print(sid_no) - if(len(sid_no))!=len(sid_mask): + + if (len(sid_no)) != len(sid_mask): sid_no = segment_by_sid_len(image, image_original, sid_mask, classifier) sid_warn.append("Trying third SID algorithm.") - if not sid_compare(sid_no, sid_mask): - sid_err=['Wrong SID!'] + sid_err = ["Wrong SID!"] - return (sid_no, sid_err, sid_warn) + return sid_no, sid_err, sid_warn -- Gitblit v1.9.3