
简介面向计算机视觉学习者与目标检测开发者的扑克牌识别数据集内容覆盖A-K共13类牌面字母适用于手牌识别、牌面等级检测、棋牌游戏自动化测试等场景可直接用于YOLOv11模型训练与效果验证。包内共2000个文件其中1850个txt标注文件、149张jpg原始图像和1个yaml配置文件整体约109.76MBtxt文件保存YOLO格式标注yaml包含类别与路径配置解压后修改yaml中的路径即可开始训练。据描述该数据集正确识别率可达98.7%能省去从零采集图像与手工标注的时间适合快速搭建扑克牌识别demo也可作为迁移学习、模型调参或课堂演示的基准数据同时支持自行增补图片做进一步验证。目前已有241人学习下载对入门YOLO检测或构建卡牌识别项目的开发者是一份高性价比的实操数据资源。1. 扑克牌识别数据集为什么一张纸牌要单独做一套标注先给结论扑克牌识别数据集不是新概念但它是验证目标检测流程的绝佳样板。这套数据里有 1850 张原始牌面图覆盖 A、2、3 一直到 K 共 13 个字母类标注按 YOLOv11 的 txt 格式给好正确识别率做到了 98.7%。为什么一张纸牌需要专门的数据集因为牌面有角标、有花色、有反光A 和 4、2 和 3 这类形近字在低分辨率下照样认错。对想训练自己的数据集、又不想一上来就碰 COCO 那种大工程的工程师来说这套数据收敛快、标注干净、踩坑点又齐全正好用来把整套 YOLO 流程走通。2. 先把数据看清楚13 个字母类是怎么从 1850 张图里拆出来的2.1 扑克牌识别和车牌、行人检测的差别在哪里做这件事之前我建议你先想明白“它和别的数据集有什么不一样”。CCPD 车牌数据集是单类识别加字符序列CrowdHuman 是密集行人检测而扑克牌识别是典型的多类小目标分类每张图上目标不多但类别有 13 个而且类间差异极小。训练集规模只有 1850 张图和 ImageNet-1K 那种百万级量级差得很远所以你基本不可能靠堆数据量取胜只能靠标注质量、数据增强和合理的模型尺寸。扑克牌还有自己的独有麻烦牌面是塑料材质光线照上去会有高光黑色字母在过曝区域边缘会丢细节大部分原始图是近景正拍但真实场景里牌经常是斜着放的再加上角标区域本身很小在 640 分辨率下可能只有 20×30 像素。这些因素叠加起来决定了它的难度不在“找不找得到目标”而在“能不能把相似字形分开”。2.2 一张扑克牌里藏着两个检测目标拿到原始图后第一步不是直接写训练脚本而是先看标注框到底画在哪里。常见做法是把每张牌的左上角角标区域作为检测目标因为角标是牌面最稳定、最不容易被遮挡的信息区。很多时候一张牌面上其实有两个角标但数据集设计者选择只标一个避免同一个目标出现两次给训练带来歧义。这里有一个关键设计决策数据集只识别 A-K 这 13 个字母不区分花色。原因是花色只有四种靠颜色和形状就能分开而字母是 13 类才是真正的分类压力。如果你想把任务扩展成“识别黑桃 A”和“红心 A”类别数会变成 52需要的数据量和训练时间完全不是一个量级。当前数据集选择了更聚焦的路径这也是它能做到 98.7% 的原因之一。2.3 YOLOv11 格式标注到底长什么样YOLOv11 沿用 YOLOv5/v8 的标注协议每张图对应一个同名 txt 文件每一行是一个目标格式是class_id x_center y_center width height。坐标全部用图片宽高归一化到 0~1 之间类别从 0 开始计数A02132…K12。3 0.5 0.25 0.125 0.125这一行表示类别 3对应牌面 4目标中心在图片坐标 (320, 120)框宽 80、高 60。对于一张 640×480 的输入图换算关系是x_center320/6400.5y_center120/4800.25width80/6400.125height60/4800.125。整个坐标系是像素坐标除以图片宽高不是左上角右下角形式。最容易翻车的点就在这里。如果你之前接触过 VOC 格式的标注里面记录的是x_min y_min x_max y_max直接拿来做 YOLO 训练loss 会飘到天上模型根本不收敛。从 VOC 转 YOLO 必须执行这一步先算目标框的宽高再算中心点最后做归一化。别偷懒跳过这是 1850 张图能不能直接用的关键。2.4 训练集和验证集怎么划1850 张图不大划分比例我建议 9:1 或者 8:2。但不要只按文件名的随机数切。扑克牌数据里同一张牌往往在不同光照、不同角度下拍了很多次如果验证集混入了和训练集来自同一张牌面的相似图mAP 会虚高现场部署立刻现原形。我一般会先做一次随机划分把流程跑通确认代码没问题后再按牌面唯一标识做严格划分。如果数据集本身没有提供牌面 ID那就按图片相似度或者拍摄批次手动分。下面这段脚本可以完成基础划分import os import random import shutil src_images images src_labels labels train_img_dir images/train val_img_dir images/val names [os.path.splitext(f)[0] for f in os.listdir(src_images) if f.lower().endswith(.jpg)] random.seed(42) random.shuffle(names) split int(len(names) * 0.9) for i, name in enumerate(names): src_img os.path.join(src_images, name .jpg) src_lbl os.path.join(src_labels, name .txt) if i split: shutil.copy(src_img, train_img_dir) shutil.copy(src_lbl, os.path.join(labels/train, name .txt)) else: shutil.copy(src_img, val_img_dir) shutil.copy(src_lbl, os.path.join(labels/val, name .txt))脚本逻辑很直接先收集所有图片主文件名固定随机种子打乱顺序前 90% 拷到 train后 10% 拷到 val。注意这里的 copy 而不是 move保留原始数据后面发现问题还有后悔药。random.seed(42)的作用是保证每次运行结果一致方便复现。13 类每类 140 张左右val 如果只有 185 张摊到每个类只有 14 张指标波动会很大。这种情况下我倾向把 val 比例降到 10% 以下甚至用 K 折交叉验证来评估而不是守着一次划分的数字不放。3. 用 YOLOv11 把 13 类牌面训起来配置、训练与日志3.1 环境准备避免依赖冲突YOLOv11 是 Ultralytics 生态里的新版本安装方式和 YOLOv5/v8 训练自己的数据集几乎一样。建议用 Python 3.9~3.11先建虚拟环境再装 PyTorch最后装 ultralytics。顺序反了容易把 torch 覆盖成 CPU 版这是最常遇到的坑。conda create -n yolo11 python3.10 -y conda activate yolo11 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121 pip install ultralytics python -c from ultralytics import YOLO; model YOLO(yolo11n.pt); print(model.task)如果最后一行打印出detect说明环境通了。这里我固定写 CUDA 12.1 的 torch 版本你机器上的 CUDA 版本如果不同去 PyTorch 官网换对应的 index-url 就行。装完后再装 ultralytics它会把 opencv-python、numpy、pandas 这些依赖一并带上。NumPy 版本冲突是最恶心的症状是训练时报_ARRAY_API not found基本只能重建环境解决。3.2 数据集目录组织规范Ultralytics 通过 data.yaml 定位图片和标签路径。标准的目录结构是这样的poker_dataset/ ├── images/ │ ├── train/ │ │ ├── 001.jpg │ │ └── ... │ └── val/ │ ├── 002.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── 001.txt │ │ └── ... │ └── val/ │ ├── 002.txt │ └── ... └── poker.yaml如果数据集打包时已经是这个结构直接进入下一步如果压缩包里是单一 images 目录加一个 labels 目录你自己用脚本划分。要注意必须保证图片和 txt 的主文件名完全一致Ultralytics 是靠文件名匹配标签的后缀不同或者大小写不一致都会导致“标签文件缺失”的告警。常见错误是有人把标签文件直接放在 images 目录下训练时它会被当成图片读进去。还有人在 Windows 上解压后文件名带上了.jpg.txt这种双层后缀检测目录里的文件清单一眼就能发现。多花两分钟确认文件数对得上比训练完才发现数据没用要省事得多。3.3 写数据集配置文件 poker.yamlpath: /absolute/path/to/poker_dataset train: images/train val: images/val nc: 13 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: Kpath建议写绝对路径尤其是第一次跑的时候相对路径的解析规则在不同版本里有点玄学。names里数字类名必须加引号比如2和3否则 YAML 解析器会把它当成整数后面类别名和标签对不上画出来的结果全是乱的。还有一件事值得提醒A-K 的类别顺序一旦确定训练出来的模型就固定了。推理阶段读取model.names时也是按这个顺序映射所以最好不要在中途调整类别编号否则已训练好的权重全部作废。3.4 训练命令与关键参数yolo detect train \ modelyolo11n.pt \ datapoker.yaml \ imgsz640 \ epochs150 \ batch16 \ device0 \ projectpoker_runs \ nameexp1参数逐个说。modelyolo11n.pt表示从预训练权重开始迁移学习比随机初始化快很多COCO 上 learned 的底层纹理特征对牌面也有用。imgsz640是输入分辨率扑克牌角标小如果显存允许建议改到 960后面第 6 章会细说。epochs150对 1850 张图足够不需要 300 轮训练后期 mAP 曲线基本是平的。batch16要看显存8G 显卡降到 816G 以上可以跑到 16 或 32。训练启动后终端会实时打印每个 epoch 的 box_loss、cls_loss、mAP50 等指标。loss 曲线下降但 mAP 一直上不去大概率是标注坐标有问题回第 2.3 节检查。如果 loss 直接跳出 NaN八成是 batch 太大导致学习率失控把batch减半或者把初始学习率从默认值调低比如lr00.001。3.5 训练日志和评估指标怎么读训练结束后的产物在poker_runs/exp1/下weights 里有两个文件best.pt和last.pt。best.pt是根据验证集指标选出来的最优权重部署时优先用它。Ultralytics 会自动生成 confusion_matrix.png、results.csv 和一堆曲线图。不要只看 mAP50扑克牌这种形近字场景重点看每类的混淆矩阵2 和 3、A 和 4、Q 和 O 如果互相误判率高说明特征没学到。补数据比调参更有效尤其是针对具体失败样本补充不同光照、不同旋转角度的牌面图。数据集声称 98.7% 的正确率指的是在同一测试条件下的结果你换到自己的现场环境一定会掉点这个是正常规律不是模型坏了。4. 推理与部署把 13 个字母类用到新牌上4.1 单张图推理与置信度调试训练完先跑一张没见过的牌验证手感。YOLO API 做单张推理很简洁from ultralytics import YOLO model YOLO(poker_runs/exp1/weights/best.pt) results model(new_card.jpg, conf0.35, iou0.45, verboseFalse) for r in results: for box in r.boxes: cls int(box.cls[0]) conf float(box.conf[0]) name model.names[cls] print(f{name}: {conf:.3f} at {box.xyxy[0].tolist()})conf是置信度阈值低于这个值的检测框会被丢掉。默认 0.25 对扑克牌来说太低容易把牌面花纹误检成字母我建议先设 0.35 试跑一批图看漏检多还是误检多再调。iou是 NMS 的 IoU 阈值控制重叠框的合并力度扑克牌角标目标小、重叠少0.45 通用调到 0.3 也能用。有个小技巧先不加 conf 参数把全部的框和置信度打印出来然后自己挑一个合适的 cutoff。这样做的好处是能摸清模型在不同姿态下的置信度分布而不是拍脑袋定阈值。4.2 批量推理与结果落盘真实项目里不会只看一张图几十上百张图同时跑的时候建议把结果写成 JSON 或 CSV 而不是人眼盯屏幕。import csv from pathlib import Path model YOLO(poker_runs/exp1/weights/best.pt) results model(list(Path(test_imgs).glob(*.jpg)), conf0.35, iou0.45) with open(poker_preds.csv, w, newline) as f: writer csv.writer(f) writer.writerow([image, class, conf, x0, y0, x1, y1]) for r in results: img_name Path(r.path).name for box in r.boxes: writer.writerow([ img_name, model.names[int(box.cls[0])], round(float(box.conf[0]), 3), *[round(v, 1) for v in box.xyxy[0].tolist()] ])这段代码把每张图的每个检测框展开成一行 CSV。注意r.path在不同版本的 Ultralytics 里可能返回绝对路径或相对路径用Path(r.path).name统一取文件名是最稳的。批量推理时如果显存不够可以把图片目录拆成几个子列表分批跑或者把 batch 参数传进去控制批大小。4.3 导出 ONNX脱离训练环境跑识别如果模型要交出去或者部署到边缘设备PyTorch 权重太重常见做法是转成 ONNX 再用 ONNX Runtime 推理。model YOLO(poker_runs/exp1/weights/best.pt) model.export(formatonnx, imgsz640, simplifyTrue)导出后同目录下会有best.onnx。接下来推理不再依赖 PyTorchimport onnxruntime as ort import numpy as np from PIL import Image sess ort.InferenceSession(best.onnx) input_name sess.get_inputs()[0].name img Image.open(new_card.jpg).resize((640, 640)) img np.array(img, dtypenp.float32) / 255.0 img img.transpose(2, 0, 1)[None] # HWC - 1CHW out sess.run(None, {input_name: img})[0] print(out.shape)这里只展示了输入预处理和后端输出的形状。YOLO 的 ONNX 输出是解码前的原始张量还要做坐标解码和 NMS这一段容易手写出 bug。实用建议是直接用YOLO(best.onnx)继续使用 Ultralytics 的推理接口它在内部封装了后处理既能拿到 ONNX 的部署轻量性又不用自己写解码。提示导出 ONNX 时simplifyTrue会做一些计算图优化能减少冗余节点但不是所有算子都支持简化。如果导出后推理结果形状对不上把 simplify 关掉再试。5. 避坑指南扑克牌识别最常见的 5 个翻车现场5.1 角标被裁掉或旋转导致漏检现象训练集里都是正摆的牌实际测试时牌斜着放或者角标被手指挡住模型直接漏检一堆牌面一个框都出不来。原因数据集的原始图以正拍近景为主旋转和遮挡覆盖不够。模型学到的是“正着的角标”的特征旋转 45 度后特征对比度下降。解决训练时开启旋转增强角度范围至少 ±30°并加入少量遮挡样本。Ultralytics 里可以设置degrees30参数但注意大角度旋转时角标可能会转到图片边界外造成标签框越界解决办法是配合mosaic增强或者训练前用脚本裁剪掉越界框。增强参数不是越大越好角度超过 60 度时牌面本身的语义可能已经反转模型会学歪。5.2 数字 2 和 3、A 和 4 混淆严重现象混淆矩阵中 2 和 3 互相误判比例超过 10%A 被识别成 4 的频率也偏高。原因牌面字体在低分辨率下形近JPEG 压缩产生的锯齿进一步抹掉了笔画细节。2 和 3 的差别就在底部弧线下采样后几个像素的区别很难被卷积核捕捉。解决把输入分辨率从 640 提到 960目标框内像素数接近翻倍分类头能拿到更多有效信息。同时检查标注框是否裁得太紧框边缘贴住字母笔画时数据增强的轻微缩放就会导致笔画被切掉。标注时给字母边缘留 5~10% 的边距能显著降低这类混淆。5.3 反光导致的漏检和误检现象牌面塑料膜反光角标区域过曝成白色模型要么漏检要么把高光边缘的暗色纹理误判成字母。原因扑克牌数据集录制环境的光线比较均匀深色字母在过曝区域边缘高频信息丢失模型只看得到残缺轮廓。解决训练阶段加入亮度扰动、高斯噪声和随机色温偏移模拟不同光照条件。推理阶段可以用直方图均衡化做预处理但前提是训练时也用同样的预处理否则训练和推理的输入分布不一致结果反而更差。这种“域不一致”问题在视觉任务里很常见改预处理一定要同步回训练流程验证。5.4 数据泄露同一副牌的不同照片被拆到 train 和 val现象训练日志里 mAP50 接近 0.99但拿到现场自己拍的牌上正确率只有 80% 左右。原因划分训练集和验证集时按文件名随机切同一张牌在不同光照、不同背景下的多张图被同时分进 train 和 val。模型相当于提前“见过”了验证集里的牌面指标虚高是必然的。解决按牌面唯一标识分组划分而不是按文件名随机切。如果数据集没有提供牌面 ID用感知哈希算法做图像去重把相似度高的图片归到同一组。很多工程师在这一步偷懒结果模型部署后被打回原形。严格划分后指标下降是正常的降多少才是这模型的真实水平。5.5 标注坐标归一化错误现象loss 前几轮下降很慢mAP50 一直在 0.5 以下徘徊训练曲线像锯齿一样乱跳。原因txt 里坐标被写成了像素绝对坐标没有除以图片宽高或者中心点坐标误写成了左上角位置。这类错误用肉眼从单张标注上看不出来。解决训练前写脚本统计所有标签文件检查坐标范围import os labels_dir labels/train for fname in os.listdir(labels_dir): path os.path.join(labels_dir, fname) with open(path) as fp: for line in fp: parts line.split() vals list(map(float, parts[1:])) if max(vals) 1.0 or min(vals) 0.0: print(f{fname}: 坐标越界 {vals})这段逻辑很简单把每个目标的四个数值全部提取出来只要有一个不在 0~1 区间就说明标注格式不对。跑一遍这个检查基本能避免 70% 的训练失败。后续训练之前你应该养成检查数据集的习惯而不是直接丢给模型跑。6. 进阶98.7% 的正确率还能怎么往上推模型训到 98.7% 已经不错但要落地到真实场景三个小技巧值得试。第一训练时把输入分辨率从 640 提到 960 甚至 1280。扑克牌角标在 640 分辨率下只有大约 20×30 像素信息量太少。提升分辨率通常能换来 1~3 个点 mAP 的上涨代价是显存占用翻倍、推理延迟变高。这是性价比最高的改动先做这个。第二引入“角标裁剪 二阶段识别”。常见做法是先用 YOLO 模型的检测头把牌面角标区域裁出来再用一个轻量分类器比如 ResNet18 或 MobileNet对裁剪图做 13 类分类。这样目标检测只负责找“角标在哪”分类压力全部转移到分类头上形近字的区分更稳。架构复杂一点但适合边缘设备部署。第三测试时增强。推理时对同一张图做水平翻转和轻微旋转把多次预测结果投票能压掉一部分置信度波动。Ultralytics 没有内置 TTA 开关我自己写脚本处理对原图和翻转图各跑一次只有两次结果置信度都超过阈值且类别一致才输出。这办法治标不治本但对不稳定光照的现场环境有效。最后说一个我自己的教训新手最容易把时间花在调 loss 上老手会先打开混淆矩阵看两眼再决定改数据还是改模型。扑克牌识别数据集的 98.7% 是它的起点不是你的终点。拿到手先完整复现一遍训练再到自己场景拍 100 张图做校验集用这 100 张图决定要不要补数据、要不要改分辨率。这套流程走完比对着指标硬调一个下午更有价值。希望帮到你。本文还有配套的精品资源点击获取