Use Python to implement answer card recognition!

Answer sheet material picture:

Ideas

1. Read in the image and do some preprocessing work.

2. Perform contour detection, and then find the largest contour of the picture, which is the answer sheet part.

3. Perform perspective transformation to remove excess parts except the answer sheet, and the answer sheet can be corrected.

4. Detect the outline again and locate each option.

5. Sort the option circles first according to the vertical coordinate, and then according to the row coordinate, so that the outline of each option is obtained from left to right and top to bottom.

6. Check the outline of each option. If there are many white dots in the outline of an option, it means that the option is selected, otherwise it is not selected. See the process for details:

1. Preprocessing (denoising, grayscale, binarization)

img = cv2.imread("1.png",1)
#GaussianDenoising
img_gs = cv2.GaussianBlur(img,[5,5],0)
# Convert to grayscale
img_gray = cv2.cvtColor(img_gs,cv2.COLOR_BGR2GRAY)
# Adaptive binarization
_,binary_img = cv2.threshold(img_gray,0,255,cv2.THRESH_OTSU|cv2.THRESH_BINARY)

Note: cv2.THRESH_OTSU|cv2.THRESH_BINARY, this parameter refers to adaptive threshold + anti-binarization. When doing adaptive threshold, the threshold should be set to 0

2. Contour detection

# Find contours
contours, hierarchy = cv2.findContours(binary_img,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
# Sort by the area of the contour from large to small
cnts = sorted(contours,key = cv2.contourArea,reverse=True)
# draw outline
draw_img = cv2.drawContours(img.copy(),cnts[0],-1,(0,255,255),2)

Note: for the findContours function, the incoming image should be a binary image. cv2.RETR_EXTERNAL refers to detecting only the external contour, and cv2.CHAIN_APPROX_NONE refers to returning all points on the contour.

# Contour approximation
# Threshold, generally 2% of the outline length
alpha = 0.02*cv2.arcLength(cnts[0],True)
approxCurve = cv2.approxPolyDP(cnts[0],alpha,True)
draw_img = cv2.drawContours(img.copy(),[approxCurve],-1,(255,0,0),2)

The purpose of contour approximation here is that the previously detected contour appears to be a polygon, but in fact it is essentially just a set of points.

cv2.approxPolyDP(contour,epsilon,True), polygon approximation, the first parameter is the point set, the second parameter is the accuracy (the maximum distance between the boundary points of the original contour and the fitted polygon), the third parameter refers to Whether the newly generated contour needs to be closed, the return value approxCurve is the polygon point set (sorted counterclockwise). Functions similar to this function include cv2.boundingRect (rectangular bounding box), cv2.minAreaRect (minimum enclosing rectangular box), cv2.minEnclosingCircle (minimum enclosing circle), cv2.filtEllipse (optimal fitting ellipse), cv2.filtLine (optimal fitting ellipse) Excellent fitting straight line), cv2.minEnclosingTriangle (minimum enclosing triangle)

3. Perspective transformation

#Perspective transformation
# The four vertices of the rectangle are approxCurve[0][0],approxCurve[1][0],approxCurve[2][0],approxCurve[3][0]
# Represents the four points of TL, BL, BR and TR of the rectangle respectively
a1 = list(approxCurve[0][0])
a2 = list(approxCurve[1][0])
a3 = list(approxCurve[2][0])
a4 = list(approxCurve[3][0])
# Original matrix
mat1 = np.array([a1,a2,a3,a4],dtype = np.float32)
  
# Calculate w and h of rectangle
w1 = int(np.sqrt((a1[0]-a4[0])**2 + (a1[1]-a4[1])**2))
w2 = int(np.sqrt((a2[0]-a3[0])**2 + (a2[1]-a3[1])**2))
h1 = int(np.sqrt((a1[0]-a2[0])**2 + (a1[1]-a2[1])**2))
h2 = int(np.sqrt((a3[0]-a4[0])**2 + (a3[1]-a4[1])**2))
w,h=max(w1,w2),max(h1,h2)
# Calculate the coordinates after perspective transformation
new_a1 = [0,0]
new_a2 = [0,h]
new_a3 = [w,h]
new_a4 = [w,0]
# Target matrix
mat2 = np.array([new_a1,new_a2,new_a3,new_a4],dtype = np.float32)
# Perspective transformation matrix
mat = cv2.getPerspectiveTransform(mat1,mat2)
#Perform perspective transformation
res = cv2.warpPerspective(img,mat,(w,h))
imshow((res))

Calculation steps of perspective transformation:

1. First obtain the four vertices of the original polygon, paying attention to the order of the vertices.

2. Then construct the original vertex matrix.

3. Calculate the length and width of the rectangle and construct the transformed target matrix.

4. Obtain the perspective transformation matrix from the original matrix to the target matrix 5. Perform perspective transformation

4. Contour detection, detect each option

res_gray = cv2.cvtColor(res,cv2.COLOR_BGR2GRAY)
_,binary_res = cv2.threshold(res_gray,0,255,cv2.THRESH_OTSU|cv2.THRESH_BINARY_INV)
contours = cv2.findContours(binary_res,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)[0]
dst = cv2.drawContours(res.copy(),contours,-1,(0,0,255),1)
imshow(dst)

Filter options outline

# Pick the right silhouette
def check(contours):
    ans = []
    for i in contours:
        area = float(cv2.contourArea(i))
        length = float(cv2.arcLength(i,True))
        if area<=0 or length<=0:
            continue
        if area/length >7.05 and area/length<10.5:
            ans.append(i)
    return ans
ans_contours = check(contours)
dst_new = cv2.drawContours(res.copy(),ans_contours,-1,(0,255,255),3 )
imshow(dst_new)

5. Draw the circumscribed circle of the outline, sort and position each option

# Traverse each circular outline and draw a circumscribed circle
circle = []
for i in ans_contours:
    (x,y),r = cv2.minEnclosingCircle(i)
    center = (int(x),int(y))
    r = int(r)
    circle.append((center,r))
# Sort center[1] according to the horizontal coordinate of the circumscribed circle, which is the height h of the center of the circle, or the y coordinate
circle.sort(key = lambda x:x[0][1])
A = []
for i in range(1,6):
    now = circle[(i-1)*5:i*5]
    now.sort(key = lambda x:x[0][0])
    A.extend(now)

Each option is stored in A in order from left to right and top to bottom according to the center of the circle.

6. Option detection

Idea: For each option circle in A, calculate the coordinates it covers, then determine the corresponding values of these coordinates in the binary image, and count the number of white points. If the proportion of white points is relatively large, Indicates that this option is selected.

def dots_distance(dot1,dot2):
    #Calculate the distance between two points in two-dimensional space
    return ((dot1[0]-dot2[0])**2 + (dot1[1]-dot2[1])**2)**0.5
def count_dots(center,radius):
    #Input the center point and radius of the circle and return all coordinates within the circle
    dots = []
    for i in range(-radius,radius + 1):
        for j in range(-radius,radius + 1):
            dot2 = (center[0] + i,center[1] + j)
            if dots_distance(center,dot2) <= radius:
                dots.append(dot2)
    return dots
   
da = []
for i in A:
    dots = count_dots(i[0],i[1])
    all_dots = len(dots)
    whilt_dots = 0
    for j in dots:
        if binary_res[j[1]][j[0]] == 255:
            whilt_dots = whilt_dots + 1
    if whilt_dots/all_dots>=0.4:
        da.append(1)
    else:
        da.append(0)
da = np.array(da)
da = np.reshape(da,(5,5))

In this way, each answer sheet is converted into a two-dimensional array, and then you can do some simple finishing work.

Interested friends will receive a complete set of Python learning materials, including interview questions, resume information, etc. See below for details.

1. Python learning routes in all directions

The technical points in all directions of Python have been compiled to form a summary of knowledge points in various fields. Its usefulness is that you can find corresponding learning resources according to the following knowledge points to ensure that you learn more comprehensively.

img
img

2. Python essential development tools

The tools have been organized for you, and you can get started directly after installation! img

3. Latest Python study notes

When I learn a certain basic and have my own understanding ability, I will read some books or handwritten notes compiled by my seniors. These notes record their understanding of some technical points in detail. These understandings are relatively unique and can be learned. to a different way of thinking.

img

4. Python video collection

Watch a comprehensive zero-based learning video. Watching videos is the fastest and most effective way to learn. It is easy to get started by following the teacher’s ideas in the video, from basic to in-depth.

img

5. Practical cases

What you learn on paper is ultimately shallow. You must learn to type along with the video and practice it in order to apply what you have learned into practice. At this time, you can learn from some practical cases.

img

6. Interview Guide

Resume template

If there is any infringement, please contact us for deletion.