dawidmajewskiit.pl / projekty / karty
Case study · Uczenie Maszynowe · 2026

System rozpoznawania kart do gry

Rozpoznawanie kart do gry w czasie rzeczywistym, w całości na urządzeniu — na Raspberry Pi 4. Dostrojony model YOLOv8n, wszystkie 52 karty i polski asystent głosowy, który odczytuje każdą kartę na głos.

Raspberry Pi 4 YOLOv8n kamera gray OLED + audio inference on-device
LIVE · Pi 4

Adnotacja Na potrzeby CV komunikaty głosowe systemu są po angielsku; reszta interfejsu — m.in. ekran OLED — pozostaje polska.

52
klasy kart
24k
zdjęć w zbiorze
0.789
mAP50-95
90–95%
pewność w praktyce
~5 fps
na Raspberry Pi
76
plików audio PL
01

Przegląd

Studia mgr · projekt z UM

Projekt zespołowy (6 osób) na przedmiot Uczenie Maszynowe (studia magisterskie, semestr letni 2026). Temat był dowolny — wybraliśmy najbardziej namacalny: rozpoznawać karty do gry w czasie rzeczywistym i wdrożyć to na urządzenie embedded — nie w notebooku, tylko w fizycznym pudełku, które trzymasz w ręku.

Wszystko dzieje się lokalnie na Pi: kamera łapie klatkę, dostrojony model YOLOv8n wykrywa kartę, maszyna stanów decyduje, kiedy jest pewna, a polski głos ją ogłasza — równolegle mały wyświetlacz OLED pokazuje figurę, kolor i pewność. Bez chmury, bez internetu w trakcie działania.

Mój obszar w zespole obejmował ścieżkę wdrożenia na Raspberry Pi oraz pipeline rozpoznawania — doprowadzenie modelu do tego, by na prawdziwych, fizycznych kartach zachowywał się tak samo dobrze jak na zbiorze walidacyjnym.

Zespół projektowy6
Gabriela Ławrynowicz
SMSebastian Mazurek
ŁMŁukasz Molenda
DMDawid Majewskito ja
KPKonrad Paulus
DPDominik Pacanowski
02

Sprzęt

pudełko, które trzymasz w ręku
montaż Wnętrze urządzenia: Raspberry Pi z okablowaniem, głośnik i akumulator LiPo w białej obudowie
Decyzja inżynierska · tryb gray

Kolorowe kamery wciąż myliły czerwone karty — różne odcienie czerwieni sprawiały, że model tracił pewność. Przełączenie kamery w tryb monochromatyczny całkowicie usunęło problem balansu kolorów: każda karta widziana jest w skali szarości i rozpoznawana po kształcie symboli i cyfr, a nie po kolorze. Czerwone i czarne karty wykrywane są teraz jednakowo dobrze.

  • Raspberry Pi 4jednostka centralna · 4 GB RAMBookworm 64-bit · 5V/3A
  • Kamera CSICamera Module · taśma640×480 · gray · AF
  • OLED SSD1306128×64 · I2C @ 0x3Cluma.oled
  • Głośnik customjack 3,5 mm · ALSApygame mixer
  • Akumulator LiPow obudowie urządzenia600 mAh · 3.7V
  • Karta SD + WiFistorage i deploy SSH32 GB · Class 10
Od włączenia do głosu

Włączasz Pi, a usługa systemd uruchamia wszystko automatycznie — bez klawiatury, bez monitora. W 30–90 sekund wita Cię słowem „Witaj” i jest gotowe do skanowania.

03

Pipeline rozpoznawania

klatka → głos

Jedna klatka przechodzi przez siedem etapów — od surowych pikseli po wypowiedzianą nazwę karty. Każdy etap robi jedną rzecz i przekazuje wynik dalej.

01

Kamera CSI camera.py

Klatka w skali szarości z Pi Camera Module. Jeden wrapper ukrywa różnicę między picamera2 na Pi a cv2.VideoCapture przy testach na PC.

640×480grayautofocus continuous
02

Preprocess preprocess.py

Balans bieli, wzmocnienie czerwieni i saturacji — zbudowane pod kolorową kamerę na PC. Na monochromatycznej kamerze Pi te kroki są w większości pomijane: obraz jest już czysty.

gray_world_wbboost_redboost_saturation
03

Detektor YOLOv8 detector.py

Dostrojony model YOLOv8n uruchamiany z Test-Time Augmentation zwraca listę detekcji — etykieta, pewność, bounding box.

augment TTAconf ≥ 0.25iou 0.45
04

Korekta koloru color_aware.py

Dla każdej detekcji rzeczywisty kolor symbolu (czerwony / czarny) klasyfikowany jest w HSV. Zgodność podbija pewność ×1.30, niezgodność karze ją ×0.30 — eliminując pomyłki czerwone–czarne.

match ×1.30mismatch ×0.30
05

Index Tracker index_tracker.py

Karta ma cztery indeksy w rogach. Jeśli w jednej klatce pojawią się dwie lub więcej detekcji tej samej klasy, karta jest potwierdzona — skraca to reakcję z ~1,5 s do ~150 ms względem trackera liczącego klatki.

min_indexes 2~150 ms
06

Announcer announcer.py · state machine

Serce interakcji — siedmiostanowa maszyna: powitanie → oczekiwanie → stabilizacja (3 s) → ogłoszenie → powtórka → prośba o następną → oczekiwanie na zabranie. Decyduje, co i kiedy powiedzieć.

stabilize 3 srepeat 2 snext 2 s
07

Audio + Display audio_player.py · display.py

Dwa wyjścia równolegle: polski plik MP3 ogłasza kartę przez pygame, a warstwa wyświetlacza auto-wykrywa OLED → LCD → HDMI i pokazuje figurę, kolor oraz pasek pewności.

pygame mixerOLED → LCD → HDMI
04

Dataset

Roboflow Universe · Public Domain
24 233
zdjęć łącznie
52
klas · 4 kolory × 13 figur
2 GB
YOLOv8 format · .txt
Train — 21 203 (87,5%) Val — 2 020 (8,3%) Test — 1 010 (4,2%)

Źródło: publiczny projekt playing-cards-ow27d na Roboflow Universe (v4, format YOLOv8). Etykiety w notacji angielskiej (KH, QD, AS…) centralna tablica tłumaczeń mapuje na polskie nazwy, symbole Unicode i slugi audio — więc „KH” staje się „Król kier” i krol_kier.mp3.

05

Trening i fine-tuning

YOLOv8n · RTX 3060

Transfer learning z YOLOv8n pretrenowanego na COCO (~3,2 mln parametrów). Jeden szczegół miał duże znaczenie: flip poziomy i pionowy były wyłączone — karty są asymetryczne, więc lustrzane odbicie to inna karta, a flip zepsułby etykiety.

mAP50-95 w trakcie treningu jedna metryka · wyżej = lepiej
0.30 0.40 0.50 0.60 0.70 0.80 1 10 20 30 40 55 epoka Trening v1 · 40 epok Fine-tune v2 0.356 0.770 ↓ 0.635 0.789

Spadek na epoce 41 to moment włączenia agresywnej augmentacji kolorów w fine-tuningu — model na chwilę „głupieje” na trudniejszych przykładach, po czym przebija swój poprzedni rekord.

Historia warta opowiedzenia: domain shift

Model v1 miał świetne metryki na zbiorze walidacyjnym — ale na fizycznych kartach, które faktycznie kupiliśmy, czerwone karty rozpoznawane były z pewnością tylko 30–50% i często myliły się między sobą. Talia treningowa miała po prostu węższy zakres czerwieni niż rzeczywistość. Zamiast zbierać nowy zbiór, zrobiliśmy fine-tuning z 3× mocniejszą augmentacją odcienia, by model nauczył się, że „Król to Król niezależnie od dokładnego odcienia czerwonego”.

v1 przed · na fizycznych kartach
Czerwone ♥ ♦30–50%
Czarne ♣ ♠95–100%
v2 po · + tryb gray
Czerwone ♥ ♦90–95%
Czarne ♣ ♠95–100%

Na papierze metryka wzrosła tylko o +2,5% (mAP50-95 0,770 → 0,789). W rzeczywistości pewność na czerwonych kartach niemal się podwoiła — przypomnienie, że metryki walidacyjne nie oddają w pełni zachowania w środowisku docelowym.

06

Wyniki

v1 vs v2 · walidacja na zbiorze Roboflow
Metryka yolo_cards.pt · v1 yolo_cards_v2.pt · v2 Zmiana
mAP500.9950.995=
mAP50-950.7700.789+2.5%
Precision0.999
Recall1.000
cls_loss0.3200.340
Rozmiar pliku23.44 MB5.98 MBfp16

Dla kontekstu — YOLOv8n na benchmarku COCO osiąga mAP50-95 ≈ 0,37. Nasze 0,789 jest około 2× wyższe: rozpoznawanie kart jest łatwiejsze niż COCO — klasy są stylistycznie spójne, symbole charakterystyczne, a tło kontrolowane.

Na fizycznym Raspberry Pi z kamerą monochromatyczną wszystkie 52 karty rozpoznawane są z pewnością 90–95%, bez podziału na czerwone i czarne. Cały pipeline — capture, YOLO, tracker, announcer — działa z prędkością około 5 FPS, co w zupełności wystarcza, gdy człowiek podkłada karty pojedynczo.

Działało bezbłędnie
76 polskich plików MP3 — bez glitchy
OLED renderuje polskie znaki + ♥ ♦ ♣ ♠ (DejaVu Sans)
Auto-detekcja wyświetlacza: OLED → LCD → HDMI
Stabilizacja 3 s wystarcza w praktyce
07

Przebieg demo

jak wygląda jedno skanowanie
  1. Startsystemd uruchamia main.py; init kamery, detektora, audio i OLED.
  2. Powitanie„Witaj”„Rozpoczynamy”„Proszę podstawić kartę”
  3. Karta podstawionaYOLO wykrywa indeksy w rogach; dwie tej samej klasy → potwierdzenie. „Skanuję kartę”.
  4. Stabilizacja (3 s)OLED pokazuje kandydata + pasek 0–100%. Jeśli klasa się zmieni → reset (anti-noise).
  5. Ogłoszenie„Karta rozpoznana” + nazwa karty (krol_kier.mp3)
  6. Powtórka i następnaPo 2 s powtórka nazwy; po kolejnych 2 s: „Proszę o następną kartę”.
  7. Zabranie → pętlaKarta zabrana → „Czekam na kartę” → powrót do kroku 3. Inna karta od razu startuje nowe skanowanie.
  8. Ctrl+C„DziękujꔄDo zobaczenia” → OLED clear
K
92%
████████████░░░░
OLED SSD1306 · 128×64 · I2C 0x3C
52cards/nazwy kart
7statusy/rozpoznana, skanuje…
4instrukcje/czekam, podstaw…
7bledy/nieczytelna…
6intro/witaj, dziekuje…

Adnotacja Przebieg powyżej opisuje wersję polskojęzyczną, na której projekt powstał. Na potrzeby CV język komunikatów systemu został zmieniony na angielski; reszta interfejsu — m.in. ekran OLED — pozostaje polska.

08

Kod i stack

src/ · app/ · deploy
camera.py
Wieloplatformowy wrapper kamery: picamera2 na Pi, cv2 fallback na PC.
detector.py
Wrapper YOLOv8 z TTA; zwraca label + conf + bbox.
color_aware.py
Sprawdzanie koloru symbolu w HSV; asymetryczny boost / kara pewności.
index_tracker.py
Potwierdza kartę z ≥2 indeksów w jednej klatce (~150 ms).
announcer.py
Siedmiostanowa maszyna koordynująca głos i wyświetlacz.
audio_player.py
pygame mixer z cache MP3 w pamięci (powtórki <1 ms).
display.py
Jeden interfejs, trzy backendy: auto-detekcja OLED → LCD → HDMI.
card_labels.py
Centralna tablica: kod Roboflow → polska nazwa, symbol, slug audio.
mode_yolo.py
Główna pętla spinająca wszystkie komponenty.
Python 3.11 / 3.13 Ultralytics YOLOv8 PyTorch 2.8 · CUDA OpenCV NumPy pygame luma.oled Pillow Roboflow systemd · bash NCNN export
Od zbioru danych do gadającego pudełka

Kompletny system ML,
a nie notebook.