askakalsky commited on
Commit
9132aca
·
verified ·
1 Parent(s): 5de5b2e

Upload pipeline.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. pipeline.py +164 -0
pipeline.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Ukrainian passport series/number OCR pipeline.
3
+
4
+ Full flow:
5
+ passport image -> YOLO ROI detection -> preprocessing -> CRNN OCR
6
+
7
+ Usage:
8
+ # Python API
9
+ from pipeline import PassportOCR
10
+ ocr = PassportOCR()
11
+ result = ocr("passport.jpg")
12
+ # {"series": "НР", "number": "430098", "full": "НР430098",
13
+ # "confidence": 0.97, "readable": True}
14
+
15
+ # CLI
16
+ python pipeline.py passport.jpg
17
+ python pipeline.py passport.jpg --show
18
+ """
19
+
20
+ from __future__ import annotations
21
+ import argparse
22
+ from pathlib import Path
23
+
24
+ import cv2
25
+ import numpy as np
26
+
27
+ from src.detect import load_detector, detect_roi, crop_roi
28
+ from src.preprocess import preprocess
29
+ from src.recognize import load_recognizer, recognize, CONFIDENCE_THRESHOLD
30
+
31
+ MODELS_DIR = Path(__file__).parent / "models"
32
+ DETECTOR_PATH = MODELS_DIR / "detector.pt"
33
+ RECOGNIZER_PATH = MODELS_DIR / "recognizer.pth"
34
+
35
+
36
+ class PassportOCR:
37
+ """
38
+ End-to-end passport series/number recognizer.
39
+
40
+ Args:
41
+ detector_path: path to YOLO .pt weights.
42
+ recognizer_path: path to CRNN .pth checkpoint.
43
+ det_conf: YOLO confidence threshold (lower = more sensitive).
44
+ """
45
+
46
+ def __init__(
47
+ self,
48
+ detector_path: str | Path = DETECTOR_PATH,
49
+ recognizer_path: str | Path = RECOGNIZER_PATH,
50
+ det_conf: float = 0.30,
51
+ ):
52
+ self.detector, = load_detector(detector_path),
53
+ self.det_conf = det_conf
54
+ self.model, self.ckpt = load_recognizer(recognizer_path)
55
+
56
+ def __call__(self, image: str | Path | np.ndarray) -> dict:
57
+ """
58
+ Run full pipeline on a passport image.
59
+
60
+ Args:
61
+ image: file path (str/Path) or BGR numpy array.
62
+
63
+ Returns dict with keys:
64
+ series — 2 Cyrillic letters (e.g. "НР")
65
+ number — 6 digits (e.g. "430098")
66
+ full — series + number (e.g. "НР430098")
67
+ confidence — avg softmax prob (0.0 – 1.0)
68
+ readable — False if confidence < threshold
69
+ roi_box — (x1,y1,x2,y2) or None if detection failed
70
+ """
71
+ # -- Load image --------------------------------------------------------
72
+ if isinstance(image, (str, Path)):
73
+ img_bgr = cv2.imread(str(image))
74
+ if img_bgr is None:
75
+ raise FileNotFoundError(f"Cannot read image: {image}")
76
+ else:
77
+ img_bgr = image
78
+
79
+ # -- Detect ROI --------------------------------------------------------
80
+ box = detect_roi(img_bgr, self.detector, conf=self.det_conf)
81
+
82
+ if box is None:
83
+ return {
84
+ "series": None,
85
+ "number": None,
86
+ "full": None,
87
+ "confidence": 0.0,
88
+ "readable": False,
89
+ "roi_box": None,
90
+ "error": "ROI not detected",
91
+ }
92
+
93
+ roi_crop = crop_roi(img_bgr, box, padding=0.05)
94
+
95
+ # -- Preprocess --------------------------------------------------------
96
+ roi_clean = preprocess(roi_crop)
97
+
98
+ # -- Recognize ---------------------------------------------------------
99
+ text, conf = recognize(roi_clean, self.model, self.ckpt)
100
+
101
+ return {
102
+ "series": text[:2],
103
+ "number": text[2:],
104
+ "full": text,
105
+ "confidence": round(conf, 4),
106
+ "readable": conf >= CONFIDENCE_THRESHOLD,
107
+ "roi_box": box,
108
+ }
109
+
110
+
111
+ # -- CLI -----------------------------------------------------------------------
112
+
113
+ def _show_result(img_bgr: np.ndarray, result: dict) -> None:
114
+ import matplotlib
115
+ matplotlib.use("TkAgg")
116
+ import matplotlib.pyplot as plt
117
+
118
+ box = result.get("roi_box")
119
+ annotated = img_bgr.copy()
120
+ if box:
121
+ x1, y1, x2, y2 = box
122
+ cv2.rectangle(annotated, (x1, y1), (x2, y2), (0, 255, 0), 3)
123
+
124
+ fig, axes = plt.subplots(1, 2, figsize=(14, 5))
125
+ axes[0].imshow(cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB))
126
+ axes[0].set_title("Detected ROI", fontsize=10)
127
+ axes[0].axis("off")
128
+
129
+ if box:
130
+ roi = crop_roi(img_bgr, box, padding=0.05)
131
+ roi_clean = preprocess(roi)
132
+ axes[1].imshow(roi_clean, cmap="gray")
133
+ label = result.get("full") or "NOT DETECTED"
134
+ conf = result.get("confidence", 0)
135
+ axes[1].set_title(f"{label} (conf {conf:.1%})", fontsize=12)
136
+ axes[1].axis("off")
137
+
138
+ plt.tight_layout()
139
+ plt.show()
140
+
141
+
142
+ if __name__ == "__main__":
143
+ ap = argparse.ArgumentParser(description="Ukrainian passport OCR")
144
+ ap.add_argument("image", help="Path to passport image")
145
+ ap.add_argument("--show", action="store_true", help="Show visual result")
146
+ ap.add_argument("--det-conf", type=float, default=0.30,
147
+ help="YOLO detection confidence threshold")
148
+ args = ap.parse_args()
149
+
150
+ ocr = PassportOCR(det_conf=args.det_conf)
151
+ result = ocr(args.image)
152
+
153
+ if result.get("error"):
154
+ print(f"ERROR: {result['error']}")
155
+ else:
156
+ status = "OK" if result["readable"] else "LOW CONFIDENCE"
157
+ print(f"Result: {result['full']}")
158
+ print(f"Series: {result['series']}")
159
+ print(f"Number: {result['number']}")
160
+ print(f"Confidence: {result['confidence']:.1%} [{status}]")
161
+
162
+ if args.show:
163
+ img = cv2.imread(args.image)
164
+ _show_result(img, result)