From e0996e35862256114826a6314dc649972972a60c Mon Sep 17 00:00:00 2001 From: Samo Penic <samo.penic@gmail.com> Date: Sat, 17 Nov 2018 13:42:52 +0000 Subject: [PATCH] Multiple SID robustness.. --- sid_process.py | 293 +++++++++++++++++++++++++++++++++++++++++----------------- 1 files changed, 205 insertions(+), 88 deletions(-) diff --git a/sid_process.py b/sid_process.py index 90d9b33..f21fafb 100644 --- a/sid_process.py +++ b/sid_process.py @@ -1,108 +1,225 @@ import cv2 import numpy as np -from skimage import morphology,img_as_ubyte -from sklearn import svm -from sklearn.externals import joblib - - - -""" - (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 - -""" +from skimage import morphology, img_as_ubyte def kernel(x, y): + """ + Function greates square kernel of size x and y + """ return np.ones((x, y), np.uint8) -def getSID(image, classifier): - image=255-image - image=img_as_ubyte(image>100) - cv2.imwrite("enSID0.png", image) - # Remove noise - image = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel(2,2), iterations=1) - # Closing. Connect non connected parts - image = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel(5, 3), iterations=4) - # Again noise removal after closing +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 - image = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel(8,8), iterations=1) - # 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) +def sid_compare(sid_no, sid_mask): + """ + 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 - 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)) - im2,ctrs, hier = cv2.findContours(image.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) +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 + """ + + 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]) - #classifier = joblib.load('filename.joblib') - - sid_no="" for i, ctr in enumerate(sorted_ctrs): # Get bounding box x, y, w, h = cv2.boundingRect(ctr) # Getting ROI - if(w<h/2): - sid_no=sid_no+"1" + if w < h / 2: + sid_no = sid_no + "1" continue - roi = image[y:y+h, x:x+w] + roi = image[y : y + h, x : x + w] roi = img_as_ubyte(roi < 128) - roi = cv2.resize(roi,(32,32)) + roi = cv2.resize(roi, (32, 32)) - #cv2.rectangle(image,(x,y),( x + w, y + h ),(0,255,0),2) - cv2.imwrite('sid_no_{}.png'.format(i), roi) - sid_no=sid_no+str(classifier.predict(roi.reshape(1,-1)/255.0)[0]) - print(sid_no) - return image + # cv2.rectangle(image,(x,y),( x + w, y + h ),(0,255,0),2) + cv2.imwrite("sid_no_{}.png".format(i), roi) + sid_no = sid_no + str(classifier.predict(roi.reshape(1, -1) / 255.0)[0]) + return sid_no + + +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) + 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)) + for i in range(0, sid_len): + num = image[:, i * numWidth : (i + 1) * numWidth] + num = img_as_ubyte(num < 128) + num = cv2.resize(num, (32, 32)) + 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): + """ + 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 = [] + for pt in zip(*loc[::-1]): + 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: + 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) + a = np.append(a, True) + loc_filtered_x = np.array(loc_filtered_x) + loc_filtered_y = np.array(loc_filtered_y) + points = [loc_filtered_y[a], loc_filtered_x[a]] + for pt in zip(*points[::-1]): + 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) + num = img_as_ubyte(num < 128) + try: + num = cv2.resize(num, (32, 32)) + except: + return "" + cv2.imwrite("sid_3no_{}.png".format(i), num) + sid_no = sid_no + str(classifier.predict(num.reshape(1, -1) / 255.0)[0]) + + 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 = [] + image = 255 - image + 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 + # 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) + + # 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) + + 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) + + 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!"] + + return sid_no, sid_err, sid_warn -- Gitblit v1.9.3