简介一个面向目标检测任务的大型扑克牌图像数据集按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、梅花A4~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 heightclass_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_okTrue) 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.yamlYOLOv5 的 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 的交并比阈值。输出的 mAP0.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 都分不清是数据集的锅还是训练参数的锅。希望这篇笔记能帮你把这个数据集的每一分价值都榨出来祝你一次跑通。本文还有配套的精品资源点击获取