5cedcaefe1
Add a dev CLI (uv run python -m vision_service.cli <image>) that runs a recognizer on an image file and prints the parsed plate(s) + confidence + region — fast feedback with no HTTP. Also a package.json `recognize` script and a vision-recognize entry point. Verified fast-alpr for real: installed the `alpr` extra, downloaded the YOLOv9 + CCT ONNX weights (~11MB, cached offline under ~/.cache), and ran recognition on the project's test image → "5AU5341" at 1.000 confidence, region "Czech Republic", ~40ms on CPU, via both the CLI and POST /analyze. Fixes result parsing against the actual fast-alpr API: ocr.confidence is a LIST of per-character confidences (not a scalar) — reduced to one plate confidence via the MIN (a plate is only as trustworthy as its weakest character); also surface ocr.region. Extracted the per-result mapping into a pure plate_from_alpr_result + _reduce_confidence and unit-tested them (no model weights needed). 7 tests pass; ruff + mypy strict clean; full turbo build/lint/test green. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
73 lines
2.5 KiB
Python
73 lines
2.5 KiB
Python
"""Dev CLI to test a recognizer against an image file — no HTTP, fast feedback.
|
|
|
|
uv run python -m vision_service.cli path/to/car.jpg
|
|
uv run python -m vision_service.cli car.jpg --recognizer stub # contract only
|
|
uv run python -m vision_service.cli car.jpg --ocr cct-s-v2-global-model
|
|
|
|
Defaults to the `fast_alpr` recognizer (the point of this tool). Prints the parsed
|
|
plate result as JSON. If the `alpr` extra isn't installed it says so and exits non-zero
|
|
rather than silently using the stub. See wiki/entities/opencv-anpr-service.md.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from .recognizer import build_recognizer
|
|
from .settings import Settings
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
parser = argparse.ArgumentParser(prog="vision-recognize", description="Run a recognizer on an image.")
|
|
parser.add_argument("image", type=Path, help="path to an image file (JPEG/PNG) with a plate")
|
|
parser.add_argument(
|
|
"--recognizer",
|
|
choices=["fast_alpr", "stub"],
|
|
default="fast_alpr",
|
|
help="which recognizer to use (default: fast_alpr)",
|
|
)
|
|
parser.add_argument("--detector", default=None, help="override the fast-alpr detector model name")
|
|
parser.add_argument("--ocr", default=None, help="override the fast-alpr OCR model name")
|
|
args = parser.parse_args(argv)
|
|
|
|
if not args.image.is_file():
|
|
print(f"error: no such file: {args.image}", file=sys.stderr)
|
|
return 2
|
|
|
|
settings = Settings(recognizer=args.recognizer)
|
|
if args.detector:
|
|
settings.detector_model = args.detector
|
|
if args.ocr:
|
|
settings.ocr_model = args.ocr
|
|
|
|
rec = build_recognizer(settings)
|
|
if not rec.ready:
|
|
err = getattr(rec, "error", "unavailable")
|
|
print(
|
|
f"error: recognizer '{args.recognizer}' not ready: {err}\n"
|
|
"hint: install the models with uv sync --extra alpr",
|
|
file=sys.stderr,
|
|
)
|
|
return 1
|
|
|
|
image_bytes = args.image.read_bytes()
|
|
result = rec.analyze(image_bytes)
|
|
# Pydantic v2: model_dump_json gives a clean, stable rendering.
|
|
print(result.model_dump_json(indent=2))
|
|
|
|
if result.plate is None:
|
|
print("\n(no plate detected)", file=sys.stderr)
|
|
else:
|
|
flag = " [LOW CONFIDENCE]" if result.low_confidence else ""
|
|
print(
|
|
f"\n→ {result.plate.text} ({result.plate.confidence:.3f}){flag} in {result.took_ms:.1f} ms",
|
|
file=sys.stderr,
|
|
)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|