扑克牌识别数据集与YOLOv11训练实战:从标注到98.7%识别率
简介扑克牌识别数据集覆盖A-K全部牌面字母包含1850张原始图像并已按YOLOv11格式完成标注适合目标检测入门学习、算法验证及扑克牌相关应用开发。数据集采集了不同角度、光线和背景下的牌面图像有助于训练更具鲁棒性的识别模型。资源包共2000个文件其中149张jpg图片提供多样视觉场景1850个txt文件以标准YOLO格式记录类别ID与归一化坐标另有1个yaml文件包含类别名称与路径配置可直接用于模型训练流程。压缩包整体约109.76MB目录结构清晰便于导入主流检测框架。目前已有241人学习下载该数据集标注正确识别率可达98.7%可帮助开发者省去数据采集与标注时间直接用于训练、调优和效果验证也可作为迁移学习或算法对比的可靠基础。1. 扑克牌识别数据集与 yolo v11 格式标注的落地价值在棋牌室的自动计牌、魔术教学里的牌面反馈、甚至桌游辅助裁判这类场景里扑克牌识别一直是个“看起来简单、做起来烦”的任务。烦在两点一是牌面纹理细A 和 4、6 和 9 这类字符在低分辨率下极易混淆二是公开数据集少多数人只能自己用手机拍一两百张图硬训练效果自然不稳定。标题里这个扑克牌识别数据集——覆盖 A-K 全部字母、1850 张原始图、98.7% 识别率、yolo v11 格式标注——恰好把这三块短板一次补齐。对正在做 OCR 类目标检测、或者想用 yolo v11 跑通一个完整数据闭环的工程师来说它既是一个可直接训练的数据源也是一个研究“小数据集如何压出高精度”的现成样本。下面从数据组织、标注校验、训练参数到调优手段逐层拆开讲。2. 从原始图到 yolo v11 训练集目录结构与数据构建2.1 扑克牌数据集的目录设计images 与 labels 的对应关系yolo v11 训练时读取的是“图片 同名 txt”的配对结构。拿到这个扑克牌识别数据集后第一步不是急着训练而是确认目录是否满足 yolo v11 的默认约定poker_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ └── labels/ ├── train/ ├── val/ └── test/images/train 里放原始 JPG 或 PNGlabels/train 里放同名 txt。例如 images/train/IMG_0042.jpg 对应 labels/train/IMG_0042.txt。每张图的 txt 中一行代表一个牌面目标格式为class_id x_center y_center width height坐标是相对图片宽高的归一化值。1850 张原始图若按 8:1:1 划分大约 1480 张训练、185 张验证、185 张测试。对 yolo v11 来说这个规模不小但也没到“随便跑跑就过拟合”的程度。2.2 类别映射A-K 共 13 类的 id 分配策略扑克牌识别数据集的类别是 13 类对应 A、2、3、4、5、6、7、8、9、10、J、Q、K。这里的常见做法是按字母顺序固定映射避免训练中途改 id 导致权重报废# poker.yaml path: ./poker_dataset train: images/train val: images/val test: images/test 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把类别名写成字符串而非数字是为了后面做混淆矩阵时能直接显示牌面字符。特别注意2到9在 YAML 里要加引号否则会被解析成数字类型训练时虽然不报错但可视化标签时会出现类型转换异常。2.3 原始图中多目标与难样本的预处理扑克牌识别的难点不在单张单牌而在一张图里出现多张牌、且牌面互相遮挡或倾斜。1850 张原始图如果是从视频帧抽取的通常存在大量连续帧相似图直接全部丢进训练集会放大背景偏置。我一般会先用脚本做一次帧去重import hashlib from PIL import Image import os def dedup_by_hash(img_dir): seen set() for fname in sorted(os.listdir(img_dir)): path os.path.join(img_dir, fname) h hashlib.md5(open(path, rb).read()).hexdigest() if h in seen: os.remove(path) print(fremoved duplicate: {fname}) else: seen.add(h) dedup_by_hash(poker_dataset/images/train)这段脚本用 MD5 做精确去重对视频抽帧产生的完全相同的帧有效。如果原始图存在同一张牌在不同旋转角度下的裁剪图则不要用 MD5改用感知哈希pHash做相似度去重否则会把手写体的细节变化也误删掉。2.4 数据集规模与 yolo v11 训练的最小可用量yolo v11 相比 v8 在特征提取层做了重设计对小目标更友好但数据需求量反而略增。1850 张图属于“紧凑但够用”的区间如果单张图平均出现 2-3 张牌则有效标注框在 4000-5500 个左右对应 13 个类别每类约 300-400 个正样本。对目标检测来说每类 300 个框是能训练出可用模型的下限。如果你的数据集里某张牌出现次数特别少比如只有 30 次建议先把该类样本复制三份并做随机旋转增强否则 yolo v11 的置信度阈值会直接把这类牌全部过滤掉。3. yolo v11 格式标注的校验、转换与增强策略3.1 标注格式自查边界框越界与零面积框的过滤拿到数据后先别急着训练。yolo 格式里最容易出现的三个脏数据问题是坐标越界、宽高为 0、类别 id 超出 names 长度。1850 张原始图的标注如果是人工标注大概率存在少量边界框压边的情况。用下面脚本统一清洗import os def clean_labels(label_dir, img_w640, img_h640): for fname in os.listdir(label_dir): if not fname.endswith(.txt): continue path os.path.join(label_dir, fname) with open(path, r) as f: lines f.readlines() valid [] for line in lines: parts line.strip().split() if len(parts) ! 5: continue cid, xc, yc, w, h parts xc, yc, w, h map(float, (xc, yc, w, h)) # 过滤零面积和超宽框 if w 0 or h 0 or w 1 or h 1: continue # 中心点越界修正 if not (0 xc 1 and 0 yc 1): continue valid.append(line) with open(path, w) as f: f.writelines(valid) clean_labels(poker_dataset/labels/train)这段脚本的核心逻辑一行标注必须恰好 5 个字段类别 id 之外的四项都是 0-1 的归一化数。越界的中心点说明标注工具的坐标系设置错误直接过滤比修正更安全因为人工标注的原始框如果中心点跑出图外即使修正也大概率框错了对象。3.2 从 LabelMe / VOC 转换到 yolo v11 格式的脚本要点有些扑克牌数据集原始标注是 XML 格式需要转成 yolo v11 可读的 txt。转换时最容易出错的是坐标归一化分母XML 里给出的是像素坐标分母必须是图片的实际宽高而不是训练时的输入尺寸。正确写法如下import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, out_dir, class_map): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.findall(object): cls obj.find(name).text if cls not in class_map: continue box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) # 转换为 yolo 归一化坐标 xc ((xmin xmax) / 2) / img_w yc ((ymin ymax) / 2) / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{class_map[cls]} {xc:.6f} {yc:.6f} {w:.6f} {h:.6f}) base os.path.splitext(os.path.basename(xml_path))[0] with open(os.path.join(out_dir, base .txt), w) as f: f.write(\n.join(lines))转换完成后建议抽 20 张图做可视化叠加验证。常见做法是直接用 OpenCV 画框import cv2 img cv2.imread(poker_dataset/images/train/IMG_0042.jpg) h, w img.shape[:2] with open(poker_dataset/labels/train/IMG_0042.txt) as f: for line in f: cid, xc, yc, bw, bh map(float, line.split()) x1 int((xc - bw/2) * w) y1 int((yc - bh/2) * h) x2 int((xc bw/2) * w) y2 int((yc bh/2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(check_0042.jpg, img)这一步能发现两类隐蔽问题一是标注框比实际牌面大一圈——通常是标注时把边框阴影也算进去了二是多张牌紧挨时标注框互相重叠——需要人工决定是否保留小框yolo v11 的 NMS 在推理时会将重叠框合并训练时不处理会导致梯度信号互相干扰。3.3 数据增强的取舍旋转与马赛克不能无脑开满yolo v11 内置的增强参数中对扑克牌识别影响最大的三个是hsv_h、degrees和mosaic。牌面字符对颜色不敏感但对旋转极其敏感——A 旋转 180 度后仍是 A但 6 和 9 旋转 180 度就互相混淆。因此在训练配置里我会这样设置# augment 参数写入训练脚本或 data.yaml 均可 degrees: 30.0 # 旋转角度上限30 度足够覆盖真实摆放偏差 translate: 0.1 scale: 0.3 # 牌面尺度变化不宜过大 hsv_h: 0.02 # 色调扰动调小避免红桃和方片颜色特征被破坏 mosaic: 0.8 # 马赛克增强开启但权重不宜过高degrees设为 30 而不是 180意图是保留 6 和 9 的语义区分空间。若你的数据集里牌面不是正对镜头而是有较大俯仰角则应关闭mosaic——因为马赛克会把四张图的边角拼在一起俯拍牌面的边缘特征被拼接缝截断反而让模型学到错误的轮廓先验。3.4 类别不均衡处理13 类牌面出现频率的统计与重采样扑克牌数据集的天然问题是均匀分布——每种牌理论上各占 1/13。但实际拍摄时发牌者的习惯可能导致某几种牌出境率更高。训练前跑一次类别频次统计import glob from collections import Counter freq Counter() for txt_path in glob.glob(poker_dataset/labels/train/*.txt): with open(txt_path) as f: for line in f: cid int(line.split()[0]) freq[cid] 1 total sum(freq.values()) for cid, cnt in sorted(freq.items()): print(fclass {cid}: {cnt} ({cnt/total*100:.1f}%))如果发现某一类低于平均值的一半我通常会在训练时给该类单独提高损失权重。yolo v11 的类权重参数可以在 loss 配置中传入cls_pw但更简单的方式是在数据划分时对该类图片做水平翻转和随机裁剪的副本补充。要避免的是直接复制原图——复制不会带来信息增益反而让模型记住像素噪声。4. yolo v11 训练配置与 98.7% 识别率的参数调优路径4.1 预训练权重选择yolo v11n 还是 yolo v11s1850 张图的规模yolo v11nnano 版通常是性价比最优解。v11n 参数量约 2.6M推理速度在 CPU 上就能跑到实时v11s 参数量约 9.4M精度提升有限但对小尺寸牌面字符的鲁棒性更好。如果你的部署设备是 GPU且对识别率要求苛刻比如标题中的 98.7%直接上 v11s 并配合完整训练轮次更稳妥。命令如下yolo train \ modelyolo11n.pt \ datapoker.yaml \ epochs200 \ imgsz640 \ batch32 \ patience30 \ device0 \ projectpoker_experiments \ nameexp_baseline参数说明imgsz640是检测精度与显存消耗的平衡点对扑克牌这类中尺寸目标足够patience30表示验证集指标连续 30 轮不提升则提前停止防止 1850 张图在后半程过拟合batch32在 24G 显存上可以跑 v11s如果显存只有 8G 则降到 16。4.2 训练轮次与学习率小数据集的收敛节奏1850 张原始图属于小数据集训练轮次设 200 是个合理起点但真正决定效果的是学习率调度。yolo v11 默认使用 cos 衰减初始学习率lr00.01。对小数据集我习惯把lr0降到 0.005避免前几个 batch 就震荡。同时配合warmup_epochs5让模型先适应数据分布。如果训练过程里验证集的box_loss先降后升而cls_loss还在降说明模型在“背”训练集而非学习牌面特征。此时优先调高weight_decay从默认的 0.0005 到 0.001并开启dropout0.1yolo v11 支持在 backbone 末端加 dropout来抑制过拟合。4.3 验证集划分技巧确保每张牌至少出现 8 次标题给出的 98.7% 是否可信很大程度上取决于验证集的构成。我一般会做分层抽样保证 A-K 在验证集中的出现频率不小于某个阈值。用 pandas 简单实现import pandas as pd from sklearn.model_selection import train_test_split # 构建 image_id - 包含类别集合 的映射表 rows [] for txt in glob.glob(labels/*.txt): base os.path.basename(txt)[:-4] with open(txt) as f: classes [int(line.split()[0]) for line in f] rows.append({image_id: base, classes: set(classes)}) df pd.DataFrame(rows) # 按类别数分层确保验证集覆盖所有 13 类 train_ids, val_ids train_test_split( df[image_id], test_size0.15, stratifydf[classes].apply(lambda s: sorted(s)[0] if s else -1), random_state42 )这段脚本的价值在于如果某张图包含 2 张不同牌stratify 只按第一张牌分类可能让验证集漏掉某些类别组合。真正严格的做法是按“牌面组合”分层但 1850 张图规模下简单按首类别分层已经足够比随机划分稳定得多。4.4 从训练曲线判断模型是否还有提升空间每轮训练后查看results.csv里的metrics/mAP50-95(B)和metrics/precision(B)。扑克牌识别这类单字符目标mAP50 到 0.98 并不稀奇关键要看 mAP50-95——它反映边界框定位精度。如果 mAP50 很高但 mAP50-95 低于 0.85说明模型虽然找到了牌的位置但框的边界不够贴合牌面边缘。此时优先调iou0.7的 NMS 阈值并考虑增大imgsz到 800让字符边缘更清晰。5. 识别率虚高陷阱与业务场景下的回归验证5.1 测试集与训练集重叠导致的准确率虚高98.7% 这个数字听起来亮眼但首先要警惕数据泄露。如果扑克牌数据集的图片来自连续视频帧按文件顺序切割训练/测试集时相邻帧可能一张在训练集、一张在测试集——两帧内容几乎相同模型等于开卷考试。正确做法是按视频片段切分或至少按时间戳间隔抽帧。对已打包的数据集你可以用文件名里的序号做间隔采样验证ls images/ | sed -n 1~10p hard_test.txt挑出每隔 10 张的图片做测试集能逼着模型面对与训练分布略有差异的画面。这样测出来的识别率才是可靠值。5.2 易混淆牌面对6/9、A/4、J/Q 的专项评测在 13 类牌面中6 和 9 的混淆是结构性的——两者互为旋转 180 度。如果你的拍摄角度存在非正对相机的旋转yolo v11 会很难区分这两个类。专项评测方法从测试集里单独筛出所有含 6 或 9 的图片计算这两类的混淆矩阵。from ultralytics import YOLO model YOLO(poker_experiments/exp_baseline/weights/best.pt) results model.predict(sourcetest_6_9/, save_txtFalse) conf_matrix {} for r in results: for box in r.boxes: cls int(box.cls[0]) conf float(box.conf[0]) conf_matrix.setdefault(cls, []).append(conf)如果 9 类的平均置信度明显低于其他类说明旋转增强强度不够。此时回到训练阶段将degrees从 30 提高到 60同时开启fliplr0.5做左右翻转——注意这会让 6 和 9 的翻转学习同时增强可能反而加剧混淆。一个变通做法是单独对含 9 的图片做 5 度以内的小角度旋转副本加入训练集。5.3 在业务场景里复现 98.7%光照与背景干扰的对策实验室环境里拍出的扑克牌数据集测试准确率高是因为背景干净、光线均匀。真实业务场景——牌桌上有手指遮挡、筹码阴影、甚至咖啡杯反光——识别率会断崖式下跌。要复现标题的 98.7% 识别率部署阶段必须做三步一是输入帧先做颜色校正扑克牌的红与黑在暖光下会偏黄偏灰用白平衡算法拉回标准色温二是检测框二次过滤把宽高比明显不符合扑克牌约 0.7:1 到 1.4:1的候选框直接丢弃三是对时序结果做投票连续 5 帧里出现 3 次以上的同一牌面才输出识别结果能有效滤除单帧误判。5.4 精确率与召回率的选择业务容错方向的权衡98.7% 如果指的是精确率Precision意味着误报极少——适合自动计分场景因为看错牌比漏看牌更致命。如果指的是召回率Recall意味着漏检极少——适合魔术教学场景因为漏掉一张牌不会扣分但会打断教学节奏。在 yolo v11 推理时调节置信度阈值可以在这两者间滑动yolo predict modelpoker_exp/weights/best.pt sourcelive.jpg conf0.65 iou0.45conf越高误报越少但漏检越多conf越低漏检减少但误报增加。我一般建议对扑克牌识别场景conf设在 0.55-0.7 之间。低于 0.55背景里的圆形物体比如筹码会被误识成牌面高于 0.75牌面有轻微遮挡时就会漏检。正确做法是先在测试集上跑一遍画出 Precision-Recall 曲线再按业务容错方向选阈值。5.5 从单一数据集到持续迭代收集难样本的闭环拿到 1850 张图的扑克牌数据集只是一个起点。要提高真实场景下的识别率最好部署后在线上持续收集conf 0.5的“低置信度但确实有牌”的样本每周补充进训练集。yolo v11 支持增量训练——在已有权重上继续跑epochs50不会从头学。关键在于新样本的标注要与原始数据保持一致特别是牌面倾斜角的分布不要让新数据把模型带偏到新的拍摄角度上。本文还有配套的精品资源点击获取