简介:面向扑克牌牌面识别场景,这套数据集包含363张扑克牌图像及配套的363个XML标注文件,覆盖queen、ten、nine、king、jack、ace共6种常见牌面类别,适合目标检测初学者用于数据准备、标注格式解析、模型训练与验证等完整流程练习。压缩包共726个文件,整体大小约36.62MB,JPG图像与XML标签一一对应,体积轻量,可在普通显卡上快速完成多轮训练迭代。XML文件由LabelImg标注工具生成,采用常见的VOC风格字段,能帮助学习者直观理解目标框坐标、类别名称等标注信息的组织方式。已有395人学习/下载,资源结构紧凑,既可作为目标检测课程的配套练习数据,也能为牌桌识别、棋牌类小游戏等轻量级项目提供现成的数据集基础。数据量适中,既适合作为标注格式的入门样例,也便于在训练过程中观察不同扑克牌类别间的特征差异,降低实验门槛。
1. 标注扑克牌目标识别数据集:为什么你花在调模型上的时间,都该花在数据上
做扑克牌识别的人,十有八九是栽在数据上:要么是网上找的扑克牌图片风格太“干净”,换到真实牌桌就失灵;要么是拿着LabelImg一张张点,点了一下午才标了两百张,手腕先废了。我做过几个牌类识别的小项目,最深的体会是:目标识别模型本身没什么神秘的,YOLO一类的开源模型已经非常成熟,真正决定项目成败的,是喂给它的数据集够不够“真实”、够不够“贴脸”。
这篇笔记就是写给准备自己做扑克牌目标识别数据集的人——不管是做棋牌室智能监控、机器人发牌、牌面识别小工具,还是AR互动游戏。我会把从任务定义、数据采集、标注工具选型、格式转换,到用YOLOv8训练自己的扑克牌数据集,再到避坑这整条路径,按我实际做过的方案讲清楚。新手的可以从零跟着走,熟手可以直接跳去避坑章节和最后的自动标注技巧。
2. 先把任务定义讲清楚:你要识别的是“牌面点数”还是“整张牌”
很多人在标注扑克牌数据集之前,根本没想过任务边界,结果标到一半才后悔。扑克牌识别看起来简单,但“识别一张牌”和“识别牌的点数花色”是两种不同的任务,数据集的标注规格完全不同。
2.1 52类目标还是13类点数:这个决定影响后面所有工作
如果你的目标是“识别出这是黑桃A”,那标准做法是把每张牌定义为一个独立类别,总共52类:黑桃A是类别0、红桃A是类别1……以此类推。这样做的好处是模型输出直接就是牌面,不需要二次判断。缺点是52个类别的样本要覆盖齐全,尤其是J、Q、K、A这种在随机发牌场景里出现频率不算低的牌,每个类别都得凑够足够的样本量,不然模型会偏向训练样本多的类别。
另一种做法是只识别点数(2到A共13类),花色另行处理或者干脆不处理。这种方案适合“只用知道牌面大小”的场景,比如某些棋牌游戏的自动记牌工具。标注工作量小很多,但后续要判断花色时,还得再多接一步分类网络或者手工看。
我一般建议,只要不是特别缺样本,直接上52类。因为扑克牌识别这个场景,类别之间的混淆是可以通过数据增强和合理标注来压下去的,但如果你先标了13类,后面想补成52类,得把之前的标注全部返工,那才是真的血亏。另外要注意的是,牌背(背面图案)要不要单独一个类别。如果你的应用场景里摄像头能看到牌背(比如监控拍到桌面上的扣牌),不加一个“back”类的话,模型会把它强行归到某一类牌面里,那推理结果就是乱套的。
2.2 扑克牌数据集的三个来源:网络公开集、自采、合成数据
明确了类别定义,接下来是数据从哪来。这个领域没有一套官方通用的“标准扑克牌数据集”,大家用的都是自己拼的。常见做法有三个来源,我分别说下利弊。
| 来源 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 网络公开数据集(Roboflow Universe、Kaggle上的扑克牌图片) | 量大、类别全、已经标注好 | 背景单一(多为白底/纯色桌面)、光照理想、几乎没有遮挡 | 先跑通pipeline,做预训练底料 |
| 自己拍摄采集 | 和实际部署场景一致、能覆盖反光/遮挡/透视变形 | 耗时、需要做标注、样本不均衡 | 最终模型精度不够时的补充数据 |
| 合成数据(代码生成/3D渲染) | 能精确控制角度、光照、牌面组合、批量生成无标注成本 | 和真实照片存在域差距,单独靠它训练会翻车 | 弥补稀有牌型、极端角度的空缺 |
我的建议很直接:先用网络公开的标注数据集把模型基线跑起来,验证标注流程和训练pipeline没毛病,然后再花力气采集真实场景数据。不要一上来就自己拿手机拍几千张,拍的时候很爽,标的时候想哭。
2.3 采集真实数据的三类姿势:贴脸近景、牌桌俯拍、握牌手势
自己采集数据的时候,别傻乎乎地只拍一种角度。扑克牌识别最常见的实际部署场景是监控摄像头俯拍桌面,但也有手持牌、发牌机出牌口、赌桌上斜视角这些情况。我一般会按三组采集,比例大约5:3:2。
第一类是贴脸近景:手机镜头距离牌面20到40厘米,正对拍。这类数据用来保证类别特征清晰,模型起码得认得清黑桃和梅花。第二类是牌桌俯拍:模拟实际监控视角,距离一到两米,桌上会同时出现多张牌、牌与牌之间有重叠遮挡。第三类是握牌手势:手拿着牌的时候只露出一部分牌面,这类数据难度最大,但恰恰是实际使用中最常见的情况。
每次采集的时候,刻意制造不同的光照:白天自然光、晚上吸顶灯、桌面有反光的亚克力材质、甚至略微逆光。扑克牌识别模型在训练集里见过多少种光线,推理时就能扛住多少种变化,这是纯靠数据增强补不出来的。
3. 用标注工具做数据标注:工具选型与格式转换的完整流程
数据采集完之后,进入标注阶段。这个环节是“标注扑克牌目标识别数据集”的核心工作流,也是最容易出问题的环节。目标检测常用标注工具其实就那几样,选对了能省一半时间。
3.1 标注工具选型:LabelImg、CVAT、X-AnyLabeling 对比
如果是个人项目,一两千张图,LabelImg就够用了。它是个老牌工具,界面简陋但稳定,安装简单,用pip就能装:pip install labelimg。打开后设置好图片目录和标注保存目录,用Pascal VOC格式(XML文件)保存,方便后续转格式。缺点是一次只能一个人标,且没有任何自动辅助功能,标扑克牌这种重复性高的任务容易疲劳。
如果是团队协作或者要标几千张,强烈建议上CVAT。它是个开源的在线标注平台,用Docker部署一套,几个人同时标,管理员能分配任务、检查标注质量,还内置了自动标注(interpolation)和AI辅助标注插件。部署命令大概是:
git clone https://github.com/cvat-ai/cvat cd cvat docker compose up -d部署起来之后,浏览器打开就能用。CVAT的优势是有一套完整的标注流程:创建任务、上传图片、分配标注员、质检、导出。对于上百个任务、多人协作时,这套流程能省掉大量沟通成本。
X-AnyLabeling是近年比较流行的自动标注工具,它的思路是用一个预训练模型先自动框出目标,再由人工修正。对扑克牌这种类别清晰的任务,效果很好,能显著减少手工框选。装好后加载一个通用的检测模型(比如YOLOv8s),先用它自动跑一遍,然后人工微调。这个“先自动后手动”的工作流,就是我后面第6章要讲的半自动标注雏形。
3.2 把Pascal VOC标注转成YOLO格式:转换脚本与路径坑
不管用哪个工具,最后训练YOLO时都需要YOLO格式的标注:每张图片对应一个同名TXT文件,每行是一个目标:类别ID 中心点x 中心点y 宽度 高度,四个坐标值都归一化到0到1之间。
LabelImg默认保存的Pascal VOC格式(XML)不能直接喂给YOLO,写个转换脚本是第一步。下面这个脚本我每次做目标检测数据集都会用到,逻辑很简单,但很可靠:
import xml.etree.ElementTree as ET import os from pathlib import Path def voc_to_yolo(xml_path, out_dir, class_dict): """ 把单张图片的Pascal VOC标注转为YOLO格式TXT class_dict: {"black_ace": 0, "red_king": 1, ...} """ tree = ET.parse(xml_path) root = tree.getroot() # 读取图片的宽高,用于归一化 size = root.find("size") img_w = int(size.find("width").text) img_h = int(size.find("height").text) txt_path = Path(out_dir) / (Path(xml_path).stem + ".txt") lines = [] for obj in root.findall("object"): name = obj.find("name").text.strip() if name not in class_dict: # 没在类别表里的目标直接跳过,避免脏数据 continue class_id = class_dict[name] bndbox = obj.find("bndbox") xmin = float(bndbox.find("xmin").text) ymin = float(bndbox.find("ymin").text) xmax = float(bndbox.find("xmax").text) ymax = float(bndbox.find("ymax").text) # 计算中心点坐标和宽高,并归一化 x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h width = (xmax - xmin) / img_w height = (ymax - ymin) / img_h # 防止标注框越界造成的坐标小于0或大于1 x_center = min(max(x_center, 0.0), 1.0) y_center = min(max(y_center, 0.0), 1.0) width = min(max(width, 0.0), 1.0) height = min(max(height, 0.0), 1.0) lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") with open(txt_path, "w", encoding="utf-8") as f: f.write("\n".join(lines)) # 使用示例:遍历所有XML,逐个转换 for xml_file in Path("annotations/voc").glob("*.xml"): voc_to_yolo(xml_file, "annotations/yolo", CLASS_DICT)逻辑说明:脚本的核心是遍历XML里的每个object节点,把边界框坐标从绝对值转为归一化值。这里有两个容易被忽视的细节:一是size节点里的宽高必须是原图尺寸,如果图片被预处理缩放过了,那这里一定要用缩放后的尺寸,不然坐标会错位;二是越界钳制(clamp)不能省,标注工具偶尔会框出图片边缘,如果坐标是负数或者大于1,训练时YOLO会直接报错或者忽略该目标。
标注之前的class_dict建议用代码生成,而不要手动敲。你可以在标注前先列一个52类的清单,每个类一个ID,保存成YAML或JSON文件,后续不管是转换格式还是写数据集配置,都从这份清单读取,避免中间某个类名拼写不一致导致类别错乱。
3.3 标注质量检查脚本:混合显示与异常兜底
转换完格式之后,我强烈建议做一次可视化检查。很多新手转换完就直接训练,结果模型学了个寂寞——根本不收敛,跑到发现标注框全画歪了。其实只要花几分钟看一眼就全暴露了。
import cv2 import numpy as np def draw_yolo_boxes(img_path, txt_path, class_names): """把YOLO格式的标注画回原图,检查是否有错位""" img = cv2.imread(img_path) with open(txt_path, "r", encoding="utf-8") as f: for line in f: parts = line.strip().split() cls_id = int(parts[0]) x_center, y_center, w, h = map(float, parts[1:]) # 反归一化回像素坐标 img_h, img_w = img.shape[:2] xmin = int((x_center - w / 2) * img_w) ymin = int((y_center - h / 2) * img_h) xmax = int((x_center + w / 2) * img_w) ymax = int((y_center + h / 2) * img_h) color = (0, 255, 0) cv2.rectangle(img, (xmin, ymin), (xmax, ymax), color, 2) cv2.putText(img, class_names[cls_id], (xmin, ymin - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1) return img # 随机抽20张图检查 for img_path in random.sample(all_imgs, 20): img = draw_yolo_boxes(img_path, get_txt_path(img_path), CLASS_NAMES) cv2.imshow("check", img) cv2.waitKey(0)逻辑说明:这段代码把TXT里的归一化坐标还原成像素坐标,直接把边界框和类名画在原图上。检查时重点看三点:框有没有偏到牌面以外、类别名和牌面内容是否对得上、有没有一张牌被框了两次。
最后,做完格式转换后,花10分钟编一个脚本检查一下数据完整性:图不能为空、标签不能为空、类别ID不能超出范围。这些基础校验能避免后面训练到一半才崩溃的低级问题。
3.4 数据划分与目录组织:训练集、验证集、测试集
YOLO训练需要标准的目录结构:images/train、images/val、labels/train、labels/val,测试集通常另放。划分比例我一般用8:1:1或7:2:1。但这里有个对扑克牌来说特别重要的点:要按“场景”而不是按“单个文件”划分。
什么叫按场景划分?如果你在一个牌局视频里抽帧,提取了200张高度相似的画面,那这200张图必须全部放进同一个数据集(训练集或验证集),而不能随机打散。不然模型在训练时已经见过几乎一样的画面,验证时又出现,mAP虚高,一到真实部署就露馅。我用一个简单的hash函数,对每张图文件名前缀做桶分配,就能保证同一场景的图不会跨集。
# 目录结构示例 datasets/ ├── cards_dataset/ │ ├── images/ │ │ ├── train/ │ │ │ ├── scene01_001.jpg │ │ │ ├── scene01_002.jpg │ │ ├── val/ │ │ └── test/ │ ├── labels/ │ │ ├── train/ │ │ ├── val/ │ │ └── test/ │ └── cards.yaml参数说明:cards.yaml就是后面训练时data参数指向的配置文件,里面写清train和val路径、类别数nc: 52、类别名names列表,这三者必须和标注时的class_dict完全一致。
4. 扑克牌标注避坑指南:五个翻车现场和事后补救方案
做数据标注工作这么久,踩过的坑比调过的参都多。下面这几条是扑克牌目标识别数据集特有的高频问题,每个都是“现象→原因→解决”的标准格式,希望帮你省下不必要的试错时间。
4.1 现象:模型对半张牌完全无感,日志显示漏检率全在这
原因:标注员习惯性地只标注完整的、能一眼认出点数的牌,桌面上露了一半的牌、互相遮挡的牌全被跳过。模型在训练时从未见过“残缺”的牌,自然不认识半张牌。
解决:标注规范里明确写一条——只要一张牌有足够识别特征的可见区域(哪怕只有牌角露出来),就给它框上。框的范围尽量贴合可见区域。不要把看不见的部分也框进去,不然类别特征就被混进了背景。
4.2 现象:训练中途报错,读入图片为空,数据加载直接崩
原因:Windows系统下图片路径或文件名是中文,OpenCV读取时直接返回None,YOLO训练脚本没做空图防护就崩了。另外,文件名里带空格也会在train.py解析路径时出现问题。
解决:干脆把整个数据集的路径和文件名全部改成英文加下划线。我一般在采集完的当天,用一个批处理脚本把文件名统一规范成scene_日期_序号.jpg的格式,省得后面改来改去。
4.3 现象:训练loss正常下降,但推理时把牌背或桌面纹理识别成某张牌
原因:标注时只标了牌面正面的目标,没单独处理牌背和背景干扰物,模型强行把所有东西都往52个类别上归类,造成了误检。
解决:在标注规范里增加一条规则:正面朝上的牌标具体类别;如果场景中会出现牌背,单独增加一个“back”类。预训练模型中常见的目标类别,如人物、杯子,遇到时直接忽略,不要标注。这能让模型的注意力集中在牌面上,而非背景杂物。
4.4 现象:J/Q/K等牌面的识别精度远低于数字牌
原因:牌面类别不平衡。随机发牌得到的牌面数据,A到K按点数均匀分布还好说,但自己采集时往往大量拍摄“顺子”或“同花”牌型,导致某个点数曝光率极高,其他点数样本稀缺,模型对样本多的类别产生了强烈的偏好。
解决:采集阶段就可以做一次类别分布日志,实时统计每个类别标了多少张,专门补拍数量少的类别。实在补不了的,用合成数据生成一批特定类别的标牌,混合进数据集。
4.5 现象:牌面是斜着摆的,标注框怎么画都不准,训练效果时好时坏
原因:扑克牌在桌面上经常是旋转一定角度的(比如发牌洗牌时),而YOLO只支持水平边界框,旋转框即使是标注工具支持斜框,也会在导回时强行变成带大量背景的正矩形。
解决:如果倾斜角度不大(比如30度以内),直接画紧贴牌面的水平矩形,保证框内背景占比小;如果倾斜角度大,先手动旋转图片让牌面正过来再标注,但这样数据量小不建议;对旋转目标更友好的做法是用带旋转角度的检测算法,比如YOLOv8-OBB或者MMRotate。扑克牌检测用OBB能提升不少精度,但部署时对硬件和推理库要求也更高。初版建议先用水平框做,精度不够再上OBB。
5. 用YOLOv8训练自己的扑克牌数据集:配置、命令与参数
数据准备好了,下一步就是训练。这一段我用YOLOv8来写,因为它安装简单、命令行友好、对新手极其友好,而且自带的数据增强(马赛克、HSV扰动)对扑克牌这种纹理丰富的目标特别有效。用yolov8训练自己的数据集,是现在做目标识别的标准起步方案。
5.1 数据集配置文件与目录检查
在训练之前,把数据集配置写好。一个最简的cards.yaml长这样:
# 数据集配置文件:路径必须用绝对路径或相对于运行位置的路径 path: D:/datasets/cards_dataset # 数据集根目录 train: images/train val: images/val # 类别定义:顺序必须与标注时的class_dict一致 nc: 52 names: 0: ace_of_spades 1: two_of_spades 2: three_of_spades # ... 一直到 51: king_of_hearts参数说明:path是数据集根目录,下面train和val写相对于根目录的子路径。names列表的顺序绝对不能变,训练和推理时模型输出的类别ID就是按这个顺序解释的。强烈建议在训练前先跑一次验证代码,把cards.yaml里的每个类别对应的图片数、标注数打印出来,确认没有类别为空。
5.2 训练命令与必调参数
训练命令相当直观,但参数的选择值得认真对待。
# 用yolov8s预训练权重做迁移学习 yolo detect train data=cards.yaml model=yolov8s.pt epochs=200 imgsz=640 batch=16 patience=30 device=0逻辑说明:model=yolov8s.pt会下载官方在COCO数据集上的预训练权重,以此为起点微调。虽然COCO里没有扑克牌类别,但模型已经具备很强的低层视觉特征(边缘、纹理、角点),迁移学习比从头训练收敛快得多,最终精度也更高。
参数调优方面,对扑克牌任务,最影响结果的三个参数是imgsz、batch、patience。
imgsz是输入网络的图像尺寸。扑克牌的目标是“文字细节型”的小目标——牌角的数字和花色只有几十个像素。如果你用默认的imgsz=640,这些细节会缩得看不清,模型很难区分6和9、Q和O这类易混淆类别。我建议至少用imgsz=960;显存足够的话直接上1280。代价是训练时间变长,但对精度提升非常明显。
batch受限于显存大小。显存不够时优先把imgsz降一点,也别把batch降得太低,因为batch太小会导致BN层统计不稳定,loss容易震荡。
patience=30是早停参数,连续30个epoch验证集mAP没有提升就停止训练。这个参数能帮你省时间,但不要设太小,不然容易在精度正要爬升的时候被提前掐掉。
5.3 模型评估:mAP、混淆矩阵怎么解读,一眼定位烂点
训练完成后,模型会自动保存在runs/detect/train/weights/best.pt。用下面的命令在验证集上验证:
yolo detect val model=runs/detect/train/weights/best.pt data=cards.yaml验证完会生成数据集指标文件,包含mAP50、mAP50-95、精确率、召回率。初始化看mAP50就行,但真正暴露问题的是混淆矩阵和验证图片上的预测可视化。
具体来说,你要在混淆矩阵里找“类别对”。扑克牌识别里常见的混淆有两类:一是数字之间的混淆,比如6和9、3和8;二是把背景误检成某种牌。如果这类混淆集中在某几个类别上,说明这些类别的训练样本不足或特征相似度过高,直接去补一类样本针对性增强,不要在全局超参上瞎折腾。
验证集的可视化图片也要翻一翻。重点看不带标签的原始图上模型“多框了”或“漏框了”哪些位置。如果漏检都发生在牌桌边缘或手部遮挡处,那说明训练数据里这类场景的比例还不够,这样的分析比盯着精确率数字有用得多。
6. 自动标注迭代法:用小成本把数据集扩充一倍
等你有了第一版能用的模型,就可以进入进阶工作流——自动标注迭代。这是我在后面几个项目里最依赖的技巧:拿已经训练好的模型去做预标注,然后人工只修正错误,不回画所有框。
具体流程是这样的:先用现有的模型,对一批未标注的新图片跑推理,生成预测结果;再把预测结果转成标注格式作为初始标签;然后人工用CVAT或X-AnyLabeling打开这批预标注数据,只修改有问题的框,删除误检,补齐漏检;最后把修正后的数据合并进训练集,重新训练。每轮这样迭代,数据集的规模能轻松翻倍,而且标注效率比纯手工高出四五倍。
需要留意的是,自动标注有个隐蔽的问题:模型会“自我强化”。如果第一版模型对某种牌型有系统性偏差(比如总是漏检角落里的牌),自动标注生成的标签也会漏掉这些牌,人工作业时又容易在检查阶段漏看几帧,错误就进入下一次迭代。所以每轮合并数据前,我要求自己在预标注的画面上把置信度阈值调得特别低,宁多勿漏,让模型把所有疑似目标都框出来,由人工删减,而不是让模型挑精的,那样会漏掉拿不准的新样本。
最后再分享一个习惯:每次做完一批标注,我都会在训练前固定跑一次第3章那个可视化检查脚本,随机抽三五十张图肉眼过一遍。这个习惯救过我很多次——有一次标注员把整批图的类别ID写错了一位,模型训练完所有牌面都被预测成相邻类别,光调参是永远查不出这种问题的。像素级的认真,要花在数据准备上,才是最有价值的部分。这个方向真正值得投入的,正是这层细功夫,节点掌握好了,后续的识别、检测、统计、部署全都是水到渠成的事。希望这些经验帮到你。
本文还有配套的精品资源,点击获取