import serial import time import cv2 import os import mysql.connector from mysql.connector import Error from mysql.connector import errorcode from PIL import Image import os import time import numpy as np import math correct_answer='' countpic=0 countcheck=0 item_no=0 image_path='' oldscore=0 ans_guide = [None, "A", "B", "C", "D", "E"] def connect(): return mysql.connector.connect( host="srv707.hstgr.io", user="u726772720_exavericheck", password="Exavericheck@01", port=3306, database="u726772720_exavericheck" ) conn = mysql.connector.connect(host='srv707.hstgr.io', database='u726772720_exavericheck', user='u726772720_exavericheck', port=3306, #ssl_disabled=True, password='Exavericheck@01') cursor = conn.cursor() try: if conn.is_connected(): print("Connected to MySQL") except Error as e: print("Error while connecting:", e) 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) ser = serial.Serial( port='COM4', \ 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 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_12_51_15_Pro.mp4' cap = cv2.VideoCapture(0) while True: 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, (1024,600)) answer_from_sheet, score, frame = OMR_Checker(frame,correct_answer) print(answer_from_sheet, score) cv2.imshow('Video', frame) 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': print("Capture and Recognize") answer_from_sheet, score, frame = OMR_Checker(frame,correct_answer) print(answer_from_sheet, score) elif joined_seq == 'save': print("Saving Data") 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}") if score != None: wrong=item_no-score if score >=passing_value : stat='Passed' else : stat='Failed' answer=np.array([answer_from_sheet]) data_list = answer.tolist() if conn.is_connected(): cursor = conn.cursor() cursor.execute("SELECT * FROM tbl_exam_summaries") else: print("Connection lost. Reconnecting...") conn = connect() # Re-establish connection here try: if conn.is_connected(): print("Connected to MySQL") cursor = conn.cursor() 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, filepath, 1, item_no, score, wrong, stat) #answers, print , reset checkno cursor.execute(sql, val) conn.commit() except Error as e: print("Error while connecting:", e) elif joined_seq == 'print': print("Printing") with open("Output.txt", "w") as text_file: text_file.write("UID : %s " % (image_path)) text_file.write("\nScore: %s" % ( score ) + "/72" ) os.startfile("Output.txt", "print") # Break on ESC key if cv2.waitKey(30) & 0xFF == 27: break cap.release() cv2.destroyAllWindows() # print(OMR_Checker("IMG_20250404_203604_606.jpg",[]))