| | |
| | | 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): |
| | | """ |
| | | 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: |
| | | 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) |
| | |
| | | |
| | | |
| | | 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)) |
| | |
| | | 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) |
| | | """ |
| | | 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) |
| | |
| | | loc_filtered_x.append(pt[0]) |
| | | # points.append(pt) |
| | | #filter points |
| | | if(len(loc_filtered_x)==0): |
| | | if len(loc_filtered_x) == 0: |
| | | return "" |
| | | loc_filtered_x, loc_filtered_y = zip( |
| | | *sorted(zip(loc_filtered_x, loc_filtered_y)) |
| | | ) |
| | | 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) |
| | |
| | | |
| | | 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 |
| | | |
| | | # 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) |
| | | 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 |
| | | sid_no = segment_by_contours(image, image_original, classifier, sid_mask) |
| | | print(sid_no) |
| | | 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_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 |