OCR / scripts /benchmark_easyocr.py
Zeyadmohamed21's picture
Deploy with CPU-only fix for ZeroGPU compatibility
a39757f verified
Raw History Blame Contribute Delete
3.53 kB
"""Reproducible local OCR benchmark; synthetic degradations are not independent documents.
Run from repository root: python -m scripts.benchmark_easyocr --output path.json
"""
import argparse
import json
import time
from pathlib import Path
import cv2
import numpy as np
from app.infrastructure.ocr.easyocr_engine import EasyOCREngine
from app.services.image_processing.quality import analyze_image_quality
from app.services.image_processing.preprocessor import AdaptivePreprocessor
from app.services.layout.reading_order import organize_reading_order
from app.services.layout.layout_analyzer import LayoutAnalyzer
from app.services.ocr.evaluator import evaluate_detection_quality
from app.services.ocr.passes import run_ocr_passes
from app.services.ocr.metrics import text_metrics
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--image', type=Path, default=Path('artifacts/easyocr_validation/source.jpg'))
parser.add_argument('--expected', type=Path, default=Path('artifacts/easyocr_validation/expected.txt'))
parser.add_argument('--output', type=Path, required=True)
args = parser.parse_args()
image = cv2.imread(str(args.image))
if image is None:
raise ValueError('Cannot decode benchmark image')
expected = args.expected.read_text(encoding='utf-8').strip()
h, w = image.shape[:2]
matrix = cv2.getRotationMatrix2D((w / 2, h / 2), 5, 1)
# Expanded canvas preserves all text in the skewed sample.
nw, nh = int(w * abs(matrix[0, 0]) + h * abs(matrix[0, 1])) + 2, int(h * abs(matrix[0, 0]) + w * abs(matrix[0, 1])) + 2
matrix[0, 2] += nw / 2 - w / 2
matrix[1, 2] += nh / 2 - h / 2
rng = np.random.default_rng(42)
samples = {
'original': image,
'blur': cv2.GaussianBlur(image, (3, 3), 0.7),
'low_contrast': np.clip(image.astype(float) * .30 + 145, 0, 255).astype(np.uint8),
'noise': np.clip(image.astype(float) + rng.normal(0, 7, image.shape), 0, 255).astype(np.uint8),
'skew_5': cv2.warpAffine(image, matrix, (nw, nh), borderValue=(255, 255, 255)),
'rotation_90': cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE),
'small': cv2.resize(image, None, fx=.65, fy=.65, interpolation=cv2.INTER_AREA),
}
engine = EasyOCREngine() # Model load excluded from timings.
rows = []
for name, sample in samples.items():
start = time.perf_counter()
quality = analyze_image_quality(sample)
primary, variants = AdaptivePreprocessor.preprocess_adaptive(sample, quality)
result, _, attempts = run_ocr_passes(engine, primary, variants, quality)
calls = len(attempts)
ordered = organize_reading_order(result.blocks, result.image_width)
layout, _, _ = LayoutAnalyzer.analyze(ordered, result.image_width, result.image_height)
actual = '\n'.join(line.text for line in layout.lines)
rows.append(dict(id=name, expected_text=expected, actual_text=actual,
metrics=text_metrics(expected, actual), calls=calls,
latency_ms=round((time.perf_counter()-start)*1000, 2),
confidence=result.average_confidence,
raw_blocks=[dict(text=b.raw_text, confidence=b.confidence, box=b.box.points) for b in ordered]))
args.output.write_text(json.dumps(rows, ensure_ascii=False, indent=2), encoding='utf-8')
print(name, rows[-1]['metrics'], 'calls', calls, flush=True)
if __name__ == '__main__':
main()