🃏 Saudi Baloot Dense Fanned Hand Card Detector (baloot-fan-v2)
baloot-fan-v2 is a specialized object detection model based on YOLO11n, trained to recognize 32 Saudi Baloot playing cards (ranks A, K, Q, J, 10, 9, 8, 7 across all 4 suits) plus 1 other class (ranks 2–6) in dense, overlapping fanned hands held by players under realistic occlusion, finger shadows, and camera tilt.
Part of the open-source Hakim Vision (AIBaloot) project.
📊 Model Summary & Performance
- Model Architecture: YOLO11n (Ultralytics)
- Input Resolution:
704 × 704 px(Stretch resized) - Target Environments: WebGPU (FP16) via
onnxruntime-web, WASM INT8 CPU fallback, native PyTorch / ONNX Runtime.
Evaluation Metrics (Holdout Validation Set at 704×704)
| Metric | Value |
|---|---|
| Precision | 98.26% |
| Recall | 94.04% |
| mAP@50 | 98.72% |
| mAP@50-95 | 95.81% |
Latency Benchmarks
| Format | File Size | Runtime Target | Latency |
|---|---|---|---|
| FP16 ONNX | 5.1 MB | WebGPU (iOS Safari 26+, Chrome 113+) | ~20–40 ms |
| INT8 ONNX | 3.16 MB | WebAssembly (CPU fallback) | ~110–220 ms |
PyTorch (best.pt) | 5.3 MB | Native GPU (RTX 2080 Ti / CUDA) | ~3–5 ms |
🏷️ 33 Detection Classes (Baloot Ontology)
The model detects corner-index bounding boxes for the 32 cards used in Saudi Baloot, mapping all irrelevant ranks (2–6) into class 32 (other):
0: Ah 1: Kh 2: Qh 3: Jh 4: 10h 5: 9h 6: 8h 7: 7h (Hearts ♥)
8: Ad 9: Kd 10: Qd 11: Jd 12: 10d 13: 9d 14: 8d 15: 7d (Diamonds ♦)
16: Ac 17: Kc 18: Qc 19: Jc 20: 10c 21: 9c 22: 8c 23: 7c (Clubs ♣)
24: As 25: Ks 26: Qs 27: Js 28: 10s 29: 9s 30: 8s 31: 7s (Spades ♠)
32: other (Ranks 2–6, non-Baloot cards)
🚀 How to Use
1. PyTorch (ultralytics)
from ultralytics import YOLO
# Load PyTorch weights
model = YOLO("best.pt")
# Predict on an image
results = model.predict("player_hand.jpg", imgsz=704, conf=0.35)
# Render results
for result in results:
result.show()
2. ONNX Runtime (Python)
import cv2
import numpy as np
import onnxruntime as ort
# Load ONNX model
session = ort.InferenceSession("baloot-fan-v2.fp16.onnx", providers=["CUDAExecutionProvider", "CPUExecutionProvider"])
# Preprocess image (Stretch resize to 704x704)
img = cv2.imread("player_hand.jpg")
img_resized = cv2.resize(img, (704, 704))
input_tensor = (img_resized.astype(np.float32) / 255.0).transpose((2, 0, 1))[np.newaxis, ...]
# Run inference
outputs = session.run(None, {session.get_inputs()[0].name: input_tensor})
3. In-Browser WebGPU (onnxruntime-web)
import * as ort from 'onnxruntime-web/webgpu';
// Load FP16 ONNX model with WebGPU EP
const session = await ort.InferenceSession.create('./baloot-fan-v2.fp16.onnx', {
executionProviders: ['webgpu']
});
// Run inference on WebGPU
const feeds = { images: inputTensor };
const results = await session.run(feeds);
🛠️ Quantization & WebGPU Constraints
- Static INT8 PTQ: Quantized using MinMax calibration on representative hand composites.
reduce_range=Truewas applied to prevent activation overflow on CPU SIMD targets. - Decode Tail Excluded: Detection head outputs combine
[0, 704]coordinate spaces with[0, 1]probabilities, so the decoding tail is preserved in FP16/FP32 to maintain score precision.
📜 Citation & License
- License: MIT License
- Repository: https://github.com/osos3lom/AIBaloot