
简介这份扑克牌识别数据集面向计算机视觉学习者与目标检测开发者用于训练可同时识别牌面数字与花色的模型适用于YOLO系列实战、课程设计或小型竞赛场景。资源包共1003个文件包含501张原始jpg图像、501个同名txt标注文件及1个yaml配置文件压缩包约11.82MBtxt为YOLO v8格式的边界框标注yaml用于定义数据集路径与类别jpg即原始牌面图像整体结构规整可直接接入训练流程。据描述该数据集在数字与花色识别任务上正确识别率可达99.3%样本覆盖多种牌面组合便于快速验证模型效果。目前已有112人学习下载适合希望以较小数据量完成端到端检测实验、对比不同YOLO版本性能或作为数据增强与迁移学习起点的读者参考使用。1. 扑克牌识别数据集501 张原始图怎么撑起 99.3% 的识别率手里有一批扑克牌照片想让模型同时认出「数字」和「花色」这件事听起来简单做起来处处是坑。数字只有 13 类花色只有 4 类可一旦把两者组合起来就是 52 类目标再加上不同角度、光照、遮挡、牌面反光难度立刻上一个台阶。这个扑克牌识别数据集给了一个很具体的起点501 张原始图按 yolo v8 格式标注官方口径的正确识别率可达 99.3%。对做目标检测落地的人来说这不是一个玩具数据集而是一个能快速验证「小样本 多类别 细粒度」方案的试验田。它适合谁一是想跑通 yolo v8 训练自己数据集全流程的新手501 张图规模不大单卡几十分钟就能出第一版权重二是做牌类游戏辅助、发牌机器人、桌面视觉统计的工程师需要一套能直接复现的基线三是研究细粒度检测的人扑克牌的数字和花色在视觉上高度相似正好用来压榨模型的特征分辨能力。接下来我会按「数据长什么样 → 怎么转成 yolo v8 能吃的格式 → 训练参数怎么设 → 翻车点在哪 → 怎么把 99.3% 稳住」这条线把能抄的步骤和参数都摊开。2. 先看清数据501 张原始图的标注结构与类别设计2.1 扑克牌识别的两类标注思路单标签 52 类 vs 双标签组合拿到扑克牌数据第一件事不是急着训练而是决定标注体系。常见做法有两种一种是把「红桃 A」当成一个独立类别总共 52 类另一种是拆成数字和花色两个维度检测框只标牌面再用两个分类头分别预测数字和花色。这个数据集走的是前一种思路标注文件里直接给出 52 类中的某一类yolo v8 格式一行一个目标格式是class_id x_center y_center width height坐标全部归一化到 0 到 1。为什么小样本更适合单标签 52 类因为 501 张图分摊到 52 类平均每类不到 10 个样本如果再做双头结构每个头的正样本更少训练时容易某一维塌缩。单标签把数字和花色的组合当成一个整体模型学的是「这张牌长什么样」而不是「先认数字再认花色」在数据量不足时反而更稳。代价是类别数多分类层参数量上升但对 yolo v8 这种 anchor-free 结构来说52 类并不算负担。提示如果你的场景里扑克牌种类固定且每类样本能到 50 张以上双标签组合会更灵活因为新增一种花色或数字时不用重新标全部数据。501 张这个量级建议先按单标签 52 类跑通。2.2 yolo v8 标注格式逐字段拆解与目录组织yolo v8 的标注文件是纯文本每行代表一个目标字段之间用空格分隔。以一张包含「黑桃 10」的图为例标注行可能是47 0.512 0.634 0.180 0.260。第一个数字 47 是类别索引对应你data.yaml里 names 列表的第 48 个名字后面四个数分别是框中心 x、中心 y、框宽、框高全部除以图像宽高做归一化。这里最容易翻车的是类别索引和 names 顺序对不上训练时 loss 能降但推理出来全是错类。目录组织上我一般会保持这样的结构让 yolo v8 的默认 dataloader 直接认poker_dataset/ ├── images/ │ ├── train/ # 训练图约 400 张 │ └── val/ # 验证图约 101 张 ├── labels/ │ ├── train/ # 与 train 图同名的 .txt │ └── val/ └── data.yamldata.yaml里写清路径和类别名注意path用绝对路径或相对于训练脚本的路径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 的长度少一个都会在训练启动时报索引越界names 的顺序必须和标注文件里的 class_id 严格一致建议用脚本生成而不是手写。501 张图按 8:2 切分训练集 400 张、验证集 101 张这个比例在小样本下比较稳验证集太小会导致 mAP 波动大太大又浪费训练样本。2.3 用脚本检查标注完整性三个必查项在训练之前我习惯跑一个检查脚本把三类问题提前揪出来标注文件缺失、坐标越界、类别索引超出 nc。这三类问题在 501 张规模的数据里很常见尤其是从其他格式转过来的时候。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缺标注: {img.name}) continue for line in lbl.read_text().strip().splitlines(): parts line.split() if len(parts) ! 5: errors.append(f字段数错: {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坐标异常: {lbl.name} - {line}) print(f共发现 {len(errors)} 个问题) for e in errors[:20]: print(e)逻辑说明遍历训练图逐个找同名 txt检查每行字段数、类别索引范围、归一化坐标是否落在合法区间。参数上nc要和 data.yaml 一致坐标检查里宽高必须大于 0中心点允许等于 0 或 1贴边目标。跑完如果输出 0 个问题说明标注基本干净如果有越界优先怀疑转换脚本把像素坐标直接写进去了没有除以图像宽高。3. 从原始图到 yolo v8 可训练转换、划分与增强配置3.1 把 VOC 或 COCO 标注转成 yolo v8 格式的脚本很多扑克牌数据最初是 VOC 的 XML 或 COCO 的 JSON要喂给 yolo v8 必须先转。转换的核心就一件事把绝对像素坐标变成归一化中心点加宽高。下面这个脚本处理 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 四个角点算中心点和宽高后除以宽高归一化。参数上classes列表顺序就是最终 class_id必须和 data.yaml 完全一致保留 6 位小数足够yolo v8 内部会再处理。转换完务必用 2.3 的检查脚本过一遍我见过太多因为 XML 里 name 拼写不一致导致某类直接消失的情况。3.2 训练集验证集划分别让同一张牌的两个角度分到两边501 张图如果随机切分很容易出现「同一张牌的不同拍摄角度」被分到训练和验证两边验证集 mAP 虚高实际部署就翻车。我的做法是按牌面组合分层抽样先统计每类出现的图片保证每个类别在验证集里至少有一张同时同一张物理牌的不同角度尽量只进一边。如果数据里同一张牌有多个角度建议按「牌」分组后再切分而不是按图随机。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)) # 按类别分组这里简化为按文件名前缀分组实际按标注统计 groups defaultdict(list) for img in imgs: key img.stem.split(_)[0] # 假设文件名含牌面标识 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)})逻辑说明用文件名前缀模拟「同一张牌」的分组实际项目里应该根据标注内容聚类。参数0.2是验证集比例seed42保证可复现。切分后把图复制到 images/train 和 images/val标注同步复制再核对一遍两边类别分布避免某类只在训练集出现。3.3 yolo v8 训练参数501 张图该设多少 epoch 和 batch数据准备好后用 ultralytics 的 yolo v8 训练命令行一行就能起。501 张图属于小样本参数不能照搬 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参数说明modelyolov8n.pt用 nano 版501 张图不需要大模型n 版在单卡上几分钟一个 epochepochs300配合patience50验证集 50 轮不涨就早停避免过拟合imgsz640是 yolo v8 默认扑克牌在图中占比不小640 够用如果牌很小可以提到 960batch16在 8G 显存上稳显存大可加到 32lr00.01是初始学习率小样本别设太大否则 loss 震荡mosaic1.0开启马赛克增强对小样本很关键能显著提升泛化degrees10.0做小角度旋转扑克牌摆放角度多变这个增强很值HSV 三参数控制颜色抖动应对不同光照。训练启动后重点看三个指标box_loss是否稳定下降、mAP50是否在 100 轮后进入平台、验证集cls_loss是否明显高于训练集过拟合信号。如果 mAP50 卡在 0.8 上不去先别调模型回头查标注里有没有类别混淆尤其是数字 6 和 9、方块和红桃这种视觉相近的。4. 99.3% 识别率背后的避坑与排查清单4.1 现象训练 mAP 很高推理却把红桃认成方块原因验证集和训练集来自同一批拍摄条件模型学到了背景或光照的捷径而不是牌面本身的特征。花色里红桃和方块都是红色形状差异在低分辨率下被抹平模型靠颜色区分一旦换光照就崩。解决在增强里加大hsv_h和hsv_s让颜色通道抖动更剧烈逼模型看形状同时把验证集换成不同光照下拍的图哪怕只有几十张也能暴露问题。如果条件允许把红桃和方块的样本单独抽出来做混淆矩阵看错分集中在哪几类。4.2 现象某些类别 mAP 为 0训练日志里也没报错原因这些类别的标注 class_id 超出了nc或者 names 列表里名字拼写和标注不一致yolo v8 在加载时静默跳过不报错。501 张图里如果某类只有两三个样本更容易被忽略。解决跑 2.3 的检查脚本重点看类别索引分布再用yolo detect val输出每类 APAP 为 0 的类逐个核对标注。我一般会统计每个 class_id 的标注框数量少于 5 个的类要么补数据要么合并到相近类。4.3 现象训练到 200 轮后验证 loss 开始上升mAP 反而降原因小样本过拟合。501 张图对 52 类来说平均每类不到 10 个样本模型在 150 轮左右就记住了训练集之后开始背噪声。解决把patience从 50 降到 30早停更敏感开启dropoutyolo v8 里通过dropout0.1设置减小模型从 yolov8s 换回 yolov8n增强里把mosaic保持 1.0再加mixup0.1。如果还压不住说明数据量确实是瓶颈考虑用同一批牌多拍几个角度补到 800 张以上。4.4 现象推理时一张图里多张牌漏检靠近边缘的牌原因yolo v8 对贴边目标的回归能力弱标注时如果框贴边归一化坐标接近 0 或 1训练时容易被裁掉。另外imgsz太小边缘目标下采样后特征消失。解决标注时给贴边目标留 2 到 3 像素余量训练时把imgsz提到 960推理时降低conf阈值到 0.15 观察是否召回如果召回但误检多再用 NMS 的iou参数压。我一般会在推理脚本里把conf0.25, iou0.45作为起点根据实际漏检情况微调。4.5 现象换一台机器或换一批牌识别率从 99.3% 掉到 80%原因99.3% 是在特定数据集分布下测的牌面印刷、相机白平衡、桌面背景一变特征分布就漂移。这不是模型的问题是数据覆盖度的问题。解决部署前用目标场景的图做一次小样本微调哪怕只有 50 张冻结 backbone 只训 head学习率设 0.00120 轮就能拉回来。同时把推理时的预处理对齐训练配置比如训练用了 letterbox推理也要 letterbox别直接 resize否则长宽比一变框就偏了。5. 把 99.3% 复现出来验证脚本与一个提点技巧训练完拿到best.pt别急着信日志里的 mAP自己写一个验证脚本按类别统计准确率才能知道 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} 偏低需补数据)逻辑说明model.val会按 data.yaml 的 val 路径跑一遍返回的metrics.box.ap50是每类 AP 数组。参数conf0.25和iou0.45要和部署时一致否则验证结果没有参考意义。跑完重点看 AP 低于 0.9 的类这些就是拉低整体 99.3% 的短板优先补它们的样本。一个提点技巧扑克牌的数字和花色在牌面上下两端是对称的如果模型对旋转敏感可以在推理前做一次 TTA测试时增强把原图和旋转 180 度的图各跑一次框合并后再 NMS。yolo v8 自带augmentTrue参数推理时加上就能开启 TTA代价是速度慢一倍但对小样本模型通常能提 1 到 2 个点。我自己的习惯是只要部署端算力够推理默认开 TTA尤其是牌面可能倒着放的时候。最后说个血泪教训别在验证集上反复调参调到 99.9%然后拿这个数去汇报。501 张图的验证集只有 101 张mAP 波动一两个点是常事真正靠谱的做法是留一批完全没参与训练的图做最终测试哪怕只有 30 张。我一般会把数据切成 train/val/test 三份test 只在最后跑一次跑完就封存这样得到的数字才敢写进文档。希望帮到你。本文还有配套的精品资源点击获取