:501張圖達到99.3%準確率)
簡介這份撲克牌識別數(shù)據(jù)集面向計算機視覺學(xué)習(xí)者與目標(biāo)檢測開發(fā)者用于訓(xùn)練可同時識別牌面數(shù)字與花色的模型適用于YOLO系列實戰(zhàn)、課程設(shè)計或小型競賽場景。資源包共1003個文件包含501張原始jpg圖像、501個同名txt標(biāo)注文件及1個yaml配置文件壓縮包約11.82MBtxt為YOLO v8格式的邊界框標(biāo)注yaml用于定義數(shù)據(jù)集路徑與類別jpg即原始牌面圖像整體結(jié)構(gòu)規(guī)整可直接接入訓(xùn)練流程。據(jù)描述該數(shù)據(jù)集在數(shù)字與花色識別任務(wù)上正確識別率可達99.3%樣本覆蓋多種牌面組合便于快速驗證模型效果。目前已有112人學(xué)習(xí)下載適合希望以較小數(shù)據(jù)量完成端到端檢測實驗、對比不同YOLO版本性能或作為數(shù)據(jù)增強與遷移學(xué)習(xí)起點的讀者參考使用。1. 撲克牌識別數(shù)據(jù)集501 張原始圖怎么撐起 99.3% 的識別率手里有一批撲克牌照片想讓模型同時認出「數(shù)字」和「花色」這件事聽起來簡單做起來處處是坑。數(shù)字只有 13 類花色只有 4 類可一旦把兩者組合起來就是 52 類目標(biāo)再加上不同角度、光照、遮擋、牌面反光難度立刻上一個臺階。這個撲克牌識別數(shù)據(jù)集給了一個很具體的起點501 張原始圖按 yolo v8 格式標(biāo)注官方口徑的正確識別率可達 99.3%。對做目標(biāo)檢測落地的人來說這不是一個玩具數(shù)據(jù)集而是一個能快速驗證「小樣本 多類別 細粒度」方案的試驗田。它適合誰一是想跑通 yolo v8 訓(xùn)練自己數(shù)據(jù)集全流程的新手501 張圖規(guī)模不大單卡幾十分鐘就能出第一版權(quán)重二是做牌類游戲輔助、發(fā)牌機器人、桌面視覺統(tǒng)計的工程師需要一套能直接復(fù)現(xiàn)的基線三是研究細粒度檢測的人撲克牌的數(shù)字和花色在視覺上高度相似正好用來壓榨模型的特征分辨能力。接下來我會按「數(shù)據(jù)長什么樣 → 怎么轉(zhuǎn)成 yolo v8 能吃的格式 → 訓(xùn)練參數(shù)怎么設(shè) → 翻車點在哪 → 怎么把 99.3% 穩(wěn)住」這條線把能抄的步驟和參數(shù)都攤開。2. 先看清數(shù)據(jù)501 張原始圖的標(biāo)注結(jié)構(gòu)與類別設(shè)計2.1 撲克牌識別的兩類標(biāo)注思路單標(biāo)簽 52 類 vs 雙標(biāo)簽組合拿到撲克牌數(shù)據(jù)第一件事不是急著訓(xùn)練而是決定標(biāo)注體系。常見做法有兩種一種是把「紅桃 A」當(dāng)成一個獨立類別總共 52 類另一種是拆成數(shù)字和花色兩個維度檢測框只標(biāo)牌面再用兩個分類頭分別預(yù)測數(shù)字和花色。這個數(shù)據(jù)集走的是前一種思路標(biāo)注文件里直接給出 52 類中的某一類yolo v8 格式一行一個目標(biāo)格式是class_id x_center y_center width height坐標(biāo)全部歸一化到 0 到 1。為什么小樣本更適合單標(biāo)簽 52 類因為 501 張圖分攤到 52 類平均每類不到 10 個樣本如果再做雙頭結(jié)構(gòu)每個頭的正樣本更少訓(xùn)練時容易某一維塌縮。單標(biāo)簽把數(shù)字和花色的組合當(dāng)成一個整體模型學(xué)的是「這張牌長什么樣」而不是「先認數(shù)字再認花色」在數(shù)據(jù)量不足時反而更穩(wěn)。代價是類別數(shù)多分類層參數(shù)量上升但對 yolo v8 這種 anchor-free 結(jié)構(gòu)來說52 類并不算負擔(dān)。提示如果你的場景里撲克牌種類固定且每類樣本能到 50 張以上雙標(biāo)簽組合會更靈活因為新增一種花色或數(shù)字時不用重新標(biāo)全部數(shù)據(jù)。501 張這個量級建議先按單標(biāo)簽 52 類跑通。2.2 yolo v8 標(biāo)注格式逐字段拆解與目錄組織yolo v8 的標(biāo)注文件是純文本每行代表一個目標(biāo)字段之間用空格分隔。以一張包含「黑桃 10」的圖為例標(biāo)注行可能是47 0.512 0.634 0.180 0.260。第一個數(shù)字 47 是類別索引對應(yīng)你data.yaml里 names 列表的第 48 個名字后面四個數(shù)分別是框中心 x、中心 y、框?qū)挕⒖蚋呷砍詧D像寬高做歸一化。這里最容易翻車的是類別索引和 names 順序?qū)Σ簧嫌?xùn)練時 loss 能降但推理出來全是錯類。目錄組織上我一般會保持這樣的結(jié)構(gòu)讓 yolo v8 的默認 dataloader 直接認poker_dataset/ ├── images/ │ ├── train/ # 訓(xùn)練圖約 400 張 │ └── val/ # 驗證圖約 101 張 ├── labels/ │ ├── train/ # 與 train 圖同名的 .txt │ └── val/ └── data.yamldata.yaml里寫清路徑和類別名注意path用絕對路徑或相對于訓(xùn)練腳本的路徑train和val指向 images 下的子目錄yolo v8 會自動去找同級的 labels。path: /home/user/poker_dataset train: images/train val: images/val nc: 52 names: 0: spade_A 1: spade_2 # ... 依次補齊 52 類 51: heart_K字段說明nc必須等于 names 的長度少一個都會在訓(xùn)練啟動時報索引越界names 的順序必須和標(biāo)注文件里的 class_id 嚴格一致建議用腳本生成而不是手寫。501 張圖按 8:2 切分訓(xùn)練集 400 張、驗證集 101 張這個比例在小樣本下比較穩(wěn)驗證集太小會導(dǎo)致 mAP 波動大太大又浪費訓(xùn)練樣本。2.3 用腳本檢查標(biāo)注完整性三個必查項在訓(xùn)練之前我習(xí)慣跑一個檢查腳本把三類問題提前揪出來標(biāo)注文件缺失、坐標(biāo)越界、類別索引超出 nc。這三類問題在 501 張規(guī)模的數(shù)據(jù)里很常見尤其是從其他格式轉(zhuǎn)過來的時候。import os from pathlib import Path img_dir Path(poker_dataset/images/train) lbl_dir Path(poker_dataset/labels/train) nc 52 errors [] for img in img_dir.glob(*.jpg): lbl lbl_dir / (img.stem .txt) if not lbl.exists(): errors.append(f缺標(biāo)注: {img.name}) continue for line in lbl.read_text().strip().splitlines(): parts line.split() if len(parts) ! 5: errors.append(f字段數(shù)錯: {lbl.name} - {line}) continue cid int(parts[0]) x, y, w, h map(float, parts[1:]) if cid 0 or cid nc: errors.append(f類別越界: {lbl.name} cid{cid}) if not (0 x 1 and 0 y 1 and 0 w 1 and 0 h 1): errors.append(f坐標(biāo)異常: {lbl.name} - {line}) print(f共發(fā)現(xiàn) {len(errors)} 個問題) for e in errors[:20]: print(e)邏輯說明遍歷訓(xùn)練圖逐個找同名 txt檢查每行字段數(shù)、類別索引范圍、歸一化坐標(biāo)是否落在合法區(qū)間。參數(shù)上nc要和 data.yaml 一致坐標(biāo)檢查里寬高必須大于 0中心點允許等于 0 或 1貼邊目標(biāo)。跑完如果輸出 0 個問題說明標(biāo)注基本干凈如果有越界優(yōu)先懷疑轉(zhuǎn)換腳本把像素坐標(biāo)直接寫進去了沒有除以圖像寬高。3. 從原始圖到 yolo v8 可訓(xùn)練轉(zhuǎn)換、劃分與增強配置3.1 把 VOC 或 COCO 標(biāo)注轉(zhuǎn)成 yolo v8 格式的腳本很多撲克牌數(shù)據(jù)最初是 VOC 的 XML 或 COCO 的 JSON要喂給 yolo v8 必須先轉(zhuǎn)。轉(zhuǎn)換的核心就一件事把絕對像素坐標(biāo)變成歸一化中心點加寬高。下面這個腳本處理 VOC 格式COCO 只需把讀取部分換成 json 解析邏輯一樣。import xml.etree.ElementTree as ET from pathlib import Path from PIL import Image classes [spade_A, spade_2, ...] # 52 類順序固定 cls2id {c: i for i, c in enumerate(classes)} def voc_to_yolo(xml_path, img_path, out_path): tree ET.parse(xml_path) root tree.getroot() w int(root.find(size/width).text) h int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in cls2id: continue bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) xc (xmin xmax) / 2 / w yc (ymin ymax) / 2 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{cls2id[name]} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) out_path.write_text(\n.join(lines)) for xml in Path(voc_annotations).glob(*.xml): img Path(images) / (xml.stem .jpg) voc_to_yolo(xml, img, Path(labels) / (xml.stem .txt))邏輯說明先讀 XML 里的圖像寬高再對每個 object 取 bbox 四個角點算中心點和寬高后除以寬高歸一化。參數(shù)上classes列表順序就是最終 class_id必須和 data.yaml 完全一致保留 6 位小數(shù)足夠yolo v8 內(nèi)部會再處理。轉(zhuǎn)換完務(wù)必用 2.3 的檢查腳本過一遍我見過太多因為 XML 里 name 拼寫不一致導(dǎo)致某類直接消失的情況。3.2 訓(xùn)練集驗證集劃分別讓同一張牌的兩個角度分到兩邊501 張圖如果隨機切分很容易出現(xiàn)「同一張牌的不同拍攝角度」被分到訓(xùn)練和驗證兩邊驗證集 mAP 虛高實際部署就翻車。我的做法是按牌面組合分層抽樣先統(tǒng)計每類出現(xiàn)的圖片保證每個類別在驗證集里至少有一張同時同一張物理牌的不同角度盡量只進一邊。如果數(shù)據(jù)里同一張牌有多個角度建議按「牌」分組后再切分而不是按圖隨機。import random from pathlib import Path from collections import defaultdict random.seed(42) img_dir Path(poker_dataset/images/all) imgs list(img_dir.glob(*.jpg)) # 按類別分組這里簡化為按文件名前綴分組實際按標(biāo)注統(tǒng)計 groups defaultdict(list) for img in imgs: key img.stem.split(_)[0] # 假設(shè)文件名含牌面標(biāo)識 groups[key].append(img) train, val [], [] for key, items in groups.items(): random.shuffle(items) split max(1, int(len(items) * 0.2)) val.extend(items[:split]) train.extend(items[split:]) print(ftrain{len(train)} val{len(val)})邏輯說明用文件名前綴模擬「同一張牌」的分組實際項目里應(yīng)該根據(jù)標(biāo)注內(nèi)容聚類。參數(shù)0.2是驗證集比例seed42保證可復(fù)現(xiàn)。切分后把圖復(fù)制到 images/train 和 images/val標(biāo)注同步復(fù)制再核對一遍兩邊類別分布避免某類只在訓(xùn)練集出現(xiàn)。3.3 yolo v8 訓(xùn)練參數(shù)501 張圖該設(shè)多少 epoch 和 batch數(shù)據(jù)準備好后用 ultralytics 的 yolo v8 訓(xùn)練命令行一行就能起。501 張圖屬于小樣本參數(shù)不能照搬 COCO 那套。yolo detect train \ datapoker_dataset/data.yaml \ modelyolov8n.pt \ epochs300 \ imgsz640 \ batch16 \ lr00.01 \ lrf0.01 \ patience50 \ augmentTrue \ mosaic1.0 \ degrees10.0 \ hsv_h0.015 \ hsv_s0.7 \ hsv_v0.4 \ projectruns/poker \ namev8n_501參數(shù)說明modelyolov8n.pt用 nano 版501 張圖不需要大模型n 版在單卡上幾分鐘一個 epochepochs300配合patience50驗證集 50 輪不漲就早停避免過擬合imgsz640是 yolo v8 默認撲克牌在圖中占比不小640 夠用如果牌很小可以提到 960batch16在 8G 顯存上穩(wěn)顯存大可加到 32lr00.01是初始學(xué)習(xí)率小樣本別設(shè)太大否則 loss 震蕩mosaic1.0開啟馬賽克增強對小樣本很關(guān)鍵能顯著提升泛化degrees10.0做小角度旋轉(zhuǎn)撲克牌擺放角度多變這個增強很值HSV 三參數(shù)控制顏色抖動應(yīng)對不同光照。訓(xùn)練啟動后重點看三個指標(biāo)box_loss是否穩(wěn)定下降、mAP50是否在 100 輪后進入平臺、驗證集cls_loss是否明顯高于訓(xùn)練集過擬合信號。如果 mAP50 卡在 0.8 上不去先別調(diào)模型回頭查標(biāo)注里有沒有類別混淆尤其是數(shù)字 6 和 9、方塊和紅桃這種視覺相近的。4. 99.3% 識別率背后的避坑與排查清單4.1 現(xiàn)象訓(xùn)練 mAP 很高推理卻把紅桃認成方塊原因驗證集和訓(xùn)練集來自同一批拍攝條件模型學(xué)到了背景或光照的捷徑而不是牌面本身的特征?;ㄉ锛t桃和方塊都是紅色形狀差異在低分辨率下被抹平模型靠顏色區(qū)分一旦換光照就崩。解決在增強里加大hsv_h和hsv_s讓顏色通道抖動更劇烈逼模型看形狀同時把驗證集換成不同光照下拍的圖哪怕只有幾十張也能暴露問題。如果條件允許把紅桃和方塊的樣本單獨抽出來做混淆矩陣看錯分集中在哪幾類。4.2 現(xiàn)象某些類別 mAP 為 0訓(xùn)練日志里也沒報錯原因這些類別的標(biāo)注 class_id 超出了nc或者 names 列表里名字拼寫和標(biāo)注不一致yolo v8 在加載時靜默跳過不報錯。501 張圖里如果某類只有兩三個樣本更容易被忽略。解決跑 2.3 的檢查腳本重點看類別索引分布再用yolo detect val輸出每類 APAP 為 0 的類逐個核對標(biāo)注。我一般會統(tǒng)計每個 class_id 的標(biāo)注框數(shù)量少于 5 個的類要么補數(shù)據(jù)要么合并到相近類。4.3 現(xiàn)象訓(xùn)練到 200 輪后驗證 loss 開始上升mAP 反而降原因小樣本過擬合。501 張圖對 52 類來說平均每類不到 10 個樣本模型在 150 輪左右就記住了訓(xùn)練集之后開始背噪聲。解決把patience從 50 降到 30早停更敏感開啟dropoutyolo v8 里通過dropout0.1設(shè)置減小模型從 yolov8s 換回 yolov8n增強里把mosaic保持 1.0再加mixup0.1。如果還壓不住說明數(shù)據(jù)量確實是瓶頸考慮用同一批牌多拍幾個角度補到 800 張以上。4.4 現(xiàn)象推理時一張圖里多張牌漏檢靠近邊緣的牌原因yolo v8 對貼邊目標(biāo)的回歸能力弱標(biāo)注時如果框貼邊歸一化坐標(biāo)接近 0 或 1訓(xùn)練時容易被裁掉。另外imgsz太小邊緣目標(biāo)下采樣后特征消失。解決標(biāo)注時給貼邊目標(biāo)留 2 到 3 像素余量訓(xùn)練時把imgsz提到 960推理時降低conf閾值到 0.15 觀察是否召回如果召回但誤檢多再用 NMS 的iou參數(shù)壓。我一般會在推理腳本里把conf0.25, iou0.45作為起點根據(jù)實際漏檢情況微調(diào)。4.5 現(xiàn)象換一臺機器或換一批牌識別率從 99.3% 掉到 80%原因99.3% 是在特定數(shù)據(jù)集分布下測的牌面印刷、相機白平衡、桌面背景一變特征分布就漂移。這不是模型的問題是數(shù)據(jù)覆蓋度的問題。解決部署前用目標(biāo)場景的圖做一次小樣本微調(diào)哪怕只有 50 張凍結(jié) backbone 只訓(xùn) head學(xué)習(xí)率設(shè) 0.00120 輪就能拉回來。同時把推理時的預(yù)處理對齊訓(xùn)練配置比如訓(xùn)練用了 letterbox推理也要 letterbox別直接 resize否則長寬比一變框就偏了。5. 把 99.3% 復(fù)現(xiàn)出來驗證腳本與一個提點技巧訓(xùn)練完拿到best.pt別急著信日志里的 mAP自己寫一個驗證腳本按類別統(tǒng)計準確率才能知道 99.3% 到底落在哪。下面這個腳本用 ultralytics 的 API 跑驗證集輸出每類 AP 和整體混淆矩陣。from ultralytics import YOLO import numpy as np model YOLO(runs/poker/v8n_501/weights/best.pt) metrics model.val(datapoker_dataset/data.yaml, imgsz640, conf0.25, iou0.45) print(fmAP50: {metrics.box.map50:.4f}) print(fmAP50-95: {metrics.box.map:.4f}) # 每類 AP for i, ap in enumerate(metrics.box.ap50): if ap 0.9: print(f類別 {i} AP50{ap:.3f} 偏低需補數(shù)據(jù))邏輯說明model.val會按 data.yaml 的 val 路徑跑一遍返回的metrics.box.ap50是每類 AP 數(shù)組。參數(shù)conf0.25和iou0.45要和部署時一致否則驗證結(jié)果沒有參考意義。跑完重點看 AP 低于 0.9 的類這些就是拉低整體 99.3% 的短板優(yōu)先補它們的樣本。一個提點技巧撲克牌的數(shù)字和花色在牌面上下兩端是對稱的如果模型對旋轉(zhuǎn)敏感可以在推理前做一次 TTA測試時增強把原圖和旋轉(zhuǎn) 180 度的圖各跑一次框合并后再 NMS。yolo v8 自帶augmentTrue參數(shù)推理時加上就能開啟 TTA代價是速度慢一倍但對小樣本模型通常能提 1 到 2 個點。我自己的習(xí)慣是只要部署端算力夠推理默認開 TTA尤其是牌面可能倒著放的時候。最后說個血淚教訓(xùn)別在驗證集上反復(fù)調(diào)參調(diào)到 99.9%然后拿這個數(shù)去匯報。501 張圖的驗證集只有 101 張mAP 波動一兩個點是常事真正靠譜的做法是留一批完全沒參與訓(xùn)練的圖做最終測試哪怕只有 30 張。我一般會把數(shù)據(jù)切成 train/val/test 三份test 只在最后跑一次跑完就封存這樣得到的數(shù)字才敢寫進文檔。希望幫到你。本文還有配套的精品資源點擊獲取