pythonimageopencvimage-processingomr

Detect OCR marker


I am working on optical mark recognition problem. I found Region of Interest(ROI) where student's roll number to be filled.Which approach can help me to decode filled circle value? i tried to code but it's not functioning properly.

Images

In this image initial ROI is given.After that i have applied segmentation.Third image is filled by student which indicate student's roll number.

this image detect 381 circle but actual circle are 100

Input: Filled circle image
Output: roll number : 4216789503

image = cv2.imread("rotatedb/ROI_omr.png")
hsvimg = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
lower_blue = np.array([0,70,0])
upper_blue = np.array([255,255,255])
mask = cv2.inRange(hsvimg, lower_blue, upper_blue)
contours, hierarchy = cv2.findContours(mask.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
print "No. of circles",len(contours)

i=0
for contour in contours:
   (x,y),radius = cv2.minEnclosingCircle(contour)
   center = (int(x),int(y))
   radius = int(radius)
   cv2.circle(image,center,radius,(0,255,0),2)
   position = (center[0] - 10, center[1] + 10)
   text_color = (0, 0, 255)
   cv2.putText(image, str(i + 1), position, cv2.FONT_HERSHEY_SIMPLEX, 0.5, text_color, 2)
   i=i+1

cv2.imshow("thresold",image)
cv2.waitKey(0)
cv2.destroyAllWindows()

Solution

  • As the Markers are in black color, So you should try to segment the black colored segments in the input image, and from that binary mask you may find the contours and filter out the circular shaped contours (you may also want to filter contours with area if you like to).

    After finding all the contours, sort the contours as per their x coordinate of bounding rect, which would yield us the order of contours as we are traversing them in horizontal direction(cv2.findContours() returns contours in random order, so it is always a good idea to sort them as per your needs.)

    Finally you calculate the mid point of each contour and estimate the circle on which they lie.

    Code:

    import cv2
    
    img = cv2.imread('/Users/anmoluppal/Downloads/QYtuv.png')
    MARKER_LOWER_BOUND = ( 0,  0,  0)
    MARKER_UPPER_BOUND = (20, 20, 20)
    
    img = cv2.blur(img, (7, 7))
    marker_seg_mask = cv2.inRange(img, MARKER_LOWER_BOUND, MARKER_UPPER_BOUND)
    
    # Number of rows and columns of number matrix
    n_rows, n_cols = 10, 10
    single_element_height, single_element_width = marker_seg_mask.shape[0]/10, marker_seg_mask.shape[1]/10
    
    # Now find the contours in the segmented mask
    img, contours, hierarchy = cv2.findContours(marker_seg_mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    
    # Sorting the contours w.r.t contour rect X
    contours.sort(key = lambda x:cv2.boundingRect(x)[0])
    
    # Now iterate over each contour and see if it is in circular shape
    roll_number = ""
    for contour in contours:
        approx = cv2.approxPolyDP(contour, 0.01*cv2.arcLength(contour,True), True)
        if len(approx) > 8:
            # Find the bounding rect of contour.
            contour_bounding_rect = cv2.boundingRect(contour)
            mid_point = contour_bounding_rect[0] + contour_bounding_rect[2]/2, contour_bounding_rect[1] + contour_bounding_rect[3]/2
            roll_num_digit = mid_point[1]/single_element_height
    
            # Since your numbering format is from 1, 2, 3, ... 0, So to parse the roll number correctly we need additional operation
            roll_num_digit = (roll_num_digit + 1) % 10
            roll_number += str(roll_num_digit)
    print "Roll Number: ", roll_number
    

    Output:

    Roll Number:  4216789503