4.1 KiB
3-variant OCR cross-referencing experiment
Tested whether running Tesseract over three image variants of the same receipt photo (original color photo, plain grayscale, and the existing denoised/contrast-enhanced preprocessing) and asking the extraction LLM to cross-reference all three per field would recover more correct data than the single-OCR-pass baseline, particularly for blurry/low-quality photos.
Result
Mixed. One more receipt matched overall, two fields improved
meaningfully, but the original target field (litres) did not improve,
and total_price regressed.
| Metric | Baseline (1 OCR pass) | New (3-variant cross-ref) |
|---|---|---|
| Matched to ground truth | 15/17 | 16/17 |
| price_per_litre | 11/15 (73%) | 13/16 (81%) ↑ |
| litres | 9/15 (60%) | 9/16 (56%) — unchanged, still worst |
| discount | 11/15 (73%) | 15/16 (94%) ↑↑ |
| total_price | 12/15 (80%) | 11/16 (69%) ↓ |
Root causes identified
- The litres/price-per-litre line is the hardest OCR target, regardless
of variant count. Noise characters (
x2p 02,*/p) precede the real numbers and the litre symbollreads as1/]/|in all three variants alike, so this field stays wrong at the same rate. - Three OCR readings don't guarantee the model picks the right one.
On the blurry Jet receipt, the correct total
57,13survives intact in the original-photo variant, but the model still output37.13. Extra (noisy) input sometimes confuses the small 3B model rather than helping it reconcile. total_priceregressed — cross-referencing conflicting reads apparently made the model second-guess fields it used to get right from a single clean reading.- Severe performance/stability cost on this 2-core, GPU-less VM: extraction latency became wildly inconsistent (1–19 minutes per receipt vs. seconds before), and one run hit a hard timeout before the request timeout was raised from 300s → 900s. The full 17-receipt batch that used to finish in ~11 minutes now takes well over an hour.
Working examples (fully correct — all 4 fields + date matched)
IMG_20260726_095120207,095321692,095428941,095442114,095510481,095519964— all Shell receipts with clean, high-contrast OCR text across all three variants.
Not-working examples
IMG_20260525_182252232(the blurry Jet receipt that prompted this experiment) — still no date match at all (extracted13.05.2026vs. true15.05.2026). All three OCR variants scramble the header; even though the correct total57,13is legible in the original-photo variant, the model output37.13instead.IMG_20260525_182144541— worst case, everything wrong except date/time/transaction number: extractedlitres=1.205(should be ~23),price_per_litre=1.389(plausible alone, but paired with nonsense litres),discount=-0.08(sign flipped),total_price=5.33. All three OCR variants are near-unreadable for this receipt.IMG_20260525_182116766—litresread correctly (20.24, matches the OCR text"20,24 1 1979 EUR/]"cleanly) butprice_per_litrecame out as6.01instead of~1.979— a pure extraction/reconciliation error, not an OCR gap, since the right number is sitting right there in the text.IMG_20260726_095312079,095335197,095416655,095452747,095501803— all wrong only onlitres, the recurring weak field.IMG_20260726_095215757— wrong onlitresandtotal_pricetogether; the OCR text for that line is heavily corrupted (j 2 N N 4) ha 1: Hi A 1.869 EUR/) in all three variants, so this one genuinely lacks clean signal anywhere.
Conclusion / recommendation
The extra OCR variants help in a few places (discount, price_per_litre)
but don't fix the core problem (litres line noise), introduce a new
failure mode (the model mis-reconciling conflicting-but-sometimes-correct
reads), and cost a lot of latency/stability on this hardware. Leaning
toward reverting to single-OCR unless a confidence-triggered fallback
(only pull extra variants when the primary reading looks incomplete) is
tried instead.