Search code examples
pythonopencvcomputer-visionobject-detectionomr

Detect All Circles in an image (Optical Mark Recognition) using Python OpenCV


I need to make a OMR detection system using Python for my High School Vacation Project(which potentially might be used by the school to some extent if it is reliable enough) , I have done quite a bit of research on it , and have tried out everything from contours to template matching, I feel template matching works fine but it can only detect one circle out of many in the OMR sheet, can someone help me in figuring out how I can detect multiple(all) circles(irrespective of whether they are bubbled or not), in the omr sheet and their respective coordinates , that would be enough for me. OMR Sheet

What I have tried:

import numpy as np
import cv2

img = cv2.resize(cv2.imread('assets/omr_match1.jpg', 0), (0, 0), fx=0.2, fy=0.5)
template = cv2.resize(cv2.imread('assets/circle.jpg', 0), (0, 0), fx=0.2, fy=0.5)
h, w = template.shape

methods = [cv2.TM_CCOEFF, cv2.TM_CCOEFF_NORMED, cv2.TM_CCORR,
            cv2.TM_CCORR_NORMED, cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]

methods=methods[0] 
# This is one among the above which works perfectly

for method in methods:
    img2 = img.copy()

    result = cv2.matchTemplate(img2, template, method)
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
    if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
        location = min_loc
    else:
        location = max_loc

    bottom_right = (location[0] + w, location[1] + h)
    cv2.rectangle(img2, location,bottom_right, 0, 1)
    cv2.imshow('Match', img2)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

Circle.jpg # circle.jpg

Result

See above, only one random circle is marked and not all circles.


Solution

  • Here we go:

    import cv2
    import numpy as np
    from matplotlib import pyplot as plt
    
    
    img = cv2.imread('/path/tabela_circle.jpg', 0)
    
    template = cv2.imread('/path/circle.jpg', 0)
    h, w = template.shape
    
    res = cv2.matchTemplate(img,template,cv2.TM_CCOEFF_NORMED)
    threshold = 0.8
    loc = np.where( res >= threshold)
    for pt in zip(*loc[::-1]):
        cv2.rectangle(img, pt, (pt[0] + w, pt[1] + h), (0,0,255), 2)
    
    cv2.imwrite('res.png',img)
    

    Solution

    Also works to detect only answers:

    Solution 2