File size: 5,838 Bytes
b42373a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | 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() |