YOLOv10扑克牌识别检测系统实战:从数据集到PyQt5界面
发布时间:2026/10/20 12:34:41 锦皓数字建站

做了这么多年目标检测我其实很少把“识别扑克牌”当玩具看。扑克牌的类别多、纹理细、经常互相遮挡还要应对反光、阴影和快速移动它天然就是一个适合检验检测模型基本功的场景。最近用YOLOv10把整套扑克牌识别检测系统跑通了从数据集制作、模型训练到PyQt5界面封装全部集成在同一个Python项目里带源码和训练好的权重可以直接拿去改造成自己的应用。这套系统能做什么简单说给它一张桌子上的扑克牌照片它能把每一张牌的位置框出来同时告诉你牌面点数是什么如果是摄像头实时画面它也能稳定跟住每一张牌。项目代码是Python的模型基于YOLOv10训练用了YOLO格式的扑克牌数据集界面是PyQt5写的。你可以把它做成棋牌游戏辅助工具、教学演示或者更复杂的桌面状态识别系统的基础模块。对哪些人有参考价值如果你是刚学目标检测想找一个比公共数据集更有“现实感”的练手项目或者你已经跑过YOLOv8想看看YOLOv10的端到端推理在实际场景里爽在哪又或者你在做类似的桌面物体识别这套系统的数据组织和界面写法都能直接参考。1. 整体设计思路与方案选型1.1 为什么在扑克牌场景里选中YOLOv10先回答一个大家都会问的问题YOLOv8都已经很成熟了为什么还要用YOLOv10我当时的判断有几条不全是因为“追新”。第一扑克牌检测对“同一种牌不能被误检成另一种”的要求特别高。传统YOLO在推理时要保留多个候选框再通过NMS非极大值抑制把重复框去掉这个过程在牌面重叠时很容易把小框误删或者把两张贴在一起的牌并成一个框。YOLOv10从设计上把NMS去掉了改成一对一的预测机制每个目标只保留一个结果。实测下来牌桌那种牌压牌的乱序场景漏检率肉眼可见地下降。第二YOLOv10的骨干网络用了增强版的C2f结构在相同的计算量下特征提取更充分。扑克牌的点数和花色差别很细微比如黑桃K和红桃K形状几乎一样只是颜色不同如果特征层不够敏感很容易混。v10在特征融合上做得更细对这类“小纹理”目标友好很多。第三部署友好。训练完可以导出ONNX、TorchScript等格式我后来为了追求更低延迟还把它转成了半精度模型。整个流程都是成熟工具链不用额外写奇奇怪怪的算子。当然选YOLOv10也有代价它的资料相比v8少一些网络结构的一些细节还需要自己看源码。但你不需要从头造轮子官方代码仓库本身就带了训练、验证、导出命令。对我这种想把时间花在业务逻辑上的人来说这个代价完全能接受。1.2 数据集定义成13类还是52类这个决策直接影响后面所有训练工作。先想清楚你的识别粒度。如果只是想知道“桌面上有几张牌、分别是什么点数”那定义13个类别就够了A、2到10、J、Q、K。这样训练简单、数据好凑、准确率也容易上去缺点是你无法区分黑桃A和红桃A。如果要做一个完整的棋牌识别系统通常需要知道具体是哪一张牌那就得定义52个类别红桃A、黑桃A、方块A、梅花A各算一类。52类的训练难度会明显上升因为有些牌之间的差异极小比如红桃9和红桃8中间那个点数排布方式不同模型很容易搞混。我在这次项目中选择了52类理由是这套系统的最终目标是要能识别“某一张特定的牌”而不是统计点数。如果你只是为了做点数统计或教学演示强烈建议先用13类跑通流程等你有余力再加花色。刚开始就用52类很可能在调试标注和训练参数上消耗掉大量时间。另外还有一个可选项要不要加一个“背景”类YOLO格式其实不需要单独标背景未标注的区域自然会被当作背景处理但如果你发现模型总是把牌桌纹理误检成牌可以考虑把牌桌边缘或记分牌当负样本采集进训练集。1.3 整体系统分为哪几块整个项目不是一个单一Python脚本而是按职责拆成了三个模块数据模块负责读取图片或摄像头流做预处理、归一化把输入尺寸resize到模型要求的640x640。模型模块加载训练好的YOLOv10权重执行推理对输出结果做阈值筛选和后处理。UI交互模块基于PyQt5的图形界面提供文件选择、摄像头开关、实时画面预览、结果保存等功能。三个模块之间通过简单的接口连接模型模块不依赖UI所以即使不放界面命令行也能单独跑。我一开始就要求自己保持这个边界后面调试效率高了很多。如果你是从零开始照着做也建议先做好命令行版的推理脚本再接UI。2. 核心细节解析与实操要点2.1 从YOLOv8升级到YOLOv10时最容易忽略的差异YOLOv10第一个要记住的事它不再需要NMS。我在刚开始用的时候习惯性地沿用v8那套推理代码把模型输出丢进torchvision的NMS函数结果发现延迟比预想的高而且检测框几乎没变。后来才意识到v10自己已经输出了最终框再多做一步NMS反而是浪费时间。如果你是自己写推理脚本请务必在模型前向之后直接读输出不要再接NMS。第二个差异是标签分配方式。v10在训练阶段用了一种“一致的匹配”策略简单理解就是让一个目标只对应一个负责预测的样本这会让训练过程更加稳定但代价是收敛速度看起来比v8慢一点。你如果在TensorBoard里看到前几十个epoch的loss下降不明显不用慌先跑完完整迭代不要中途放弃。第三个差异是模型文件命名。yolov10n.pt、yolov10s.pt、yolov10m.pt分别对应nano、small、medium三个尺寸。针对扑克牌这种不算特别小的目标nano也能出效果但如果你想在保证精度的情况下速度还不差我推荐从yolov10s.pt开始尝试。用nano省下来的算力不如留一部分给输入分辨率。2.2 扑克牌识别场景里最难处理的是反光和遮挡我在标注完第一批训练图片后信心满满地训练了一次结果测试视频里大量“误检”。排查了很久发现最大问题是牌面反光。扑克牌表面有一层塑料覆膜在室内灯光下会形成不规则亮斑亮斑把牌面的花色纹理挡住模型就把A看成了8或者把J看成了其他牌。解决办法不是去物理层面消除反光我没那条件而是在数据层面做对抗。第一采集数据时故意在不同光源角度下拍摄生成一批带反光的训练样本第二数据增强里加入亮度抖动、对比度调整以及一小像素大小的高斯噪声提升模型对光照变化的鲁棒性第三推理时的置信度阈值不要设太低我最终把阈值定在0.3到0.35之间宁可漏检一盘也不给误检留机会。遮挡也是跑不掉的。牌桌上的牌经常互相叠压一张牌可能只露出三分之一。如果标注时把整张牌的完整框标出来模型会被露出来的部分误导。我的经验是标注时只标肉眼能明确看到牌面内容的最大区域不要把完全被遮住的部分强行划进框里。否则模型会学到“牌一定是个完整矩形”的错误先验。2.3 标注工具和标签格式需要注意的三个细节YOLO格式的标签是txt文件每行对应一个目标格式是类别ID、中心点x坐标、中心点y坐标、标注框宽度、标注框高度其中坐标全部除以图片宽高做了归一化。我用的标注工具和大多数教程类似就是网页版或桌面版的LabelImg类工具导出时选择YOLO格式。但有三个细节是教程里经常没提的第一坐标归一化后很多工具会把坐标保留到小数点后六位这已经够用了不用手动改精度。第二如果一张图片里有多个同名文件比如1.png和1.jpg标注工具可能在对应标签时产生混乱。我踩过一次坑数据集里同时存在1.jpg和1.png标注工具把标签都绑到了同一个前缀下面导致有一半图片的训练标签错位。后来我统一把所有图片转为jpg格式文件名用五位数编号彻底消灭了这个问题。第三标注框的最小宽度建议设成大于等于2个像素。如果一张牌非常小你为了“严谨”把它标成一个1像素宽的小框模型会很难收敛因为下采样几层之后这个目标可能直接消失了。当牌在图片里实在太小看不清时宁可不标它也不要给它画个几乎看不到的框。3. 实操过程从训练到UI界面落地3.1 环境配置与依赖安装我的环境是Windows 11 Python 3.9 CUDA 11.8显卡是8GB显存的消费级显卡。这套配置跑yolov10s完全够用再大一点的模型也能勉强跑但训练时间会很长。安装依赖的步骤大致是这样的先用虚拟环境隔离项目我用的是conda然后安装PyTorch接着从YOLOv10官方代码仓库安装相关依赖。命令我简单列一下# 创建环境 conda create -n poker_yolo python3.9 conda activate poker_yolo # 安装PyTorch按你的CUDA版本选对应命令 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装YOLO相关依赖 pip install ultralytics注意如果你用的显卡比较新请去PyTorch官网查对应的CUDA版本装错会直接导致后面训练时“找不到设备”。装完之后可以用一条命令快速验证CUDA是否可用import torch print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0))打印True和显卡型号就没问题。如果打印False多半是CUDA和PyTorch版本不匹配或者显卡驱动太旧。3.2 数据集标注与目录结构数据集的根目录我建议做成这样poker-dataset/ images/ train/ val/ labels/ train/ val/用Ultralytics的YOLO框架也好用YOLOv10官方仓库也好最终都需要一个数据配置文件告诉训练脚本去哪里找图片和标签。我写的配置文件大概长这样path: ./poker-dataset train: images/train val: images/val names: 0: A 1: 2 2: 3 # ... 一直到51号对应某张具体花色点数的牌如果你的数据集是52类names列表里得写52个名字别写错否则训练时会报“标签类别ID超出范围”。我自己就犯过一次把52个类别写漏一个的错结果训练到一半报错返回。数据集划分时尽量保证每类牌在train和val中的数量比例差不多。我是拿了个Python脚本先按文件名哈希值做粗划分再手动检查每个类别的数量。之前偷懒直接随机切分导致某一类牌的val集为空训练完的mAP虽然很高但一到推理就拉胯后来才发现是特定类别根本没在验证集出现过。3.3 模型训练与关键参数数据集准备好以后训练命令本身很简单yolo train datapoker.yaml modelyolov10s.pt epochs120 imgsz640 batch16 device0这里几个参数值得展开说。epochs如果从零训练100到150轮是比较合理的区间。YOLOv10因为端到端训练的特点前期收敛慢后期还有一个爬坡阶段我给120轮。imgsz训练时输入分辨率我用的640。不要为了追求速度降到320扑克牌点数的纹理细节在320下基本糊成一团。batch如果显存不够系统会自动OOM。你可以在16的基础上减半到8或者把epochs延长。我建议显存小于8G的用batch8。device0代表第一张显卡如果你没有显卡可以改成cpu但训练时间会非常感人还是建议去云GPU跑。训练过程中我习惯盯几个关键指标loss曲线、mAP50和mAP50-95。当loss下降到一定程度后出现小幅震荡是正常的但如果发现val loss在一路回升那可能是过拟合了可以提前结束训练。训练结束后best.pt就是验证集上表现最好的权重文件后面一律用这个。3.4 模型评估与导出训练完成后先做一次评估确认效果符合预期yolo val modelruns/train/exp/best.pt datapoker.yaml这一步会输出每个类别的精确率、召回率和mAP。我在52类设置下的总体mAP50能到0.95左右mAP50-95在0.72上下。如果你的数值远低于这个水平大概率是数据集或标注的问题而不是模型的问题。部署到UI界面时用.pt原文件没问题但如果是其他项目要集成建议导成ONNX格式yolo export modelbest.pt formatonnx导出后ONNX文件可以用ONNXRuntime做推理速度比在PyTorch里跑要快不少。如果你的UI界面只依赖torch也没关系毕竟用户机器不一定装了onnxruntime。3.5 UI界面核心代码结构UI部分我用的PyQt5。整个界面只有一个主窗口上面放一个图片显示区、两个按钮打开图片、打开摄像头、一个“开始推理”开关和一个状态栏。关键点在于推理必须放到子线程里跑不能直接写进按钮的槽函数。我定义了一个DetectorThread类继承自QThread专门循环读取帧并执行模型推理然后把结果图片通过信号发回主线程。主线程的QLabel只负责把图片显示出来这样界面就不会卡住。核心代码逻辑可以简化为下面这个骨架class DetectorThread(QThread): change_pixmap_signal pyqtSignal(QImage) def run(self): cap cv2.VideoCapture(self.source) while self.running: ret, frame cap.read() if not ret: break # 推理 results model.predict(frame, conf0.3) annotated results[0].plot() # BGR转RGB再转QImage rgb_image cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB) h, w, ch rgb_image.shape bytes_per_line ch * w qimage QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888) self.change_pixmap_signal.emit(qimage) cap.release()为了方便保存结果我给界面加了一个“保存当前画面”的按钮会在当前帧上叠加时间戳和检测到的类别信息然后写入本地jpg文件。这一块不复杂但很实用你在演示给朋友看的时候一张带检测框的截图比口头吹牛有说服力得多。4. 常见问题与排查技巧实录4.1 训练过程中loss不降或NaN遇到loss变成NaN我的第一反应是学习率过大或batch里混入了脏数据。YOLOv10默认的学习率对大多数数据集是合适的所以更常见的问题是数据本身。检查一下标签文件里有没有坐标为0或宽度为负数的行如果标注时手滑大概率会出现这种非法值。举个例子我批量转换标注时曾经生成过一个超长行里面多写了一个类别ID导致一个框的坐标全乱了。这种错误只有训练到那个样本时才会爆出来非常隐蔽。我在清洗数据时加了一个校验脚本凡是发现类别ID大于总类别数、或者框坐标不在0到1之间的行直接输出文件名并停止训练。如果loss不降但也没变成NaN问题多半出在“模型从预训练权重继续训练”时被新数据集带偏。我的处理技巧是一开始就用yolov10s.pt做预训练但冻结前几层等loss降到一个平台期后再解冻所有层细调这样总训练时间其实更短。4.2 显存不足怎么保训练进度8G显存的显卡跑yolov10simgsz640batch16大概率会OOM。我实测下来batch8是8G显存的安全线。如果你还是不够有两个思路把imgsz从640降到480但扑克牌细节会恶化我一般不建议。打开AMP混合精度训练显存占用能减少三分之一。在Ultralytics框架里加一行参数即可yolo train datapoker.yaml modelyolov10s.pt epochs120 imgsz640 batch8 ampTrueAMP还有一个额外好处训练速度在支持Tensor Core的显卡上有明显提升。我自用的训练时间从纯FP32的两小时出头缩短到一小时多一点。4.3 UI界面上推理很慢或画面卡滞如果你发现UI上检测一张图就要一两秒先别急着骂模型。很大概率是主线程被推理阻塞了。前面说过要把推理丢进QThread这里再补一个细节不要在UI线程里做Opencv的imshow也不要在QThread里直接操作QLabel。正确做法是让QThread发送QImage信号主线程的槽函数负责更新界面。如果你已经用QThread但还是卡可能是每一帧都跑了完整的模型推理。我后来做了一个很简单的FPS限制通过一个计时器让推理线程每秒最多跑8帧。扑克牌不是极端高速运动物体每秒8帧完全够用换来的是CPU占用率从95%降到40%。UI的流畅感反而更好。4.4 特定花色总是被搞混最常见的是红桃和方块混黑桃和梅花混因为它们的形状轮廓接近区别主要在颜色和内部细节。我的排查步骤是先看测试图片是不是存在光照偏色如果有加入白平衡处理。再看数据增强中是否包含了过度的色相变化。YOLO的HSV增强默认会调整色相如果色相变化范围太大红桃和方块之间的颜色区别会被拉近。我把增强参数里的hsv_h从默认值调低了一截误检率立刻下降。如果仍不行给这两种易混牌增加专属训练样本让比重大概是普通牌的两倍。这个坑非常现实如果你直接拿别人预训练好的通用模型跑业务几乎必定会遇到类似的类别混淆问题。5. 从扑克牌识别到更通用的目标检测扩展5.1 换个思路把UI界面复用成通用检测工具这套项目虽然标题是扑克牌识别但背后代码的通用性很强。模型加载、推理、画框、界面显示这几个环节完全和数据类别解耦。我后来在某次接到的桌面零件计数需求里就是直接复用这套UI只换了数据集和配置文件名从接到需求到交付demo不到两天。如果想把UI界面做成通用的你可以在界面上加一个下拉框用来选择加载不同的YAML配置和权重文件。这样不重新编译代码就能切换模型。逻辑上没什么难度但观感会专业很多。5.2 往更高帧率走ONNX加TensorRT当你的应用场景不再只是一张静态图片而是需要在实时视频流里持续检测数十张牌时PyTorch的推理速度会变成瓶颈。我的下一步计划就是把ONNX文件转成TensorRT的engine格式在N卡上跑INT8量化。性能提升非常显著对一个2毫秒级推理的模型来说用TensorRT可以把时间再压到一半以下。这个方向需要有对应的显卡和SDK但收益是实打实的。5.3 与业务场景结合的想象空间扑克牌识别只是一个相对轻量的容器底层能力完全可以扩展到更通用的桌面物体识别棋牌教学工具实时告诉你当前牌型帮助新手识别牌面。桌面游戏自动化测试验证发牌逻辑时用视觉系统核对牌面是否正确。娱乐互动设备比如自动记分牌、魔术道具辅助。总之别把数据集局限在扑克牌上。标注规范的迁移成本很低你可以把目录结构改一改重新标一套完全不同物体数据集第一步就是把这套框架给搬过去。最后分享一点实操心得。我在这个项目里踩得最深的坑不是YOLOv10用不明白而是数据集质量。扑克牌看着简单真到训练时你会发现类别多、纹理细、反光严重稍微有一批标签不规范整个模型的可靠性都会被打折。我的建议是宁可花三天把数据集清洗干净也不要急着把训练跑起来。你磨刀的时间最终都会在mAP和实际效果的验证上省回来。另外UI界面虽然看起来是个“加分项”但在这个项目里它反而是让系统变得可用的关键。代码写得再漂亮没有一个能直观展示检测结果的界面你很难向别人说明它有多好用。先做命令行版跑通再花半天时间接上UI整个项目的成就感是完全不一样的。这套系统的后续扩展空间也很大。你完全可以把它改成识别麻将牌、识别手写数字、识别桌面物品计数。把YOLOv10这一套底子打牢了以后换任何目标检测任务都只是换数据的事。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。