JackFurby/playing-cards
Updated β’ 213 β’ 1
How to use osos3lom/baloot-fan-v2 with ultralytics:
# Couldn't find a valid YOLO version tag.
# Replace XX with the correct version.
from ultralytics import YOLOvXX
model = YOLOvXX.from_pretrained("osos3lom/baloot-fan-v2")
source = 'http://images.cocodataset.org/val2017/000000039769.jpg'
model.predict(source=source, save=True)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.
704 Γ 704 px (Stretch resized)onnxruntime-web, WASM INT8 CPU fallback, native PyTorch / ONNX Runtime.| Metric | Value |
|---|---|
| Precision | 98.26% |
| Recall | 94.04% |
| mAP@50 | 98.72% |
| mAP@50-95 | 95.81% |
| 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 |
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)
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()
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})
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);
reduce_range=True was applied to prevent activation overflow on CPU SIMD targets.[0, 704] coordinate spaces with [0, 1] probabilities, so the decoding tail is preserved in FP16/FP32 to maintain score precision.