Upload a handwritten answer sheet, it detects each answer block, reads the text using OCR, and gives marks by comparing it with the teacher's answer.
- Takes a photo/scan of a handwritten answer sheet
- Detects where each answer block is on the page
- Reads the text inside each block using Tesseract OCR
- Compares extracted text with the teacher's answer using TF-IDF cosine similarity
- Returns marks out of 10, a grade, matched/missed keywords, and the sheet with colored boxes drawn on each block
AUTOMATED_EDTECH_GRADING/
│
├── project/ # main Flask app (production code)
│ ├── templates/
│ │ └── index.html # frontend UI
│ ├── uploads/ # uploaded sheets get saved here
│ ├── app.py # Flask server — connects everything
│ ├── ocr.py # block detection + Tesseract OCR
│ ├── marking.py # TF-IDF scoring logic
│ └── requirements.txt
│
├── answer_sheet_ocr.ipynb # testing OCR pipeline (notebook version)
├── marking.ipynb # testing marking logic (notebook version)
├── approach.md # research notes — what I tried and why
├── sheet.png # sample answer sheet for testing
├── teacher_answer.txt # sample teacher answer
├── annotated_sheet_results.json
└── presentation.html # project presentation for teacher
The
.ipynbfiles outside theproject/folder were for testing and experimenting. The actual working code is insideproject/as.pyfiles.
First I read about LayoutLM and Tesseract, but Tesseract OCR will not automatically detect "answers" as logical regions — it only detects text-level boxes like characters, words, lines, and paragraphs.
That's why I thought of using YOLO, which can automatically detect answer boxes. Instead of training YOLO from scratch, I tried using a pre-trained YOLO model through LayoutParser. These models often work reasonably well on answer sheets because answers appear as large text blocks — but the pre-trained ones weren't trained on exam sheets and training a custom one needed too much labelled data.
So I ended up using OpenCV's horizontal projection — count ink pixels per row, rows with zero ink are the gaps between answers, group the rest into blocks. No training data needed, runs instantly.
| Tool | What it does |
|---|---|
| OpenCV | Block detection, image preprocessing, drawing boxes |
| Tesseract OCR | Reads text from each detected block |
| scikit-learn (TF-IDF) | Converts answers to vectors for comparison |
| Cosine Similarity | Measures how similar student and teacher answers are |
| Flask | Web server — connects browser to Python code |
| scipy | Smoothing the projection to bridge gaps within one answer |
1. Install Tesseract
# Linux / Colab
sudo apt install tesseract-ocr
# macOS
brew install tesseract
# Windows — download from:
# https://github.com/UB-Mannheim/tesseract/wiki2. Install Python dependencies
cd project
pip install -r requirements.txt3. Run the server
python app.pyOpen http://127.0.0.1:5000 in your browser.
- Upload a photo or scan of the answer sheet (PNG or JPG)
- Type or paste the correct answer in the text box
- Click Evaluate
- It shows the annotated sheet with colored blocks, marks out of 10, and which keywords were matched or missed
For multiple questions on one sheet — right now paste all model answers together in the text box (Q1 answer, then Q2, then Q3). The system combines all detected blocks and compares them as one. Per-question scoring is planned for the next version.
- Tesseract struggles with very messy handwriting — words get misread
- No per-question scoring yet — whole sheet gets one combined score
- TF-IDF matches words, not meaning — paraphrased answers score lower than they should
- Only image uploads supported, no PDF yet
Phase 2
- Detect question number labels ("Q1", "1)", "2.") automatically to split and score each question separately
- Accept PDF uploads
- Deploy online
Phase 3
- Replace TF-IDF with Sentence-BERT for meaning-aware scoring
- Batch process an entire class's answer sheets
- Generate a full report per student
flask
opencv-python
pillow
pytesseract
numpy
scipy
scikit-learn