--- license: agpl-3.0 tags: - object-detection - yolov11 - playing-cards - ultralytics - onnx library_name: ultralytics pipeline_tag: object-detection --- # lgd-cards-gen4 — playing-card corner-pip detector (spread-recall fine-tune) **4th-generation** card detector for [Live Game Defender](https://live-game-defender.cz), an on-prem computer-vision integrity monitor for live casino table games. Detects the **52 rank+suit corner index pips** (`AS`, `10H`, `KD`, …) on an overhead table camera — one card shows up to two pip boxes; whole-card assembly happens downstream in the tracker, not here. > **License / provenance.** AGPL-3.0. This is a fine-tune of **Ultralytics YOLO11s** (COCO > pre-trained), so the weights are a YOLO derivative — **not "ours"**. Downstream use inherits AGPL. > **⚠️ Not casino accuracy.** All numbers below are on our **own PoC / Czech Croupier Academy** > footage with **LLM-verified labels**, not human casino ground truth. Treat as PoC metrics only. ## Status: spread specialist — NOT the production model gen4 was activated on 2026-07-20 and **reverted the same day**: its gameplay-precision trade broke the ante-integrity pillar on gameplay footage. Two measured issues (KAS-35 E2E, `uth_player3_ante_paid.mp4`): **(a)** hot nested double-reads — it read a 7H pip's bare rank digit as "7C" at 73% confidence, minting a phantom board pair that faked "dealer qualifies"; **(b)** it missed two players' hole cards outright. **[`lgd-cards-gen3`](https://huggingface.co/sroot/lgd-cards-gen3) is the production/served model.** gen4 remains the best deck-spread specialist (45/52 live deck check vs fewer under gen3); the planned gen5 warm-starts from gen3's weights + this model's spread data and must beat both the spread gate and the gameplay E2E. ## Why gen4 gen4 targets the one thing prior gens were weak at: **recall on a fully spread deck** (the dealer fanning all 52 cards face-up — the KAS-52 "cards missing / only 1 card" report, and the input to the KAS-36 deck-completeness check). It is the same recipe as gen3 with one added dataset: **day-3 deck-spread video** (`lgd-cards-video-day3`). ## Metrics (frozen held-out real video, `eval_real.py`, serve-style 6×4 tiling @ 4K, threshold 50) Scored against the previous served model (**gen3** = day-2) in one run, both on the **same expanded 318-frame holdout** (225 prior frames + 93 newly-held-out deck-spread frames — never trained on): | | Recall (cards found) | Precision-proxy | |---|---|---| | **gen4 (this model)** | **0.786** | 0.752 | | gen3 (day-2, prior served) | 0.750 | 0.774 | On the **93 deck-spread-only** frames — the target scenario: | | Recall | count-of-52 unique codes (mean) | best frame | |---|---|---|---| | **gen4** | **0.665** | **26.2 / 52** | **32 / 52** | | gen3 | 0.574 | 23.0 / 52 | 24 / 52 | **+9 points of spread recall.** Normal-gameplay recall is held (~0.848 → 0.853). gen4 does **not** reach a full 52, and its precision-**proxy** dips — partly a genuine trade (it detects more aggressively) and partly an artifact: the LLM-made holdout GT is not exhaustive, so real cards gen4 recovers that the GT lacks score as false positives. It **fails the strict "beat the previous model on both axes" gate**; it was activated for the recall win on 2026-07-20 and reverted the same day (see Status above). Full numbers in `metrics.json`. > Numbering note: gen3's headline figure elsewhere (0.85) was on an older 225-frame holdout; on the > **same 318-frame** holdout used here gen3 scores 0.750, which is the fair comparison. Naming was > **unified 2026-07-20**: `genN` means the same model on HF, in the project's `generations/genN-*` > archive (this one: `generations/gen4-spread`) and in every doc. ## Files - `model.onnx` — deployable ONNX (dynamic batch) — the exact export served during its brief 2026-07-20 activation (not currently deployed; see Status). - `model.classes.json` — the 52 class names, in model output order. - `best.pt` — Ultralytics checkpoint (for resuming / re-export). - `metrics.json` — full eval incl. per-label recall and the reference-model comparison. ## Usage (onnxruntime, no Ultralytics at runtime) ```python import onnxruntime as ort, numpy as np, json sess = ort.InferenceSession("model.onnx", providers=["CUDAExecutionProvider", "CPUExecutionProvider"]) names = json.load(open("model.classes.json")) # letterbox your BGR frame to 640×640, NCHW float32 /255; run sess.run(None, {input: x}); # decode YOLO11 output [1,56,8400] -> boxes+52 class scores, NMS. For 4K, tile (serve uses 6×4). ``` The engine runs this tiled (full frame + a 6×4 overlap grid at 4K) at a confidence threshold of 50. ## Training YOLO11s (COCO) → fine-tuned 40 epochs (patience 15, batch 16, imgsz 640, seed 7) on the Roboflow `ow27d` base set (oversampled, not redistributed here — get it from [Roboflow Universe](https://universe.roboflow.com/augmented-startups/playing-cards-ow27d)) mixed with `lgd-cards-video-day1` + `-day2` + **`-day3`** (the new deck-spread tiles). Labels are detector-proposed, OpenAI-`gpt-5-mini`-verified against the closed 52-code vocabulary. ## Family Cards: [gen1](https://huggingface.co/sroot/lgd-cards-gen1) · [gen2](https://huggingface.co/sroot/lgd-cards-gen2) · [gen3](https://huggingface.co/sroot/lgd-cards-gen3) · **gen4 (this)** · Chips: [gen1](https://huggingface.co/sroot/lgd-chips-gen1) · [gen2](https://huggingface.co/sroot/lgd-chips-gen2)