ocr / learn.py
borreooo's picture
Add project files
b42373a
Raw
History Blame Contribute Delete
5.84 kB
import os
import json
import cv2
import numpy as np
from collections import defaultdict
from barcode_scanner import scan_all_barcodes
from preprocess import preprocess_chassis
BARCODE_DIR = "images/barcode"
CHASSIS_DIR = "images/chassis"
CONFIG_PATH = "config.json"
def run_ocr_ensemble(image_path, ocr):
variations = preprocess_chassis(image_path)
best_text, best_score = "", -1
for var in variations:
result = ocr.ocr(var, cls=True)
if not result or not result[0]:
continue
texts = [line[1][0] for line in result[0]]
confs = [line[1][1] for line in result[0]]
text = "".join(texts).upper()
text = "".join(c for c in text if c.isalnum())
conf = sum(confs) / len(confs) if confs else 0.0
score = conf * max(len(text), 1)
if score > best_score:
best_text, best_score = text, score
return best_text
def best_alignment(got, expected):
exp_len = len(expected)
if len(got) == exp_len:
return got
best_start, best_diffs = 0, exp_len + 1
for start in range(max(0, len(got) - exp_len) + 1):
cand = got[start:start + exp_len]
if len(cand) != exp_len:
continue
diffs = sum(1 for a, b in zip(cand, expected) if a != b)
if diffs < best_diffs:
best_diffs = diffs
best_start = start
return got[best_start:best_start + exp_len]
def learn_confusion_map(ocr_results, ground_truths):
counts = defaultdict(lambda: defaultdict(int))
for key, expected in ground_truths.items():
got = ocr_results.get(key, "")
if not got or got == expected:
continue
aligned = best_alignment(got, expected)
if len(aligned) != len(expected):
continue
for g, e in zip(aligned, expected):
if g != e:
counts[g][e] += 1
confusion_map = {}
print("\n Learned confusions:")
for char in sorted(counts.keys()):
wants = sorted(counts[char], key=lambda w: counts[char][w], reverse=True)
confusion_map[char] = wants
print(f" '{char}' -> {wants} (counts: {dict(counts[char])})")
return confusion_map
def can_fix(got_str, expected_str, confusion_map, max_errors):
if len(got_str) != len(expected_str):
return False
diffs = [(g, e) for g, e in zip(got_str, expected_str) if g != e]
if len(diffs) > max_errors:
return False
return all(
e in confusion_map.get(g, []) or g in confusion_map.get(e, [])
for g, e in diffs
)
def learn_thresholds(ocr_results, ground_truths, confusion_map):
best_correct, best_config = 0, {"max_errors": 2, "window_size": 3}
for max_err in [1, 2, 3, 4]:
for win in [2, 3, 4, 5]:
correct = 0
for key, expected in ground_truths.items():
got = ocr_results.get(key, "")
if not got:
continue
if got == expected or expected in got:
correct += 1
continue
if abs(len(got) - len(expected)) <= win:
aligned = best_alignment(got, expected)
if can_fix(aligned, expected, confusion_map, max_err):
correct += 1
elif len(got) < len(expected):
suffix = expected[-len(got):]
if can_fix(got, suffix, confusion_map, 1):
correct += 1
if correct > best_correct:
best_correct = correct
best_config = {"max_errors": max_err, "window_size": win}
print(f"\n Best thresholds: {best_config} "
f"(estimated correct: {best_correct}/{len(ground_truths)})")
return best_config
def main():
print("=" * 60)
print("LEARNING FROM DATA")
print("=" * 60)
print("\n[1/3] Scanning barcodes for ground truth...")
ground_truths = scan_all_barcodes(BARCODE_DIR)
ground_truths = {k: v for k, v in ground_truths.items() if v}
print(f" Got {len(ground_truths)} ground truth labels")
print("\n[2/3] Running ensemble OCR on all chassis images...")
from paddleocr import PaddleOCR
ocr = PaddleOCR(use_angle_cls=True, lang='en',
use_gpu=False, show_log=False)
chassis_files = sorted([
f for f in os.listdir(CHASSIS_DIR)
if f.lower().endswith(('.jpg', '.jpeg', '.png'))
])
ocr_results = {}
for fname in chassis_files:
key = os.path.splitext(fname)[0]
path = os.path.join(CHASSIS_DIR, fname)
text = run_ocr_ensemble(path, ocr)
ocr_results[key] = text
expected = ground_truths.get(key, "???")
match = "[OK]" if text == expected else "[--]"
print(f" {match} {key}: got='{text}' expected='{expected}'")
print("\n[3/3] Learning confusion map and thresholds...")
confusion_map = learn_confusion_map(ocr_results, ground_truths)
thresholds = learn_thresholds(ocr_results, ground_truths, confusion_map)
existing = {}
if os.path.exists(CONFIG_PATH):
with open(CONFIG_PATH) as f:
existing = json.load(f)
config = {
"confusion_map": confusion_map,
"preprocessing": existing.get("preprocessing", {
"clahe_clip": 3.0,
"clahe_grid": 8,
"bilateral_d": 9,
"bilateral_sigma": 75,
"adaptive_blocksize": 21,
"adaptive_c": 8,
"padding": 20
}),
"error_correction": thresholds
}
with open(CONFIG_PATH, "w") as f:
json.dump(config, f, indent=2)
print(f"\n[DONE] Config saved -> {CONFIG_PATH}")
print("Now run: python evaluate.py")
print("=" * 60)
if __name__ == "__main__":
main()