import cv2
import json
import base64
import requests
from paddleocr import PaddleOCR
from pydantic import BaseModel
---------------------------
CONFIG
---------------------------
IMAGE_PATH = "voter_card.jpg"
OLLAMA_URL = "
http://localhost:11434/api/chat"
MODEL = "qwen2.5vl:7b"
---------------------------
SCHEMA
---------------------------
class VoterID(BaseModel):
epic_number: str | None = None
name: str | None = None
father_name: str | None = None
husband_name: str | None = None
dob: str | None = None
gender: str | None = None
---------------------------
OCR
---------------------------
ocr = PaddleOCR(
use_angle_cls=True,
lang="en"
)
result = ocr.ocr(IMAGE_PATH, cls=True)
ocr_blocks = []
for line in result[0]:
bbox = line[0]
text = line[1][0]
confidence = float(line[1][1])
ocr_blocks.append({
"text": text,
"confidence": round(confidence, 3),
"bbox": bbox
})
sort by reading order
ocr_blocks.sort(
key=lambda item: (
min(p[1] for p in item["bbox"]),
min(p[0] for p in item["bbox"])
)
)
---------------------------
IMAGE TO BASE64
---------------------------
with open(IMAGE_PATH, "rb") as f:
image_b64 = base64.b64encode(f.read()).decode()
---------------------------
PROMPT
---------------------------
prompt = f"""
You are an expert Indian document extraction engine.
Document may be:
- Voter ID
- PAN Card
- Aadhaar
- Driving License
Use BOTH:
- The image
- OCR text
- OCR coordinates
OCR Results:
{json.dumps(ocr_blocks, ensure_ascii=False, indent=2)}
Return ONLY valid JSON.
Schema:
{{
"document_type": null,
"id_number": null,
"name": null,
"father_name": null,
"husband_name": null,
"dob": null,
"gender": null
}}
Rules:
- Correct obvious OCR mistakes.
- Use image when OCR is incorrect.
- Return null if field missing.
- No explanation.
- Output JSON only.
"""
---------------------------
OLLAMA VISION CALL
---------------------------
payload = {
"model": MODEL,
"messages": [
{
"role": "user",
"content": prompt,
"images": [image_b64]
}
],
"stream": False
}
response = requests.post(
OLLAMA_URL,
json=payload,
timeout=300
)
response.raise_for_status()
content = response.json()["message"]["content"]
remove markdown wrappers
content = (
content.replace("json", "") .replace("", "")
.strip()
)
print("\nRAW RESPONSE\n")
print(content)
---------------------------
PARSE JSON
---------------------------
try:
data = json.loads(content)
print("\nSTRUCTURED DATA\n")
print(json.dumps(data, indent=4))
except Exception as e:
print("JSON parsing failed")
print(e)