
简介扑克牌识别数据集共包含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用的是mAPIoU0.5COCO用的是mAP[0.5:0.95]还有人报告Top-1分类准确率。对13类扑克牌字母这种高对比印刷体任务mAP0.5能到98.7%是合理的但mAP0.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加归一化坐标YOLOv11Ultralytics 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, encodingutf-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(parentsTrue, exist_okTrue) # 假设文件名格式是 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和valtest留给训练完做最终验证。如果你懒得维护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.yamlYOLOv11用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 \ datadataset/poker.yaml \ modelyolo11s.pt \ epochs150 \ imgsz640 \ batch16 \ patience30 \ lr00.01 \ degrees45 \ flipud0.0参数逐个说明epochs设为150但配合patience30使用意思是如果连续30轮验证集指标没有提升自动停止训练并保存最佳权重。对1850张图的数据规模一般50到100轮就能收敛150轮是上限防止训练过度。imgsz640是默认值如果显存允许可以开到960角标这种小目标的检测效果会更好但训练时间大约增加一倍我一般先用640跑通再考虑提分辨率。batch16在8GB显存上是安全值16GB显存可以到32。lr00.01是YOLO系列的默认初始学习率对yolo11s来说可行。如果你发现训练前10轮的loss下降很慢可以把lr0调到0.02如果loss出现震荡发散就降到0.005这是典型的调参手段。degrees45是对旋转增强的限制这组参数对扑克牌识别极其关键扑克牌角标有上下朝向如果训练时允许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被识别成46被识别成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推理一遍用脚本统计按IoU0.5判定的类别正确率这份数字才是可以对外声称的识别率。命令很简单yolo detect predict \ modelruns/detect/train/weights/best.pt \ sourcedataset/images/test \ conf0.3 \ save_txtTrueconf0.3的意思是置信度低于0.3的检测结果会被丢弃。扑克牌字母是高对比目标conf阈值可以放到0.3到0.4之间太高会漏掉因运动模糊而变糊的牌。save_txtTrue会输出每个检测框的坐标和类别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( source0, # 0代表本机摄像头 showTrue, conf0.35, imgsz640, max_det10, )这段代码的核心只有三个参数source0是摄像头设备号换成视频文件就写成视频路径max_det10限制单帧最多检测10个目标避免桌面上杂物的错检干扰conf0.35是置信度阈值实时识别时比测试集高一点宁可漏掉模糊帧也不能满屏乱报。运行时会实时画出检测框并显示类别名称。我在做这类印刷体识别项目时有一个固定习惯测试集上跑一次实拍视频里再跑一次两次指标取保守值。扑克牌识别看起来不难但实拍里的运动模糊、手指遮挡、牌面反光都会让离线指标打折扣。只要把标注区域限定在角标、增强参数关闭大幅旋转、类别顺序校验做好1850张图的体量是够用的。如果你后续想进一步提升方向不是盲目加图而是用训练好的模型做hard negative mining挑出实拍里误检最严重的帧补充进训练集重训一轮这比随机加原始图更有效。希望这些踩过的坑能帮你把扑克牌识别这个方向做得更稳少走一些我走过弯路。本文还有配套的精品资源点击获取