From 6fde5fba87aaf2f2ad8c4af08a59454f06d8dc30 Mon Sep 17 00:00:00 2001
From: Samo Penic <samo.penic@gmail.com>
Date: Sat, 17 Nov 2018 12:28:39 +0000
Subject: [PATCH] Improving the robustness of all three algorithms. Again...

---
 aoiOcr.py      |    8 +-
 sid_process.py |  226 +++++++++++++++++++++++++++++---------------------------
 2 files changed, 120 insertions(+), 114 deletions(-)

diff --git a/aoiOcr.py b/aoiOcr.py
index bb74503..74c7b15 100644
--- a/aoiOcr.py
+++ b/aoiOcr.py
@@ -6,8 +6,8 @@
 settings = {"sid_mask": "64xx0xxx", "answer_treshold": 0.25}
 classifier = joblib.load("filename.joblib")
 
-# p = Paper(filename="testpage300dpi_scan1.png")
-#p=Paper(filename='sizif111.tif', sid_classifier=classifier, settings={"sid_mask": "11xx0xxx", "answer_treshold": 0.25})
+#p = Paper(filename="testpage300dpi_scan1.png")
+p=Paper(filename='sizif111.tif', sid_classifier=classifier, settings={"sid_mask": "11xx0xxx", "answer_treshold": 0.25})
 #p=Paper(filename='processed_scans/20141016095134535_0006.tif', sid_classifier=classifier, settings=settings)
 #p = Paper(filename="processed_scans/20151111080408825_0001.tif",sid_classifier=classifier,settings=settings,)
 #p=Paper(filename='processed_scans/20151028145444607_0028.tif', sid_classifier=classifier, settings=settings)
@@ -20,7 +20,7 @@
     "processed_scans/20141021095744144_0009.tif",
     "processed_scans/20141028095553745_0018.tif",
 ]
-p=Paper(filename=pa[6], sid_classifier=classifier, settings=settings)
+#p=Paper(filename=pa[6], sid_classifier=classifier, settings=settings)
 
 # print(p.QRData)
 # print(p.errors)
@@ -33,7 +33,7 @@
 
 
 print(p.get_paper_ocr_data())
-
+exit(0)
 filelist = glob("processed_scans/*.tif")
 for f in sorted(filelist):
     print("processing: {}".format(f))
diff --git a/sid_process.py b/sid_process.py
index 1f93d3c..ab8aaa3 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
+    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):
+    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