Prześlij pliki do ''

This commit is contained in:
Denis Pagowski 2020-02-01 00:54:18 +00:00
parent 678278effc
commit 2e70f141bd
5 changed files with 379 additions and 0 deletions

41
answers.csv Normal file
View 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
1 Question Answer
2 1 A
3 2 B
4 3 C
5 4 A
6 5 D
7 6 D
8 7 E
9 8 A
10 9 A
11 10 D
12 11 D
13 12 B
14 13 A
15 14 C
16 15 D
17 16 E
18 17 E
19 18 C
20 19 A
21 20 D
22 21 B
23 22 D
24 23 A
25 24 C
26 25 D
27 26 D
28 27 A
29 28 E
30 29 A
31 30 B
32 31 A
33 32 C
34 33 D
35 34 E
36 35 A
37 36 C
38 37 B
39 38 E
40 39 A
41 40 D

41
answers_report.csv Normal file
View 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
1 Question number Selected answer Expected answer Correct
2 1 C A False
3 2 B B True
4 3 C C True
5 4 D A False
6 5 A D False
7 6 A D False
8 7 A E False
9 8 B A False
10 9 C A False
11 10 A D False
12 11 C D False
13 12 D B False
14 13 B A False
15 14 D C False
16 15 A D False
17 16 C E False
18 17 C E False
19 18 A C False
20 19 C A False
21 20 B D False
22 21 B B True
23 22 D D True
24 23 C A False
25 24 D C False
26 25 B D False
27 26 A D False
28 27 C A False
29 28 C E False
30 29 B A False
31 30 A B False
32 31 A A True
33 32 B C False
34 33 C D False
35 34 D E False
36 35 D A False
37 36 B C False
38 37 C B False
39 38 A E False
40 39 C A False
41 40 D D True

BIN
arkusz OCR.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 KiB

297
main.py Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB