Upload src/detect.py with huggingface_hub
Browse files- src/detect.py +65 -0
src/detect.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
ROI detection: full passport image -> bounding box of the dotted series/number strip.
|
| 3 |
+
Uses a YOLO model fine-tuned on Ukrainian passport images (class: "Dotted").
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
import cv2
|
| 8 |
+
import numpy as np
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def load_detector(model_path: str | Path):
|
| 12 |
+
from ultralytics import YOLO
|
| 13 |
+
return YOLO(str(model_path))
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def detect_roi(image: np.ndarray, model, conf: float = 0.30) -> tuple[int, int, int, int] | None:
|
| 17 |
+
"""
|
| 18 |
+
Detect the dotted passport series/number strip in a full passport image.
|
| 19 |
+
|
| 20 |
+
Args:
|
| 21 |
+
image: BGR image (numpy array) or grayscale — will be converted as needed.
|
| 22 |
+
model: loaded YOLO model (from load_detector).
|
| 23 |
+
conf: minimum confidence threshold.
|
| 24 |
+
|
| 25 |
+
Returns:
|
| 26 |
+
(x1, y1, x2, y2) pixel coordinates of the best detection,
|
| 27 |
+
or None if nothing found.
|
| 28 |
+
"""
|
| 29 |
+
results = model(image, conf=conf, verbose=False)
|
| 30 |
+
boxes = results[0].boxes
|
| 31 |
+
|
| 32 |
+
if boxes is None or len(boxes) == 0:
|
| 33 |
+
return None
|
| 34 |
+
|
| 35 |
+
# Pick highest-confidence box
|
| 36 |
+
best = boxes[boxes.conf.argmax()]
|
| 37 |
+
x1, y1, x2, y2 = map(int, best.xyxy[0].tolist())
|
| 38 |
+
return x1, y1, x2, y2
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def crop_roi(image: np.ndarray, box: tuple[int, int, int, int],
|
| 42 |
+
padding: float = 0.05) -> np.ndarray:
|
| 43 |
+
"""
|
| 44 |
+
Crop the ROI from the image with optional relative padding.
|
| 45 |
+
|
| 46 |
+
Args:
|
| 47 |
+
image: BGR or grayscale image.
|
| 48 |
+
box: (x1, y1, x2, y2)
|
| 49 |
+
padding: fraction of box size to add as margin on each side.
|
| 50 |
+
|
| 51 |
+
Returns:
|
| 52 |
+
Cropped numpy array (same channels as input).
|
| 53 |
+
"""
|
| 54 |
+
h, w = image.shape[:2]
|
| 55 |
+
x1, y1, x2, y2 = box
|
| 56 |
+
|
| 57 |
+
pw = int((x2 - x1) * padding)
|
| 58 |
+
ph = int((y2 - y1) * padding)
|
| 59 |
+
|
| 60 |
+
x1 = max(0, x1 - pw)
|
| 61 |
+
y1 = max(0, y1 - ph)
|
| 62 |
+
x2 = min(w, x2 + pw)
|
| 63 |
+
y2 = min(h, y2 + ph)
|
| 64 |
+
|
| 65 |
+
return image[y1:y2, x1:x2]
|