ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

YOLOV5扑克牌检测实战:52类数据集构建与训练全指南

YOLOV5扑克牌检测实战:52类数据集构建与训练全指南 简介一份面向目标检测实战的大型扑克牌图像数据集已按YOLOV5目录结构整理涵盖训练集与验证集下载后可直接用于模型训练无需额外格式转换。数据包含52种扑克牌类别四种花色的A到K训练集16000张、验证集4000张共20000张720×720 RGB图片每张均配有同名的txt标注文件同时提供类别字典txt文件方便映射标签。压缩包总大小约947.74MB内部共2000个文件除1999个txt标注与字典外还附赠一个可直接运行的Python可视化脚本随机传入一张图片即可绘制边界框并保存在当前目录能直观检查标注质量。无论你是刚接触检测算法的学习者还是正在训练自定义YOLO模型的开发者这份数据量和类别粒度都比较充足。当前已有133人学习下载适合作为扑克牌识别、卡牌检测等场景的基准数据集使用。1. 52类扑克牌检测先过数据集这一关做棋牌识别的工程师都有一个共同体会模型结构不是瓶颈数据集才是。真正的扑克牌检测涉及 52 个类别公开数据里要么只有 13 种牌面的分类集要么是单张牌特写的分类图直接拿去训练多目标检测器根本不成立。YOLOV5 目录格式的价值是把图片、标签、类别清单三者的绑定关系用目录结构固定下来拿到一个符合规范的大型扑克牌检测数据集从解压到跑通训练可以控制在半天内。这次按目录规范、标签转换、数据校验、训练参数、难类处理五个环节推进每个环节都有直接可复制的命令和脚本覆盖从拿到数据集到产出权重的完整路径。不少团队在这条路上踩过同一个坑数据集格式不对训练日志只给一个 warning却能让人白跑好几天。适合读的人是准备做牌面识别、AR 发牌引导或牌桌自动化监控的工程师。新手能照着目录结构把数据集理顺熟手可以重点看第 3 章的按组划分和第 5 章的难类过采样这两处是扑克牌这类语义高度相似的目标与通用检测差异最大的地方。2. 扑克牌数据集目录结构YOLOV5格式的硬规范2.1 images与labels的目录对应关系YOLOV5 对数据集目录的要求比很多人以为的更严格。它不允许 images 与 labels 用不同的子目录命名也不支持把标注文件全部堆在一个文件夹里。标准结构是poker_dataset/ ├── images/ │ ├── train/ │ │ ├── frame_000001.jpg │ │ ├── frame_000002.jpg │ │ └── ... │ └── val/ │ ├── table_001_0001.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── frame_000001.txt │ │ ├── frame_000002.txt │ │ └── ... │ └── val/ │ ├── table_001_0001.txt │ └── ... └── data.yamlYOLOV5 的 dataloader 构建样本列表时会取 images 下每个图片的相对路径把路径中的 images 替换成 labels、把扩展名替换成 .txt然后检查这个文件是否存在。目录树里任何一级名字写错比如写成 label 或 annotations样本会被静默跳过训练日志里只有一行 dataset warnings不注意就漏掉。所以拿到扑克牌数据集的第一步不是看图片质量而是用 tree 命令核对目录层级。文件名规范同样容易被忽略。图片名必须是纯文件名加扩展名不能含空格和中文路径深度控制在 images/train 这种两级以内。我遇到过一张名为 IMG_0421 copy.jpg 的样本对应的 txt 也带空格Windows 和 Linux 对路径空格的处理不一致同一份数据换机器后大量标签加载失败。批量重命名脚本是拿到数据集后第一个要跑的脚本。提示统一改成纯数字前缀命名例如 frame_000001.jpg比保留原始照片名省去后续大量路径问题。标签文件每行一个目标格式固定为五列class_id x_center y_center width height四个坐标全是归一化到 [0,1] 的浮点数不能写像素坐标。扑克牌数据集的标签里最典型的错误是直接写0 320.0 240.0 100.0 150.0这类像素值模型不会报错但训练时 xy 损失爆炸置信度永远不收敛。3.1 的校验脚本能提前暴露这类问题别等训练跑完才查。2.2 52类别索引与data.yaml的绑定52 个类别的命名常见做法是「点数花色首字母」例如 Ah 表示红桃 A、Ks 表示黑桃 K组成固定顺序后与 class_id 一一对应class_id牌面class_id牌面0Ah红桃A266h红桃61Ad方块A276d方块62Ac梅花A286c梅花63As黑桃A296s黑桃64Kh红桃K......5Kd方块K512s黑桃2类别顺序一旦定下来整个项目生命周期都不能改。训练时 YOLOV5 从 data.yaml 的 names 列表读类别名推理时模型输出的 class_id 再反向查表换算成牌面字符串两端用的必须是同一份顺序。中途改动 names 排序而不动标签文件模型不会报错只会让全部预测结果整体错位这种错误在牌桌场景里最难排查。data.yaml 里最容易踩的坑是 nc 与 names 数量不一致或者 train、val 路径写成了 labels 目录。YOLOV5 6.0 之后对路径检查比较宽松path 写错时只打印 warning 并回退到默认训练集训练照常跑等发现时已经白耗几小时。注意训练前打印一次数据集统计信息确认 images 和 labels 数量一致再启动这个习惯能省掉最多的无效训练时间。2.3 把VOC/COCO标注转成YOLOV5标签大型扑克牌标注的原始来源一般是 VOC XML 或 COCO JSON转换脚本的本质是坐标换算。VOC 的 bndbox 给左上和右下角点像素坐标COCO 的 bbox 给左上角坐标加宽高转成中心点格式的公式不同。下面这段脚本处理 VOC XMLimport cv2 from xml.etree import ElementTree as ET POKER_CLASSES [Ah,Ad,Ac,As,Kh,Kd,Kc,Ks,Qh,Qd,Qc,Qs, Jh,Jd,Jc,Js,Th,Td,Tc,Ts,9h,9d,9c,9s, 8h,8d,8c,8s,7h,7d,7c,7s,6h,6d,6c,6s, 5h,5d,5c,5s,4h,4d,4c,4s,3h,3d,3c,3s, 2h,2d,2c,2s] def voc_xml_to_yolo(xml_path, img_w, img_h): 读VOC XML把绝对像素坐标换算成YOLO归一化坐标。 返回 [(class_id, xc, yc, w, h), ...]每个元素都是浮点。 tree ET.parse(xml_path) boxes [] for obj in tree.getroot().iter(object): name obj.find(name).text if name not in POKER_CLASSES: continue b obj.find(bndbox) xmin float(b.find(xmin).text) ymin float(b.find(ymin).text) xmax float(b.find(xmax).text) ymax float(b.find(ymax).text) w (xmax - xmin) / img_w h (ymax - ymin) / img_h boxes.append((POKER_CLASSES.index(name), (xmin xmax) / 2 / img_w, (ymin ymax) / 2 / img_h, w, h)) return boxes # 使用前用 cv2.imread 读图拿真实宽高不要信任XML里的尺寸这段脚本里三个位置要格外注意。第一img_w 和 img_h 必须用 cv2.imread 读出的真实尺寸因为不少数据集缩放过但没有同步更新 XML用 XML 里记录的分辨率会导致所有框的位置偏移。第二POKER_CLASSES 的索引顺序必须与 data.yaml 的 names 一致脚本里用 index(name) 天然保证这一点。第三name not in POKER_CLASSES的样本被直接跳过这是有意为之源数据里常见的 JOKER 和背面标记不进入 52 类但要单独记录数量避免类别凭空消失。COCO JSON 的换算不同它的 bbox 是 [x, y, width, height] 左上角坐标x, y, w, h ann[bbox] xc (x w / 2) / img_w yc (y h / 2) / img_h # class_id 需要经过 coco_category_id - 牌面名 - POKER_CLASSES.index 两级映射转换之后不要急着删原始标注先随机抽 20 张图把 YOLO txt 画回图片上人工核对一遍框是否贴边。扑克牌的角标很小标注时容易把整张牌框进去这种框不是错但会让模型学习到「带牌边的完整牌面」特征部署时遇到裁切过的牌面会漏检。3. 训练前必做的数据集校验与划分3.1 图片标签对齐与越界框检查大型数据集翻车通常不是标注质量而是文件缺失和坐标越界。一个五万张的扑克牌数据集缺几百个 txt 不会让训练崩溃只会让 mAP 莫名下降。训练前跑一遍完整性校验python - EOF import os from pathlib import Path base Path(poker_dataset) issues [] for split in (train, val): img_dir base / images / split lbl_dir base / labels / split for img in sorted(img_dir.iterdir()): if img.suffix.lower() not in (.jpg, .jpeg, .png): continue lbl lbl_dir / (img.stem .txt) if not lbl.exists(): issues.append(f{img}: label missing) elif lbl.stat().st_size 0: issues.append(f{img}: empty label) print(fissues: {len(issues)}) for msg in issues[:50]: print(msg) EOF校验通过后还要查越界框。归一化坐标理论上都在 [0,1] 内标注工具偶尔会生成 x_center w/2 超过 1 的框。YOLOV5 训练时会把越界部分直接裁剪掉导致实际参与学习的区域跟标注意图不一致。写几行统计把所有框的最小最大坐标打出来越界比例超过 0.5% 就得回源数据修不建议在训练时用裁剪逻辑掩盖。3.2 类别不平衡与牌面目标尺寸分析52 类扑克牌天然存在不平衡。按真实牌局采集的数据里亮出来的多是出过的牌A、K 这类大牌占比偏高合成数据集则取决于生成程序是否均匀抽样。统计脚本如下import os from collections import Counter def analyze_labels(label_dir): cls Counter() size_ratio [] total 0 for f in os.listdir(label_dir): if not f.endswith(.txt): continue with open(os.path.join(label_dir, f)) as fh: for line in fh: parts line.split() if len(parts) ! 5: continue cls[int(parts[0])] 1 w float(parts[3]) h float(parts[4]) if h 0: size_ratio.append(w / h) total 1 return cls, size_ratio, total cls_cnt, ratios, total analyze_labels(poker_dataset/labels/train) print(total boxes:, total) print(class num:, len(cls_cnt)) print(most common:, cls_cnt.most_common(3)) print(least common:, cls_cnt.most_common()[-3:])两个指标决定后续策略。第一最多类与最少类的数量比超过 10:1 时训练会明显偏向高频类最少类的 AP 长期为零第二框宽高比如果普遍大于 2说明数据集里横向特写偏多模型对竖屏远距离牌的召回会差。前者用第 5 章的难类过采样解决后者需要在验证集里补充竖构图样本或者在数据增强时调高旋转概率。3.3 按牌局组划分训练验证集扑克牌检测的划分策略与通用检测不一样。常见做法是不按图片随机划分而是按拍摄场景分组同一张桌子、同一台相机连续拍的帧必须进同一个集合。随机划分会让验证集和训练集包含同一牌局的相邻帧牌面位置和光线几乎相同val mAP 虚高 3 到 5 个点部署到新牌桌立刻现原形。import random random.seed(42) videos [fvideo_{i:03d} for i in range(100)] random.shuffle(videos) cut int(len(videos) * 0.85) train_groups, val_groups set(videos[:cut]), set(videos[cut:]) # 按文件名的 group 前缀把图片和标签移入对应目录 # 移动前先复制一份到 staging 目录不要原地挪便于反复调整划分划分比例的参考值数据规模训练集比例验证集比例说明1万张以下0.900.10样本少验证集只做趋势判断1万至5万0.850.15扑克牌数据集常见区间5万以上0.800.20验证集足够大单类 AP 更可信划分完之后检查一个容易漏的点同一个牌局里 52 类牌不会全部出现如果验证集恰好缺少某几个类YOLOV5 在计算该类 AP 时会直接跳过最终的 mAP 统计偏乐观。用 3.2 的统计脚本分别跑 train 和 val确认两边都覆盖 52 类如果 val 缺类把该类所属的视频组整体划回训练集再补录一段该类的数据做验证。这种按组划分的做法后续新增数据时只需要维护一个 group 到 split 的映射文件比每次重新随机划分的可重复性高得多。4. 用YOLOV5训练52类扑克牌检测的参数清单4.1 data.yaml的写法与类别顺序冻结52 类扑克牌数据集的配置文件长这样path: ../poker_dataset train: images/train val: images/val nc: 52 names: 0: Ah 1: Ad 2: Ac 3: As 4: Kh 5: Kd 6: Kc 7: Ks # 其余类别按第2章的顺序写满到 51: 2strain 和 val 指向 images 子目录YOLOV5 会自动推导 labels 的对应路径不需要再写 labels 字段。names 顺序一旦训练开始就不允许改建议把这份 yaml 放进 git并在 CI 里加一条断言检查 len(names) 等于 nc 等于 52且每个名字在 POKER_CLASSES 里能找到。这个断言看起来很笨但实际项目里改错 names 顺序的概率比我预想的高得多。类别顺序冻结还有一个连带影响换模型规模时不要重建 yaml。从 yolov5s 切到 yolov5ldata.yaml 原样复用只换 weights 参数否则训练出来的权重和推理端类别映射对不上线上会输出错牌面。4.2 训练命令与扑克牌场景的超参数52 类扑克牌检测不需要从零训练常见做法是加载 COCO 预训练权重后冻结浅层微调。扑克牌数据集的规模和场景多样性通常远小于 COCO从零训练收敛慢三倍以上而且容易过拟合到数据集的背景。推荐命令cd yolov5 python train.py \ --weights yolov5s.pt \ --data ../poker_dataset/data.yaml \ --epochs 150 \ --batch-size 32 \ --img 640 \ --freeze 10 \ --patience 30 \ --save-period 10参数说明用 --freeze 10 冻结前 10 层保留 COCO 预训练的通用纹理特征只训练高层的牌面图案匹配部分能明显抑制过拟合--patience 30 是 EarlyStopping 耐心值mAP 连续 30 轮不提升自动停--img 640 是输入尺寸扑克牌属于中小目标下到 512 会明显损失角标细节上到 1280 收益有限且训练时间翻倍。显存不够时优先降 batch-size 到 16并同步把学习率减半不要直接改 imgsz。超参数建议表参数默认值扑克牌数据集建议原因imgsz640640牌面角标是小细节512 以下明显掉点batch816~3252 类收敛需要足够多样本参与每轮更新lr00.010.005微调场景下调一半避免扰动预训练权重optimizerSGDAdamW牌面数据噪声低AdamW 在验证集上更稳hsv_h0.0150.0色相改变会混淆红桃与方块必须关掉degrees0.010模拟牌在桌面上的任意旋转角fliplr0.50.0牌面左右翻转会改变类别语义不能开mosaic1.00.8全开让合成图与真实桌面布局偏差变大这张表里最容易被忽略的是 hsv_h 和 fliplr。红桃和方块靠形状与颜色区分随机色相偏移后两类特征混在一起左右翻转则直接把 Qh 变成 Qd 的镜像属于语义级错误增强。这两项不关52 个类别里至少有两三类 AP 会无解地低排查半天最后发现是增强策略的问题浪费的时间比训练本身还长。4.3 训练监控、混淆类别与提前终止训练启动后不能只看 loss。YOLOV5 每轮输出 P、R、mAP0.5、mAP0.5:0.95 四项对 52 类模型重点盯 mAP0.5 的单类最低值和类别混淆矩阵。总 mAP 容易被高频大目标拉高个别难类的 AP 长期为零时总指标看不出来。训练过程的三个检查点第 10 轮看 train 和 val 的 P、R 是否同时上升。如果 train 的 loss 下降但 val 不动先确认验证集是否按 3.3 做了按组划分排除数据泄漏导致的虚高。第 50 轮把混淆矩阵导出看相邻类混淆。黑桃与梅花在低分辨率下最容易互相认错方块与红桃在灰度场景容易混这两组混淆是数据增强问题的信号。第 100 轮mAP 增速变缓是正常现象patience 30 会在连续 30 轮无提升时停训。此时用 --save-period 10 保存的中间权重里取 val mAP 最高的一版不要用最后一个 epoch 的权重。训练结束时把 results.png 里的 P、R、mAP 曲线和类别 AP 分布一起归档连同 data.yaml 和 commit hash 打成一个版本标签。后续调参对比时只看这些归档就能定位是数据变了还是参数变了避免两个月后对着三个相似的 results 文件夹无从下手。5. 难类过采样、牌背负样本与端到端验证模型训练完第一步不是看测试集指标而是跑一次真实牌桌视频的批量推理把置信度低于 0.3 的检测框和原图一起导出来。这批低置信样本里占比最高的是角标被手指遮挡的牌、洗牌运动模糊帧、以及浅色背景上白色牌边缘反光的图。把这些样本单独归一个目录作为下一版数据集扩充的起点。难类过采样我常用带权采样而不是简单复制。对最高的 A、K 类随机丢弃 30% 的训练样本对最少的 2、3 类除了保留原图外每张再做两次随机透视变换加亮度扰动两张增广图与原图一并进入训练。这样 52 类的样本量被人工拉平比依赖 YOLOV5 内置增强更可控。注意过采样只作用于训练集验证集保持原始分布否则 mAP 统计会失真。牌背负样本是扑克牌场景特有的一环。很多数据集的标注只覆盖牌面正面但真实摄像头拍到的牌经常背面朝上。这些未标注的背面区域会被当作背景参与训练模型学到的是把背面误判成某种正面牌。处理方法是往训练集混入 10% 只含牌背或空桌面的图这类图不写任何标签。目标检测的损失函数里背景项天然存在混入负样本图等价于给分类分支强化了一个隐式的「无目标」输出推理时背面被正确过滤掉。端到端验证建议用一段完整的发牌动作视频而不是随机抽帧。逐帧跑推理把每帧的检测框数量、置信度、类别序列输出成 CSV再和真实记录逐帧对比统计漏检集中在哪个动作阶段。这个环节能顺带暴露 NMS 的问题扑克牌相邻排布时两张牌的 IoU 经常大于 NMS 默认的 0.45导致相邻牌被合并成一个框。遇到这种情况把 NMS 阈值降到 0.3同时在验证脚本里加一条断言断言每帧最多输出 54 个框且不存在 class_id 相同又相邻重叠的框。等漏检率降到 1% 以下再考虑转 TensorRT 或 ONNX 做边缘部署。部署侧的类别映射表直接复用训练时的 data.yaml52 类检测的推理开销本身不是瓶颈预处理分辨率和多路并发吞吐才是真正决定线上帧率的地方。本文还有配套的精品资源点击获取
返回列表