OpenCV realizes automatic scoring of answer sheets!

Table of Contents

1. Main principles and function introduction

All codes start with

2. Implementation process

3. Result display


1, Introduction to main principles and functions

ap = argparse.ArgumentParser()

Create an ArgumentParser object and assign it to the variable ap. This object can accept the command line parameters of our script, thereby performing different operations based on the command line parameters.

ap.add_argument(“-i”, “–image”, required=True,help=”path to the input image”)

Add a command line argument -i/–image and specify that it is required and the user must provide an image path as input.

args = vars(ap.parse_args())

Parse command line arguments and store them in args dictionary

ANSWER_KEY = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1}

Define a dictionary that contains the correct answer for each question. The key is the index of the question and the value is the number of the correct answer.

def order_points(pts):

Define the function order_points to find four coordinate points in the specified order

def four_point_transform(image, pts):

Define the function four_point_transform to perform perspective transformation.

Perspective transformation is a commonly used transformation method in the field of image processing, which can project an image on a plane to a new viewing plane. Perspective transformation is often used to correct perspective distortion in an image, such as converting a photo taken at an angle into a normal plan view. During perspective transformation, parallel lines in an image may become non-parallel lines, and originally non-parallel lines may become parallel lines. The function four_point_transform uses four specific points to determine the perspective transformation matrix and perform perspective transformation on the input image.

def sort_contours(cnts, method=”left-to-right”):

Define the function sort_contours for sorting contours

def cv_show(name,img):

Define the function cv_show for displaying images.

image = cv2.imread(args[“image”]):

Read the input image and store it in the variable image

contours_img = image.copy():

Copy the image, used to draw outlines on the image

cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1]

Find contours in the image, use external contour checking mode (RETR_EXTERNAL) to detect only external contours, use simple contour approximation algorithm (CHAIN_APPROX_SIMPLE) to reduce the number of points. The return value is a tuple where the second element represents the list of contours

cv2.drawContours(contours_img,cnts,-1,(0,0,255),3)

Draw the found contours on the image, color red

docCnt = None

Initialize the docCnt variable, used to store the four found coordinate points

if len(cnts) > 0:

If the contour is found, do the following

cnts = sorted(cnts, key=cv2.contourArea, reverse=True)

Sort the contours according to their area size, from largest to smallest

peri = cv2.arcLength(c, True) calculates the contour perimeter

approx = cv2.approxPolyDP(c, 0.02 * peri, True)

Obtain the approximate shape of the contour using an iterative approximation algorithm. If the approximated contour has four vertices, these are used as the four found coordinate points.

if len(approx) == 4: If four coordinate points are found, save them to the docCnt variable and break out of the loop.

warped = four_point_transform(gray, docCnt.reshape(4, 2))

Perform perspective transformation on the image and map the four found coordinate points into a new matrix

thresh = cv2.threshold(warped, 0, 255,cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]

The transformed image is binarized, and Otsu’s adaptive threshold processing algorithm is used to obtain the optimal threshold. The return value is a tuple where the second element is the processed image.

hresh_Contours = thresh.copy()

Copy the processed image for drawing outlines on it

cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1]

Find contours in the processed image and use the RETR_EXTERNAL and CHAIN_APPROX_SIMPLE parameters.

cv2.drawContours(thresh_Contours,cnts,-1,(0,0,255),3)

Draw the found contours on the binarized image

cv_show(‘thresh_Contours’,thresh_Contours)

Call the cv_show function to display the image after drawing the contour

questionCnts = []: Initialize the questionCnts list.

for c in cnts:: Traverse all contours:

(x, y, w, h) = cv2.boundingRect(c): Get the rectangle representing the position and size of the outline area.

ar = w / float(h): Calculate the aspect ratio of the rectangle.

if w >= 20 and h >= 20 and ar >= 0.9 and ar <= 1.1:: If the rectangle meets the specified conditions, it is added to the questionCnts list.

questionCnts = sort_contours(questionCnts,method="top-to-bottom")[0]: Sort the contours in the questionCnts list from top to bottom.

for (q, i) in enumerate(np.arange(0, len(questionCnts), 5)): Process each row of questions.

cnts = sort_contours(questionCnts[i:i + 5])[0]: Sort the five options in each question.

bubbled = None: Initialize the variable bubbled, used to save the selected answer.

for (j, c) in enumerate(cnts):: Iterate through each option.

mask = np.zeros(thresh.shape, dtype="uint8") : Creates an all-black image with the same size as thresh.

cv2.drawContours(mask, [c], -1, 255, -1): Draw the outline of the option.

mask = cv2.bitwise_and(thresh, thresh, mask=mask): Multiply the binary image thresh and mask, and only retain the intersection part.

total = cv2.countNonZero(mask): Count the number of non-zero pixels to determine whether this option is selected.

if bubbled is None or total > bubbled[0]: Update the selection if it has not been selected before or if there are more pixels currently selected.

bubbled = (total, j): Save the currently selected number of pixels and option index to the bubbled variable.

color = (0, 0, 255): The initial color is red.

k = ANSWER_KEY[q]: Get the correct answer to the current question.

if k == bubbled[1]:: If the current option is correct, set the color to green and increment the correct counter.

color = (0, 255, 0)
correct + = 1

cv2.drawContours(warped, [cnts[k]], -1, color, 3): Draws the contours of the options on the transformed image in the correct or wrong color.

score = (correct / 5.0) * 100: Calculate the score.

print("[INFO] score: {:.2f}%".format(score)): Output the score.

cv2.putText(warped, "{:.2f}%".format(score), (10, 30),cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2): In the transformed image Score is plotted on.

cv2.imshow("Original", image): Display the original image.

cv2.imshow("Exam", warped): Displays the processing results.

cv2.waitKey(0): Wait for the user to press any key. 

All codes, with

2, implementation process

  1. Import the required libraries and modules: First import libraries such as numpy, argparse, imutils and cv2 for scientific computing, command line parameter parsing, image processing and machine vision tasks.

  2. Parse command line parameters: Use the argparse library to create an ArgumentParser object and add a command line parameter -i/–image to ask the user to provide a path to the input image. The command line arguments are then parsed using the ap.parse_args() method and the results are stored in the args dictionary.

  3. Define correct answer dictionary: Create a dictionary containing the correct answer number for each question.

  4. Function definition: Four functions are defined, which are used to find four coordinate points, perform perspective transformation, sort outlines, and display images.

  5. Read the input image: Use the cv2.imread() function to read the input image and store it in the variable image.

  6. Image preprocessing: Convert the image to grayscale and then apply Gaussian blur to smooth edges and reduce noise. Next, the Canny algorithm is used for edge detection to obtain a binarized edge image.

  7. Find contours: Use the cv2.findContours() function to find contours in the image, and use the external contour inspection mode (RETR_EXTERNAL) to detect only external contours, using a simple contour approximation algorithm (CHAIN_APPROX_SIMPLE) to reduce the number of points. The obtained contour is stored in the cnts variable.

  8. Find the answer sheet area: traverse the contour, calculate the contour perimeter and approximate shape, and find the contour containing the answer sheet through a series of conditional judgments. Save the four coordinate points found to the docCnt variable.

  9. Perform perspective transformation: Use the four_point_transform() function to perform perspective transformation on the image and map the four found coordinate points into a new matrix.

  10. Binarize the transformed image: Use Otsu’s adaptive threshold processing algorithm to binarize the image to obtain a binarized image.

  11. Find contours: Use the cv2.findContours() function to find contours in the processed image again with the RETR_EXTERNAL and CHAIN_APPROX_SIMPLE parameters.

  12. Process options for each question: Iterate over the row of each question and process the five options in that row. First sort the options, then use a traversal method to calculate the number of non-zero pixels in each option, determine whether the option is selected based on the number, and record the most selected option and its index.

  13. Calculate score: Calculate the score based on the number of correct options and the total number of questions, and output the score at the same time.

  14. Show results: Plots the score on the transformed image and displays the original image and the processing results.

  15. Wait for the user to press any key to end the program.

3, Result Display

The result after Gaussian filtering

Edge detection results

Image contour detection results:

Perspective transformation results:

Binarization processing results:

mask, only retain the intersection part

Iterate through each result and count the number of non-zero pixels to determine whether this option is selected.

Score calculated and displayed