AI-Based
Exam Paper
Evaluator
Build a robot that reads answer sheets and grades them automatically — just like a real AI teacher! 🤖📝
What Makes This Project Amazing? 🌟
This is not just a robotics project — it's a real AI system! You'll combine a camera, a microcontroller, and Python code to build something that can actually READ handwriting and GRADE answers. The same technology powers real grading systems used in universities around the world!
The camera "sees" and captures the answer sheet as an image
Tesseract OCR converts handwritten/printed text into digital words
Python compares answers using smart text similarity algorithms
LED, buzzer, and LCD display the score in real time
AI calculates percentage, grade, and pass/fail automatically
Python sends results to Arduino via USB serial port
The AI Pipeline ⚙️
Seven steps happen in seconds when you press the scan button!
📊 LIVE EVALUATION EXAMPLE
TOTAL SCORE
37 / 50 marks
✅ PASS
What You Need 🛒
Most software is free and open-source! The hardware is affordable and reusable for many future projects.
💿 Free Software to Install
Circuit Diagram & Wiring ⚡
The Arduino hardware receives the score from Python and displays it. Wire everything up carefully — check twice before powering on!
🔌 Complete Wiring Table
| Component | Wire / Pin | Arduino Pin | Notes |
|---|---|---|---|
| Green LED 1 (Pass) | Anode + | PIN 3 | 220Ω resistor on cathode → GND |
| Green LED 2 (Pass) | Anode + | PIN 4 | 220Ω resistor on cathode → GND |
| Red LED 1 (Fail) | Anode + | PIN 5 | 220Ω resistor on cathode → GND |
| Red LED 2 (Fail) | Anode + | PIN 6 | 220Ω resistor on cathode → GND |
| Piezo Buzzer | Positive + | PIN 7 | Negative − → GND directly |
| Push Button (Scan) | Pin 1 | PIN 2 | Other pin → GND. Uses INPUT_PULLUP |
| 16×2 LCD (I2C) | SDA / SCL | A4 / A5 | VCC → 5V, GND → GND. I2C: 0x27 |
| Arduino ↔ Computer | USB Serial | USB Port | This is how Python sends scores to Arduino |
Software Architecture 🏗️
This project has two parts that talk to each other — the Python AI brain on your computer, and the Arduino display system.
🐍 Python AI Script (computer)
- Captures image from webcam using OpenCV
- Pre-processes image (grayscale, threshold, denoise)
- Extracts text using pytesseract OCR
- Parses question numbers and answers from text
- Compares each answer to answer_key.json using difflib
- Calculates total score and percentage
- Assigns grade (A/B/C/D/F) and pass/fail
- Sends result to Arduino via pyserial
- Saves evaluation report as a text file
⚙️ Arduino Sketch (hardware)
- Waits for scan button press (Pin 2)
- Signals Python script via Serial when button pressed
- Receives score string from Python (e.g., "74:B:PASS")
- Parses the score, grade, and pass/fail values
- Lights green LEDs for PASS, red for FAIL
- Plays melodic tone on buzzer (happy vs sad)
- Displays score and grade on 16×2 LCD
- Returns to ready state after 8 seconds
Step-by-Step Instructions 🔧
Follow each step carefully. This project has more software than hardware — take your time with the Python setup!
💻 Set Up Python Environment
First, install all the software you need on your computer:
- Download and install Python 3.10+ from python.org (check "Add to PATH" during install!)
- Download and install Tesseract OCR from github.com/tesseract-ocr/tesseract
- Note the Tesseract install path — you'll need it in the Python code
- Open Command Prompt (Windows) or Terminal (Mac/Linux) and run:
pip install opencv-python pytesseract pyserial- Also install:
pip install pillow numpy
⚡ Build the Arduino Circuit
Wire all components to your Arduino using the circuit table above:
- Set up power and GND rails on your breadboard
- Connect Push Button to Pin 2 (other pin to GND)
- Connect 2 Green LEDs with 220Ω resistors to Pins 3 and 4
- Connect 2 Red LEDs with 220Ω resistors to Pins 5 and 6
- Connect Buzzer (+) to Pin 7, (–) to GND
- Connect LCD I2C: SDA → A4, SCL → A5, VCC → 5V, GND → GND
📝 Prepare Your Answer Key File
Create a file called answer_key.json in the same folder as your Python script. This is where you type the correct answers:
{ "subject": "General Science", "total_marks": 50, "pass_percentage": 40, "answers": { "Q1": "photosynthesis is the process by which plants make food using sunlight", "Q2": "mercury venus earth mars jupiter saturn uranus neptune", "Q3": "newton first law second law third law motion inertia", "Q4": "evaporation condensation precipitation collection", "Q5": "nucleus mitochondria cell membrane cytoplasm" }, "marks_per_question": 10 }
🎛️ Set Up the Camera Stand
Position your webcam so it can clearly see the entire answer sheet:
- Mount the webcam about 30–40cm above a flat white surface
- A cardboard box with the webcam taped to the top works great!
- Make sure the lighting is even — no shadows on the paper
- Use a ruler to mark the paper placement area so every sheet goes in the same spot
- Avoid shiny paper — it causes glare that confuses OCR
- Test with cv2.imshow() first to check camera image quality
📤 Upload the Arduino Code
Open Arduino IDE, install required libraries, then upload the Arduino sketch below:
- Install LiquidCrystal_I2C library: Sketch → Include Library → Manage Libraries → search "LiquidCrystal I2C"
- Copy the Arduino code from the section below into a new sketch
- Select: Tools → Board → Arduino Uno
- Select: Tools → Port → your Arduino's COM port
- Note the COM port number — you'll need it in the Python code!
- Click Upload (right arrow button)
🐍 Run the Python AI Script
Now run the main Python evaluator script:
- Open your Python file in VS Code or IDLE
- Update the
SERIAL_PORTvariable to match your Arduino's COM port - Update
TESSERACT_PATHto where you installed Tesseract - Run:
python exam_evaluator.py - A camera preview window will open — you should see live video
- If the camera preview looks good, you're ready to scan!
📄 Prepare the Exam Answer Sheet
Format your answer sheet so OCR can read it properly:
- Print or write answers clearly with question labels (Q1:, Q2:, Q3: etc.)
- Each answer should start on a new line
- Use black pen on white paper for best OCR accuracy
- Example format:
Q1: Plants make food using sunlight
Q2: Mercury Venus Earth Mars Jupiter
Q3: Objects in motion stay in motion
🎉 Test the Full System!
Everything is connected — let's test your AI evaluator!
- Python script should be running and showing camera preview
- LCD on Arduino should show "Exam Evaluator — Ready to Scan"
- Place a completed answer sheet under the camera
- Press the scan button on your Arduino circuit
- Watch the terminal — OCR extracts text, AI compares answers
- Score appears on LCD within 5–10 seconds!
- Green LEDs + happy tune = PASS, Red LEDs + sad tune = FAIL
Python Evaluator Script 🐍
Save this as exam_evaluator.py in the same folder as your answer_key.json file.
# ============================================================ # 🤖 AI-BASED EXAM PAPER EVALUATOR — Python Script # Uses OpenCV + Tesseract OCR + NLP similarity matching # Communicates results to Arduino via Serial (USB) # ============================================================ import cv2 # OpenCV — camera and image processing import pytesseract # Tesseract OCR wrapper for Python import serial # PySerial — talk to Arduino via USB import json # Read the answer key JSON file import time # For delays import re # Regular expressions for text parsing from difflib import SequenceMatcher # NLP-style text similarity from datetime import datetime # For timestamps in reports # ── CONFIGURATION — Change these to match YOUR setup ── TESSERACT_PATH = r"C:\Program Files\Tesseract-OCR\tesseract.exe" # Windows # TESSERACT_PATH = "/usr/bin/tesseract" # Linux / Mac SERIAL_PORT = "COM3" # Change to your Arduino's port (COM3, COM5, /dev/ttyUSB0) BAUD_RATE = 9600 # Must match Arduino Serial.begin() baud rate CAMERA_INDEX = 0 # 0 = default webcam, 1 = second webcam ANSWER_KEY_FILE = "answer_key.json" MIN_SIMILARITY = 0.35 # 35% match minimum to award any marks # Set Tesseract path pytesseract.pytesseract.tesseract_cmd = TESSERACT_PATH # ── Load the Answer Key from JSON ────────────────────── def load_answer_key(): with open(ANSWER_KEY_FILE, "r") as f: data = json.load(f) print(f"✅ Loaded answer key: {data['subject']}") return data # ── Capture Image from Webcam ────────────────────────── def capture_image(cam): print("📸 Capturing image...") ret, frame = cam.read() if not ret: print("❌ Camera capture failed!") return None # Save the raw capture for debugging cv2.imwrite("last_capture.jpg", frame) return frame # ── Pre-process Image for Better OCR ────────────────── def preprocess_image(img): print("🔧 Pre-processing image for OCR...") # Step 1: Convert to grayscale gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Step 2: Resize to 2× size (improves OCR on small text) scaled = cv2.resize(gray, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC) # Step 3: Apply Gaussian blur to remove noise blurred = cv2.GaussianBlur(scaled, (3, 3), 0) # Step 4: Threshold — make black text, white background _, thresh = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) # Step 5: Deskew if needed (optional — helpful for tilted papers) cv2.imwrite("processed.jpg", thresh) return thresh # ── Run OCR to Extract Text ──────────────────────────── def extract_text(processed_img): print("🔤 Running OCR on image...") # Custom config for better accuracy config = "--oem 3 --psm 6 -c tessedit_char_whitelist=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789:. " raw_text = pytesseract.image_to_string(processed_img, config=config) print(f"📄 Extracted text:\n{raw_text[:300]}...") # Show first 300 chars return raw_text # ── Parse Answers from Extracted Text ───────────────── def parse_answers(text): print("🔍 Parsing question answers from text...") answers = {} # Match patterns like "Q1: answer text" or "Q1. answer text" pattern = re.findall(r"Q(\d+)[:\.\s]+([^\n]+)", text, re.IGNORECASE) for q_num, answer in pattern: key = f"Q{q_num}" answers[key] = answer.strip().lower() print(f" Found {key}: {answers[key][:50]}") return answers # ── NLP Similarity: Compare Student Answer to Key ───── def similarity_score(student_ans, correct_ans): # Method 1: difflib SequenceMatcher (fuzzy string match) seq_ratio = SequenceMatcher(None, student_ans.lower(), correct_ans.lower()).ratio() # Method 2: keyword matching (count how many key words appear) key_words = set(correct_ans.lower().split()) stud_words = set(student_ans.lower().split()) common = key_words & stud_words kw_ratio = len(common) / max(len(key_words), 1) # Combined score: 60% keyword + 40% sequence similarity combined = (kw_ratio * 0.6) + (seq_ratio * 0.4) return round(combined, 3) # ── Evaluate All Answers Against the Key ────────────── def evaluate_paper(student_answers, answer_key): print("\n🧠 Evaluating answers with AI...") results = {} total_earned = 0 mpq = answer_key["marks_per_question"] for q, correct in answer_key["answers"].items(): student = student_answers.get(q, "") if not student: results[q] = {"marks": 0, "similarity": 0, "status": "not answered"} print(f" {q}: ❌ Not answered — 0/{mpq}") continue sim = similarity_score(student, correct) marks = round(sim * mpq) if sim < MIN_SIMILARITY: marks = 0 marks = max(0, min(marks, mpq)) # Clamp 0 to max total_earned += marks status = "correct" if sim >= 0.7 else ("partial" if sim >= MIN_SIMILARITY else "wrong") results[q] = {"marks": marks, "similarity": sim, "status": status} print(f" {q}: {marks}/{mpq} ({status}, sim={sim:.2f})") total_marks = answer_key["total_marks"] percentage = round((total_earned / total_marks) * 100) pass_threshold = answer_key["pass_percentage"] passed = percentage >= pass_threshold # Assign letter grade if percentage >= 90: grade = "A+" elif percentage >= 80: grade = "A" elif percentage >= 70: grade = "B" elif percentage >= 60: grade = "C" elif percentage >= 40: grade = "D" else: grade = "F" print(f"\n📊 RESULT: {total_earned}/{total_marks} = {percentage}% | Grade: {grade} | {'✅ PASS' if passed else '❌ FAIL'}") return total_earned, percentage, grade, passed, results # ── Send Result to Arduino via Serial ───────────────── def send_to_arduino(arduino, percentage, grade, passed): if arduino is None: print("⚠️ Arduino not connected — skipping serial send") return # Format: "SCORE:74:B:PASS\n" result_str = f"SCORE:{percentage}:{grade}:{'PASS' if passed else 'FAIL'}\n" arduino.write(result_str.encode("utf-8")) print(f"📡 Sent to Arduino: {result_str.strip()}") # ── Save Evaluation Report ───────────────────────────── def save_report(percentage, grade, passed, results, subject): fname = f"report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt" with open(fname, "w") as f: f.write(f"AI EXAM EVALUATION REPORT\n{'='*40}\n") f.write(f"Subject : {subject}\n") f.write(f"Date/Time: {datetime.now().strftime('%Y-%m-%d %H:%M')}\n") f.write(f"Score : {percentage}% Grade: {grade} Result: {'PASS' if passed else 'FAIL'}\n\n") f.write("QUESTION BREAKDOWN:\n") for q, r in results.items(): f.write(f" {q}: {r['marks']} marks | {r['status']} | similarity {r['similarity']:.0%}\n") print(f"📁 Report saved: {fname}") # ── MAIN PROGRAM ────────────────────────────────────── def main(): print("🚀 AI Exam Evaluator starting...") answer_key = load_answer_key() # Connect to Arduino (optional — system works without it) arduino = None try: arduino = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=2) time.sleep(2) # Arduino resets on serial connect — wait for it print(f"✅ Arduino connected on {SERIAL_PORT}") except: print(f"⚠️ Arduino not found on {SERIAL_PORT} — hardware display disabled") # Open webcam cam = cv2.VideoCapture(CAMERA_INDEX) if not cam.isOpened(): print("❌ Webcam not found! Check CAMERA_INDEX.") return print("📷 Camera ready. Press SPACEBAR in preview window to scan!") print(" (Or press the hardware button on your Arduino circuit)") while True: ret, frame = cam.read() if not ret: break # Show live camera preview with instructions display = frame.copy() cv2.putText(display, "Place paper under camera", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,255,100), 2) cv2.putText(display, "SPACE = Scan | Q = Quit", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (100,200,255), 2) cv2.imshow("AI Exam Evaluator — Press SPACE to scan", display) # Check for Arduino button signal via serial scan_triggered = False if arduino and arduino.in_waiting > 0: msg = arduino.readline().decode("utf-8").strip() if msg == "SCAN": scan_triggered = True print("🔘 Hardware button pressed!") key = cv2.waitKey(1) & 0xFF if key == ord('q'): break if key == 32 or scan_triggered: # Spacebar = 32 img = capture_image(cam) processed = preprocess_image(img) raw_text = extract_text(processed) stu_ans = parse_answers(raw_text) earned, pct, grade, passed, results = evaluate_paper(stu_ans, answer_key) send_to_arduino(arduino, pct, grade, passed) save_report(pct, grade, passed, results, answer_key["subject"]) cam.release() cv2.destroyAllWindows() if arduino: arduino.close() print("👋 Evaluator closed. Goodbye!") if __name__ == "__main__": main()
Arduino Evaluator Sketch ⚙️
Upload this to your Arduino Uno. It listens for the result from Python and displays it with LEDs, buzzer, and LCD.
// ================================================== // 🤖 AI EXAM EVALUATOR — Arduino Display Module // Receives score from Python via Serial USB // Displays result: LEDs + Buzzer + 16x2 LCD // ================================================== #include <Wire.h> #include <LiquidCrystal_I2C.h> #include <Servo.h> // LCD: I2C address 0x27, 16 columns, 2 rows LiquidCrystal_I2C lcd(0x27, 16, 2); // --- Pin Definitions --- const int BTN_PIN = 2; // Scan trigger button (INPUT_PULLUP) const int GREEN_LED1 = 3; // PASS indicator LED 1 const int GREEN_LED2 = 4; // PASS indicator LED 2 const int RED_LED1 = 5; // FAIL indicator LED 1 const int RED_LED2 = 6; // FAIL indicator LED 2 const int BUZZER_PIN = 7; // Piezo buzzer // --- State --- bool waitingForResult = false; String serialBuffer = ""; // ================================================== // SETUP // ================================================== void setup() { Serial.begin(9600); // Set pin modes pinMode(BTN_PIN, INPUT_PULLUP); // Button uses internal pull-up resistor pinMode(GREEN_LED1, OUTPUT); pinMode(GREEN_LED2, OUTPUT); pinMode(RED_LED1, OUTPUT); pinMode(RED_LED2, OUTPUT); pinMode(BUZZER_PIN, OUTPUT); // All LEDs off at start allLedsOff(); // Start LCD lcd.init(); lcd.backlight(); showReadyScreen(); // Startup blink for (int i = 0; i < 2; i++) { allLedsOn(); delay(200); allLedsOff(); delay(200); } Serial.println("READY"); // Tell Python we're ready } // ================================================== // LOOP — Check button + receive serial data // ================================================== void loop() { // --- Check scan button press --- if (digitalRead(BTN_PIN) == LOW && !waitingForResult) { delay(50); // Debounce if (digitalRead(BTN_PIN) == LOW) { waitingForResult = true; Serial.println("SCAN"); // Tell Python to start scanning showScanningScreen(); playBeep(800, 100); // Short beep = button acknowledged } } // --- Listen for result from Python via Serial --- while (Serial.available() > 0) { char c = (char)Serial.read(); if (c == '\n') { serialBuffer.trim(); if (serialBuffer.startsWith("SCORE:")) { processResult(serialBuffer); serialBuffer = ""; waitingForResult = false; } serialBuffer = ""; } else { serialBuffer += c; } } } // ================================================== // PROCESS RESULT: parse "SCORE:74:B:PASS" // ================================================== void processResult(String data) { // Split by ':' → [SCORE, 74, B, PASS] int p1 = data.indexOf(':'); int p2 = data.indexOf(':', p1+1); int p3 = data.indexOf(':', p2+1); String scoreStr = data.substring(p1+1, p2); String grade = data.substring(p2+1, p3); String result = data.substring(p3+1); int score = scoreStr.toInt(); bool passed = (result == "PASS"); // Show result on LCD lcd.clear(); lcd.setCursor(0, 0); lcd.print("Score: "); lcd.print(score); lcd.print("% Gr:"); lcd.print(grade); lcd.setCursor(0, 1); if (passed) { lcd.print("** PASS - Well Done"); showPassAnimation(); playHappyTune(); } else { lcd.print("** FAIL - Study More"); showFailAnimation(); playSadTune(); } delay(8000); // Show result for 8 seconds allLedsOff(); showReadyScreen(); } // ── LED Helpers ────────────────────────────── void allLedsOff() { digitalWrite(GREEN_LED1, LOW); digitalWrite(GREEN_LED2, LOW); digitalWrite(RED_LED1, LOW); digitalWrite(RED_LED2, LOW); } void allLedsOn() { digitalWrite(GREEN_LED1, HIGH); digitalWrite(GREEN_LED2, HIGH); digitalWrite(RED_LED1, HIGH); digitalWrite(RED_LED2, HIGH); } void showPassAnimation() { for (int i = 0; i < 4; i++) { digitalWrite(GREEN_LED1, HIGH); delay(120); digitalWrite(GREEN_LED2, HIGH); delay(120); allLedsOff(); delay(120); } digitalWrite(GREEN_LED1, HIGH); // Keep green on digitalWrite(GREEN_LED2, HIGH); } void showFailAnimation() { for (int i = 0; i < 5; i++) { digitalWrite(RED_LED1, HIGH); digitalWrite(RED_LED2, HIGH); delay(200); allLedsOff(); delay(200); } digitalWrite(RED_LED1, HIGH); // Keep red on digitalWrite(RED_LED2, HIGH); } // ── Buzzer Tunes ───────────────────────────── void playBeep(int freq, int dur) { tone(BUZZER_PIN, freq, dur); delay(dur+20); } void playHappyTune() { // Three rising notes — success sound! playBeep(523, 150); // C5 playBeep(659, 150); // E5 playBeep(784, 300); // G5 playBeep(1047, 400); // C6 — high finish! } void playSadTune() { // Three falling notes — try again sound playBeep(440, 200); // A4 playBeep(349, 200); // F4 playBeep(294, 400); // D4 — low end } // ── LCD Screens ────────────────────────────── void showReadyScreen() { lcd.clear(); lcd.setCursor(0, 0); lcd.print(" Exam Evaluator "); lcd.setCursor(0, 1); lcd.print(" Ready to Scan! "); } void showScanningScreen() { lcd.clear(); lcd.setCursor(0, 0); lcd.print(" Scanning... "); lcd.setCursor(0, 1); lcd.print(" AI thinking..."); }
Safety & Ethics 🛡️
This project teaches responsible use of AI — just as important as the coding skills!
Adult Supervision
Have a parent or teacher help with software installation and wiring verification
AI is Not Perfect
This evaluator makes mistakes! It should assist teachers, not replace human judgment completely
No Power While Wiring
Always disconnect the Arduino before changing any circuit connections
Protect Answer Keys
Keep your answer_key.json file private — don't share it with students being evaluated!
Camera Privacy
Only use the webcam for scanning exam papers. Turn it off when not in use
Keep Electronics Dry
No drinks near your circuit — liquids and electronics are dangerous together
Frequently Asked Questions ❓
Something not working? These are the most common issues young engineers run into!
Pytesseract says "TesseractNotFoundError" — what do I do?
Tesseract OCR needs to be installed separately from pytesseract. Download Tesseract from github.com/tesseract-ocr and install it. Then update the TESSERACT_PATH in your Python code to the exact folder where you installed it (e.g., C:\Program Files\Tesseract-OCR\tesseract.exe on Windows).
OCR reads garbage text — letters and symbols look wrong!
This usually means poor lighting or a blurry image. Try: (1) move to a brighter room, (2) add a desk lamp pointing at the paper, (3) hold the camera steady or use a tripod, (4) print the exam paper instead of handwriting it, (5) increase the resize scale in preprocess_image() from 2 to 3.
Python can't find the Arduino serial port!
Open Arduino IDE → Tools → Port to see which port your Arduino is on (e.g., COM3 on Windows, /dev/ttyUSB0 on Linux). Make sure the Arduino IDE's Serial Monitor is CLOSED — only one program can use the serial port at a time. Update SERIAL_PORT in the Python script.
The score is always 0% even though answers look correct!
Check that your question labels in the answer sheet match exactly (Q1:, Q2: etc.) including the colon. Also check that MIN_SIMILARITY = 0.35 isn't too high for your answer key. Try printing to the terminal to see what text OCR is extracting and what the parsed answers look like.
Can this AI evaluator grade handwritten answers?
Handwriting recognition (HTR) is much harder than printed text OCR! The current version works best with printed/typed answers. For handwriting, you'd need a neural network model like TrOCR from Microsoft (pip install transformers) — a great upgrade challenge for advanced students!
How accurate is the NLP answer matching?
For keyword-based questions with factual answers, accuracy is around 70–85%. It works best when answers contain specific keywords (planet names, science terms, dates). It's less accurate for opinion questions or complex explanations. This is why the project is a learning tool — not a replacement for human teachers!
Level Up Challenges 🚀
Mastered the basic evaluator? These upgrades will take your project to a whole new level!
Handwriting OCR
Integrate TrOCR neural network to read actual handwritten answers
Web Dashboard
Build a Flask web app to view all evaluation reports with charts and history
Mobile App
Use Kivy or Flutter to build a phone app that scans papers with your phone camera
Voice Feedback
Add pyttsx3 to make the AI speak the score and grade out loud

Comments
Post a Comment