简介:一个面向目标检测任务的大型扑克牌图像数据集,按YOLOV5目录结构整理,包含四种花色从1到K的52种扑克牌类别,可直接用于YOLO系列模型训练与性能验证。压缩包内共2000个文件,其中1999个为txt标注文件,另1个为Python可视化脚本,整体压缩包大小约947.74MB。所有图片规格统一为720×720 RGB格式,训练集包含16000张图片及对应同名txt标签,验证集包含4000张图片及标签,数据量充足且类别均衡,并附带类别字典txt文件,便于快速完成类别映射。随包提供的show.py脚本无需修改即可运行,随机传入图片即可自动绘制边界框并保存至当前目录,省去格式转换与人工标注检查的繁琐步骤。目前已有133人浏览学习,尤其适合需要开展扑克牌检测、小目标识别或自定义数据集制作教学的研究者与开发者。
1. 目标检测数据集(YOLOv5目录格式):52类扑克牌,从拿到手到训练出模型的完整路径
做棋牌类视觉项目的人,十有八九卡在数据上:要么自己拿手机拍几百张牌,标到手抽筋;要么从网上爬图,格式乱七八糟,根本喂不进 YOLOv5。这个大型扑克牌图像检测数据集,52 个类别正好对应一副牌去掉大小王,并且已经整理成 YOLOv5 标准目录格式——images 和 labels 分好,每张图片配一个同名 txt 标注文件。拿到手解压就能训练,省掉的不是一点半点功夫。适合谁用?想做发牌机视觉校验、棋牌游戏自动化、牌面识别算法的工程师和学生,或者想跑通 YOLOv5 训练流程但嫌数据标注太麻烦的人。下面直接拆目录、讲格式、跑训练。
2. 数据集目录结构拆解:images/labels 对齐与标注文件格式
2.1 这个数据集的目录是怎么组织起来的
解压之后目录一般是这样的,前提是数据集作者没有做额外的嵌套,这种结构也是 YOLOv5 官方推荐的最简组织方式:
poker_yolo/ |-- images/ | |-- train/ | | |-- 000001.jpg | | |-- 000002.jpg | | `-- ... | `-- val/ | |-- 000101.jpg | `-- ... |-- labels/ | |-- train/ | | |-- 000001.txt | | |-- 000002.txt | `-- val/ | |-- 000101.txt | `-- ... `-- data.yamlimages/train、images/val 和 labels/train、labels/val 四个目录严格对应。YOLOv5 的加载逻辑非常简单粗暴:读取 images 下某张图,就在 labels 下找同名 txt。后缀无所谓,jpg、png 都能认,但文件名必须一致。如果拿到手的数据集里只有 images 和 labels 两套目录,没有 data.yaml,就自己补一个,后面第四章会讲怎么写。
有些版本的数据集会多出一个classes.txt,每行一个类别名,顺序就是训练时的类别编号顺序。这个文件主要用来给标注工具喂类别列表,YOLOv5 训练时不直接读它,只读 data.yaml 里配置的 names。
2.2 52 个类别是按照什么顺序编号的
一副牌 52 张,通常的编号规则是:红桃 A~K 为 0~12,黑桃为 13~25,方块为 26~38,梅花为 39~51。有的数据集会把四种花色分开编号,有的则直接按黑红两种颜色、四种花色交替排,比如 0~3 都是 A(红桃A、黑桃A、方块A、梅花A),4~7 都是 2。拿到数据集后第一件事就是打开data.yaml看 names 列表,千万别靠猜。
如果 data.yaml 里的 names 是['HA', 'H2', ..., 'HK', 'SA', ...]这种缩写写法,H 是 Hearts 红桃,S 是 Spades 黑桃,D 是 Diamonds 方块,C 是 Clubs 梅花,数字是牌面点数。搞清楚缩写才能保证后面换自己的数据集时不至于把类别弄反。第一行对应编号 0,这个顺序在你的训练和推理阶段必须一致,模型只认编号不认名字。
2.3 单张图片的标注内容到底长什么样
YOLO 格式的标注文件是纯文本,每行描述一个目标框,总共五行信息:
<class_id> <x_center> <y_center> <width> <height>class_id 是整数,从 0 开始;后面的四个值全部是归一化坐标,范围在 0 到 1 之间,等于像素坐标除以图片宽高。举个例子,一张 640×480 的图片里有一张牌,牌的中心在像素坐标 (320, 240) 处,宽 200 像素,高 280 像素,归一化后中心为 (0.5, 0.5),宽高为 (0.3125, 0.5833),如果这张牌是红桃 A,对应类别 0,那这一行就是0 0.5 0.5 0.3125 0.5833。
我习惯用一段脚本来核对标注文件里有没有越界坐标,因为很多数据集在清洗时会出现边界框超出图片范围的情况,YOLOv5 训练时虽然能跑,但会导致 loss 异常甚至训练崩掉。下面这个脚本扫描整个 labels 目录,把越界的行打印出来:
import os labels_dir = "poker_yolo/labels/train" for filename in os.listdir(labels_dir): if not filename.endswith(".txt"): continue path = os.path.join(labels_dir, filename) with open(path, "r") as f: for line_num, line in enumerate(f, 1): parts = line.strip().split() if len(parts) != 5: print(f"[格式错误] {filename} 第{line_num}行字段数不对") continue try: x_c, y_c, w, h = map(float, parts[1:]) except ValueError: print(f"[非数字] {filename} 第{line_num}行") continue if not (0 <= x_c <= 1 and 0 <= y_c <= 1 and 0 <= w <= 1 and 0 <= h <= 1): print(f"[越界] {filename} 第{line_num}行: {line.strip()}")这段脚本的逻辑是:逐行拆分标注内容,先检查字段数,再把后四个值转成浮点数,最后判断是否落在 0~1 区间内。出现越界通常有两种原因:标注工具导出没按归一化格式来,坐标还是像素值直接写进去了;或者是数据增强跑了一半被打断,gamma 值没同步更新。这两种情况都必须修完再训练,不能偷懒跳过。
3. 训练前的数据体检:把 52 类分布和标注质量完全摸清
3.1 统计每类的样本数量,先确认类别均不均匀
拿到数据集先看看有没有哪一类样本明显偏少。牌面检测有个天然特点:一副牌里每张牌的出现频率理论上是一致的,但拍摄场景里出牌概率不一样,很多数据集里的 A 和 K 会比中间的 6、7、8 多。如果某个类别的样本数只有别的类别的十分之一,训练出来的模型对那张牌的召回率会低得离谱。用 Python 跑一遍数量统计:
import os from collections import Counter def count_classes(labels_dir): counter = Counter() for filename in os.listdir(labels_dir): if not filename.endswith(".txt"): continue with open(os.path.join(labels_dir, filename), "r") as f: for line in f: parts = line.strip().split() if len(parts) == 5: counter[int(parts[0])] += 1 return counter train_counts = count_classes("poker_yolo/labels/train") val_counts = count_classes("poker_yolo/labels/val") print("训练集各类别数量:") for class_id in range(52): print(f"类别 {class_id}: {train_counts.get(class_id, 0)}") print(f"\n验证集总标注数: {sum(val_counts.values())}")上面这段脚本用一个Counter对象记录每个类别出现的次数,遍历 labels 目录下所有 txt,读每行第一个数字作为类别 ID 累加。跑完后如果发现某个类别的数量是 0,直接翻data.yaml里对应的名字,看看是不是类别总数写错了——比如数据集是 52 类,但 data.yaml 里只列了 13 个点数没区分花色,这时候类别 ID 对不上,训练会直接报错。
如果样本不均衡,优先考虑的办法是weighted sampler或者对少的类别做针对性数据增强,不要一开始就删样本。扑克牌 52 类,如果某个类别只有一二十张,删了就几乎等于没有这个类别了。
3.2 把标注框画到图片上看是否贴合牌面
标注文件数值上没错,不代表标注质量就高。YOLO 格式的坐标是归一化的,人眼看不出框和牌面的贴合度,需要把框用 OpenCV 画回原图上。下面这段脚本自动抽查若干张训练图片,把标注框可视化输出到check目录,肉眼检查:
import cv2 import os img_dir = "poker_yolo/images/train" label_dir = "poker_yolo/labels/train" output_dir = "check_output" os.makedirs(output_dir, exist_ok=True) class_names = ["HA","H2","H3","H4","H5","H6","H7","H8","H9","H10","HJ","HQ","HK", "SA","S2","S3","S4","S5","S6","S7","S8","S9","S10","SJ","SQ","SK", "DA","D2","D3","D4","D5","D6","D7","D8","D9","D10","DJ","DQ","DK", "CA","C2","C3","C4","C5","C6","C7","C8","C9","C10","CJ","CQ","CK"] files = os.listdir(img_dir)[:10] for fname in files: stem, ext = os.path.splitext(fname) if ext.lower() not in [".jpg", ".jpeg", ".png"]: continue img = cv2.imread(os.path.join(img_dir, fname)) h, w = img.shape[:2] label_path = os.path.join(label_dir, stem + ".txt") if not os.path.exists(label_path): continue with open(label_path, "r") as f: for line in f: parts = line.strip().split() if len(parts) != 5: continue cls_id, x_c, y_c, bw, bh = int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) x1 = int((x_c - bw / 2) * w) y1 = int((y_c - bh / 2) * h) x2 = int((x_c + bw / 2) * w) y2 = int((y_c + bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, class_names[cls_id], (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) out_path = os.path.join(output_dir, fname) cv2.imwrite(out_path, img) print(f"已输出: {out_path}")这个脚本的核心逻辑是:读取图片尺寸,将归一化坐标还原为像素坐标,画矩形框和类别名。特别注意y1 - 5处的max,防止类别文字在图片顶部的牌面写出边界。看可视化结果时,重点检查两类问题:一是框大到把两张牌都包进去了,这是标注合并错误;二是框只框住了牌面中心区域,而牌面的点数或花色字符在框外,这会影响模型对牌面特征的提取。
3.3 检查 train/val 切分是否有泄露
如果数据集的images/train和images/val里出现了同一张牌面照片,那验证集的指标就失真了,因为这个模型相当于提前看到了答案。检查方法很简单:对比两个目录下图片文件的 MD5 值。有的数据集作者在切分时直接把连续编号的前 80% 分给 train,后 20% 分给 val,如果原始数据是按时间顺序拍摄的,同一张牌的前后帧就可能被切进两个集合。
md5sum poker_yolo/images/train/*.jpg | sort > /tmp/train_md5.txt md5sum poker_yolo/images/val/*.jpg | sort > /tmp/val_md5.txt comm -12 /tmp/train_md5.txt /tmp/val_md5.txtcomm -12的作用是找出两个文件中相同的行,也就是 MD5 相同的图片。如果这两条命令结果为空,说明没有完全相同的图;如果非空,就需要手动剔除重复。但即便图片不重复,还可能存在相邻帧相似度极高的情况,肉眼根本分不出来——这需要计算感知哈希或结构相似度(SSIM),工程上比较耗时,超过 0.95 相似度的照片如果原图尺寸一致基本可以直接判为重复帧。
4. 在本地跑通 YOLOv5 训练:data.yaml 配置与完整命令
4.1 写一个能直接用的 data.yaml
YOLOv5 的 data.yaml 是训练入口的配置文件,数据集的路径、类别数、类别名都定义在这个文件里。对于这个扑克牌数据集,一个可用的配置是这样的:
path: poker_yolo train: images/train val: images/val nc: 52 names: 0: HA 1: H2 2: H3 3: H4 4: H5 5: H6 6: H7 7: H8 8: H9 9: H10 10: HJ 11: HQ 12: HK 13: SA 14: S2 15: S3 16: S4 17: S5 18: S6 19: S7 20: S8 21: S9 22: S10 23: SJ 24: SQ 25: SK 26: DA 27: D2 28: D3 29: D4 30: D5 31: D6 32: D7 33: D8 34: D9 35: D10 36: DJ 37: DQ 38: DK 39: CA 40: C2 41: C3 42: C4 43: C5 44: C6 45: C7 46: C8 47: C9 48: C10 49: CJ 50: CQ 51: CKpath字段是数据集的根目录路径,可以写绝对路径也可以写相对于当前工作目录的路径;train和val写的是 images 下子目录的相对路径。nc必须是 52,类别数量写错最直接的后果是运行时报AssertionError: nc does not match len(names)。names列表的索引位置就是标注文件里的 class_id,这个序列一旦训练开始就不能改,否则你训练出来的模型推理结果会全部错位。如果不想写 52 行 names,也能写成一行数组形式,比如names: ['HA', 'H2', ...],效果完全一样,只是 YAML 里列表写法更紧凑,但可读性差点。
4.2 克隆 YOLOv5 代码并安装依赖
常见做法是直接从官方仓库拉最新版代码,不要用 pip 里装的零散版本。跑下面的命令把环境和代码准备好:
git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt如果你的机器是笔记本,没有独立显卡或者显存只有 4~6G,别急着跑 yolo5s 或者 yolo5m,先跑 yolo5n 或者 yolo5s,这两个模型参数量小,一张 4G 显存的卡训练 640×640 输入时 batch size 设 8 左右比较稳。如果运行pip install时报版本冲突,常见原因是本机已经装了老版本的 torch,建议先建一个独立的 conda 环境再装,别把系统环境搞乱了。安装完成后可以跑一次python train.py --help确认依赖没问题,这一步能挡掉一大堆莫名其妙的环境错误。
4.3 启动训练:预训练权重、batch size 和超参数的选择
带预训练权重的迁移学习,是这种棋牌类检测任务最优的启动方式。预训练权重让模型从 COCO 的 80 类特征出发,而不是从零开始学怎么识别边缘和纹理。对于 52 类扑克牌数据集,命令如下:
python train.py \ --data poker_yolo/data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --workers 4 \ --name poker_train各参数的含义:--data指向刚才写的 yaml 文件;--weights指定预训练权重,第一次运行会自动下载 yolov5s.pt 到当前目录;--img 640是把所有输入图片缩放到 640×640 再训练;--batch 16是每轮迭代喂入网络的图片张数,显存不够就降到 8 或者 4;--epochs 100是训练轮数,扑克牌这种目标特征简单,100 轮内通常能收敛;--workers 4是数据加载线程数,Windows 上如果报多进程错误,把它改成 0 最省心。训练时会打印每轮的 mAP、loss、P 和 R 指标,观察前 10 轮如果 box_loss 完全没有下降趋势,说明学习率设得太高或数据集路径配错了,尽早停下来改。
训练过程中生成的模型输出在runs/train/poker_train/weights/下,best.pt是验证集 mAP 最高的权重,last.pt是最后一轮的权重。实际部署时用best.pt,不要用last.pt,因为训练后期模型可能已经在验证集上过拟合了,last 不一定是泛化能力最好的那个。
4.4 验证训练结果:用 val 集的 mAP 说话
训练完成后,跑一次验证命令把指标重新计算出来:
python val.py \ --data poker_yolo/data.yaml \ --weights runs/train/poker_train/weights/best.pt \ --img 640 \ --conf-thres 0.25 \ --iou-thres 0.5--conf-thres 0.25是置信度阈值,低于这个值的检测结果会被丢弃;--iou-thres 0.5是 NMS 的交并比阈值。输出的 mAP@0.5 对扑克牌检测来说至少要达到 0.95 才算合格,因为 52 类牌面差异非常明显,达不到说明数据本身有脏标注或模型容量不够。验证完再跑一次推理,随便拿一张验证集图片看输出的框准不准:
python detect.py \ --weights runs/train/poker_train/weights/best.pt \ --img 640 \ --conf-thres 0.25 \ --source poker_yolo/images/val/000101.jpg检测结果会输出到runs/detect/exp目录,打开看框有没有漏检、有没有把两张牌标成一个框。这一步是对标注质量的一个终检。
5. 避坑指南:52 类扑克牌数据集最常见的 5 个翻车点
5.1 类别编号从 0 开始还是从 1 开始,搞错直接全盘错
现象:训练不报错,损失函数正常下降,但推理时模型把红桃 A 识别成黑桃 2,整体预测结果往右偏一位。
原因:标注工具导出类别时从 1 开始编号,而 YOLOv5 的类别索引从 0 开始,把标注文件里所有 class_id 减 1 才能对齐。有些数据集的 labels txt 里写的是1 0.5 0.5 ...,对应的是类别 0,但作者在整理时忘了改。同样的坑还出现在 data.yaml 里,names 列表的索引位置和标注文件的 class_id 必须严格一致。
解决:写一个检查脚本,把所有 txt 里的类别最大值打印出来,看是否小于 52。如果出现等于 52 的值,说明是从 1 编号的,直接批量减 1 重写文件。这个操作要在训练前完成,训练后才发现就只能重训了。
5.2 牌面在画面中占比太小,小目标检测能力跟不上
现象:训练出来的模型虽然整体 mAP 很高,但对画面远处的牌漏检严重,特别是牌面小于 32×32 像素时几乎全军覆没。
原因:扑克牌数据集里牌面占整张图的比例差异极大,靠近镜头的牌可能占了图片一半,角落里的牌可能只有 30×40 像素。YOLOv5 在 640×640 输入下,下采样到 P3 特征层时每个网格对应 8×8 像素区域,牌面占比过小会导致特征信息在多层卷积后消失殆尽。
解决:优先把输入分辨率从 640 提到 1280,代价是显存翻倍、训练时间拉长;其次调整--hyp超参数里的 mosaic 增强和 copy-paste 增强,让算法能更多次看到小牌面。如果这两招有限且场景不复杂,可以直接用--img 960训练,实测比 640 对小目标的 mAP 提升明显。这个数据集的图片如果原始分辨率足够高,先升分辨率是最直接的办法。
5.3 训练时把正反牌面切进同一集合,评估结果虚高
现象:训练损失正常下降,但一跑到真实场景里做推理就大幅变差,和验证集上的 mAP 完全不匹配。
原因:train/val 切分按文件名顺序直接切,同一张牌的正反面图像(牌面A和牌面B在同一次拍摄中只会有其中一面,但拍摄多帧时相邻帧可能包含同一张牌的不同帧画面)被分别放进了 train 和 val。模型在训练时已经看过这个目标,val 指标自然虚高。
解决:切分前先按图片拍摄时间或场景聚类,保证同一个场景的帧全部落在同一集合。拿到手的数据集如果已经切好,就看 train 和 val 的图片序列号,凡是差值小于 10 的都要警惕。稳妥的做法是重新按场景切分:提取每张图的文件名前缀(通常是场景 ID),按前缀分桶后再切 8:2。
5.4 数据增强把牌面的花色特征增强成了噪声
现象:训练到后半程 mAP 波动厉害,验证集 loss 不降反升,模型对红色牌面的识别越来越不稳定。
原因:YOLOv5 默认开启的 HSV 色彩增强里,色相变化范围是hsv_h: 0.015,饱和度变化是hsv_s: 0.7,明度变化是hsv_v: 0.4。对于扑克牌这种高度依赖花色颜色的目标,色相偏移过大会把红桃 A 变成紫色,模型被迫去学习颜色特征以外的形状特征,但牌面花色本身就是最强的区分信号。
解决:在超参数文件里把hsv_h调低到 0 或 0.005,特别是牌面有明显的红色和黑色区分时,色彩扰动尽量小。另外degrees: 0(不做旋转增强)对于扑克牌也不合理——现实中牌有各种角度,但旋转太多会让识别困难,建议限制在 ±15 度附近。改超参数的做法是复制data/hyps/hyp.scratch-low.yaml为自定义文件,在训练命令里用--hyp 自定义文件.yaml传入。这个点属于典型的“不调不知、一调吓一跳”,很多人翻车在默认增强上。
5.5 数据空洞:验证集与训练集图像背景分布差异过大
现象:训练 mAP 很高,但 val 上全是误检,尤其是把桌面木纹、手指、筹码误检成牌面。
原因:训练集里牌面背景大多是牌桌绿色或深蓝色,验证集来自另一个场景是浅木色桌面,模型学到的是背景和牌面的组合特征,而不是纯粹的区域特征。这在棋牌类数据集里特别常见,来源于不同拍摄环境的混合。
解决:训练时打开 mosaic 增强就是在隐式地解决这个问题,mosaic 把 4 张不同场景的图拼成一张,让模型看到更多样的背景。如果数据集本身已经切好了 train/val 且背景差异大,建议把两套图混在一起重新随机切分,或按场景来源分组切。扑克牌检测不应该依赖背景特征,模型只需要关注牌面图案本身,所以训练时随机遮挡增强(--hyp里mixup参数)也值得试试。
6. 把 mAP 再抬一档:锚框聚类与针对性数据增强的落地技巧
对着这个 52 类扑克牌数据集,如果已经能稳定跑到 0.95 以上的 mAP,还想继续优化,优先做两件事:重新聚类锚框,以及针对牌面长宽比做自适应增强。
YOLOv5 默认的锚框是基于 COCO 数据集统计的,COCO 里有大量行人、车辆这类目标,锚框的长宽比分布和扑克牌差异很大。扑克牌的物理尺寸比较固定,长宽比约 1:1.4,但在不同拍摄角度下会出现透视变形,极端侧拍时长宽比接近 1:3。用这个数据集自己的标注统计出来的锚框,比 COCO 的默认值贴合得多。跑一次聚类命令:
python utils/anchors.py \ --data poker_yolo/data.yaml \ --img-size 640 \ --num-anchors 9聚类完成会输出 9 组锚框宽高,把这些值手动更新到模型 yaml 配置文件的 anchors 字段中,比如models/yolov5s.yaml。更新后重新训练,通常能在首轮就看到更好的收敛速度。这个操作本质上是在告诉模型“我要找的目标大概长这样”,降低前期探索成本。
数据增强方面,除了第五章提到的降低色相扰动,我还会在训练命令里加上--multi-scale。这个参数让模型在训练时随机把输入图片缩放到 0.5 到 1.5 倍之间,模拟同一张牌在画面中大小不一的情况。扑克牌检测中,同一帧画面里牌的位置和角度各不相同,multi-scale 能有效提升模型在低分辨率输入下的鲁棒性。代价是训练时间增加约 20%,显存占用也会波动。
最后提一个我自己的习惯:每次训练完都把best.pt导出成 ONNX 格式跑一遍推理速度测试,而不是只在 PyTorch 环境里看 mAP。扑克牌检测场景通常会部署到嵌入式设备或树莓派之类的边缘硬件上,PyTorch 模型在 GPU 上跑得再快,到了 CPU 设备上也可能掉速严重。导出命令很简单:
python export.py \ --weights runs/train/poker_train/weights/best.pt \ --include onnx \ --img 640如果在导出或推理优化上多花半小时,能省下后续部署时的大量返工。这个数据集本身格式规范,真正决定项目成败的是你把格式背后的逻辑吃透了多少——类别编号对不对、增强参数合不合理、切分有没有泄露。我的经验是,先用最小配置把整个流程跑通,再逐步加增强、调超参,别一上来就追求满分指标,否则出了 bug 都分不清是数据集的锅还是训练参数的锅。希望这篇笔记能帮你把这个数据集的每一分价值都榨出来,祝你一次跑通。
本文还有配套的精品资源,点击获取