Download learn.py from borreooo/ocr: direct link, hf CLI and curl.
- Browser
- Download file 5.84 kB
-
https://huggingface.co/spaces/borreooo/ocr/resolve/main/learn.py
- Command line
-
hf download hf://spaces/borreooo/ocr/learn.py
-
curl -L -o learn.py https://huggingface.co/spaces/borreooo/ocr/resolve/main/learn.py
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() |