52类扑克牌检测数据集详解:YOLOV5格式与训练避坑指南

发布时间:2026/10/26 8:16:05
52类扑克牌检测数据集详解:YOLOV5格式与训练避坑指南

简介:这是一份面向目标检测任务的大型扑克牌图像数据集,按YOLOv5目录格式组织,适合算法工程师、学习者和竞赛爱好者直接用于模型训练与效果验证,免除自行采集与标注的成本。数据集涵盖扑克牌四种花色的A到K共52个类别,图片为720×720的RGB图像;训练集包含16000张图片与对应txt标签,验证集包含4000张图片与对应txt标签,并附类别编号字典txt,训练/验证划分清晰,标签与图片同名对应,便于快速理解标注规则和开展模型评估。资源共2000个文件,主体为txt标注与说明文件,另附一个可视化脚本,传入任意一张图片即可自动绘制边界框并保存到当前目录,无需修改或调参即可运行。整体压缩包为7z格式,大小约947.74MB,目录结构与YOLOv5训练要求一致,下载后可直接接入训练流程。目前已有134人学习,特别适合目标检测入门练习、模型评测及扑克牌场景相关应用开发。

1. 52类扑克牌检测数据集:为什么说格式比图像数量更先决定成败

做目标检测的人第一次拿到扑克牌数据集时,往往先被“52类别”这个数字吸引——不同于常见的猫狗分类或者行人检测,扑克牌天然就是细粒度识别场景,红桃A和方块A长得像,黑桃3和梅花3的结构也几乎一致,模型要把它们分开,靠的是花色符号、角标位置这些细节特征,而不是整体轮廓。另一个容易被忽略的点才是标题里的关键词:YOLOV5目录格式。这个格式决定了你拿到数据后能不能直接开训,还是得先花半天写转换脚本。很多从业者拿到的是VOC格式的XML或者COCO格式的JSON,要喂给YOLOV5还得自己折腾解析;而这个数据集直接把labels按txt文件放好,目录结构对齐YOLOV5的训练约定,省掉了中间环节。这篇笔记就按“格式拆解 → 52类难点 → 数据准备 → 训练验证 → 避坑 → 进阶评估”的顺序,把这个数据集从目录里看不出门道的东西全部讲透。

2. 拆开目录看门道:YOLOV5 格式下的扑克牌数据长什么样

2.1 目录树与三类文件:images、labels 和 data.yaml

YOLOV5 的数据集约定在官方文档里写得清楚,但实际拿到手时,很多人第一反应是“这不就是个文件夹吗”。确实是文件夹,但里面每一层都有讲究。最常见的标准结构如下:

poker_dataset/ ├── images/ │ ├── train/ │ │ ├── poker_001.jpg │ │ ├── poker_002.jpg │ │ └── ... │ └── val/ │ ├── poker_101.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── poker_001.txt │ │ ├── poker_002.txt │ │ └── ... │ └── val/ │ ├── poker_101.txt │ └── ... └── data.yaml

images 和 labels 必须一一对应,同名不同后缀,图片是 JPG 或 PNG,标签是同名的纯文本文件。data.yaml 是数据集的心脏,YOLOV5 训练时第一步读的就是它。

# data.yaml train: ./poker_dataset/images/train val: ./poker_dataset/images/val nc: 52 names: 0: 'ace_of_spades' 1: 'two_of_spades' # ... 一直到 51

这里最容易犯的错是想当然地认为 names 顺序无所谓,反正模型只是记编号。实际上 YOLOV5 在训练和推理时都是按索引找类别名,标签文件里写的数字是几,就对应 names 里第几个名字。如果数据集作者给的类别顺序是“先黑桃后红心”,而你在 data.yaml 里写成了“先红心后黑桃”,模型训练出来的 mAP 会非常难看,因为你等于把所有标签都映射错了位置。

2.2 标签文件读法:一行一个目标,五个数字缺一不可

打开任何一个 labels/train 下的 txt 文件,你会看到类似这样的内容:

5 0.483521 0.314286 0.142188 0.228571 17 0.794245 0.657143 0.108516 0.182857 31 0.213416 0.728571 0.115625 0.214286

每一行对应图片里的一张牌,五个数字的顺序是固定的:类别编号、归一化中心点x坐标、归一化中心点y坐标、归一化宽度、归一化高度。这里“归一化”是重中之重——所有数值都在 0 到 1 之间,用像素值除以图片的宽或高得到。比如一张 640×640 的图里,某张牌左上角在 (120, 160),右下角在 (240, 280),那么中心点就是 ((120+240)/2/640, (160+280)/2/640),也就是 (0.28125, 0.34375),宽高分别是 ((240-120)/640, (280-160)/640)。标签文件里绝对不允许出现像素坐标,这是新手最容易踩的坑,后面避坑章节再细说。

还要注意一点:类别编号从 0 开始,不是从 1 开始。52 个类别,编号范围是 0 到 51。如果某张图里没有目标,这个 txt 文件就是空的,或者该文件根本不存在——两种做法 YOLOV5 都能接受,但存在会减少不少麻烦。

2.3 拿到数据集后第一件事:先校验再开训

我见过太多次这样的场景:数据集下载完,训练脚本一跑,loss 降不下去,或者 mAP 直接是 0,最后定位到是标签文件出了问题。所以拿到任何 YOLOV5 格式的数据集,第一步别急着训练,先写一个校验脚本,把下面几个问题过一遍:

import os from collections import Counter image_dir = 'poker_dataset/images/train' label_dir = 'poker_dataset/labels/train' label_files = os.listdir(label_dir) image_files = os.listdir(image_dir) # 1. 检查缺失:图片有没有对应的标签文件 missing = [f for f in image_files if f.replace('.jpg', '.txt') not in label_files] print(f'缺标签的图片: {len(missing)}') if missing: print(missing[:5]) # 2. 检查类别分布:52个类别是不是都有样本 class_counter = Counter() errors = [] for f in label_files: with open(os.path.join(label_dir, f)) as fp: for line in fp: parts = line.strip().split() if len(parts) != 5: errors.append((f, '字段数不对')) continue cls = int(parts[0]) if cls < 0 or cls > 51: errors.append((f, f'类别编号越界: {cls}')) continue x, y, w, h = map(float, parts[1:]) if not (0 <= x <= 1 and 0 <= y <= 1 and w > 0 and h > 0): errors.append((f, f'坐标异常: {parts}')) class_counter[cls] += 1 print(f'分类错误: {len(errors)}') print(f'类别覆盖: {len(class_counter)}/52')

这段代码的核心逻辑很简单:检查图片和标签文件数量是否对得上;逐个标签文件读取,确认每行是五个字段、类别编号在 0~51 范围内、坐标数值合法。跑完之后,如果类别覆盖不足 52,说明验证集里大概率也会缺类别,训练时就要心里有数:有些类的样本本来就少,模型很难学会。这一步不花多少时间,但能拦下后面一大半的训练翻车。

3. 52类牌面识别的三个硬骨头:类别稀疏、旋转朝向与花色混淆

3.1 每类仅4张样本:类别极不平衡对训练的约束

一副扑克牌去大小王,正好 52 张,这意味着每个类别天然只有 4 张物理牌面。数据集即便是“大型”,每张牌在不同照片里出现的次数也远少于常规数据集里每个类别的样本量。这就是类别稀疏问题:你在训练集里能看到某张牌几十甚至上百次,但这几十次本质上是同一张牌在不同角度、不同光线下的拍摄结果,它的外观变化范围很有限。

这带来一个直接后果:模型对训练集里出现过的牌面样式容易过拟合,对新牌面样式(比如不同厂商印刷的牌,颜色深浅、角标字号不一样)泛化能力会很弱。如果你打算拿训练好的模型去识别另外一副牌,mAP 大概率会掉。缓解的办法有两个维度:一是数据增强,对图像做平移、缩放、旋转、HSV 抖动,相当于把每张牌的有效样本量放大几倍;二是训练时适当提高 mosaic 和 copy_paste 的概率,让模型在混合图像里看到更多牌的上下文组合。但要清楚,这些手段只能缓解类别稀疏,不能根治。如果你要部署到生产环境,最好在训练数据里额外补充几种不同风格牌面的图像,或者做一次简单的 domain adaptation。

3.2 牌面旋转与镜像翻转:要不要做数据增强

扑克牌是矩形,四个朝向都可能出现。YOLOV5 默认的数据增强里有随机旋转,角度范围默认是 0,需要自己在 hyp.yaml 里打开。常见做法是把 angle 设成 90 的倍数场景才有效,因为扑克牌旋转 90 度后牌面依然是正的,旋转 45 度角反而会产生大量无效的倾斜框。我在做这类细长矩形目标时会把 angle 设成 0,fliplr 和 flipud 保持默认开启。

但这里有个坑:如果数据集里本身已经包含了各种朝向的牌,你再做翻转增强,会导致同一张牌以两种完全对称的方式出现,模型在推理时对镜像牌面的置信度会出现偏差。我的经验是:先看数据集本身是否已经覆盖四个朝向,如果覆盖了,增强里的旋转就设小一点;如果数据集只有一种朝向,那 fliplr 和 flipud 一定要开。标注规范上也要注意,牌面旋转后类别不变,但角标的位置变了,模型学的是“角标的相对位置”,不会因为旋转而混淆类别。

3.3 花色混淆:红心与方片、黑桃与梅花为什么容易分错

52 类扑克牌检测的真正难点不是定位,而是分类。牌的检测框很好画——一个矩形框住整张牌就行,难的是框住之后告诉模型“这是红桃7”而不是“方片7”。红心和方片的形状差异极小,一个是圆底尖顶,一个是菱形,在低分辨率下几乎就是同一个形状的两种轻微变体。黑桃和梅花也类似,两者的差异在牌面缩到 32×32 像素时肉眼都很难分。

这种细粒度分类问题,光靠 YOLOV5 默认的检测头是不够的。实践里常见的做法有三种:一是提高输入分辨率,YOLOV5 默认 imgsz=640,扑克牌任务我建议至少 960,让角标和花色符号占更多像素;二是把牌面先裁剪出来,单独训练一个分类器,检测器只负责“找到牌”,分类器负责“识别牌面”,两步方案的准确率往往比端到端检测高出不少;三是用 YOLOV5 的 cls head 做辅助监督,虽然 YOLOV5 没有内置双头结构,但你可以把类别数拆成“花色 + 点数”两组来训练,用两个模型或一个多输出模型来解耦。第二种方案在工程上最常见,因为它的容错性更好——检测框稍微偏一点不影响分类结果,而端到端方案里框的误差会直接干扰分类特征提取。

4. 从数据集到模型:训练前做实数据准备的四步操作

4.1 划分训练/验证集:按牌面成组切分,不能随机打乱

如果数据集本身没有划分好 train 和 val,而只给了一堆图片,你就要自己切分。扑克牌数据集的切分和普通数据集不一样,不能简单地random.shuffle后按 8:2 分。因为同一张牌的多张照片之间相似度极高,如果同一张牌的照片同时出现在训练集和验证集,验证集就失去了意义——模型见过这张牌了,验证集上的 mAP 虚高,一到真实场景就露馅。

import os import random from collections import defaultdict image_dir = 'poker_dataset/images' # 假设文件名格式是 like: ace_of_spades_01.jpg # 按牌面名分组,保证同一张牌的所有照片只进训练集或只进验证集 groups = defaultdict(list) for fname in os.listdir(image_dir): # 取去掉编号后的牌面名作为组名 # 比如 'ace_of_spades_01.jpg' -> 'ace_of_spades' card_name = fname.rsplit('_', 1)[0].replace('.jpg', '') groups[card_name].append(fname) train_files, val_files = [], [] for card_name, files in groups.items(): # 每张牌的照片随机取 20% 进验证集 random.shuffle(files) split_idx = max(1, int(len(files) * 0.2)) val_files.extend(files[:split_idx]) train_files.extend(files[split_idx:])

这个脚本的关键在按牌面名分组这一步。图片文件名里带类别信息有多重要,这里就体现出来了:如果文件名是纯编号00001.jpg,你就没法分组,只能靠读标签文件里的类别号来分组。更极端的情况是标签文件里同一张牌的照片分散在多个类别,那就得看图像内容本身判断,人工介入成本极高。所以数据集作者在做文件命名时用了可读的类别前缀,这本身就是一种隐形的工程质量。

4.2 标签归一化校验:检查中心点越界与框宽高异常

划分完数据集,还要再走一遍标签校验。第 2.3 节的校验脚本可以做基础检查,但边界条件要更严格。扑克牌检测框有一个特殊性:牌是矩形,检测框应该紧贴牌的边缘,框的宽高比应该在 0.5 到 0.8 之间。如果标签里出现宽高比大于 1.5 的框,大概率是标注时把牌和牌之间的间隙框进去了,或者坐标写错了。

# 校验宽高比:扑克牌的宽高比一般在 0.55~0.72 之间 import os label_dir = 'poker_dataset/labels/val' for f in os.listdir(label_dir): with open(os.path.join(label_dir, f)) as fp: for line_no, line in enumerate(fp, 1): parts = line.strip().split() if len(parts) != 5: print(f'{f}:{line_no} 字段数不为5') continue _, _, _, w, h = map(float, parts) aspect = w / h if aspect > 1.2 or aspect < 0.4: print(f'{f}:{line_no} 宽高比异常: {aspect:.3f}')

为什么宽高比异常需要单独挑出来?因为 YOLOV5 在训练时会对 anchor 做自适应计算,如果某个标签的框宽高比远超正常范围,它会被分配到不合适的 anchor,导致这个框的回归损失始终降不下去,拉低整体收敛速度。这个检查不像字段数检查那样能立刻发现致命错误,但它能暴露标注质量问题,而标注质量直接决定了模型精度的上限。

4.3 配置 data.yaml 并跑通首轮训练

数据准备就绪后,配置 data.yaml 并启动训练。52 类数据集的首轮训练建议用小 batch、低 epoch 快速验证 pipeline 是否通畅,不要一上来就上 300 epoch。

python train.py \ --data poker_dataset/data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 32 \ --epochs 50 \ --project poker_runs \ --name first_try

data.yaml 里的路径建议用绝对路径或相对训练脚本位置的相对路径,不要用~/开头,因为 YOLOV5 的 Path 解析在部分环境下不会展开用户目录。weights 用 yolov5s,别一上来就上 yolov5x——52 类的细粒度任务,s 模型先跑通流程,确认 loss 能往下走,再换大模型追精度。batch 设 32 是因为扑克牌图片单张不大,显存占用可以接受;如果显存紧张就降到 16,但不要低于 8,否则 BN 层的统计量会不稳定。

跑完 50 个 epoch 后,去poker_runs/first_try下看results.png和confusion_matrix.png。results.png 里关注 box_loss 和 cls_loss 两条曲线,如果两者都在稳定下降,说明数据没问题;如果 cls_loss 降得很慢甚至反弹,大概率是类别混淆问题,也就是第 3.3 节说的花色混淆。confusion_matrix.png 里看对角线是否足够亮,以及哪些类别互相混淆最多——通常红心系和方片系之间、黑桃系和梅花系之间的混淆块最明显。

4.4 首轮训练后看什么:loss 曲线和类别分布

首轮训练的意义不是直接拿模型去部署,而是给整个数据链路做一个体检。除了 loss 曲线,还要做两个检查:一是每个类别在验证集上的 AP 分布,二是漏检类别的分布。YOLOV5 的val.py跑完会输出每个类别的 AP,52 行结果里如果某个牌面的 AP 特别低,先看这个类别在训练集里的样本量是否太少,再看它的牌面外观是否和另一类高度相似。

python val.py \ --data poker_dataset/data.yaml \ --weights poker_runs/first_try/weights/best.pt \ --img 640 \ --task val

这里有一个容易忽略的参数:--conf-thres。val 阶段默认的置信度阈值是 0.001,这个值很低,目的是把所有的预测都保留下来,方便在 PR 曲线里看到不同阈值下的表现。如果你把阈值设成 0.25 去跑 val,得到的 mAP 会虚高,因为低置信度的错误预测被过滤掉了。真正评估模型质量时,用默认阈值跑出来的 mAP 更像一面诚实的镜子。

5. 避坑清单:52类数据集最常见的五个翻车现场

5.1 翻车一:标签类别和索引对不上

现象:训练完成后,模型对红桃牌基本能检出来,但预测的类别总是比真实类别差一个固定数字,比如红桃7被识别成红桃8,黑桃A被识别成黑桃2。

原因:数据集作者的类别排序习惯和你写的 data.yaml 里 names 的顺序不一致。常见情况是作者按“先点数后花色”排序,而你按“先花色后点数”写了 names,导致整个索引全部错位。YOLOV5 不关心 names 里写什么名字,它只按索引匹配标签,索引错了模型学到的映射关系就是错的。

解决:写一个脚本,对训练集里的每个类别随机抽几张图,人工对照图片内容和标签文件里的类别编号,建立一个“编号 → 牌面名”的真实映射表,再按这个表去写 data.yaml。不要相信数据集附带的 README 里的类别表,要以标签文件实际内容为准。

5.2 翻车二:训练集里有的类别,验证集里完全没有

现象:训练 loss 正常下降,val loss 开始也能降,但到某个 epoch 后 val 的 mAP 开始剧烈震荡,某些类别的 AP 直接是 0。

原因:划分训练/验证集时用了随机切分,没有按牌面分组。同一张牌的照片在训练集和验证集里各出现一半,导致验证集上模型“见过”大部分目标,mAP 虚高;但某些牌面因为照片总共只有 3~4 张,被全部分到了训练集,验证集里完全没有这个类别,所以这个类的 AP 就是 0,拉低了整体 mAP。

解决:严格按第 4.1 节的方式分组切分,保证每个牌面至少有一张进验证集。如果数据集里某些牌面的照片数量实在太少,少于 3 张,那就要考虑用数据增强扩充后再切分,或者放弃验证集中的该类别,在评估时单独标注“GAP”,不要简单归因于模型能力不行。

5.3 翻车三:做了旋转增强,但标签没有跟着转

现象:训练时 loss 在某个阶段突然上升,然后恢复,反复出现;推理时对旋转角度较大的牌检测框偏移严重。

原因:YOLOV5 的数据增强 pipeline 里,空间变换(旋转、缩放、平移)和标签是同步变换的,但如果你在外部自己对图像做了旋转,或者用别的脚本预处理过数据集,新生成的图像里牌的朝向变了,标签却还是旧坐标。这种情况常见于“先增强再标注”的流程:先用脚本旋转图像,再用标注工具手动标注,但标注结果存错了图像对应的文件。

解决:如果用了外部增强工具,增强后必须用工具导出的新标签,不要沿用原标签。如果是 YOLOV5 内部的增强参数导致的,检查 hyp.yaml 里的 angle、degrees 设置。扑克牌场景建议 angle 设为 0,fliplr 和 flipud 保持默认开启,让模型靠自己学会朝向不变性,而不是靠增强硬扛。

5.4 翻车四:以为置信度阈值越低越能找到漏检

现象:推理时把 conf-thres 从 0.25 降到 0.05,结果误检框数量翻了几倍,precision 大幅下降,但 recall 只提升了一点点。

原因:52 类扑克牌里,每类样本少,模型对某些类别的置信度普遍偏低,把阈值降下来确实能捞回一些真检,但同时也放进来大量“看起来像牌”的误检——比如牌桌纹理、手指、筹码阴影。目标检测永远存在 precision 和 recall 的权衡,阈值不是越低越好,而是要找到应用场景的平衡点。

解决:用 val 脚本输出 PR 曲线,找曲线拐点处对应的置信度作为阈值。如果当前模型在 0.25 阈值下召回率太低,优先考虑提升模型能力(更高分辨率、更大模型、更好的增强),而不是无脑降低阈值。部署阶段建议设到 0.35~0.4 之间,宁可漏掉几张低置信度的,也别让误检干扰后续业务逻辑。

5.5 翻车五:把归一化坐标当成了像素坐标

现象:标签可视化时,画出来的框只出现在图片的左上角,而且特别小;或者训练时 loss 非常大,完全降不下来。

原因:标注工具导出的坐标是像素值,直接在原图上画框没问题,但 YOLOV5 要求的是归一化坐标。像素值除以图片宽高才是归一化值,这一步没做就会导致检测框的中心点和宽高远小于正常值,模型学到的框和真实目标的位置对不上。

解决:写一个可视化脚本,把标签文件的坐标乘上图片宽高,换算回像素值再画框,确认框是否准确贴合牌面。这一步是所有数据准备里最基础也最不能跳过的检查。我自己在拿到任何数据集时,第一件事永远是随机抽 30 张图做可视化,拿真实框和标签框比对,确认坐标系没有问题再继续往下走。

6. 验证模型做得对不对:用混淆矩阵和 mAP 卡住底线

最后一层的验证不只是看一个总 mAP 数字。52 类细粒度检测场景里,总 mAP 会掩盖很多问题,所以我的习惯是看三样东西:每类 AP 的分布、混淆矩阵的热点区域、以及实际场景中的抽样测试。

每类 AP 分布通过 val.py 的输出就能看。52 行结果里,如果多数类的 AP 在 0.85 以上,但某几个类在 0.5 以下,重点看这几个类是什么——大概率是红心系和方片系里点数较小的牌,因为小点数的花色符号在图像里占的像素更少。这种情况下不要急着加训练样本,先尝试把输入分辨率从 640 提到 960,往往比加数据更有效。分辨率上来以后,角标和花色符号的像素数翻倍,模型提取细节特征的压力小很多。

# 用 960 分辨率重新训练,对比 640 的 mAP python train.py \ --data poker_dataset/data.yaml \ --weights yolov5m.pt \ --img 960 \ --batch 16 \ --epochs 80 \ --hyp hyp.poker.yaml \ --project poker_runs \ --name img960

混淆矩阵在 YOLOV5 的训练输出目录里会自动生成confusion_matrix.png,横纵轴都是 52 个类别,对角线越亮越好。你要重点看的是非对角线上的亮点集中在哪些类之间——比如黑桃K和梅花K互相混淆严重,说明这两个类的外观特征太接近,模型提取的特征没能区分它们。这种情况下可以引入一个针对性的裁剪分类模型,专门对牌面中心区域做细粒度分类,比不断调主检测模型参数更省时间。

  • 示例 1:写一个推理脚本,对视频流截帧,统计每帧的检测数量和类别分布,看有没有异常跳变的类别概率。扑克牌检测如果出现同一张牌在连续帧里类别来回跳,说明模型对这张牌的置信度在两个类别之间摇摆,这是典型的花色混淆信号。
  • 示例 2:如果目标场景是识别桌面上的摊牌,可以直接拍一段真实视频,标注每帧的牌面结果,跑完推理后对比真实牌面和预测类别,算一版“场景级准确率”,而不是只看 mAP。mAP 衡量的是框和类别综合的匹配度,场景级评估才更贴近最终业务指标。

我自己的习惯是把这套验证流程固化成三个步骤:先看每类 AP 分布,再盯混淆矩阵的热点区域,最后抽真实场景验证。前两步在训练后半小时内就能完成,第三步花的时间多一些,但这一步能发现 mAP 完全暴露不了的问题——比如光照变化、牌面反光、摄像头角度导致的系统性偏差。做了三轮以上 52 类扑克牌的数据集和模型迭代之后,我的血泪经验是:这类细粒度数据集的模型上限往往不是由网络结构决定的,而是由数据标注质量和验证方式的严密程度决定的。希望这些流程和踩坑记录能帮你少走几趟弯路。

本文还有配套的精品资源,点击获取