ARTICLE DETAIL

资讯详情

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

扑克牌目标识别数据集标注实战指南:从采集到验证的完整避坑路径

扑克牌目标识别数据集标注实战指南:从采集到验证的完整避坑路径 简介这是一套面向目标检测学习与算法验证的扑克牌识别数据集涵盖queen、ten、nine、king、jack、ace六种常见牌面适合初学者用于训练轻量级检测模型也可作为课程设计或对比实验的数据基础。整个压缩包共包含726个文件由363张JPG图像和363个对应的XML标注文件组成压缩后体积约36.62MB。XML标签由LabelImg标注工具生成记录了每张图像中目标牌面的边界框位置与类别信息格式规范便于直接转换为YOLO、PASCAL VOC等常见训练格式。目前已有395人学习下载数据规模适中训练速度快尤其适合用来熟悉数据标注规范、跑通目标检测流程或验证不同网络结构的识别效果。标注类别明确、样本分布均衡使用者可快速完成数据划分与格式转换集中精力于模型调参与精度优化。1. 标注扑克牌目标识别数据集先想清楚这几件事再动手不迟做牌桌识别、棋牌室行为分析或者 AR 牌面扫描的开发者拿到摄像头素材后第一件事往往就是给扑克牌做目标检测。但标注扑克牌目标识别数据集和标人、标车不一样牌面类别多、尺寸小、任意旋转还带着反光和透视。很多人一上来就开标注工具框框点点标了两千张才发现类别体系不合理、角度定义混乱、模型怎么训都不过最后全部推倒重来。这篇笔记我把做这个数据集时会遇到的采集、标注、增强、踩坑和验证路径完整捋一遍适合正准备自建牌面数据集的算法工程师和独立开发者照着落地。2. 牌面数据从哪来采集方案与类别体系设计2.1 拍摄与采集的落地路径扑克牌目标识别数据集的原始图像常见做法有三种自己拍摄、抓取公开数据集、程序合成。自己拍摄最可控推荐作为主力。拍摄环境不要追求“干净”反而要故意制造难度背景用牌桌纹理、木纹桌面、深色毛毡都行光线要混合自然光和顶灯因为实际部署时你不可能控制牌桌上方的光源角度。相机架设高度建议 40 到 60 厘米俯拍画面要保证单张牌短边不小于 60 像素。低于这个值后面做检测时小目标很容易漏检标注时人眼也容易点偏。分辨率不低于 1080p格式 JPEG 就行不要用 BMP 这种大体积格式。拍摄场景至少要包含三种整齐发牌、随机摊开、牌与牌部分重叠。摊开拍摄时把牌从中间往两侧推开重叠率控制在 20% 到 50% 之间不要全部叠成一摞。程序合成适合做数据补充常见做法是拿牌面贴图渲染到复杂背景上做随机旋转、透视扰动。但合成数据只能当“开胃菜”贴图渲染的色调和真实摄像头图像差距很大模型容易学到“假”的纹理特征。一般我会把合成数据控制在训练集总量的 20% 以内并且最后几轮训练只喂真实拍摄数据做微调。2.2 类别设计按“点数×花色”还是“点数花色”拆这是整个标注工作里最难回头的一步因为一旦类别定义错了几万张标注全部白做。业内常用方案有三个按需要选择方案类别数优点缺点适用场景全类别5452 张 大小王一张牌一个类模型输出直接可用类别太多样本分散训练成本高最终目标是直接识别整张牌点数花色双分支13 类点数 4 类花色分开输出类别少样本更集中需要设计多分支头推理后还得自己组合牌面识别下游还要做逻辑分析仅点数13 类最简单标注最快丢失花色信息只需要判断牌面大小我的建议是除非你的检测头本身支持多分支输出否则走全类别 54 类最省事。双分支听起来优雅但实际部署时多一个头就多一份调参工作量而且商用识别牌面基本都是直接要“方块 A”这种完整结果。如果未来要多模态目标识别比如牌面还要同时输出文字区域那可以单独再做一组文本框标注不要把文字区域混进检测类别里。2.3 数据集目录结构与标签格式规范从第一天起就把目录按训练、验证、测试分开别等标注完了再整理。推荐结构是datasets/poker/ ├── images/ │ ├── train/ # 训练集原始图片 │ ├── val/ # 验证集原始图片 │ └── test/ # 测试集原始图片 ├── labels/ │ ├── train/ # 训练集对应标签 │ ├── val/ │ └── test/ ├── classes.txt # 类别名一行一个 └── poker.yaml # 数据配置文件图片和标注文件名必须一一对应比如IMG_001.jpg对应IMG_001.txt。训练集和验证集的划分建议在采集时就按“不同牌局、不同时间”分开不要随机打乱。因为同一局里光线、角度高度相似随机划分会让验证集虚高模型实际泛化能力比测试结果差一大截。标注内容我采用归一化坐标这是 YOLO 系列框架的标准格式每行对应一张图里的一个目标class_id x_center y_center width height坐标值都除以图片宽高范围 0 到 1。比如一张 1920x1080 的图像里某张牌的框中心在 (960, 540)宽 400高 600那标签行就是12 0.5 0.5 0.2083 0.5556class_id是从 0 开始的下标classes.txt里第几行就是类别几。这个格式不用记只要记得“全部归一化”就不会翻车。2.4 最小样本量估算起步别少于这个数全类别 54 类的方案里每类至少要保证 150 个标注实例整个训练集最低也要 3000 到 5000 张图。这个数字不是一个拍脑袋的阈值是因为扑克牌类别之间差异很小——红桃 3 和方块 3 的轮廓完全一样只有颜色和花色符号不同模型需要足够多的样本才能把“红桃”和“方块”这种细微差异学好。如果实在凑不够 54 类各 150 张有一个取巧的办法先把红牌和黑牌按点数合并比如只区分“红桃 A / 黑桃 A”后面再用一个轻量分类器去分花色。这样检测模型先解决“是哪张牌形”分类模型再解决“红还是黑”两个模型都不需要太多数据。这是我在样本不足时常用的降级方案效果比硬凑全类别好很多。3. 标注方式选型矩形框、四点旋转框与工具链3.1 三种标注模式对比为什么推荐四点旋转框扑克牌在画面里是任意角度的标注方式直接决定检测头能不能学到旋转不变特征标注模式描述优点缺点水平矩形框框永远和图像坐标轴平行工具兼容性最好随便一个标注工具都能标框会框进大量背景牌面倾斜时 IoU 偏低四点旋转框用四个点表示旋转矩形贴合牌面轮廓角度信息准确工具选择少后期转换容易出错多边形沿牌面轮廓描边最精准标注效率太低不适合批量生产我一般会选四点旋转框。原因很简单扑克牌在牌桌上没有固定的“正方向”发牌时牌子可以旋转成任何角度水平框在这种场景下会引入大量背景噪声。旋转框检测在遥感数据集里已经非常成熟很多检测框架现在都支持旋转头拿来做牌面是顺理成章的事。如果你的检测框架不支持旋转框那退而求其次用水平框但采集和增强阶段就得多做工作保证训练集里每张牌的旋转角度覆盖 0 到 360 度的所有象限让水平框“包住不同角度的牌面”这件事本身成为模型要学习的模式。不要混合标注——一张图里既有旋转框又有水平框这种数据训练出来的模型输出坐标会非常混乱。3.2 标注工具怎么选四款工具的取舍标注工具的选型决定了团队效率。实际用过一轮之后我按场景做了个对比工具部署方式旋转框支持导出格式适合场景LabelImg单机极简仅水平框Pascal VOC / YOLO小规模快速起步X-AnyLabeling单机可执行支持四点框多种格式单人标注旋转框的最佳选择CVAT服务器 / Docker完整支持COCO / VOC / 自定义多人团队需要任务分配和审计Labelbox云端托管支持云端存储预算充足不想维护服务器单人做数据集我推荐直接用 X-AnyLabeling。它支持四点标注标注完可以直接导出 JSON导入导出都不折腾。多人协作就用 CVAT 起一个 Docker 实例管理任务、抽检、二次审核都方便但前期部署和学习成本高。LabelImg 只适合做初步的快速标注旋转框支持缺失这个硬伤让它很难用于扑克牌这种全角度目标。如果只是自己一个人标几千张说实话 CVAT 不值得。Docker 起一个服务、配用户、学界面够你多标几百张图了。选工具的原则是人数越少工具越轻。3.3 从工具格式到训练格式JSON 转 YOLO 四点坐标X-AnyLabeling 导出的 JSON 结构是shapes数组每个 shape 的points字段是一个四点坐标列表label是类别名。转 YOLO 训练格式的核心脚本如下import json import os import glob def convert_anylabeling_json(json_path, class_map, out_dir): with open(json_path, r, encodingutf-8) as f: data json.load(f) img_w data[imageWidth] img_h data[imageHeight] base_name os.path.splitext(os.path.basename(json_path))[0] out_txt os.path.join(out_dir, base_name .txt) lines [] for shape in data[shapes]: label shape[label] if label not in class_map: continue class_id class_map[label] points shape[points] # 归一化四个点坐标 norm_pts [(x / img_w, y / img_h) for x, y in points] # 展平成 x1,y1,x2,y2,x3,y3,x4,y4 flat [coord for pt in norm_pts for coord in pt] lines.append(f{class_id} .join(f{v:.6f} for v in flat)) with open(out_txt, w, encodingutf-8) as f: f.write(\n.join(lines)) # 用法示例 class_map {红桃A: 0, 方块A: 1, 黑桃A: 2, 梅花A: 3} convert_anylabeling_json( json_pathannotation/001.json, class_mapclass_map, out_dirlabels/train )这段代码干了两件事读取图像宽高做归一化再把每个 shape 的四个点坐标展平成一行输出。注意输出的是归一化四点坐标而不是中心点加宽高加角度因为四点坐标是各种旋转框检测框架之间交换最不容易出错的中间格式。如果你要用 MMPose 或者 MMDetection 里的旋转检测头它们各自有角度定义到时候从四点坐标转成cx, cy, w, h, angle就很容易。跑之前先确认 JSON 里是图像宽高不是最大边。有的工具导出的宽高是原始分辨率有的做了缩放不查这个直接转换坐标全部错位把标注喂给模型就是一场灾难。3.4 半自动预标注用粗糙模型把标注工作量砍半标注两三千张图不是个小工程。常用做法是先拿已有的公共牌面模型或者自己训一个粗糙版模型对全部原始图片做批量推理把预测结果转成 X-AnyLabeling 可以导入的标注初始值然后人工逐张修正。这能把标注时间压缩到原来的 40% 左右。推理模型不需要准把框画到牌面大概位置就够了。人工修正的重心放在边界贴合和类别错误上。这个流程里有一个经验第一次预标注的模型如果只用了水平框导出的标注也要转成四点坐标不要偷懒保留水平框。因为人工在一张水平框上把牌面的四个角拉出来比重新画一个四点框更费劲。预标注做完之后一定要抽样检查特别是类别错乱。粗糙模型会把方块和红桃混淆这种错误如果混进训练集模型后面会一直学歪。抽检比例至少 10%重点看图里牌面多的情况。4. 数据增强与类不平衡让数据集真正能训练4.1 增强该做什么从牌面特征反推扑克牌是平面目标但它的难点恰好来自平面任意旋转、灯光反光、透视形变、牌与牌重叠遮挡。数据增强要做的事就是把这些真实世界的变化模拟出来。注意这不是越多越好而是必须保住“花色和点数语义不变”这条底线。先列一个我常用的增强组合亮度对比度扰动模拟不同光线色温偏移模拟暖光灯和冷光灯轻微模糊模拟手抖和低端摄像头的 CMOS 噪声透视变换模拟相机角度偏斜整体旋转模拟牌在桌面上的任意朝向。这套组合做完模型对实际牌桌环境的抵抗能力会明显提升这也是后面做多模态目标识别里视觉模态时绕不开的一步。4.2 增强参数怎么调一份可以直接抄的配置用 albumentations 库实现这份配置直接抄可以用import albumentations as A transform A.Compose([ A.RandomBrightnessContrast( brightness_limit0.2, contrast_limit0.2, p0.5 ), A.HueSaturationValue( hue_shift_limit10, sat_shift_limit20, val_shift_limit10, p0.4 ), A.MotionBlur(blur_limit5, p0.3), A.Rotate(limit180, border_mode0, p0.8), A.Perspective(scale(0.02, 0.08), p0.3), A.RandomSizedBBoxSafeCrop( height640, width640, erosion_ratio0.1, p0.5 ), ], bbox_paramsA.BboxParams( formatalbumentations, label_fields[class_labels] ))逐个参数说清楚。brightness_limit和contrast_limit都是 0.2超过这个值会让深色牌面黑桃、梅花直接糊成一片标注框虽然还在但图像特征已经被破坏。HueSaturationValue的hue_shift_limit只能给 10 左右太大红心和方块的颜色会互相串这是牌面识别里绝对不能容忍的。Rotate的limit180表示旋转范围是 -180 到 180 度扑克牌在桌面上没有正方向旋转必须给满。border_mode0表示旋转后空缺区域填黑边这个要配合后面的裁剪一起用单纯旋转会引入黑色角标。RandomSizedBBoxSafeCrop是我强烈建议加的把图像随机裁剪成 640x640并且保证所有标注框不被切除。这模拟了摄像头拍摄时只看到牌桌局部的场景同时把图像尺寸统一到训练要求。增强应该在线做还是离线做在线增强每次训练迭代都随机变换效果好而且不占磁盘。离线增强只适合一种情况原始图像数量太少连“见过的样本量”都不够。我会把在线增强作为主力离线增强最多做 2 倍不要生成几千张重复感很强的增强图。4.3 类不平衡数字牌和人头牌天生不是均匀分布一副牌 54 张数字牌 40 张人头牌和 A 共 16 张。拍照时如果只随手摊开拍人头牌在画面里出现的频率天然偏低。类别不平衡会让模型对频率高的类别过拟合对频率低的类别漏检。先做一个类别统计把问题量化。用以下脚本看训练集每个类别的实例数量import os from collections import Counter label_dir labels/train counts Counter() for txt_file in os.listdir(label_dir): with open(os.path.join(label_dir, txt_file), r) as f: for line in f: class_id int(line.split()[0]) counts[class_id] 1 for class_id, cnt in sorted(counts.items()): print(fclass {class_id}: {cnt} instances)这个脚本输出每个类别的实例总数。如果发现某些类别数量差到 3 倍以上优先用的手段不是删数据而是对低频率类别做定向增强单独拎出这类牌的样本额外做旋转、透视和亮度增强把它们复制到原来的 2 到 3 倍。注意这里用的是“复制增强”不是真实新样本所以增强程度要克制避免模型过拟合到增强纹理上。另一个手段是给损失函数加类别权重。常见做法是按“类别实例数的倒数归一化”计算权重数量少的类权重高数量多的类权重低。这个方案在 YOLO 系框架里实现起来要改 loss 代码如果项目周期紧可以直接跳过优先用定向增强。4.4 增强的边界不能改变语义有一些非常容易翻车的增强做了等于自杀。首当其冲是水平翻转。扑克牌的数字和字母在水平翻转后会变成镜像比如“3”翻转后长得完全不像 3模型学到的是错误的图案。第二是重度的颜色偏移。方块和红桃的区分靠红色系但如果色相偏移过大红桃会变成紫色甚至蓝色模型会把红桃学成另一种“未知类别”。第三是过度压缩。JPEG 质量降到 50 以下花色符号会被压缩出噪点小尺寸牌面在压缩后几乎不可辨认。还有一个常被忽视的边界透视变换的尺度不要超过 0.1。Perspective(scale0.1)已经能模拟很大的倾斜角了超过这个值牌面会变成不规则四边形标注框和实际牌面的贴合度急剧下降。我见过有人 scale 给到 0.3增强后的图里牌面完全变形模型收敛倒是快但检测的是“变形后的牌”而不是真实视角下的牌。5. 牌面数据集标注的避坑与排查清单5.1 现象框的定位总差几像素模型 mAP 一直在 80 附近上不去最初我用 X-AnyLabeling 标注时没注意图像预览缩放。工具界面里图像显示是缩到屏幕大小的看到的框边缘和实际像素坐标有偏差。标注时把框贴得很紧导出后坐标微差 3 到 5 个像素。模型训练后精度卡住排查了模型结构最后才发现是标注噪声。解决标注界面的缩放比例要固定在 100%或者至少要在 200% 缩放下完成关键点位微调。另外我写了一个小检查脚本算每张牌的标注框宽高如果大量框的宽度都集中在某几个固定值说明标注员在贴边时“默认留白”需要重新校准标注习惯。5.2 现象牌面旋转角度一大检测框就漂这个问题多出在旋转框的角度定义上。同一个四点框有的工具输出角度范围是 -90 到 90 度有的是 0 到 180 度还有的会输出“长边角度”而不是“矩形角度”。如果你训练框架的角度定义和标注工具不一致模型对旋转目标的定位就会周期性偏移特别是 90 度、45 度这些特殊角度位置误差特别明显。解决转换脚本里强制统一我一般把标注输出为四点坐标作为中间格式再根据训练框架要求转成角度格式时单独写一个函数做范围映射。这个函数建议单测用十个已知旋转角度的矩形验证转换前后 IoU 大于 0.95。5.3 现象模型把人物剪影或者牌背也当成牌这种情况是负样本缺失导致的。牌面目标识别数据集的标注只标了“牌面”的正样本但拍摄画面里出现的牌背、手部、筹码倒影等物体模型没有见过对应的“非目标”样本它就会强行把相似纹理归类为正样本。解决采集时专门拍一批“空桌面”“手持牌”“牌背朝上”的图片不给它们做任何标注作为背景负样本放进训练集。这类图片不需要太多占训练集总量的 10% 到 15% 就够。如果你的检测框架支持自定义背景图片也可以直接作为额外训练数据喂进去。5.4 现象不同标注员对“遮挡多少算遮挡”判断不一致多人标注时有人把被遮挡的牌标全框有人只标可见部分还有人干脆跳过遮挡严重的牌。这种不一致是数据集质量的隐形杀手。模型面对真实牌桌上的重叠牌时输出框会摇摆不定。解决写一个明确的标注规范只要牌的可见面积超过 30%就标注完整矩形框包括被遮挡部分的延展低于 30% 则不标注。并且要求每张图至少要保留一个完全可见的参考目标方便镜头畸变检查。规范写清楚后抽检复核才能有统一的判断标准。复核时算两个标注结果之间的 IoU低于 0.8 就退回重标。5.5 现象数据集总量看着很大训练时某些类一个样本都见不到很多人只统计图片总量不统计每个类别的实例数。我碰到过一次训练集 6000 张图看起来非常充足但梅花 9 总共只出现了 9 次模型对梅花 9 的召回率低到不到 20%。最后翻拍摄日志才发现那次采集的牌局里梅花 9 几乎没被发出来过。解决每次标注批次完成跑一遍 4.3 里的统计脚本把类别分布表打出来哪个类别低于 100 个实例就立刻补拍或定向增强不要攒到最后统一处理。6. 验证数据集的最后一步用小模型快速体检别直接上大网络数据集标完别急着上大模型。我的习惯是先拿一个最小的检测模型架构跑 50 到 100 个 epoch然后看两个指标mAP0.5 和类别混淆矩阵。mAP0.5 是牌面这种小目标最合适的评估指标比 mAP0.5:0.95 直观得多因为牌面识别不需要像素级精准框考得差不多就能用。小模型跑得快迭代成本低适合用来做数据集体检。如果 mAP0.5 低于 0.85先不要调模型结构回头查数据和标注。混淆矩阵能直接告诉你哪些类别互相混淆——最常见的混类是红桃和方块、黑桃和梅花因为形状完全相同只有颜色不同。出现这种混淆第一反应是白天采集时光线不足导致红色通道和蓝色通道的区分度下降。最后我还会做一次抽检式二次标注随机挑 20 张图让同一个标注员隔一天重新标注计算新旧标注的 IoU。平均 IoU 低于 0.75说明标注规范执行不稳定返回去重新校准高于 0.85这份数据集基本可以放心交付给训练流程。这个“二次标注验证”是我接手任何数据集后都会做的第一件事它能用很短的时间暴露标注质量问题避免你把两周时间浪费在一个有系统性偏差的数据集上。我现在每次标注完还会顺手做一个动作把所有标注失败的图片截图单独归档这些“错例”用来做训练后的回归测试防止模型更新后旧问题复现。这个习惯帮我少踩了很多次“模型这版好了下一版又退化”的坑。希望帮到你。本文还有配套的精品资源点击获取
返回列表