product-recognition-demo/recognization
kiyo9w 4bdad66bbe docs(decision): record the far-unknown calibration and the gate status
The far-unknown claim in the verdict said 0.2-0.7% on 602 crops. That was the
audit's variant matcher on the add/ photos; the shipped lane at the shipped
lines was 3.61% on the Wikimedia sets. Both are now stated, with the sweep that
chose 4.0/4 and the live-endpoint result: 44 named, 42 correct, 95.5% precision
against 53/48/90.6% before.

Gate status is recorded per gate rather than as a blanket claim: G2 and G3's
far half pass offline, G8 passes, and G1/G4/G5/G6/G7 are not yet measured on
held-out data because Tier 2 does not exist.

The Wikimedia manifests and results are committed; the 59 MB of images are not,
because collect_shelf_images.py fetches them from the source URLs the manifests
record.
2026-09-29 04:11:00 +09:00
..
add Initial commit for push folder 2026-09-18 09:13:53 +09:00
code fix(identity): calibrate both accept lines against unrelated shelves 2026-09-29 04:08:59 +09:00
data docs(decision): record the far-unknown calibration and the gate status 2026-09-29 04:11:00 +09:00
demo-images feat: two demo shelves, enrolled, plus the price-card filter they forced out 2026-09-25 09:34:29 +09:00
docs docs(decision): record the far-unknown calibration and the gate status 2026-09-29 04:11:00 +09:00
eval/labels docs(labels): the README described the pre-correction label set 2026-09-29 03:33:16 +09:00
runs/detect/train Initial commit for push folder 2026-09-18 09:13:53 +09:00
stock fix: restore reliable authenticated live recognition 2026-09-28 12:38:15 +09:00
stock-demo fix: restore reliable authenticated live recognition 2026-09-28 12:38:15 +09:00
stock-lopia fix(catalog): enroll the current l06 package, closing F-12 2026-09-29 03:22:03 +09:00
stock-lopia-clean fix(catalog): enroll the current l06 package, closing F-12 2026-09-29 03:22:03 +09:00
stock-mediya fix: restore reliable authenticated live recognition 2026-09-28 12:38:15 +09:00
stock-milk150 fix: restore reliable authenticated live recognition 2026-09-28 12:38:15 +09:00
.gitignore feat: add bounded crop evidence and keyed gallery cache 2026-09-28 15:49:34 +09:00
DEMO.md chore: the film-making shortcut leaves the server, and failures stop hiding 2026-09-25 01:30:16 +09:00
image.jpg Initial commit for push folder 2026-09-18 09:13:53 +09:00
pyproject.toml fix: restore reliable authenticated live recognition 2026-09-28 12:38:15 +09:00
README.md feat(identity): local-feature matcher plus text lane as the default backend 2026-09-28 22:27:21 +09:00
requirements-dev.txt refactor: make the matcher and detector safe for concurrent clients 2026-09-24 11:43:24 +09:00
requirements.lock fix: restore reliable authenticated live recognition 2026-09-28 12:38:15 +09:00
requirements.txt feat: reconciliation and the price-tag compliance rule family 2026-09-24 13:54:00 +09:00
run-demo-server.sh feat(identity): local-feature matcher plus text lane as the default backend 2026-09-28 22:27:21 +09:00
run_app.bat docs: API contract, README for the server and live mode, real-model test 2026-09-24 12:46:54 +09:00
run_app.sh docs: API contract, README for the server and live mode, real-model test 2026-09-24 12:46:54 +09:00
supermarket_diagram.png Initial commit for push folder 2026-09-18 09:13:53 +09:00
uv.lock feat(identity): local-feature matcher plus text lane as the default backend 2026-09-28 22:27:21 +09:00

🍡 Dorayaki - Smart Supermarket Shelf AI & Inventory Recognition

Hệ thống nhận diện sản phẩm trên kệ siêu thị và quản lý tồn kho theo kiến trúc client - server: mô hình thị giác máy tính (YOLO + Local Feature Matching: SURF/SIFT, ORB, BRISK, Hybrid theo paper Electronics 2023, 12, 3640) chạy trong một recognition server, còn các client (web, thiết bị chụp ảnh) chỉ gửi khung hình và nhận kết quả.

Server phục vụ 2 chế độ:

  1. Live (nhận diện thời gian thực) - camera hoặc video gửi liên tục qua WebSocket, sản phẩm được phát hiện trên từng khung hình, theo dõi (tracking) xuyên khung hình, phân loại một lần cho mỗi sản phẩm và gắn nhãn ngay trên màn hình.
  2. Photo (ảnh tĩnh) - chụp/upload/chọn ảnh kệ, nhận diện toàn bộ kệ trong một lần, bấm vào từng sản phẩm để xem chi tiết, tồn kho và điểm matching.

📋 Yêu cầu hệ thống (System Requirements)

  • Hệ điều hành: Windows 10/11, macOS hoặc Linux.
  • Python: 3.10 - 3.12 (khuyên dùng 3.11/3.12).
  • Phần cứng: CPU 4 lõi trở lên, RAM 8GB. GPU không bắt buộc: trên máy M5 đo được CPU nhanh hơn MPS (28ms so với 112ms mỗi khung hình).

🚀 Cài đặt và chạy

Bước 1: Chuẩn bị thư mục dự án

Cần có đầy đủ:

  • code/ - mã nguồn server, client web, client mẫu.
  • runs/ - trọng số YOLO đã train (runs/detect/train/weights/best.pt).
  • weights/dinov2_vits14_lvd142m.safetensors - DINOv2 backbone for the identity gallery. Not in Git. See "DINOv2 weights" below.
  • stock/ - catalog stock.csv và ảnh mẫu sản phẩm sku_001 -> sku_022.
  • add/ và image.jpg - ảnh kệ mẫu để test/demo.
  • requirements.txt, requirements-dev.txt.

Bước 2: Tạo môi trường ảo

conda create -n dorayaki python=3.11 -y
conda activate dorayaki

hoặc venv:

python3 -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate

Bước 3: Cài đặt thư viện

pip install -r requirements-dev.txt

DINOv2 weights

The identity gallery embeds crops with DINOv2 ViT-S/14 (LVD-142M), Apache-2.0. It loads only a local file and does not contact Hugging Face, including when YOLO_OFFLINE is unset. Without the file, gallery identity fails and the server log says the weights are missing.

The pinned file is model.safetensors from timm/vit_small_patch14_dinov2.lvd142m, sha256 04d27f3400d059fc0cfd7d17dd1909a75bf3ea8fb3eeb48b97cb99e57ee20081 (~84 MB). Copy a cache you already have; do not fetch one from an unknown URL:

mkdir -p weights
cp "$HOME/.cache/huggingface/hub/models--timm--vit_small_patch14_dinov2.lvd142m/snapshots/"*/model.safetensors \
  weights/dinov2_vits14_lvd142m.safetensors

DORAYAKI_DINOV2_WEIGHTS overrides that path. A file with any other sha256 is refused.

With the gallery identity backend, enrolled views are embedded into an ignored stock-dir/.gallery_cache_<image_size>.npz feature cache. The cache is reused only when its key matches the preprocessing version, image size, pinned DINOv2 SHA-256, catalog CSV, enrolled SKU/view order and image bytes; its matrix shape, numeric finite values and row identities are also checked. A stale or malformed cache is rebuilt. A cache hit still checks the local model file's SHA-256, and gallery startup refuses readiness if the pinned weights or usable gallery are missing. OCR continues on the separate tag worker (tag_executor).

(requirements-dev.txt = requirements.txt + httpx cho test.)

Bước 4: Khởi chạy recognition server

python code/app.py --port 8080

Script có sẵn: run_app.bat (Windows) hoặc ./run_app.sh (Linux/macOS).

Các tham số thường dùng:

Tham số Mặc định Ý nghĩa
--host 127.0.0.1 Đổi thành 0.0.0.0 để mở cho máy khác trong LAN (nên đặt DORAYAKI_API_TOKEN).
--device cpu cpu, mps, cuda, cuda:0.
--imgsz 640 Kích thước đầu vào của detector.
--live-method hybrid Thuật toán mặc định cho chế độ live.
--max-sessions 4 Số client live đồng thời.
--ssl-certfile / --ssl-keyfile - Bật HTTPS (cần cho camera trên điện thoại).

Bước 5: Mở ứng dụng

Mở trình duyệt: http://127.0.0.1:8080 (hoặc http://127.0.0.1:8080)

  • Tab Live: chọn camera (hoặc "Video file…") rồi bấm Start. Sản phẩm hiện khung xanh kèm tên và giá khi đã nhận diện chắc chắn; khung vàng "要確認" là trường hợp chưa chắc chắn.
  • Tab Photo: chọn ảnh mẫu, chụp ảnh hoặc upload ảnh kệ. Nhấn vào sản phẩm để xem chi tiết và bấm Show matching để xem các điểm matching.

Camera trên điện thoại chỉ hoạt động khi trang mở bằng https hoặc localhost (yêu cầu bảo mật của trình duyệt).


🔌 API (dùng cho client khác)

Server có REST + WebSocket đầy đủ, tài liệu chi tiết trong docs/API.md. Swagger UI: http://127.0.0.1:8080/docs.

Live WebSocket protocol 1 also supports an optional full-resolution crop lane: clients advertise capabilities, then answer per-track crop_request messages from their buffered capture. Clients without the capability are unchanged; wire examples, byte limits, expiry and errors are in docs/API.md.

# Nhận diện một ảnh kệ
curl -X POST --data-binary @image.jpg -H 'Content-Type: image/jpeg' \
  'http://127.0.0.1:8080/v1/recognize?classify=true'

# Client live mẫu (camera, video, hoặc pan qua ảnh tĩnh)
python code/stream_client.py --source image.jpg --pan 12 --seconds 20
python code/stream_client.py --source 0                 # webcam

Bất kỳ client nào gửi được ảnh JPEG đều dùng được: điện thoại, iPad, camera gắn xe đẩy, camera đeo cổ, robot. Giao thức không phụ thuộc trình duyệt.

For the native app demo, ./run-demo-server.sh uses stock-lopia-clean by default: stock-lopia is shelf crops cut from the image6 evaluation photo, so scoring against it is memorization. Pass another stock directory explicitly to change stores; the script writes ignored local app endpoints and a local token.

Identity backends

--identity picks what names a product. The default is local:

  • local — RootSIFT over a per-SKU reference pyramid, similarity-constrained RANSAC, a hue check, then a text lane (OCR of the crop against each catalog row's name plus its packshot OCR) that vetoes a match whose package text names a different row and answers on its own when geometry finds nothing. Measured on the corrected image6 labels at live resolution: 59% of in-catalog facings named at 95% precision, 0 of 48 unknowns accepted. Rows with no enrolled image are still answerable, because the text lane builds their signature from the catalog name.
  • matcher — Van's original SURF/ORB/BRISK composite, kept as the E-1 baseline lane for comparison. Always answers, so it accepts near-unknown siblings.
  • gallery — the DINOv2 embedding gallery. Fenced off: it is not the default and it refuses to start without the pinned weights. Its recall collapses as the catalog grows (7/42 recall@10 at 493 SKUs), so it is not the recognition path.

The text lane needs OCR. On macOS that is Vision, which is already wired in code/ocr.py; DORAYAKI_OCR=none disables it and the local lane then stands alone (43% coverage at 79% precision on the same labels).


🛠️ Kiểm tra hệ thống (Tests)

From recognization/, after pip install -r requirements.lock (or pip install -r requirements-dev.txt):

YOLO_OFFLINE=1 YOLO_AUTOINSTALL=false python -m unittest discover -s code -p 'test_*.py' -v

That discovers every code/test_*.py module. test_real_model is included and skipped unless DORAYAKI_REAL_MODEL=1 (it loads the YOLO weights). CI runs the same command from .github/workflows/test.yml.

YOLO_OFFLINE=1 python code/bench_engine.py   # speed measurement, not part of the suite

🌟 Tính năng chính

  1. Nhận diện thời gian thực (Live): phát hiện sản phẩm trên từng khung hình, theo dõi xuyên khung hình, phân loại một lần cho mỗi sản phẩm, gắn nhãn tên/giá ngay trên khung hình; hiển thị số khung/giây và độ trễ.
  2. Phát hiện chỗ trống trên kệ (Out-of-Stock): khoanh đỏ các vị trí hết hàng kèm ước lượng số lượng thiếu; có bộ lọc ổn định để khung không nhấp nháy.
  3. Nhận diện ảnh tĩnh (Photo): nhận diện toàn bộ kệ trong một lần thay vì bấm từng sản phẩm.
  4. Phân loại đa thuật toán: Hybrid (Paper), SURF/SIFT, ORB, BRISK - đổi được ngay trong lúc chạy.
  5. Trạng thái trung thực: chỉ hiện tên sản phẩm khi kết quả được chấp nhận; trường hợp mơ hồ hiện "要確認" kèm các ứng viên, không đoán bừa.
  6. Tương tác chi tiết: bấm vào sản phẩm để xem tên, giá, ngày nhập, hạn sử dụng, tồn kho và điểm matching.
  7. Đa client: nhiều client kết nối đồng thời, mỗi client có session và tracking riêng.
  8. Đa ngôn ngữ: Tiếng Anh và Tiếng Nhật.
  9. Gợi ý chất lượng ảnh: cảnh báo lóa (glare), rung (blur), chụp quá xa.

📁 Cấu trúc mã nguồn

code/
  app.py             # server: REST /v1 + WebSocket /v1/stream + phục vụ web client
  engine.py          # RecognitionEngine: detector + matcher + catalog, thread-safe
  live.py            # LiveSession: tracking, phân loại nền, gắn nhãn, chất lượng ảnh
  tracking.py        # SessionTracker: ByteTrack với ID riêng cho từng session
  detector.py        # YOLO detector
  feature_matcher.py # SURF/SIFT, ORB, BRISK, Hybrid
  identity.py        # accepted / ambiguous / unknown / rejected
  empty_shelf_detector.py, inventory.py, gallery.py, tags.py, tag_cards.py
  stream_client.py   # client mẫu (camera / video / pan ảnh tĩnh)
  bench_engine.py    # đo tốc độ
  test_*.py          # unittest modules (discover -s code -p 'test_*.py')
  web/               # web client (index.html, styles.css, app.js)
docs/API.md          # tài liệu API đầy đủ
docs/design-price-tags-and-compliance.md   # thiết kế OCR giá, đối chiếu, compliance, mobile