Prześlij pliki do ''
This commit is contained in:
parent
678278effc
commit
2e70f141bd
41
answers.csv
Normal file
41
answers.csv
Normal file
@ -0,0 +1,41 @@
|
||||
Question,Answer
|
||||
1,A
|
||||
2,B
|
||||
3,C
|
||||
4,A
|
||||
5,D
|
||||
6,D
|
||||
7,E
|
||||
8,A
|
||||
9,A
|
||||
10,D
|
||||
11,D
|
||||
12,B
|
||||
13,A
|
||||
14,C
|
||||
15,D
|
||||
16,E
|
||||
17,E
|
||||
18,C
|
||||
19,A
|
||||
20,D
|
||||
21,B
|
||||
22,D
|
||||
23,A
|
||||
24,C
|
||||
25,D
|
||||
26,D
|
||||
27,A
|
||||
28,E
|
||||
29,A
|
||||
30,B
|
||||
31,A
|
||||
32,C
|
||||
33,D
|
||||
34,E
|
||||
35,A
|
||||
36,C
|
||||
37,B
|
||||
38,E
|
||||
39,A
|
||||
40,D
|
|
41
answers_report.csv
Normal file
41
answers_report.csv
Normal file
@ -0,0 +1,41 @@
|
||||
Question number,Selected answer,Expected answer,Correct
|
||||
1,C,A,False
|
||||
2,B,B,True
|
||||
3,C,C,True
|
||||
4,D,A,False
|
||||
5,A,D,False
|
||||
6,A,D,False
|
||||
7,A,E,False
|
||||
8,B,A,False
|
||||
9,C,A,False
|
||||
10,A,D,False
|
||||
11,C,D,False
|
||||
12,D,B,False
|
||||
13,B,A,False
|
||||
14,D,C,False
|
||||
15,A,D,False
|
||||
16,C,E,False
|
||||
17,C,E,False
|
||||
18,A,C,False
|
||||
19,C,A,False
|
||||
20,B,D,False
|
||||
21,B,B,True
|
||||
22,D,D,True
|
||||
23,C,A,False
|
||||
24,D,C,False
|
||||
25,B,D,False
|
||||
26,A,D,False
|
||||
27,C,A,False
|
||||
28,C,E,False
|
||||
29,B,A,False
|
||||
30,A,B,False
|
||||
31,A,A,True
|
||||
32,B,C,False
|
||||
33,C,D,False
|
||||
34,D,E,False
|
||||
35,D,A,False
|
||||
36,B,C,False
|
||||
37,C,B,False
|
||||
38,A,E,False
|
||||
39,C,A,False
|
||||
40,D,D,True
|
|
BIN
arkusz OCR.jpg
Normal file
BIN
arkusz OCR.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 320 KiB |
297
main.py
Normal file
297
main.py
Normal file
@ -0,0 +1,297 @@
|
||||
import cv2
|
||||
import np
|
||||
import argparse
|
||||
import csv
|
||||
|
||||
class AnswersChecker:
|
||||
|
||||
def __init__(self, answers_file):
|
||||
self.path = answers_file
|
||||
self.answers = []
|
||||
self.correct_answers = []
|
||||
self.incorrect_answers = []
|
||||
self.answered_questions = []
|
||||
self.total_questions = 0
|
||||
self.score_percent = 0
|
||||
|
||||
def load_answers_from_csv(self):
|
||||
file = open(self.path)
|
||||
reader = csv.DictReader(file)
|
||||
|
||||
for row in reader:
|
||||
self.answers.append([row["Question"], row["Answer"]])
|
||||
|
||||
def compare(self, input_answers):
|
||||
self.answered_questions = input_answers
|
||||
self.correct_answers = [element for element in self.answers if element in input_answers]
|
||||
self.incorrect_answers = [element for element in self.answered_questions if element not in self.correct_answers]
|
||||
|
||||
def get_percentage(self):
|
||||
return (len(self.correct_answers) / len(self.answers)) * 100
|
||||
|
||||
def get_report(self):
|
||||
output = "Total number of questions: " + str(len(self.answers)) + "\n"
|
||||
output += "Total number of questions answered: " + str(len(self.answered_questions)) + "\n"
|
||||
output += "Correct answers: " + str(len(self.correct_answers)) + "\n"
|
||||
output += "Percentage of correct answers:" + str(self.get_percentage()) + "%"
|
||||
return output
|
||||
|
||||
def get_incorrect_answers(self):
|
||||
output = "Incorrect answers:" + "\n"
|
||||
|
||||
for i in self.incorrect_answers:
|
||||
output += str(i[0]) + ": " + str(i[1]) + "\n"
|
||||
|
||||
return output
|
||||
|
||||
def get_correct_answers(self):
|
||||
output = "Correct answers:" + "\n"
|
||||
for i in self.correct_answers:
|
||||
output += str(i[0]) + ": " + str(i[1]) + "\n"
|
||||
|
||||
return output
|
||||
|
||||
def has_passed(self, passmark_points = 0, passmark_percentage = 0):
|
||||
|
||||
if passmark_points != 0:
|
||||
if len(self.correct_answers) >= passmark_points:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
if passmark_percentage != 0:
|
||||
if self.get_percentage() >= passmark_percentage:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def write_report_to_csv(self):
|
||||
try:
|
||||
path_first = self.path.split(".")[0]
|
||||
|
||||
csv_writer = CsvWriter(path_first + "_report.csv")
|
||||
csv_writer.write_report(self.answered_questions, self.answers)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
class CsvWriter:
|
||||
def __init__(self, output_file_path):
|
||||
self.path = output_file_path
|
||||
self.file = open(self.path, 'w', newline='')
|
||||
|
||||
def write_report(self, selected_answers, model_answers):
|
||||
column_names = ["Question number", "Selected answer", "Expected answer", "Correct"]
|
||||
csv_writer = csv.DictWriter(self.file, fieldnames=column_names)
|
||||
csv_writer.writeheader()
|
||||
|
||||
for i in range(1, len(model_answers)+1):
|
||||
try:
|
||||
iscorrect = str(selected_answers[i-1]) == str(model_answers[i-1])
|
||||
csv_writer.writerow({"Question number": str(i), "Selected answer": selected_answers[i-1][1], "Expected answer": model_answers[i-1][1], "Correct": str(iscorrect)})
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
class Selection:
|
||||
def __init__(self, x, y, selection: ""):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.selection = selection
|
||||
self.question_number = 0
|
||||
|
||||
def get_answer(self):
|
||||
return str(self.question_number) + ": " + str(self.selection)
|
||||
|
||||
|
||||
# Default values if no input file is provided
|
||||
ANSWERS_OPTIONS = 4
|
||||
SUBSECTIONS = 2
|
||||
DEFAULT_FILE = "wypelniony_arkusz_poprawne_odp.jpg"
|
||||
DEFAULT_ANSWERS_FILE = "answers.csv"
|
||||
SELECTED_INTENSITY_THRESHOLD = 100
|
||||
DEFAULT_PASSMARK_PERCENTAGE = 50
|
||||
DEFAULT_PASSMARK_POINTS = 20
|
||||
|
||||
# mapping the integer values to capital letters
|
||||
ANSWERS_CHOICES = {0: "A", 1: "B", 2: "C", 3: "D", 4: "E", 5: "F"}
|
||||
X_OFFSET = 2
|
||||
Y_OFFSET = 2
|
||||
MIN_OPTIONS = 3
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description='Specify the input arguments.')
|
||||
percentage_passmark = 0
|
||||
points_passmark = 0
|
||||
options = 0
|
||||
passmark_selected = False
|
||||
parser.add_argument('file', help='The image to be processed', nargs="?")
|
||||
parser.add_argument('-percent', type=int, help='The image to be processed', nargs="?")
|
||||
parser.add_argument('-passmark', type=int, help='The image to be processed', nargs="?")
|
||||
parser.add_argument('-options', type=int, help='The image to be processed', nargs="?")
|
||||
args = parser.parse_args()
|
||||
answers_path = ""
|
||||
|
||||
if args.file is not None:
|
||||
answers_path = args.file
|
||||
else:
|
||||
answers_path = DEFAULT_FILE
|
||||
|
||||
if args.percent is not None:
|
||||
percentage_passmark = args.percent
|
||||
else:
|
||||
percentage_passmark = DEFAULT_PASSMARK_PERCENTAGE
|
||||
|
||||
if args.options is not None:
|
||||
options = args.options
|
||||
else:
|
||||
options = ANSWERS_OPTIONS
|
||||
|
||||
|
||||
if args.passmark is not None:
|
||||
points_passmark = args.passmark
|
||||
passmark_selected = True
|
||||
else:
|
||||
points_passmark = DEFAULT_PASSMARK_POINTS
|
||||
|
||||
img = cv2.imread(answers_path, 0)
|
||||
|
||||
gray_img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
|
||||
gray_img = cv2.bitwise_not(gray_img)
|
||||
|
||||
img = cv2.bitwise_not(img)
|
||||
|
||||
# Detect circles
|
||||
circles = cv2.HoughCircles(img, cv2.HOUGH_GRADIENT, 1.1, 5, param1=90, param2=30, minRadius=12, maxRadius=20)
|
||||
circles = circles[0]
|
||||
|
||||
# Sort the detected circles along x-axis
|
||||
x_sorted = circles[np.argsort(circles[:, 0]), :]
|
||||
|
||||
index = -1
|
||||
sorted_list = []
|
||||
previous = 0
|
||||
|
||||
# Assign the circles to columns based on their X-coordinate
|
||||
for s in x_sorted:
|
||||
if s[0] > previous + X_OFFSET:
|
||||
index += 1
|
||||
sorted_list.append([])
|
||||
sorted_list[index].append(s)
|
||||
|
||||
previous = s[0]
|
||||
|
||||
sorted_list_filtered = []
|
||||
|
||||
# Accept columns above a given value
|
||||
for i in sorted_list:
|
||||
if len(i) > MIN_OPTIONS:
|
||||
sorted_list_filtered.append(i)
|
||||
|
||||
selected_circles = []
|
||||
question_number = 0
|
||||
|
||||
sorted_list2 = []
|
||||
|
||||
number_of_columns = int(len(sorted_list_filtered)/SUBSECTIONS)
|
||||
|
||||
start = 0
|
||||
end = options
|
||||
for i in range(number_of_columns):
|
||||
sorted_list2.append(sorted_list_filtered[start:end])
|
||||
start = end
|
||||
end += end
|
||||
|
||||
column_number = 0
|
||||
|
||||
question_per_column = 0
|
||||
|
||||
# Get the expected number of questions per column
|
||||
try:
|
||||
columns_number = []
|
||||
for i in range(0, len(sorted_list2[0])):
|
||||
columns_number.append(len(sorted_list2[0][i]))
|
||||
|
||||
question_per_column = max(columns_number)
|
||||
except:
|
||||
pass
|
||||
|
||||
average_y_distance = 0
|
||||
|
||||
# Calculate average y-axis distance between points
|
||||
for i in range(0, len(sorted_list2[0])):
|
||||
|
||||
if len(sorted_list2[0][i]) == question_per_column:
|
||||
np_ar = np.asarray(sorted_list2[0][i])
|
||||
|
||||
diff = np.diff(np_ar, axis = 1)
|
||||
average_y_distance = np.mean(diff)
|
||||
break
|
||||
|
||||
for a in sorted_list2:
|
||||
index = 0
|
||||
|
||||
for l in a:
|
||||
l = np.asarray(l)
|
||||
l = l[l[:, 1].argsort()]
|
||||
|
||||
question_number = 0 + column_number*question_per_column
|
||||
|
||||
for i in l:
|
||||
cv2.circle(gray_img, (i[0], i[1]), i[2], (0, 255, 0), 2)
|
||||
avg = gray_img[int(i[1])-int(i[2]):int(i[1])+int(i[2])+1, int(i[0])-int(i[2]):int(i[0])+int(i[2])+1]
|
||||
question_number += 1
|
||||
if np.mean(avg) > SELECTED_INTENSITY_THRESHOLD:
|
||||
cv2.circle(gray_img, (i[0], i[1]), 2, (0, 0, 255), 3)
|
||||
selection = Selection(int(i[0]), int(i[1]), ANSWERS_CHOICES[index])
|
||||
selection.question_number = question_number
|
||||
selected_circles.append(selection)
|
||||
index += 1
|
||||
column_number += 1
|
||||
|
||||
# Sort selected answers by the question number
|
||||
selected_circles.sort(key=lambda x: x.question_number, reverse=False)
|
||||
|
||||
answers_checker = AnswersChecker(DEFAULT_ANSWERS_FILE)
|
||||
|
||||
answers_checker.load_answers_from_csv()
|
||||
|
||||
answers_list = []
|
||||
|
||||
for i in selected_circles:
|
||||
q_number = i.question_number
|
||||
q_ans = i.selection
|
||||
answers_list.append([str(q_number), q_ans])
|
||||
|
||||
answers_checker.compare(answers_list)
|
||||
|
||||
# Print the entire report
|
||||
print(answers_checker.get_report())
|
||||
|
||||
print("\n")
|
||||
|
||||
# Get incorrect answers
|
||||
print(answers_checker.get_incorrect_answers())
|
||||
|
||||
# Get correct answers
|
||||
print(answers_checker.get_correct_answers())
|
||||
|
||||
# Save the answers to CSV
|
||||
answers_checker.write_report_to_csv()
|
||||
|
||||
print("\nList of answers")
|
||||
for circle in selected_circles:
|
||||
print(circle.get_answer())
|
||||
|
||||
print("-"*10)
|
||||
# Passed?
|
||||
if passmark_selected:
|
||||
print("Has the person passed the assessment?", answers_checker.has_passed(passmark_points=points_passmark))
|
||||
else:
|
||||
print("Has the person passed the assessment?", answers_checker.has_passed(passmark_percentage=percentage_passmark))
|
||||
|
||||
cv2.imshow('Detected answers', gray_img)
|
||||
|
||||
cv2.waitKey(0)
|
||||
cv2.destroyAllWindows()
|
BIN
odpowiedzi_1.jpg
Normal file
BIN
odpowiedzi_1.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 260 KiB |
Loading…
Reference in New Issue
Block a user