ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

扑克牌识别数据集:COCO转YOLO训练与98.7%准确率复现指南

扑克牌识别数据集:COCO转YOLO训练与98.7%准确率复现指南 简介该扑克牌识别数据集面向计算机视觉、目标检测及深度学习初学者完整覆盖A至K所有扑克牌面字母可用于训练扑克牌检测与识别模型也可作为分类任务基准。数据集包含1850张原始jpg图像均采用COCO json格式标注提供类别标签、边界框坐标等标准信息可直接导入YOLO、MMDetection等主流检测框架无需额外转换。据描述模型正确识别率可达98.7%验证了数据标注质量与可用性。压缩包共1853个文件1850个jpg图片、3个json标注文件整体大小110.21MB文件结构清晰便于按批次训练与测试。数据集图像涵盖不同拍摄角度、光照条件和背景样式有助于提升模型在真实场景中的泛化能力。目前已有259人学习下载适合课程设计、算法实验或扑克牌相关应用开发。1. 扑克牌识别数据集是什么1850张原始图如何撑起A-K全类别识别扑克牌识别数据集的核心卖点很直白1850张真实牌面照片A、2、3一直到K共13个类别都有独立标注配套coco json格式的标注文件标称正确识别率能达到98.7%。这个规模放在目标检测领域不算大但扑克牌自身类别清晰、形变有限只要标注一致、训练参数得当小数据集反而比通用大场景更容易收敛到高正确率。它解决的典型问题是桌面视觉计数、牌局过程复盘、魔术教学拆解这类场景里的“牌面状态”自动盘点。适合已经有yolo基础、但缺干净数据集的开发者拿来练手也适合做自动发牌机和牌桌辅助系统的原型验证。这组数据最容易被低估的是coco json标注带来的格式成本。接下来我按读json、转yolo、训练、复现指标、避坑、复核标注这条线展开把命令和参数都摆出来尽量让你能照着跑通并且理解98.7%这个数字到底在什么条件下成立。2. 拆解coco json标注字段含义、类别映射与扑克牌bbox的统计体检2.1 为什么扑克牌识别数据集偏偏用coco json标注目标检测的标注格式五花八门Pascal VOC用xmlyolo用txt还有各类工具的自定义格式。常见做法是选coco json原因不复杂第一coco json能同时承载检测框、实例分割多边形和关键点三种信息扑克牌虽然是矩形物体但牌面出现透视形变时标注员偶尔会画多边形json格式两种都能装第二主流数据标注工具对它的支持最完整cvat标注工具和labelme都能直接导出coco jsonlabelimg标注工具也可以通过脚本互转省去手工改文件第三coco官方提供了pycocotools和统一的评估脚本后面复现98.7%的识别率时不必自己从头写评估逻辑。当数据集只有1850张原始图时标注成本本身是可控的所以更值得把精力花在格式规范化上。扑克牌这个物体的特点是牌面是刚性平面绝大多数场景下用一个水平检测框就能包住整张牌不需要像素级分割但A、K、Q、J这类牌面上的花体与角落数字辨识度差异大标注一致性比格式本身更重要。也就是说coco json在这里并不是因为它复杂才被选中而是因为它在“类别、位置、图像尺寸、分割掩码”之间提供了一个结构化载体后续无论转到yolo训练还是做切片推理都有现成的解析路径。2.2 coco json的五个核心字段images、annotations、categories不能只看名字拿到扑克牌数据集的标注文件我一般先把它当普通json处理用编辑器里的json格式化功能打开看结构。顶层固定是五个键info、licenses、images、annotations、categories。对训练真正起作用的是后三个info是版本信息licenses是版权声明可以忽略。images数组里每个元素描述一张原始图常用字段是id、file_name、width、height。annotations数组是核心每个元素对应一个目标实例最关键的是image_id、category_id、bbox、area、iscrowd。bbox写法是[x, y, width, height]x和y是框左上角在原始图上的绝对像素坐标width和height是框宽高。area在检测任务里基本用不到但分割标注中它会作为掩码面积被记录。iscrowd在扑克牌目标上通常全是0只有遇到一堆牌叠在一起、无法逐张框时才会标成1如果出现iscrowd1训练前要单独处理。一张牌对应几条annotation大多数情况下一个实例对应一条包含一个外接框。一张图里有两张牌就有两条annotation牌被转成背面而又需要识别花色时就得按数据集说明决定是否单独建类。这里有个容易忽略的点coco的category_id是自定的正整数不要求从0开始而yolo要求从0连续编号转换脚本里必须显式做一次映射否则后续训练成绩全错。很多人第一次跑这类数据集翻车就翻在这里。2.3 读取coco json做体检先看类别分布和bbox尺寸再谈训练训练前我习惯写一段最短的python脚本把coco json读进来统计三类信息各类别的标注条数、每张图的平均目标数、bbox相对图片的尺寸分布。这一步能提前暴露长尾类别、漏标和框画得异常的情况。import json from collections import Counter, defaultdict with open(cards_coco/annotations/instances_train.json, r, encodingutf-8) as f: data json.load(f) # 顶层键名确认防止拿到非标准coco文件 print(data.keys()) # 类别表映射coco的category_id - 牌面名称 cats {c[id]: c[name] for c in data[categories]} print(cats) # 每个类别的实例数 cls_cnt Counter() # 每张图对应的目标数量 img_cnt defaultdict(int) for ann in data[annotations]: cls_cnt[cats[ann[category_id]]] 1 img_cnt[ann[image_id]] 1 print(每个类别的实例数:, cls_cnt.most_common()) print(每张图目标数分布:, Counter(img_cnt.values()))这段代码的逻辑很直白加载json用字典推导把category_id映射成牌面名称遍历annotations同时统计类别和每张图的目标数。如果某个类别明显偏少比如A只有80张而K有200张说明数据集自带长尾后面针对少数类做增强。如果每张图目标数基本都是1说明1850张图主要是单牌照片复现时就不要指望模型能稳定处理多张牌堆叠的实拍画面。再看bbox尺寸分布用来发现标注框是否异常import json import numpy as np with open(cards_coco/annotations/instances_train.json, r, encodingutf-8) as f: data json.load(f) img_size {im[id]: (im[width], im[height]) for im in data[images]} rel_w, rel_h [], [] out_of_bound [] for ann in data[annotations]: x, y, w, h ann[bbox] iw, ih img_size[ann[image_id]] rel_w.append(w / iw) rel_h.append(h / ih) # 显式检查标注框是否越过图像边界 if x 0 or y 0 or x w iw or y h ih: out_of_bound.append(ann[id]) print(rel_w 百分位:, np.percentile(rel_w, [5, 50, 95])) print(rel_h 百分位:, np.percentile(rel_h, [5, 50, 95])) print(越界框数量:, len(out_of_bound))大多数扑克牌照片中单张牌的框宽高比接近牌的实际比例约2.5:1到3.5:1。如果rel_w的中位数在0.2到0.6之间、rel_h在0.1到0.3之间说明标注尺度正常。越界框一旦出现yolo训练时不会报错但会把背景噪声带进标签复现准确率时被无端拖低。注意x、y可以等于0但不能是负数xw也不能大于图片宽度这是最基础的coco标注合法性标准。注意json文件用什么打开本身也是个常见问题。别用记事本直接编辑大json会卡而且容易把编码改坏。用VS Code或Notepad的json格式化功能看结构就够了真正处理交给python脚本。3. coco json转yolov8训练格式转换脚本、data.yaml与必调参数3.1 coco转yolo的最小脚本归一化坐标和类别重映射是两件独立的事yolov8训练自己的数据集时默认不直接接收coco json它要的标签是每张图对应一个同名txt文件每行五个值class x_center y_center width height全部归一化到0和1之间。因此中间必须加一层转换。我每次处理类似数据集都保留coco json原文件不动只生成一套新的txt标签避免来回编辑把原始标注改坏。import json import os def coco_to_yolo(json_path, out_dir): with open(json_path, r, encodingutf-8) as f: data json.load(f) # coco类别id - yolo类别id从0连续编号 cat_id_map {c[id]: i for i, c in enumerate(data[categories])} img_map {im[id]: im for im in data[images]} os.makedirs(out_dir, exist_okTrue) for im in data[images]: base os.path.splitext(im[file_name])[0] txt_path os.path.join(out_dir, base .txt) lines [] for ann in data[annotations]: if ann[image_id] ! im[id]: continue x, y, w, h ann[bbox] # bbox左上角 - 中心坐标 cx (x w / 2) / im[width] cy (y h / 2) / im[height] nw w / im[width] nh h / im[height] lines.append(f{cat_id_map[ann[category_id]]} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}) with open(txt_path, w, encodingutf-8) as out: out.write(\n.join(lines)) coco_to_yolo(cards_coco/annotations/instances_train.json, labels/train) coco_to_yolo(cards_coco/annotations/instances_val.json, labels/val)这段脚本有三个细节值得较真。第一类别映射不能直接拿category_id当class用coco的id通常从1开始而yolo的class必须从0开始连续编号差一个序号就会导致所有类别整体错位。第二归一化用的是框中心不是左上角x/y照搬VOC写法会生成偏移半张图的错误框。第三txt文件名必须和图片文件名完全一致只换后缀yolo通过stem匹配图片和标签IMG_001.jpg对应IMG_001.txt多一个空格都不行。3.2 data.yaml与训练命令六个影响收敛的参数转换完标签下一步组织目录结构并写data.yaml。常见做法是images和labels分开放train和val各一份在data.yaml里写相对路径避免换机器后绝对路径失效。path: ./cards_dataset train: images/train val: images/val nc: 13 names: [A, 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K]names顺序必须和上一步转换脚本里cat_id_map枚举的顺序完全一致。转换脚本是以categories数组为基准生成的这里如果手写names时按字母A到K排序而categories数组是另一个顺序就会造成A变成K之类的混乱。稳妥的办法是把coco json里categories打印出来复制过去不要凭印象写。训练命令如下建议先用yolov8n跑基线yolo detect train \ datacards.yaml \ modelyolov8n.pt \ epochs120 \ imgsz640 \ batch16 \ lr00.01 \ patience15 \ augmentTrue \ projectruns/cards六个参数按影响程度排一下。imgsz决定输入分辨率扑克牌花体笔画细640起步显存足够就试960分辨率对牌面边缘细节帮助明显。epochs在1850张图规模下120轮基本收敛跑满300轮容易过拟合到背景纹理。batch与lr0联动调大batch时lr0要跟着放大否则收敛很慢。patience设为15能早停省时间但不要设为0那会让模型即使不再收敛也一直跑满全部epochs。augment默认开启Mosaic和色彩抖动对扑克牌识别是把双刃剑后面避坑章细说。3.3 小数据集下的收敛策略为什么这套参数能把准确率推上去小数据集训练最怕的是“loss很低但测试很差”。98.7%级别的高正确率依赖三件事类别均衡、增强温和、早停合理。在1850张图下如果直接用yolo默认增强Mosaic会频繁把四张牌拼成一张模型见过更多组合但也容易被拼贴的边界干扰单牌轮廓。我通常会把Mosaic概率降到0.5以下把hsv饱和度增强幅度调低因为扑克牌的红桃黑桃颜色差异是重要的类别线索过度调色会让模型混淆红色与黑色的判断。训练过程中盯住三条曲线train/box_loss持续下降val/box_loss同步下降metrics/mAP50稳步上升。如果train_loss降而val_loss不降说明过拟合先降epochs或调大正则项。如果mAP50涨到0.95之后反复跳动不必追求最后1%的涨幅拿best.pt去跑实拍验证比继续调参更有意义。另一个容易被忽略的地方是数据增强里的旋转角度。扑克牌识别中旋转增强角度范围建议控制在正负20度以内。超过30度之后水平框内会混入大量牌桌背景模型学到的不再是“牌面特征”而是“框内纹理”反而不利于真实桌面的任意角度识别。4. 如何复现98.7%的识别准确率测试集划分、指标口径与预测验证4.1 先搞清楚98.7%说的是哪种准确率拿到数据集先冷静一下页面里写的98.7%和训练日志里的mAP50不是同一个东西。目标检测的mAP50是把每个类别的PR曲线按50%IoU阈值平均数值到0.9以上已算不错而98.7%更像“整体识别正确率”或“类别正确率”也就是预测框与真实框匹配后类别正确的样本数占总样本数的比例。两种口径对数据和评估方式的要求完全不同。所以第一步是先跑一次验证预测打印类别层面的正确率不要因为和页面宣传对不上就怀疑模型坏了。常规做法是把IoU阈值定在0.5对每个预测框和真实框做匹配只统计置信度高于0.35的框计算正确分类的样本数除以总牌数。如果这个指标在验证集上能稳定到95%以上说明数据集标注质量是过硬的98.7%大概率是在特定划分和置信度阈值下测出来的。养成先看指标口径的习惯能少走弯路。4.2 划分测试集按“牌面实例”而不是按“照片”随机化朴素的随机划分在这里容易翻车。假设1850张图中同一张牌在不同角度、不同光照下重复出现多次随机划分会把同一张牌的多个视角同时分进train和val模型在val上的成绩会被“记忆”抬高。真实桌面识别关心的是没见过的牌面不是同一个A换个角度换个亮度再认一次。建议划分的粒度是“牌面实例”而不是“照片”。如果数据集没有提供图像分组字段一个可行近似是按采集批次或连续文件名前缀拆分保证同一个批次只在train或只在val。另一种更稳的做法是KFold交叉验证折数建议5每个fold单独训练并上报平均准确率这样复现出来的数字才有说服力。4.3 用best.pt跑预测并统计单牌正确率一段能落地的评估脚本用yolov8的python接口做一次类目级评估比看训练曲线直观得多。from ultralytics import YOLO import os, json model YOLO(runs/cards/weights/best.pt) # 读取val集的coco json按image_id收集真实类别 gt_by_img {} with open(cards_coco/annotations/instances_val.json, encodingutf-8) as f: data json.load(f) cats {c[id]: c[name] for c in data[categories]} for ann in data[annotations]: gt_by_img.setdefault(ann[image_id], []).append(cats[ann[category_id]]) img_paths {im[id]: im[file_name] for im in data[images]} correct 0 total 0 for img_id, gts in gt_by_img.items(): path os.path.join(cards_dataset/images/val, img_paths[img_id]) res model(path, conf0.35, iou0.45, verboseFalse)[0] # 只取类别名忽略空框 preds [model.names[int(b.cls[0])] for b in res.boxes] # 按整张图片的类别集合比对忽略数量差异 correct len(set(preds) set(gts)) total len(gts) print(f类别正确率: {correct}/{total} {correct/total:.3f})这段脚本有意做得简单只统计每张图里预测类别集合和真实类别集合的交集没有做逐框IoU匹配。真正要较真精度时需要用IoU把预测框映射到真实框上再看类别是否一致否则会出现“预测出一个真值里没有的K”但照样计入正确的情况。做基线评估时这个简化够用因为先回答的是“类别方向对不对”定位误差的具体来源留给后续分析。跑完这步基本就能判断98.7%能否复现。如果类别正确率明显低于98.7%先查两件事一是coco json里category_id和yolo class的映射是否错位二是少数类样本是否过少。这两个因素的排查方法写在下一章。5. 扑克牌数据集避坑记录类别错位、长尾样本与旋转牌面的血泪教训5.1 类别ID错位明明是A预测出来却是K现象训练结束后mAP50很高但可视化预测时发现模型把A牌整体识别成K或2错得非常均匀。原因转换脚本的cat_id_map和data.yaml的names顺序不一致。比如转换脚本用enumerate后category_id映射到class 0对应A而data.yaml里names写成了[K, Q, ...]yolo的class 0被分配给了另一个类别。这个错误在训练日志里完全不报错最容易发现的方式是混淆矩阵对角线整体偏移。解决把coco json里categories数组的原始顺序打印出来直接复制到data.yaml的names。我的习惯是在转换脚本里多打印一行映射表写完data.yaml后再读回来和映射表做一次断言两者完全一致才开始训练。用python脚本生成data.yaml比手写更不容易出错。注意不要用“看起来顺眼”的排序去重写names。扑克牌数据集的categories可能是按牌面出现顺序录入的和字母表顺序无关一切以json内容为准。5.2 长尾样本K、Q、J的识别率为什么总垫底现象整体mAP在0.98左右单独统计每个类别的准确率发现A、2、3这些数字牌很高K、Q、J相对偏低个别花色尤其差。原因1850张图按13类均分时每类约140张但实际数据往往有偏重普通数字牌的数量多于人头牌而且K的轮廓与A相似牌面转角更复杂类别间的类间距离更小。解决先做类别计数如果差距超过两倍考虑两件事。一是对少数类做复制粘贴增强二是按类别加权采样让少数类在每次迭代中有更高概率被抽中。我常用的是把Q、K这些类的原始图做轻度旋转和亮度增强后再补进训练集不直接复制同一张图否则等于重复加权会让模型记住具体图片的噪声而不是类别的规律。判定标准很简单单独统计每个类的预测召回率人头牌低于92%就需要干预。5.3 旋转牌与水平框的冲突mAP好看实拍总漏检现象验证指标和数据集内部测试都不错放到真实牌桌上牌被斜着拿、任意旋转角度时漏检率立刻升高。原因数据集的标注框是水平外接框yolo学到的是水平矩形内的牌面花纹一旦牌旋转超过45度水平框内混入大量背景特征被背景冲淡尤其红心、方块这类中心对称花色更容易误判。解决轻度旋转时外接框变化不大超过30度之后标注工具通常不会自动计算旋转后的外接框只是简单拉伸原框。我一般对训练图做正负20度以内的旋转增强推理时用更大的imgsz弥补旋转带来的特征衰减。如果业务目标是全向识别那就该换旋转目标检测器而不是抱着水平框硬调。这个坑最隐蔽因为模型在原数据集上的分数始终很漂亮只有到真实牌桌前才能暴露。5.4 json编码与转义符看着能打开脚本却读不进去现象json.loadf直接报JSONDecodeError或者标注内容读出来是一串乱码。原因标注文件里混了注释、没有用utf-8编码、某些牌面名称里含有引号或emoji但没有转义。coco json是严格json注释、单引号、尾部多逗号都是非法写法在Windows下保存为ANSI编码后python默认utf-8读取就会报错。解决打开文件统一写encodingutf-8遇到JSONDecodeError时用json.tool或编辑器格式化检查具体行。实际经验是标注工具导出的json本身通常没问题问题大多出在中间手工编辑环节。所以我在转换前从不在原文件上另存只读不改。如果确实要修改复制一份再改避免引入编码偏差。6. 复核coco json标注质量的三个自查脚本越界、漏标、重复框与牌面回显扑克牌识别数据集值不值得拿来训练最终取决于原始标注怎么维护。下面是组合自查脚本我每一轮训练前都会跑一遍。import json, cv2, os with open(cards_coco/annotations/instances_train.json, encodingutf-8) as f: data json.load(f) images {im[id]: im for im in data[images]} ann_map {} for ann in data[annotations]: ann_map.setdefault(ann[image_id], []).append(ann) bad [] for img_id, anns in ann_map.items(): im images[img_id] boxes [] for ann in anns: x, y, w, h ann[bbox] # 检查越界与非法宽高 if x 0 or y 0 or w 0 or h 0 or \ x w im[width] or y h im[height]: bad.append(img_id) # 检查重复框中心位置和尺寸完全一致视为重复 key (round(x, 1), round(y, 1), round(w, 1), round(h, 1)) if key in boxes: bad.append(img_id) boxes.append(key) print(有问题的图片数量:, len(bad))这段脚本把越界框和重复框都标为bad。标完以后把有问题的框直接画到原图上回显肉眼确认是漏标还是误标。这一步不要省因为polygon转bbox、cvat导出以及多轮修改标注时最容易产生精度损失。回显代码就是读取图片后用cv2.rectangle把bbox画在原图上再叠加类别名保存。自己做一次就能发现很多自动化体检发现不了的问题比如两张牌叠在一起时只标注了上面一张下面的牌被当成背景训练出来的模型在这个角度附近会随机丢牌。这类漏标问题是扑克牌识别特有的黑匣子靠统计脚本看不出来必须可视化。我的固定习惯是正式训练前一天把越界、重复框、类别分布和可视化结果一起过一遍。前面几章讨论的大多数训练问题其实都能在这一轮检查里提前预判。真正值得投入的并不是训练命令上的微调而是把数据质量检查养成流程的一部分。等你在生产环境里把识别率推到98%以后回头再看会确认这套基本功才是让模型站住脚的原因。希望帮到你。本文还有配套的精品资源点击获取
返回列表