import serial import time import cv2 import os import mysql.connector from PIL import Image import os import time import numpy as np correct_answer='' countpic=0 countcheck=0 item_no=0 image_path='' ans_guide = [None, "A", "B", "C", "D", "E"] mydb = mysql.connector.connect(host='srv707.hstgr.io', database='u726772720_exavericheck', user='u726772720_exavericheck', password='Exavericheck@01') cursor = mydb.cursor() sql = "SELECT exam_unique_id,correct_ans,item_no,sub_value,passing_value FROM tbl_settings s INNER JOIN tbl_exam_answers ea ON s.value=ea.id WHERE s.id=1" cursor.execute(sql) # get all records records = cursor.fetchall() for row in records: correct_answer = row[1] image_path=row[0] item_no=row[2] passing_value=row[4] sql = "SELECT value,sub_value FROM tbl_settings s WHERE s.id=2" cursor.execute(sql) # get all records records = cursor.fetchall() for row in records: countpic = int(row[0]) countcheck = int(row[1]) if not os.path.exists(image_path): os.makedirs(image_path) #Image Capture capcam = cv2.VideoCapture(0) if not capcam.isOpened(): print("Cannot open camera") exit() ser = serial.Serial( port='COM7',\ baudrate=9600,\ parity=serial.PARITY_NONE,\ stopbits=serial.STOPBITS_ONE,\ bytesize=serial.EIGHTBITS,\ timeout=0) print("connected to: " + ser.portstr) #this will store the line seq = [] count = 1 def round_off(number): DP = number - int(number) if DP > 0.99: return int(number) +1 else: return int(number) def rotate_image_bound(image, angle): (h, w) = image.shape[:2] center = (w / 2, h / 2) # Get rotation matrix M = cv2.getRotationMatrix2D(center, angle, 1.0) # Calculate the new bounding dimensions of the image cos = abs(M[0, 0]) sin = abs(M[0, 1]) # Compute new dimensions new_w = int((h * sin) + (w * cos)) new_h = int((h * cos) + (w * sin)) # Adjust the rotation matrix to take into account translation M[0, 2] += (new_w / 2) - center[0] M[1, 2] += (new_h / 2) - center[1] # Perform the rotation return cv2.warpAffine(image, M, (new_w, new_h)) def getAnswer(roi): hh, ww, ss = roi.shape # Convert the image to grayscale gray_image = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY) # Apply Gaussian blur to reduce noise and improve edge detection blurred_image = cv2.GaussianBlur(gray_image, (5, 5), 1.4) # Perform Canny edge detection edges = cv2.Canny(blurred_image, 50, 100) kernel = np.ones((5, 5), np.uint8) edges = cv2.dilate(edges, kernel, iterations=1) contours1, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) bboxes = [] for c1 in contours1: x, y, w, h = cv2.boundingRect(c1) bboxes.append((x, y, w, h)) ans = [] bboxes.sort(key=sort_criteria_mc) for idx, (x, y, w, h) in enumerate(bboxes, start=1): answer = ans_guide[int(((x + x+w)/(2*ww))*6)] number = round_off(((y + y+h)/(2*hh))*6) cv2.rectangle(roi, (x, y), (x + w, y + h), (0, 255, 0), 2) # cv2.putText(roi, str(answer), (x + 5, y + 20), # cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2) ans.append([number, answer]) required_numbers = {0, 1, 2, 3, 4, 5} # Create a dictionary of the current numbers sublist_dict = {item[0]: item[1] for item in ans} # Fill in missing numbers with empty string for num in required_numbers: if num not in sublist_dict: sublist_dict[num] = '' # Rebuild the sublist with filled values filled_sublist = [[num, sublist_dict[num]] for num in sorted(sublist_dict.keys())] # print(ans) # cv2.imshow("roi", roi) # cv2.imshow("edges", edges) # cv2.waitKey(0) return filled_sublist def sort_criteria_mc(box): x, y, w, h = box return (round(y / 10) * 10, x) def sort_criteria(box): x, y, w, h = box return (round((x+x+w)/ 10) * 10, y) def captureMe(): timestamp = int(time.time()) #filename = f"image_{timestamp}.jpg" filename = f"{countpic}.png" filepath = os.path.join(f"{image_path}", filename) filepath1 = os.path.join("record", f"{image_path}_" + filename) # Save the image with the generated filename cv2.imwrite(filepath, frame) cv2.imwrite(filepath1, frame) print(f"Image saved as {filepath}") countpic += 1 sql = "UPDATE tbl_settings SET value = %s WHERE id = %s" val = (countpic, "2") cursor.execute(sql, val) mydb.commit() return "Capture Success" def OMR_Checker(img, ans_key:list): """ Inputs: img_filename (str): Path or filename of the image containing the filled OMR sheet. ans_key (list): List of correct answers (e.g., ['A', 'C', 'B', 'D', ...]). Returns: answer_from_sheet (list): List of detected answers from the OMR sheet. score (int): Number of correct answers based on comparison with the answer key. """ if isinstance(img, str): img = cv2.imread(img) img = rotate_image_bound(img, 90) height, width, size = img.shape img = cv2.resize(img, (int(width/3), int(height/3))) #print(img.shape) clone = img.copy() hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # Fixed H and V ranges lower_h, upper_h = 89, 130 lower_v, upper_v = 0, 255 # Thresholds min_area = 15000 max_area = 35000 min_w, max_w = 100, 200 target_boxes = 13 found = False answer_from_sheet = [] for s in range(0, 256): lower = np.array([lower_h, s, lower_v]) upper = np.array([upper_h, 255, upper_v]) mask = cv2.inRange(hsv, lower, upper) kernel = np.ones((5, 5), np.uint8) mask = cv2.dilate(mask, kernel, iterations=4) contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) bounding_boxes = [] for cnt in contours: area = cv2.contourArea(cnt) x, y, w, h = cv2.boundingRect(cnt) if min_area < area < max_area: bounding_boxes.append((x, y, w, h)) if len(bounding_boxes) == target_boxes: bounding_boxes.sort(key=sort_criteria) temp_img = img.copy() for idx, (x, y, w, h) in enumerate(bounding_boxes, start=1): cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2) cv2.putText(img, str(idx), (x + 5, y + 20), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2) if idx > 1: anss = getAnswer(temp_img[y:y+h,x:x+w]) for a in anss: answer_from_sheet.append(a[1]) score = sum(1 for a, b in zip(ans_key, answer_from_sheet) if a == b) return answer_from_sheet, score, img return None,None, img if __name__ == "__main__": # import cv2 # Load the video video_path = 'WIN_20250409_14_04_51_Pro.mp4' answer_key = [] #72-item key to correction answer_key = ['A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A' ,'A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A'] answer_key = ['B', 'C', 'C', 'C', 'B', 'D', 'A', 'B', 'C', 'D', 'D', 'E', 'A', 'B', 'C', 'D', 'B', 'A', 'B', 'A', 'B', 'C', 'D', 'E', 'B', 'B', 'B', 'B', 'D', 'E', 'A', 'A', 'B', 'C', 'D', 'E', 'A', 'A', 'B', 'B', 'C', 'C', 'A', 'B', 'B', 'A', 'A', 'B', 'E', 'E', 'D', 'C', 'B', 'A', 'A', 'A', 'B', 'C', 'C', 'D', 'A', 'B', 'B', 'A', 'A', 'C', 'B', 'B', 'C', 'B', 'A', 'A'] answer_key = correct_answer.split(",") print(answer_key) # Output: ['apple', 'banana', 'cherry'] # print ( data_list = answer.tolist()) while True: ret, frame = capcam.read() if not ret: print("Can't receive frame. Exiting ...") break # Display the frame cv2.imshow('Capture Process', frame) key = cv2.waitKey(1) for c in ser.read(): seq.append(chr(c)) #convert from ANSII joined_seq = ''.join(str(v) for v in seq) #Make a string from array if chr(c) == '\n': print("Line " + str(count) + ': ' + joined_seq) seq = [] count += 1 break if joined_seq == 'capture': timestamp = int(time.time()) #filename = f"image_{timestamp}.jpg" filename = f"{countpic}.png" filepath = os.path.join(f"{image_path}", filename) filepath1 = os.path.join("record", f"{image_path}_" + filename) # Save the image with the generated filename cv2.imwrite(filepath, frame) cv2.imwrite(filepath1, frame) print(f"Image saved as {filepath}") countpic += 1 sql = "UPDATE tbl_settings SET value = %s WHERE id = %s" val = (countpic, "2") cursor.execute(sql, val) mydb.commit() if joined_seq == 'process': print("Detection on process...") folder_dir = image_path for images in os.listdir(folder_dir): # check if the image ends with png if (images.endswith(".png")): print(images) filename = f"{images}" filepath = os.path.join(f"{image_path}", filename) print(filepath) cap = cv2.VideoCapture(filepath) ret, frame = cap.read() if not ret: # If video ends or cannot read the frame, reset to beginning cap.set(cv2.CAP_PROP_POS_FRAMES, 0) continue # Display the frame frame = cv2.resize(frame, (1280,720)) answer_from_sheet, score, frame = OMR_Checker(frame,answer_key) print(answer_from_sheet, score) wrong=item_no-score if score >=passing_value : stat='Passed' else : stat='Failed' answer=np.array([frame]) data_list = answer.tolist() sql = "INSERT INTO tbl_exam_summaries (exam_unique_id, exam_pic, exam_stud_answer, exam_total_item,exam_total_correct, exam_wrong, exam_status) VALUES (%s,%s,%s,%s,%s,%s,%s)" val = (image_path,images,1,item_no,score,wrong,stat) #delete images, print , reset checkno cursor.execute(sql, val) mydb.commit() os.remove(filepath) #cv2.imshow('Image', frame) #break #ser.write(bytes(b'done')) if key == 27: # ESC to exit break elif key == 32: # Spacebar to capture timestamp = int(time.time()) #filename = f"image_{timestamp}.jpg" filename = f"{countpic}.png" filepath = os.path.join(f"{image_path}", filename) filepath1 = os.path.join("record", f"{image_path}_" + filename) # Save the image with the generated filename cv2.imwrite(filepath, frame) cv2.imwrite(filepath1, frame) print(f"Image saved as {filepath}") countpic += 1 sql = "UPDATE tbl_settings SET value = %s WHERE id = %s" val = (countpic, "2") cursor.execute(sql, val) mydb.commit() elif key == ord('a'): # If 'a' is pressed, exit the loop print("select process"); sql = "SELECT exam_unique_id,correct_ans,item_no,sub_value,passing_value FROM tbl_settings s INNER JOIN tbl_exam_answers ea ON s.value=ea.id WHERE s.id=1" cursor.execute(sql) # get all records records = cursor.fetchall() for row in records: correct_answer = row[1] image_path=row[0] item_no=row[2] passing_value=row[4] sql = "SELECT value,sub_value FROM tbl_settings s WHERE s.id=2" cursor.execute(sql) # get all records records = cursor.fetchall() for row in records: countpic = int(row[0]) countcheck = int(row[1]) if not os.path.exists(image_path): os.makedirs(image_path) elif key == ord('q'): # If 'q' is pressed, exit the loop print("Recognition process"); folder_dir = image_path for images in os.listdir(folder_dir): # check if the image ends with png if (images.endswith(".png")): filename = f"{images}" filepath = os.path.join(f"{image_path}", filename) print(filepath) cap = cv2.VideoCapture(filepath) ret, frame = cap.read() if not ret: # If video ends or cannot read the frame, reset to beginning cap.set(cv2.CAP_PROP_POS_FRAMES, 0) continue # Display the frame score=0 frame = cv2.resize(frame, (1280,720)) answer_from_sheet, score, frame = OMR_Checker(frame,answer_key) print(answer_from_sheet, score) if score>0 : wrong=item_no-score if score >=passing_value : stat='Passed' else : stat='Failed' answer=np.array([frame]) data_list = answer.tolist() sql = "INSERT INTO tbl_exam_summaries (exam_unique_id, exam_pic, exam_stud_answer, exam_total_item,exam_total_correct, exam_wrong, exam_status) VALUES (%s,%s,%s,%s,%s,%s,%s)" val = (image_path,images,1,item_no,score,wrong,stat) #answers, print , reset checkno cursor.execute(sql, val) mydb.commit() #os.remove(filepath) #cv2.imshow('Image', frame) #break cap.release() cv2.destroyAllWindows()