2026/10/19 10:23:21

YOLO扑克牌识别实战:从数据集构建到实时部署指南

YOLO扑克牌识别实战:从数据集构建到实时部署指南 前阵子接了个需求要对着牌桌拍一段视频实时识别每个人手上的扑克牌把每张牌的花色、点数在画面上标出来。这类任务说白了就是目标检测里的一个细分方向YOLO扑克牌识别。我花了两周时间从数据集构建到训练、部署完整跑了一遍最后在桌面俯拍场景下做到了实时识别普通笔记本上推理速度约30ms一帧单张牌识别准确率在98%以上。这篇东西不是理论科普就是我自己的实操记录。我会把整个流程拆开讲为什么扑克牌识别不能无脑套通用检测方案、数据集该怎么拍怎么标、YOLO训练时哪些参数值得动、部署时怎么压缩模型提速还有我踩过的几个坑。不管你是刚接触目标检测想找个练手项目还是已经在做牌类识别但精度上不去应该都能从这里拿到点能直接用的东西。1. 需求拆解与方案选型为什么扑克牌识别选YOLO1.1 扑克牌识别的难点是什么很多人觉得识别扑克牌太简单了52张牌长得清清楚楚把图丢给模型不就行了。真上手做才发现这里面有几个点比想象中刁钻。第一是类别数不少。标准扑克去掉大小王是52类如果连同花色点子一起算再加大王小王就是54类。类别越多模型要区分的边界就越多训练数据量和训练难度都成倍涨。52类目标检测虽然比不上上千类的细粒度分类但在日常项目里已经算中等规模了。第二是相似类别容易混。红心和方块都是红色梅花和黑桃都是黑色在牌面小、分辨率低或者光线偏色的时候模型很容易把红心看成方块、把梅花看成黑桃。数字本身也存在天然歧义6倒过来就是9Q和K在旋转或者低分辨率下轮廓也接近。这类混淆对检测模型来说非常致命因为人眼能靠上下文判断模型只能靠像素特征。第三是目标的尺度变化。视频里有人把牌举在胸前有人把牌甩在桌面上牌离摄像头远近不一一张牌在画面里可能占200个像素也可能只占40个像素。小目标检测本身就是YOLO系列的弱项搞不好就漏检。第四是环境干扰。牌面是光滑的塑料或覆膜纸灯光一打就有反光反光区域会直接把牌面的颜色信息盖掉。拍摄角度也不是每次都正对桌面俯拍、斜拍都会让牌产生透视形变。再加上手指遮挡、牌与牌叠压这些问题凑在一起泛化能力稍微差一点的模型立马现出原形。1.2 常见技术方案对比YOLO为什么合适我最早考虑过三条技术路线这里直接说对比结果。方案一是传统图像处理也就是轮廓提取加模板匹配。大概思路是先通过边缘检测把矩形牌面从画面里抠出来做透视校正然后和预先处理的52张模板比对。这套路子对固定机位、固定光照、单张牌的桌面扫描场景可行一旦换到手持拍摄、角度变化、遮挡叠加的场景轮廓都提不干净模板匹配误差直接失控。而且每新增一种牌面风格就得重新做模板扩展性太差。方案二是两步法先用通用目标检测模型把画面里的牌找出来再裁出每个牌的图像区域单独用一个分类模型去判断花色点数。这个方案精度其实不错但整套链路里串了两个模型工程复杂度高推理耗时翻倍。对于实时视频流来说每帧跑两个模型对算力的压力有点大。方案三是端到端的YOLO检测直接把52类当成检测类别去做一个模型同时输出牌的位置和类别。YOLO把定位和分类合成一个网络推理时只需要一次前向传播速度和工程成本都最优。虽然早期YOLO系列在细粒度分类上表现一般但现在的YOLOv8、YOLO11这类版本已经引入了更深的特征融合和注意力机制分类能力有了明显提升。实测下来用YOLOv8s做牌面识别mAP50能到0.97左右完全够用。我最后就选了YOLO路线具体版本用的YOLOv8s。选s而不是选n或者m是出于综合平衡n速度最快但精度余量小换上复杂背景容易崩m精度更高但显存占用和推理耗时会涨在部署设备性能不确定的情况下s是性价比最稳的档位。1.3 整体实现路线规划整个项目大致可以拆成五个阶段我给一个当时自己画的路线清单照着走就行数据准备拍摄原始视频和图像完成标注整理成YOLO格式数据集做好训练集、验证集、测试集划分。模型训练基于公开预训练权重做迁移训练先快速收敛再针对性调参。精度评估用验证集计算mAP、召回率、混淆矩阵找到失败的典型样本。导出优化把PyTorch模型导出为ONNX再转推理引擎格式做半精度或者TensorRT加速。部署集成把检测结果接入业务逻辑比如实时视频流处理、牌型判断、自动报牌。这套流程不是我拍脑袋定的而是大部分视觉项目通用的迭代路径。关键原则是先让整个链路跑通哪怕精度只有80%再去针对性地优化数据或调参而不是一上来就想着做一个满分的模型。目标检测项目的天花板由数据决定模型只是把数据的上限发挥出来而已。下面我从数据开始细讲。2. 数据集构建与标注精度上限由数据决定2.1 数据采集要贴近真实场景我在最开始犯过一个错误就是从网上批量下载扑克牌图片当训练集。这些图片干净、清晰、正对镜头模型在上面收敛得飞快验证集指标漂亮得吓人。结果一放到实际视频里直接拉了跨——真实场景里的反光、模糊、角度问题网上图片完全没有覆盖到。后来我换成了自己的采集方案。拿了两副牌在办公室、家里、楼道多个地方拍素材覆盖不同背景和光线条件。在大多数场景下模拟三种典型视角俯拍桌面全景、手持摄像头对准牌堆、牌面斜放在桌面上。每种视角都拍了静止图像和短视频两类素材静止图像用来做主要训练样本短视频用来抽帧补充连续角度变化。在数量上面我给一个参考标准52个类别每个类别单独的有效样本尽量不少于400张总量在一万五到两万张之间。这里说的有效样本是指牌面占画面主体、能被清晰标注出来的图片。扑克牌识别不同于通用物体检测类别多且彼此相似数据量少一定会导致某几个类别掉链子。采集时的几个实操细节值得记一下牌面反光严重的场景试着调整光源位置或者把牌稍微挪个角度让反光条不正好盖住数字。拍摄分辨率至少1080p后期缩放到训练尺寸时细节保留更多。录制视频时拿牌转一圈再放下可以获得同一个牌面的连续角度变化这比单张拍照的信息量大多了。对焦要锁定在牌面上特别是手持拍摄时宁可不拍也不要让整个画面糊掉。有一点要特别提醒如果数据集里只有顺光直拍的牌面模型学到的其实是“非反光状态下的牌”一旦实际部署遇到反光就废。所以采集时一定要故意制造一些反光样本必要时甚至可以用手机闪光灯直射牌面拍一批让模型见过最恶劣的情况。2.2 标注细节与类别体系设计素材准备好之后就是标注。社区里常用的开源标注工具我用过两种LabelImg和X-AnyLabeling。LabelImg是老牌工具界面简洁上手快支持YOLO格式输出。X-AnyLabeling功能更全带交互式分割辅助能自动吸附物体边缘对密集场景效率高不少。我的话批量标注用的是LabelImg因为扑克牌的框就是标准矩形不需要太高级的功能简单稳定最重要。标注规范方面有个关键选择框到底框哪里。我试过只框牌面上的数字和花色区域也试过框整张牌的外轮廓。最终选择框整张牌。原因有二一是整张牌的矩形特征更明显模型学到的形状特征更稳定二是只框中间数字区域遇到牌面旋转、遮挡时定位会飘因为标注框的大小和位置在不同的牌上变化太大而整张牌的外框在绝大多数情况下是稳定且唯一的。类别体系我直接定义成52类命名格式是“点数_花色”比如“A_spade”“10_heart”。没有单独拆成点数和花色两个分支也没有把大小王加进去。52类的方案工程上最直接端到端模型自会学习点数和花色的联合特征不需要人为拆分。项目里不涉及大小王就以实际业务范围为准。YOLO格式的标注文件是纯文本每行对应一个目标格式是类别id 中心点x(归一化) 中心点y(归一化) 框宽(归一化) 框高(归一化)我举个实际例子。一张1920x1080的图里某张牌的中心点坐标是(960, 540)宽400像素、高560像素类别id是0那这一行就是0 0.5 0.5 0.2083 0.5185这里的归一化就是拿实际坐标除以图像宽高。如果你是人工标注工具生成的格式基本都已经是这样了但要注意两个坑一是类别id和类别名的映射表要单独存一份模型训练和部署阶段都要用同一个映射二是工具导出的路径别带中文和空格否则读取数据时容易报错。标注完成后必须做一轮全量复查。让同一个人标注完再随机抽20%的图逐张过一遍重点检查两类错误框没有完全贴合牌边缘以及漏标了被遮挡一半但依然可见的牌。漏标对训练的负面影响比错标还要大因为模型会把“这张牌不应该被检测”当成正确信号学进去。2.3 数据增强与训练集划分数据集只有原始素材是不够的牌面识别的泛化能力要靠增强来补。我用的是YOLO自带的在线增强也就是训练过程中每轮喂给模型的图都是实时变换过的。几个关键参数记录在这里hsv_h: 0.02 hsv_s: 0.4 hsv_v: 0.4 degrees: 15 translate: 0.1 scale: 0.5 fliplr: 0.5 mosaic: 1.0这些参数的含义是色调轻微扰动饱和度和明度进行较强扰动旋转角度正负15度水平翻转概率50%随机缩放0.5倍使用马赛克增强。特别是马赛克增强把四张图拼成一张训练对小目标检测的提升非常明显相当于让模型在一个画面里同时看到多个尺度的牌。初始训练阶段建议保持mosaic开启但到了最后几十个epoch建议关掉让模型在接近真实分布的图像上做微调mAP一般能再涨零点几个点。还有一个细节是关于牌面旋转的特殊性。扑克牌是个矩形横向牌和纵向牌在真实场景里都在出现。训练时如果把rotate角度设得过大比如超过45度会让模型学到大量现实中根本不存在的倾斜姿态反而不利于收敛。15度是我测试后比较稳妥的值再配合水平翻转和垂直翻转其实已经覆盖了牌的所有朝向。数据划分这里提醒一个特别容易犯的错不要把同一段视频里连续帧抽出的图像同时放进训练集和验证集。因为相邻帧除了牌位置略有不同内容和背景几乎一样模型等于是在开卷考试验证集指标虚高。正确做法是按采集批次划分一批视频和图片只进训练集另一批完全独立的素材只进验证集。我当时是80%的素材用于训练10%用于验证10%留作最终测试。3. 模型训练与调参把YOLO性能榨出来3.1 预训练选择与训练启动训练阶段的第一步不是直接随机初始化权重从头训而是用官方在COCO数据集上的预训练权重做迁移学习。为什么会这样选因为COCO预训练权重里已经包含了大量通用特征比如纹理、边缘、颜色分布、物体形状这些底层特征对扑克牌同样有效。从头训练意味着模型要从零学习这些基本概念不仅训练时间更长最终精度也往往不如迁移学习。我的训练目录结构是这样的poker-detection/ ├── dataset/ │ ├── images/ │ │ ├── train/ │ │ └── val/ │ ├── labels/ │ │ ├── train/ │ │ └── val/ │ └── data.yaml ├── runs/ └── train.py 或使用命令行训练data.yaml内容如下path: dataset train: images/train val: images/val nc: 52 names: 0: A_spade 1: A_heart 2: A_diamond 3: A_club # 后续类别按顺序补齐启动训练的命令很直接我用的官方YOLO框架yolo detect train \ modelyolov8s.pt \ datadataset/data.yaml \ epochs200 \ imgsz640 \ batch16 \ device0 \ projectpoker_experiment \ nameyolov8s_poker几个参数简单解释下model指定的是COCO预训练权重路径data指定数据集配置epochs是最大训练轮数imgsz是训练输入分辨率batch是批大小device0表示使用第一张显卡。如果没有独显devicecpu也能跑就是慢很多。训练分辨率这里多说一句。官方默认是640但我建议直接在模型训练阶段就用800或者960的输入尺寸。因为扑克牌属于小尺度目标高分辨率输入能保留更多牌面的纹理细节对花色判断有直接帮助。代价是训练速度变慢、显存占用增加。我在自己的显卡上正好跑得动800就用800训的最终在640推理下的表现也好于直接用640训练的结果。如果你的显存不够可以先640训一版跑通流程再决定要不要加分辨率。3.2 核心超参数调整经验先给结论YOLO的默认超参已经调得比较均衡不要一上来就大改。但我试下来有几个值得手工干预的点。第一是epochs和早停。200个epoch看起来多实际因为配合了早停机制模型可能在100轮左右就因为验证集指标不再上升而自动停止了。早停的patience参数我设在30也就是连续30轮验证集mAP没有提升就停止。如果数据集规模在2万张左右100到150轮是常见的收敛范围。第二是批大小和学习率的配合。YOLO默认学习率是0.01这个值在batch16时是合理的。如果因为显存限制只能用到batch8建议把学习率同比例降一点比如调到0.005到0.006之间。反过来说如果你有幸用到了batch32以上学习率可以适当往上加到0.02。这个经验来源于一个基本规律批越大噪声越小步子可以迈得更大。第三是损失权重的处理。YOLOv8的损失函数里有三个主要分支边界框损失、类别损失、分布聚焦损失。默认权重是box7.5、cls0.5、dfl1.5。我做扑克牌识别时把类别损失的权重从0.5提到0.7目的是让模型在区分类别时更敏感。之前出现过红心和方块混在一起的情况提升类别权重后混淆明显减少。但这也带来了一个副作用——漏检率略微上升因为模型把注意力更多放在分类上对定位的响应弱了一点。最终我用0.7这个折中值整体mAP是最高的。第四是两阶段训练法。第一阶段正常开启马赛克增强让模型快速见多识广训练到验证集指标平稳后关掉马赛克和一部分强扰动用小学习率再微调20轮。这个操作能让模型摆脱马赛克样本带来的分布偏移在真实分布上做精细调整我自己实测mAP50提升了大概0.5到1个点。3.3 评估指标与失败模式分析训练完别急着部署先拿验证集做量化分析。我主要看三个指标mAP50、mAP50-95和混淆矩阵。mAP50是指IoU阈值在0.5时的平均精度对工程来说最直观代表“框得差不多而且类别对了”的概率0.95以上就是优秀水平。mAP50-95更严格把所有IoU阈值的结果平均能反映定位框的精细程度一般在0.75到0.85之间都算正常。如果发现mAP50很高但mAP50-95偏低说明框不够精准比如框比牌大了一圈这时候优先检查标注框是否紧贴牌边缘而不是急着调模型。混淆矩阵是我每轮实验必看的图。它能直接显示哪些类别互相混淆。我做这个项目时最常见的迷惑组合就是红心A和方块A、梅花与黑桃。如果你看到这种红色系混淆或者黑色系混淆说明模型并不缺位置信息而是颜色通道的特征没有被充分利用可以考虑在数据层面增加色彩扰动的强度。如果看到的是特定点数之间的混淆比如10和Q大概率是样本量不足回去补数据比调参有效得多。我把训练过程中观察到的失败模式整理成一个简单的对照表失败现象可能原因处理优先级红心和方块混淆训练色彩扰动不足/反光样本少高加强数据增强小角度牌漏检目标尺度小/输入分辨率低高提升输入尺寸遮挡牌漏检负样本遮挡样本缺乏中补充遮挡素材定位框偏移标注框不精准/马赛克未关闭中修正标注并二次微调梅花黑桃混淆黑色系特征不足低增加该类样本量这套分析做完你基本就能明确知道模型的短板在数据还是参数下一步该做什么也有方向了。千万不能只盯着一个总mAP数字看那只会告诉你“行不行”不会告诉你“差在哪”。4. 推理部署与实战应用4.1 模型导出与速度优化训练完的模型是PyTorch格式直接拿去跑视频流太浪费我后面做了一系列压缩加速。第一步先将模型导出为ONNX格式然后再转成TensorRT引擎。ONNX是中间格式转出来之后可以在各种推理框架里用TensorRT则是英伟达显卡上推理最快的一种优化方式。yolo export modelpoker_experiment/yolov8s_poker/weights/best.pt formatonnx imgsz640导出时有几个选项需要留意。opset尽量保持默认dynamic动态尺寸会降低优化效率我这个场景输入尺寸固定就不开。导出后在ONNX Runtime下跑一下对比原模型的输出确保数值差异在可接受范围内。GPU部署的话推荐改成FP16半精度。半精度的原理是用更少的位数存权重推理速度几乎翻倍而扑克牌识别这类场景对精度损失不敏感实测mAP下降不到0.3%速度提升了接近一倍。我测过一组部署数据给个大概参考。在笔记本的独立显卡上模型导出TensorRT FP16后640分辨率输入的单帧推理耗时从原始PyTorch模型的约50ms降到了约18ms。如果换成CPU环境也可以用ONNX Runtime加int8量化速度提升明显但对牌面这类颜色敏感任务量化后红心方块更容易混淆需要你实测后决定能不能接受。如果你没有NVIDIA显卡还有两个替代方案。一是把输入分辨率降到480通过牺牲精度换速度二是做检测区域的裁剪只对画面中的桌面区域跑推理而不是整帧。两种办法我都试过按性价比排序降分辨率是最快的调优手段但注意牌面太小后漏检率会明显上升别降到384以下。4.2 后处理和业务逻辑整合模型输出的原始结果是很多个预测框每个框带着类别id、置信度和坐标。为了让结果能用于业务需要做好后处理。第一步是置信度过滤。推理结果里置信度低于0.25的框可以先丢掉这一下就能过滤掉大半误检。实际部署场景中对置信度阈值的选取可以在0.25到0.45之间调整。逻辑很简单宁可漏检也不要错检的场景用高阈值反过来用低阈值。第二步是非极大值抑制。同一张牌可能被输出多个重叠的框NMS通过按置信度排序、抑制重叠框的方式来保留最合适的那个。官方框架里的NMS已经内置了参数里主要是IoU阈值。扑克牌场景用默认的0.45即可如果牌与牌靠得很近可以降到0.35防止相邻牌的框被错误合并。第三步是业务逻辑对接。我这里的目标是实时把画面中每个玩家的手牌识别出来所以接到检测结果后需要做跨帧跟踪。常用的方案是用ByteTrack这类多目标跟踪算法给每个检测到的牌分配一个唯一ID连续帧中同一个ID代表同一张牌。有了ID就可以统计哪些牌一直出现在画面里哪些牌被拿起放回。再往下就能做牌型判断了。比如德州扑克场景拿到五张公共牌加两张手牌按规则判断玩家当前牌力。这部分和检测模型无关纯粹是后端逻辑。我的建议是检测和业务逻辑解耦检测模型只负责输出“画面里有什么牌”把“这些牌意味着什么”交给上层逻辑来处理方便后续换模型或者扩展玩法。4.3 可以落地的应用方向这个项目做完之后适用范围比想象中要宽。我自己列了几个方向供参考也帮你判断一下投入产出比专业扑克教学与复盘工具。比赛时录下视频流赛后自动回放每一手牌的出牌顺序辅助教练讲解策略。直播娱乐互动。在直播画面中自动高亮主播的手牌提升观众参与感。自动化牌局记录。多路摄像头同时识别多桌牌局省去人工记录的工作量。棋牌游戏AI前端的视觉感知模块。桌上实体牌的实时状态作为决策系统的输入。防作弊辅助监控。识别出牌速度、出牌顺序的异常作为人工复核的线索。这些场景的共同要求是实时性、稳定性、适应复杂光照。你如果在这些方向里做产品模型精度是一部分更重要的其实是整体链路的稳定性尤其是摄像头角度的变化、牌的花色在不同灯光下的色偏这两个问题才是长期运行中真正考验模型的地方。5. 常见问题速查与经验实录5.1 训练阶段高发问题排查我把项目过程中自己遇到过或者帮别人排查过的问题列个速查表每一条都是实际会踩的坑。问题现象排查方向解决办法训练loss一开始就很大且不下降数据没读对标注和图像不对应检查data.yaml路径、标签文件路径、类别数是否匹配训练正常但验证mAP极低数据划分不合理存在泄漏按批次重新划分数据集确保验证集完全独立训练中loss为nan学习率过高或数据里有异常值降低学习率检查图像是否有损坏文件类别严重不均衡某些牌样本数量太少补充对应类别样本或者做离线复制增强模型一直不收敛类别id映射错误逐个检查步骤间的映射表是否一致最隐蔽的一个问题发生在标注阶段用LabelImg导出时类别顺序可能跟data.yaml里的顺序不一致。比如你标注软件里第一个类别是红心A但data.yaml里第一个类别是黑桃A那么所有红心A的标注在被模型解读时全变成了黑桃A。排查方法很简单训练完成后随机挑几张验证集图片按预测结果画框看看一眼就能发现问题。loss为nan这个问题我多解释两句。除了学习率过高和数据异常还有一个可能原因是显存不够导致梯度更新失败。如果你看到loss突然变成nan先去看显存占用如果接近满负荷把batch调小一半再跑。5.2 推理部署阶段问题排查部署的时候常见问题跟训练时完全不一样主要集中在速度和精度两个维度上。问题现象可能原因解决办法推理速度达不到实时模型没做优化/输入分辨率太高导出TensorRT或ONNX开FP16CPU上推理极慢模型参数量过大换更小的nano版本或做int8量化测试时视频卡顿帧处理和后处理在同一线程拆成采集、推理、绘制三个线程用队列串联测试精度比训练低很多数据预处理不一致确保推理时的resize、归一化方式和训练时一致新环境误检严重模型没见过该环境的光照收集新环境素材做增量训练线程那里我多说几句。视频流推理如果所有事都塞在主线程里画面必然卡顿。我当时是开了三个线程一个线程负责抓取视频帧一个线程负责模型推理一个线程负责画框输出。线程之间用队列传递数据推理线程处理不过来就丢旧帧保新帧。这样一来即使模型推理偶尔变慢视频画面也不会明显掉帧最多就是检测框跟实时画面的位置有些许延迟。5.3 我的几条独家实操心得按说写到这儿技术部分就结束了最后分享几条不太容易在文档里看到的经验。第一点数据集里的“负样本”很重要。我这里说的负样本是指画面上有明显扑克牌但被故意不标注的样本。最初我所有的训练图都标得干干净净一张牌不漏模型表现很好。但实际测试时发现当牌桌边缘露出一半的牌或者背景里有一张被杂物挡住的牌时模型会疯狂误检。后来我专门收集了一批这种场景的图完全不标注这些牌相当于明确告诉模型“这种牌你不是目标”误检率瞬间降下来了。第二点训练时记录每一次实验的配置和结果。我习惯每个实验都在项目里建一个同名文件夹专门记下数据集的版本、epoch数量、增强参数、最终mAP。过了一个月再回来看如果不知道当时参数为什么这么调优化就是纯碰运气。记录这些配置不会花多少时间但能帮你省下大量重复实验的时间。第三点先跑通小模型再上大模型。一开始直接用YOLOv8m训练光是等一轮训练就要半小时调一次参来回折腾半天。后来我先用nano版本把整个流程跑通确认数据没问题、代码没bug再上s版本和m版本比效果。这个经验放任何项目里都成立先让流程看到结果再做精细化。第四点关掉增强做最后微调这个技巧值得当成标准操作。我对比过同一数据集关掉马赛克和强色彩扰动后再用小学习率微调20轮mAP50提升约0.4个点。成本低、收益稳定建议每次训练完都试一下。这个项目做完后最大的体会是扑克牌识别看起来是个玩具项目但把52类、实时、小目标、反光干扰这些条件摆在一起对数据工程、模型调优、部署加速的考验一点不少。整套流程跑下来你收获的不只是一个能识别牌的模型而是一套可复用的目标检测项目方法论。就算以后换赛道做其他细粒度识别任务这套从数据采集到部署优化的思路依然用得着。