简介:扑克牌识别数据集共包含1850张原始扑克牌图像,覆盖A到K全部13种牌面字母,总体标注正确识别率可达98.7%。数据采用YOLOv11标准格式,每个jpg原图对应一个txt标注文件,txt内记录目标类别与归一化边界框坐标,同时附带yaml配置文件定义类别名称与路径,共2000个文件,压缩包整体约109.76MB。该数据集既可用于扑克牌目标检测模型的训练、验证与测试,也适用于棋牌游戏视觉处理、智能发牌系统、牌面识别等应用开发。数据集结构清晰,已吸引241人浏览学习,属于可直接导入YOLOv11训练流程的现成资源,无需额外格式转换。对于算法工程师、计算机视觉学生或游戏开发者,这份数据能大幅节省人工采集与标注时间,快速支撑起一个高精度的扑克牌识别模型实验。
1. 扑克牌识别数据集:1850张图、13个类别,YOLOv11训练的赌注都押在哪
做棋牌视觉项目的工程师看到“扑克牌识别数据集,可识别A-K所有的牌字母,1850张原始图,正确识别率可达98.7%,yolo v11格式标注”这句话,第一反应不该是“数据集够不够大”,而应该是“这套标注能不能直接用”。这个标题背后是一种典型的小目标印刷字符检测任务:13个类别(A到K)、每张图可能有多张牌、牌角上的字母是真正的判别区域,而牌面的花色和图案反而是噪声。1850张图不算多,但足够验证流程;98.7%也不是玄学,而是要在确定的测试集、确定的IoU阈值下才成立的数字。这篇文章适合正在做棋牌计数、牌型识别、或任何“在小尺寸印字符上做目标检测”项目的工程师,我会把数据组织、标注规范、训练参数和容易翻车的地方按可复现的方式拆开讲。
2. 扑克牌识别任务的难点拆解:A-K的13类到底在学什么特征
2.1 字母牌与花色牌:为什么不能把整张牌当目标
做扑克牌识别,最常见的错误是把整张牌当作检测框。整张牌包含的内容太多:中央的图案、背景纹理、四个角标、牌背。模型如果学的是整张牌,它必须同时记住很多与牌面数字无关的外观特征,比如红色背面、白边、甚至桌面反光都会成为隐式特征,导致换一副牌、换一张桌面就掉点。
正确的做法是只把牌面左上角(或右上角)的数字/字母区域作为检测目标。这样每张牌就只有两类判别信息:字母(A-K)本身,以及是否需要连带判断花色。标题里写的是“可识别A-K所有的牌字母”,也就是不区分黑红梅方,只区分13个数字符号。这让任务变成一个标准的13类小目标分类检测问题,而不是54类全牌面识别。
我在做类似项目时一般会再问一句:你的下游逻辑需不需要区分花色。如果只需要知道“有几张A、几张K”,13类就够了;如果要判断同花顺、炸弹这类组合,就需要把花色和数字联合成52类,或者拆两个检测头。常见做法是先跑通13类字母识别,再在业务层接花色判断,因为花色识别可以单独用一个轻量分类器,没必要混在同一个检测模型里增加类别间的混淆。
2.2 角标区域才是突破口:小目标细节与标注锚点
扑克牌的角标区域大约只占整张牌的8%-12%。“A”和“4”的区别在于一个三角形笔画和一根竖线,放在640x640的输入图像里,角标区域可能只有40x60像素,属于典型的小目标。检测模型在深层特征图上对这种小目标非常不敏感,因此标注时有两个关键选择。
第一个选择是是否把角标和旁边的小花色图标一起框进去。我的建议是:只框字母和数字部分,不框花色小图标。原因有两个:一是花色是圆点或桃心形状,会和字母特征混在一起增加类内方差;二是如果你后续要单独判花色,需要保留这个区域的独立性。标题里的数据集如果标注的就是纯字母区,训练时会省很多事。
第二个选择是保持标注框的宽高比例一致。A是横向笔画多,2/3/4是纵向笔画多,如果标注框一会儿是正方形一会儿是长方形,模型学到的是“框住的区域形状”而不是“字符形状”。我一般会把所有角标框统一成一个固定比例,比如宽高比1.2:1或1:1.3,按照原图角标实际占位微调,但不要让同一个类别的框比例跨度超过1.5倍。
2.3 98.7%从哪来:测试集、IoU置信度与类别均衡
看到“正确识别率可达98.7%”时,要追问它是怎么算的。目标检测的准确率从来不是一个单一数字:PASCAL VOC用的是mAP@IoU=0.5,COCO用的是mAP@[0.5:0.95],还有人报告Top-1分类准确率。对13类扑克牌字母这种高对比印刷体任务,mAP@0.5能到98.7%是合理的,但mAP@0.75通常会掉3-5个点,因为A和4、7和T这类近形字符的框稍微偏移一点,IoU就会掉下去。
还有一个容易被忽略的点:类别均衡。一副牌的A-K分布不是均匀的,如果你是随机拍摄真实牌堆,A和K出现频率会偏低;再加上2、3、4这类中间牌数量多,模型天然偏向高频类。98.7%如果是加权平均准确率,低频类的单类AP可能只有94%。拿到任何自称高准确率的数据集,我第一时间会要求按类别看混淆矩阵,而不是只看平均分。
因此,这份数据集的用户应该自己再做一次分布核对:统计每类标注框的数量,确认最低类别不少于总样本量的5%。如果某类特别少,训练时对这类单独上调cls_loss权重,或者做针对性的贴图增强。这算是我做过多个印刷体检测项目后总结出的血泪经验,平均指标好看不代表你能在真实场景里站稳。
3. 用YOLOv11格式重新组织数据集:目录结构、标签转换与数据划分
3.1 YOLOv11的标签格式:class_id加归一化坐标
YOLOv11(Ultralytics YOLO11)延续了YOLOv5/v8的标注规范,一个txt文件对应一张图,文件名与图片同名,内容每一行代表一个目标框:
0 0.5000 0.3100 0.0820 0.1200 1 0.7200 0.6800 0.0900 0.1100这五个字段依次是:类别ID、框中心点X坐标(除以图宽)、框中心点Y坐标(除以图高)、框宽度(除以图宽)、框高度(除以图高)。坐标全部归一化到0到1之间,所以无论原始图是1080p还是720p,标签都一样。
这个格式有三个容易出错的地方:一是类别ID必须从0开始连续编号,A到K正好是0到12;二是宽高必须是归一化后的值,如果直接填像素值,训练时锚框匹配会完全错乱;三是精度建议保留6位小数,太粗会导致多个框在特征图上落到同一个网格,影响训练初期损失曲线的收敛。
如果你拿到的是别人标注好的数据集,第一步不是直接训练,而是写一个小脚本遍历所有txt,检查坐标是否越界、类别ID是否在0到12之间、有没有空行。我见过不止一次标注工具导出时把width和height字段写成整像素,训练了半天loss不降,最后发现是标签数据的问题。
3.2 读取检查脚本:验证标签的合法性
下面这个脚本用来检查一套YOLO格式标签是否合法,适用于任何YOLO系列模型,不只是YOLOv11。它会把越界框、空标签文件和未知类别ID打印出来,方便你快速定位坏数据。
# check_labels.py: 检查YOLO格式标签的合法性 import os from pathlib import Path label_dir = Path("dataset/labels/train") num_classes = 13 errors = [] for txt_path in sorted(label_dir.glob("*.txt")): with open(txt_path, "r", encoding="utf-8") as f: lines = f.readlines() if len(lines) == 0: errors.append((txt_path.name, "空标签文件")) continue for line_num, line in enumerate(lines, 1): parts = line.strip().split() if len(parts) != 5: errors.append((txt_path.name, f"第{line_num}行字段数不对")) continue try: cls_id = int(parts[0]) coords = [float(x) for x in parts[1:]] except ValueError: errors.append((txt_path.name, f"第{line_num}行含非数字")) continue if cls_id < 0 or cls_id >= num_classes: errors.append((txt_path.name, f"第{line_num}行类别ID越界: {cls_id}")) x_c, y_c, w, h = coords if not (0 <= x_c <= 1 and 0 <= y_c <= 1 and 0 <= w <= 1 and 0 <= h <= 1): errors.append((txt_path.name, f"第{line_num}行坐标越界")) # 归一化后的宽高不可能为负 if w <= 0 or h <= 0: errors.append((txt_path.name, f"第{line_num}行宽高为负")) for err in errors: print(f"{err[0]}: {err[1]}") if not errors: print("所有标签检查通过")这个脚本的逻辑很直接:逐行读取txt,拆出5个字段,先判断字段数量,再判断类别ID和坐标范围。它的价值在于把“肉眼检查”变成“程序检查”,1850张图如果每张图平均2-4个框,就是5000个左右的标注实例,人工根本看不过来。索引错位、坐标归一化错误、类别ID重复这类问题,这个脚本都能在训练前抓出来。
参数上需要注意num_classes这个常量,它必须和你数据集的类别数量一致。扑克牌A-K是13类,class_id范围是0到12。如果你后续把花色加进来变成52类,要改成52。坐标检查里我允许等于0和等于1,是因为标注工具在极端情况下会导出贴边框,但实际训练时建议把贴边的框内缩2个像素,避免在mosaic增强时越界。
3.3 数据划分脚本:train/val/test不串图
数据划分是整个流程里最容易被低估的一步。常见做法是随机打乱后按8:1:1或9:1划分train/val/test。但对于扑克牌识别,有一个特殊要求:同一张桌上拍摄的连拍图像不要同时出现在训练集和验证集,否则验证指标虚高。因为连拍图的背景、光线、牌位置几乎一样,模型等于见过接近原图的画面。
下面这个划分脚本会先按图片所属的拍摄批次分组(通过文件名前缀),再把整个批次分到同一个子集,避免串图。
# split_dataset.py: 按批次划分train/val/test import random from pathlib import Path import shutil img_dir = Path("raw_images") train_img = Path("dataset/images/train") val_img = Path("dataset/images/val") test_img = Path("dataset/images/test") for p in [train_img, val_img, test_img]: p.mkdir(parents=True, exist_ok=True) # 假设文件名格式是 batch01_003.jpg,前缀为批次号 batches = set() for img_path in img_dir.glob("*.jpg"): batch_name = img_path.name.split("_")[0] batches.add(batch_name) batches = sorted(batches) random.seed(42) random.shuffle(batches) n = len(batches) train_batches = batches[:int(n * 0.7)] val_batches = batches[int(n * 0.7):int(n * 0.85)] test_batches = batches[int(n * 0.85):] def copy_images(batch_list, dest_dir): for batch in batch_list: for img_path in img_dir.glob(f"{batch}_*.jpg"): shutil.copy(img_path, dest_dir / img_path.name) # 同步拷贝同名txt标签 txt_path = img_path.with_suffix(".txt") if txt_path.exists(): shutil.copy(txt_path, dest_dir.parents[0] / "labels" / dest_dir.name / txt_path.name) copy_images(train_batches, train_img) copy_images(val_batches, val_img) copy_images(test_batches, test_img)这里的关键逻辑是:先按批次序号分组,再以批次为单位切分。random.seed(42)保证每次跑出来的划分一致,方便复现。文件名前缀我用了batch01这种约定,如果你的文件名没有批次信息,就需要按拍摄时间或目录分组。同步拷贝txt标签时,用with_suffix(".txt")保证图片和标签文件名严格一致,这是YOLO系列训练时的硬性要求,字母大小写、后缀都必须一致。
拷贝后的目录结构应该长这样,这也是YOLOv11默认识别的标准结构:
dataset/ images/ train/ val/ test/ labels/ train/ val/ test/ poker.yamltest目录在训练时用不到,YOLOv11的data.yaml里只需要指定train和val,test留给训练完做最终验证。如果你懒得维护test目录,也可以把全部数据按9:1切分成train/val,验证完再把val里的图挪回来做一次全量训练,这种“训练后合并再训”的做法能多榨出一点数据,属于常规优化手段。
4. 跑通YOLOv11训练:最小命令、模型选型与参数设置
4.1 环境准备与模型选型:yolo11s是扑克牌的起步型号
训练YOLOv11不需要编译源码,直接用Ultralytics安装包。安装命令很简单:
pip install ultralytics yolo versionUltralytics的YOLOv11官方提供了n、s、m、l、x五个重量档位。对扑克牌角标这种小目标任务,我建议从yolo11s起步而不是yolo11n。原因很直接:yolo11n参数量小,推理快,但对角标区域这种几十像素的小目标,浅层特征图的表达力不够,容易漏检;yolo11s只重几十MB,对1850张图的数据规模来说,过拟合风险也可控。如果你的机器显存小于6GB,可以用yolo11n,但要接受漏检率上升。
在开始训练前,先确认本机能正常加载预训练权重。这一步经常被跳过,结果训练到一半发现权重下载不下来,或者CUDA版本和torch不匹配,白白浪费半天时间。
4.2 数据集配置文件:poker.yaml
YOLOv11用data.yaml描述数据集路径和类别名。文件内容如下:
# poker.yaml: 扑克牌字母识别数据集配置 path: dataset # 数据集根目录,相对当前工作目录 train: images/train val: images/val names: 0: A 1: "2" 2: "3" 3: "4" 4: "5" 5: "6" 6: "7" 7: "8" 8: "9" 9: "10" 10: J 11: Q 12: K注意names里的顺序必须和标注txt里的class_id完全对应,不允许跳号或重排。如果把A和K的顺序调换,训练出来的模型预测结果就全乱了,这是最典型的低级错误之一。数字类别的名字用引号包起来是YAML语法规范,避免被解析成数字,10这类双字符类别尤其要注意。
另外一个容易忽略的点:path字段是相对路径时,取决于你执行命令时的工作目录;建议用绝对路径或者保证py文件和数据在同级目录。常见做法是把poker.yaml放在dataset根目录下,然后在dataset的上级目录运行训练命令,这样path: dataset能正确解析。
4.3 训练命令与超参调整
训练命令是整个流程里最短但信息量最大的部分。我用的是下面这条:
yolo detect train \ data=dataset/poker.yaml \ model=yolo11s.pt \ epochs=150 \ imgsz=640 \ batch=16 \ patience=30 \ lr0=0.01 \ degrees=45 \ flipud=0.0参数逐个说明:
epochs设为150,但配合patience=30使用,意思是如果连续30轮验证集指标没有提升,自动停止训练并保存最佳权重。对1850张图的数据规模,一般50到100轮就能收敛,150轮是上限,防止训练过度。imgsz=640是默认值,如果显存允许可以开到960,角标这种小目标的检测效果会更好,但训练时间大约增加一倍,我一般先用640跑通,再考虑提分辨率。batch=16在8GB显存上是安全值,16GB显存可以到32。
lr0=0.01是YOLO系列的默认初始学习率,对yolo11s来说可行。如果你发现训练前10轮的loss下降很慢,可以把lr0调到0.02;如果loss出现震荡发散,就降到0.005,这是典型的调参手段。degrees=45是对旋转增强的限制,这组参数对扑克牌识别极其关键:扑克牌角标有上下朝向,如果训练时允许90度或180度旋转,A和4、6和9这些字符会互相混淆,因为模型看到的字符是倒着的。把degrees限制在45度以内,只做小幅旋转扰动,能显著减少方向混淆。flipud(上下翻转)直接设为0.0,因为上下翻转会把角标倒置,对识别来说等于制造错误标签。
训练过程会输出到runs/detect/train目录,里面包含loss曲线图、混淆矩阵、验证集预测图。第一次训练建议每50步就看一眼loss曲线:box_loss和cls_loss应该平稳下降,如果cls_loss反复横跳,很大概率是标注里有类别噪声,或者A和4这类近形字符太多,这时需要回到第3章的检查脚本查标签。
4.4 训练结果解读:不要只看mAP
训练结束后,Ultralytics会打印一个指标汇总,里面包括mAP50、mAP50-95、precision、recall。对扑克牌识别,我建议按这个顺序看指标:recall优先,precision其次,最后才是mAP。原因是漏检比误检更致命——业务上如果要做牌型判断,漏了一张K就会算错牌;误检顶多多算一张。
如果recall低于95%,优先检查是不是小目标漏检:尝试调高imgsz到960,或者把anchor的默认尺寸降低。如果precision低,说明模型把非牌的桌面纹理、牌背图案误判成了字母牌,这时要检查背景负样本是否充足。YOLO的训练不需要专门提供背景图,但训练集里的每张图都包含多张牌和桌面背景,模型会从负样本区域学到背景模式。如果所有训练图都是整齐摆好的牌,没有干扰物,实拍时就会出现大量误检。
5. 扑克牌数据集训练避坑指南:四个真实的翻车场景
5.1 把整张牌框进来会导致模型学习牌面图案
现象:使用别人标注好的数据集训练,验证集mAP高达98%,但自己实拍一段推牌视频,模型把牌面中央的印花图案也当成了目标,同一张牌框出好几个结果。
原因:标注者把整张扑克牌框为一个目标,而牌面中央的图案和数字字母区域高度相关。模型学到的是“这是牌”而不是“这是字母”,一旦换一幅不同风格的牌,中央图案变化,检测框就开始乱飘。
解决:重新检查label的框坐标,确认每个框都只覆盖左上角的字母/数字区域。如果你拿到的是这类整牌数据集,可以写脚本把大框按固定比例裁成角标框,再微调边界。正确识别率98.7%一定是在正确标注的前提下成立的,标注区域错了,这个数字就没有参考价值。
5.2 A和4、6和9在实拍中频繁翻转误判
现象:训练集里A的识别率很高,但测试视频里把牌稍微旋转一点,A被识别成4,6被识别成9,而且集中出现在拍摄角度倾斜的帧里。
原因:A的角标和4的角标在某些角度下轮廓相似,如果训练时使用了较大的旋转增强参数,模型学会了“圆圈加竖线”这类模糊特征,没有学到A的三角形缺口。同理,6和9在旋转90度后几乎是同一个字符。
解决:把增强参数里的degrees改小,设置成30以内,同时把flipud和fliplr都关闭。扑克牌角标是有方向性的,上下翻转和左右翻转会造成标签语义错误。标题里的数据集如果本身是按正立方向拍摄的,在训练配置里也要做同样的限制,这是印刷字符类检测和通用目标检测最大的区别。
5.3 同花异色导致识别漂移
现象:红色牌和黑色牌的识别率有明显差异,黑色K的识别率明显高于红色K,且红色牌的漏检集中出现在深色桌面上。
原因:训练集中的红色牌样本占比偏高或偏低,破坏了类别内部的均衡性。更隐蔽的问题是,标注框如果包含了角标区左上角的一点红底,模型会把“红色”当成特征的一部分。印刷体字符检测里,颜色应该是忽略项,但目标检测不会自动忽略颜色信息。
解决:统计红色牌和黑色牌的样本数量,按颜色做均衡采样,确保每个类别下两种颜色数量接近。如果数据集本身标注区域包含了花色小图标,可以考虑把标注区域上移,只保留字母和留白部分,减少颜色特征对模型的影响。
5.4 标签索引错位导致所有预测整体偏移
现象:训练时cls_loss降到很低,但验证集的混淆矩阵显示对角线全是0,预测结果普遍比真实类别大1或小1。
原因:数据集的names顺序与标注txt的class_id不一致。比如标注工具从1开始编号,而YOLO要求从0开始,所有id都偏移了一位。这类错误不会导致loss异常,mAP会很高,但预测结果在业务层完全不可用。
解决:在训练前用脚本扫描labels目录,统计每个class_id的出现次数,再与names列表长度对比,确保最大id等于类别数减1。我一般会在数据划分脚本里加一段校验逻辑:一旦发现标注文件里的class_id超出范围立即抛错。这个校验代码很短,但能救你半天时间。
6. 验证与落地:从测试集准确率到真实推牌识别
6.1 用测试集独立出报告,不拿验证集当成绩
训练过程中看到的val指标只是选模型的依据,不是最终成绩。把划分出来的test目录用训练好的best.pt推理一遍,用脚本统计按IoU=0.5判定的类别正确率,这份数字才是可以对外声称的识别率。命令很简单:
yolo detect predict \ model=runs/detect/train/weights/best.pt \ source=dataset/images/test \ conf=0.3 \ save_txt=Trueconf=0.3的意思是置信度低于0.3的检测结果会被丢弃。扑克牌字母是高对比目标,conf阈值可以放到0.3到0.4之间,太高会漏掉因运动模糊而变糊的牌。save_txt=True会输出每个检测框的坐标和类别ID,存成YOLO格式标签,方便用脚本计算混淆矩阵。如果你想把测试集指标提得更好看,也可以把conf调低到0.1再结合业务层的多数投票规则,但不要拿低conf结果直接对外宣称识别率。
6.2 实牌推牌测试:摄像头连续帧的部署验证
离线测试集指标再高,也替代不了真实场景测试。我最后一步习惯用摄像头或一段实拍视频做连续帧推理,脚本如下:
# live_reco.py: 摄像头实时扑克牌识别 from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") results = model.predict( source=0, # 0代表本机摄像头 show=True, conf=0.35, imgsz=640, max_det=10, )这段代码的核心只有三个参数:source=0是摄像头设备号,换成视频文件就写成视频路径;max_det=10限制单帧最多检测10个目标,避免桌面上杂物的错检干扰;conf=0.35是置信度阈值,实时识别时比测试集高一点,宁可漏掉模糊帧也不能满屏乱报。运行时会实时画出检测框并显示类别名称。
我在做这类印刷体识别项目时,有一个固定习惯:测试集上跑一次,实拍视频里再跑一次,两次指标取保守值。扑克牌识别看起来不难,但实拍里的运动模糊、手指遮挡、牌面反光都会让离线指标打折扣。只要把标注区域限定在角标、增强参数关闭大幅旋转、类别顺序校验做好,1850张图的体量是够用的。如果你后续想进一步提升,方向不是盲目加图,而是用训练好的模型做hard negative mining:挑出实拍里误检最严重的帧,补充进训练集重训一轮,这比随机加原始图更有效。
希望这些踩过的坑能帮你把扑克牌识别这个方向做得更稳,少走一些我走过弯路。
本文还有配套的精品资源,点击获取