OpenCV/main.py

298 lines
9.1 KiB
Python

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()