I have an answer sheet. I want to identify the fields marked on it and check whether they are correct or incorrect.
I have come to a certain stage.
I get the results in the picture below.
But even though they are all correct, the result I found and marked is incorrect.
They are all correct in marking.
At this stage I got help from many examples and chatgpt but I can't move forward.
The situation I expect
If the correct answer is marked, circle it in green and increase the number of correct answers by 1.
If the wrong answer is marked, circle the correct answer in green and the wrong answer in red.
And increase the number of incorrect by one.
If there is no marking, do not take any action and move to the next row.
If there are two markings on a line, for example, if A and C are marked, circle them both in red and move to the next line as if they were empty.
Can you help me about the error in my code and how to fix it?
Here is my code
import cv2
import numpy as np
import imutils
from imutils import contours
# Load the image
image_path = 'mmm.jpg'
image = cv2.imread(image_path)
image_path2 = 'anabos.jpg'
image2 = cv2.imread(image_path2)
ANSWER_KEY = {0:0,1:3,2:1,3:2,4:0,5:4,6:3,7:4,8:0,9:0,10:0,11:0,12:2,13:4,14:3,15:0,16:1,17:1,18:3,19:1,20:2,21:1,22:2,23:3,24:0,25:1,26:1,27:1,28:0,29:4,30:4,31:0,32:1,33:1,34:3,35:3,36:1,37:4,38:1,39:3}
# Convert the image to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
x=837
y=746
w=168
h=1335
img=image[y:y+h, x:x+w]
img2=image2[y:y+h, x:x+w]
lower=(0,0,0)
upper=(0,0,255)
thresh = cv2.inRange(img, lower, upper)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (10,10))
morph = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
morph = cv2.morphologyEx(morph, cv2.MORPH_OPEN, kernel)
centers = []
contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
contours = contours[0] if len(contours) == 2 else contours[1]
i = 1
for cntr in contours:
M = cv2.moments(cntr)
cx = int(M["m10"] / M["m00"])
cy = int(M["m01"] / M["m00"])
centers.append((cx,cy))
cv2.circle(img2, (cx, cy), 15, (0, 0, 0), -1)
pt = (cx,cy)
print("circle #:",i, "center:",pt)
i = i + 1
# Extract the region of interest (ROI) for the selected rectangle
gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
paper = img2
#cv2.imshow("gray2", gray2)
#cv2.imshow("paper", paper)
thresh = cv2.threshold(gray2, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
questionCnts = []
# loop over the contours
for c in cnts:
# compute the bounding box of the contour, then use the
# bounding box to derive the aspect ratio
(x, y, w, h) = cv2.boundingRect(c)
ar = w / float(h)
# in order to label the contour as a question, region
# should be sufficiently wide, sufficiently tall, and
# have an aspect ratio approximately equal to 1
#print(str(w))
if w >= 10 and h >= 10 and ar >= 0.9 and ar <= 1.1:
questionCnts.append(c)
# sort the question contours top-to-bottom, then initialize
# the total number of correct answers
questionCnts = sorted(questionCnts, key=lambda x: cv2.boundingRect(x)[1])
correct = 0
# each question has 5 possible answers, to loop over the
# question in batches of 5
for (q, i) in enumerate(np.arange(0, len(questionCnts), 5)):
# sort the contours for the current question from
# left to right, then initialize the index of the
# bubbled answer
cnts = sorted(questionCnts[q:q + 5], key=lambda x: cv2.boundingRect(x)[0])
bubbled = None
# loop over the sorted contours
for (j, c) in enumerate(cnts):
# construct a mask that reveals only the current
# "bubble" for the question
mask = np.zeros(thresh.shape, dtype="uint8")
cv2.drawContours(mask, [c], -1, 255, -1)
# apply the mask to the thresholded image, then
# count the number of non-zero pixels in the
# bubble area
mask = cv2.bitwise_and(thresh, thresh, mask=mask)
total = cv2.countNonZero(mask)
print(total)
# if the current total has a larger number of total
# non-zero pixels, then we are examining the currently
# bubbled-in answer
if bubbled is None or total > bubbled[0]:
bubbled = (total, j)
# initialize the contour color and the index of the
# *correct* answer
color = (0, 0, 255)
k = ANSWER_KEY[q]
#print(bubbled)
# check to see if the bubbled answer is correct
if k == bubbled[1]:
color = (0, 255, 0)
correct += 1
cv2.drawContours(paper, [cnts[k]], -1, color, 3)
# grab the test taker
score = (correct)
print("[INFO] score: {:.2f}%".format(score))
cv2.imshow("Original", image)
cv2.imshow("Exam", paper)
cv2.imshow("thresh", thresh)
cv2.imshow("img", img)
cv2.waitKey(0)
My main sheet is; Main sheet
Threshold image like this; thresh
My result is; result
My expected result is like this; Expected result
Can you show me the way? How can I fix this?
Here is how to use HoughCircles in Python/OpenCV to detect your circles. I hope it helps you.
Input:
cropped image:
gray image:
circles on gray image:
If you now follow the link I gave you in my comment. The next step would be to count the number of non-zero pixels in the contours inside an Otsu threshold gray image to determine which contours are mostly filled (empty circles) and which are hardly filled (marked circles)