资讯动态

基于YOLOv10的扑克牌识别系统:从数据标注到PyQt界面全流程实战

发布时间:2026/10/20 3:36:48 来源:尧图企业网站定制
扑克牌识别算不上什么新课题但把它真正做成一个能上手用的系统还是会踩到不少坑。最近我基于YOLOv10把整套流程走通了一遍——从数据集标注、模型训练、一直到PyQt界面封装最终交付的是一套包含完整Python源码和预训练模型的识别检测系统。这篇文章就把完整的实操过程拆开来讲包括为什么选YOLOv10而不是v5/v8、扑克牌数据集怎么构建才不容易把红心和方块搞混、训练参数怎么设置更稳以及UI界面跑推理时怎么避免卡成幻灯片。无论你是刚接触目标检测的初学者还是想在项目里集成扑克识别功能都可以照着这套流程复现自己的版本。1. 项目概述与核心价值1.1 这套系统到底能做什么扑克牌识别检测系统核心任务是从图像或视频流中定位每一张扑克牌的位置并判断它到底是哪一张牌。别小看这个任务它跟通用的目标检测不太一样52张牌算上大小王是54张很多类别之间长得高度相似比如红心6和方块6、黑桃7和梅花7在低分辨率、角度倾斜、光照偏色的情况下很容易被模型搞混。这个系统最终交付的东西包括训练好的YOLOv10模型权重文件、完整的Python训练与推理源码、带图形界面的交互程序以及一份可复用的标注数据集。它适用的场景也很广棋牌游戏的辅助教学、牌面统计与算牌演示、洗牌发牌设备的自动校验、扑克牌印刷质量检测等等。对个人开发者来说这是一个典型的目标检测全流程落地项目麻雀虽小五脏俱全从数据到训练到部署的所有环节都能学到东西。我记得最开始拿到这个需求的时候第一反应是“这不就是个目标检测嘛”真正动手才发现扑克牌这种细粒度分类场景比想象中要微妙得多。1.2 为什么在这个项目里选YOLOv10我见过不少人在项目里无脑选最新模型实际上每一次模型选型都要结合推理环境、开发成本、精度要求来权衡。YOLOv10是2024年的新一代YOLO系列它的核心卖点是去掉了传统目标检测里的NMS后处理。之前v5、v8在推理之后需要先按类别把重复框合并掉这一步骤不仅增加耗时还引入了额外的超参数NMS阈值YOLOv10通过一种“一致双分配”的训练策略让模型在输出阶段天然不产生重复框推理路径短了一截。在公开的COCO数据集上YOLOv10-S和YOLOv10-M在同等精度下的延迟相比YOLOv8都明显下降。另一个实际理由是部署灵活性。YOLOv10和YOLOv8一样提供N、S、M、B、L、X多个尺寸而且都基于同一套训练框架训练、导出、部署链路非常成熟。我在项目里可以直接用ultralytics的训练命令把v10模型训好再导出成ONNX或者TensorRT。如果你只有CPU环境跑DemoYOLOv10-N这个轻量版是合适选择如果手头有RTX级别显卡直接上YOLOv10-M或L在640分辨率下检测一张图都在几十毫秒量级足够支撑摄像头实时识别。当然我也要提醒一句YOLOv10发布相对较晚在个别嵌入式平台的旧版本推理引擎上算子兼容性可能不如v8如果你的目标设备非常老建议先在目标环境上做一次算子测试再决定。这套系统本质上就是“模型数据界面”三件事的整合YOLOv10替我们把模型这条主线做得足够强我们就可以把精力集中在数据质量和UI体验上。2. 数据集的构建与处理2.1 扑克牌数据怎么来、怎么标扑克牌识别本质上是一个细粒度分类目标检测任务类别之间差异小类内差异不同工艺、不同磨损程度也小所以数据质量直接决定模型上限。先说数据量每个类别例如方块K如果只有50张模型基本不可能泛化我建议每类至少准备200到500张54类加起来大概是1万到2万张图。听起来很多但仔细想想这里面可以靠技巧分摊成本。数据来源主要有三种方式。第一是实物拍摄用手机或摄像头在不同光线、不同背景下拍自己手上的牌。这个方法最接近真实部署环境尤其是摄像头识别场景拍出来的数据分布和推理时最一致我强烈建议这个方式占大头。第二是开源数据集的利用网上能找到一些早期的扑克牌检测数据但要注意牌面风格、颜色偏差问题最好作为补充。第三是合成数据把扫描好的牌面贴图随机粘贴到真实背景图上再通过仿射变换生成新样本这个方法适合快速扩充数量但合成和真实数据之间始终有域差异不能只用合成数据训练。标注工具我推荐用LabelImg或X-AnyLabeling直接导出YOLO格式的txt文件。YOLO格式是每一行代表一个目标五个数字依次是类别索引、归一化中心x、归一化中心y、归一化宽、归一化高。标注的时候我特别强调一点框一定要贴紧牌的四条边尤其不要把手指或者桌面阴影包进去否则模型学到的边界就被污染了。还有牌面如果有花纹只要整个牌面被框住就行不用纠结角上的花色是否完整可见。2.2 数据增强与易混淆类别处理扑克牌这个任务有个天生的坑红心和方块都是红色系梅花和黑桃在低分辨率下也相似数字牌之间的差异就是花色符号加点数模型必须靠局部细节来区分。所以在数据增强上不能只做简单的翻转。常用的增强策略包括HSV颜色扰动色相、饱和度、亮度在小范围内随机变化模拟不同光线条件随机仿射变换包括旋转、缩放、平移、错切模拟牌在桌面上的任意摆放姿势随机模糊和噪声模拟运动模糊和传感器噪声随机遮挡模拟牌被其他物体部分遮挡的情况。需要注意旋转增强对扑克牌是安全的因为52张牌无论旋转90度、180度还是270度它对应的类别不变。这和车牌识别不一样——车牌倒过来内容就变了所以车牌不能随便旋转。类别平衡这块也要专门处理。如果你拍的素材里红桃A特别多方块7特别少模型天然会偏向高频类别导致方块7的AP值明显偏低。一个实用的办法是先统计所有类别的数量对数量少于平均数的类别做复制过采样或者对这批样本使用更强的增强让模型在每次epoch里都能见到足够多的该类别样本。2.3 目录组织与配置文件编写训练之前数据目录的整理非常关键。ultralytics框架要求图片和标签文件分别放在images和labels目录下train和val各自成组。结构大概是这样的poker_dataset/ images/ train/ img_001.jpg img_002.jpg val/ img_9001.jpg labels/ train/ img_001.txt img_002.txt val/ img_9001.txt data.yamldata.yaml是告诉框架“类别数量和类别名”的配置文件内容类似path: ../poker_dataset train: images/train val: images/val nc: 54 names: 0: HA 1: H2 2: H3 ...这里要极度小心的是names的排序必须和标签txt里的类别索引一一对应。我踩过的最大坑就是在训练完模型之后修改了这个yaml文件的names顺序结果推理出来的牌名全部错位看起来是模型坏了其实是索引映射错了。建议的做法是整个项目固定一份names映射单独存成一个poker_labels.json训练、推理、UI显示都从这份json里读取绝对不要散落多份拷贝。数据划分上我一般按90/10划分训练集和验证集并且在划分前先把同一张牌的不同拍摄角度放进同一个集合避免高相似度样本同时出现在train和val里造成验证分数虚高。这一点很多人会忽略尤其是从视频里截帧生成数据集时如果不按“视频片段”来划分相邻帧几乎相同模型等于提前见过了答案val指标会非常好看但一上真实场景立刻打回原形。3. 模型训练的关键设置3.1 训练参数怎么选训练阶段的第一件事是确定用哪个尺寸的模型。我的经验法则如果在CPU上跑推理Demo就选YOLOv10-N或SN是轻量版参数最少推理速度快但精度要做出妥协如果有普通独立显卡例如6到8GB显存选S或者M最均衡如果不在乎部署体积、只追求最高精度选L甚至X。对这个扑克牌项目类别就54种且输入场景相对单一S和M已经足够用不到L。然后是imgsz。默认640是一个效率与精度的平衡点。如果你的部署场景是远距离拍摄、牌在画面里很小建议把imgsz上调到960或1280——更高分辨率对小目标检测的提升非常明显但训练时间和显存占用也翻倍。我项目里一般默认640先跑通流程再根据实际效果决定是否调高。batch size根据显存来定。8GB显存跑YOLOv10-Sbatch可以给16如果显存只有4GBbatch降到8同时把workers调小。batch太小会让batch normalization统计不稳定一般不低于8。epochs方面150是一个比较稳的起步值如果val指标还在涨就加到250或300配合早停避免浪费算力。优化器选型上我直接用ultralytics默认的SGDlr00.01weight_decay0.0005通常就很稳如果你的数据集很小AdamW反而更容易在初期快速收敛。学习率的warmup建议保留默认3个epoch就够让模型在最初几个epoch用较小步长适应数据分布避免一上来就震荡。3.2 训练命令与监控指标解读如果用的是ultralytics包训练命令非常简洁yolo detect train datadata.yaml modelyolov10s.pt epochs200 imgsz640 batch16 device0也可以写Python脚本的方式调用from ultralytics import YOLO if __name__ __main__: model YOLO(yolov10s.pt) model.train( datadata.yaml, epochs200, imgsz640, batch16, device0, patience30, projectruns/poker, nameexp1 )这里有个细节model参数传yolov10s.pt框架会自动加载预训练权重如果你传的是yolov10s.yaml那就从零训练没有预训练的加持。在数据量不够大的情况下强烈建议用预训练权重做迁移学习收敛速度快得多最终精度也更高。训练过程中主要盯着几个指标mAP0.5比较好理解就是IoU阈值0.5时的平均精度mAP0.5:0.95是更严格的综合指标要求框和真值的重合度更高数值通常比mAP0.5低不少。我看到很多课设同学只报mAP0.5其实两者都有参考价值。另外注意train/box_loss和val/box_loss的变化趋势如果train持续下降但val在上升那就是过拟合信号需要增加增强强度或提前结束训练。我遇到过的一个情况是loss曲线一直在震荡、mAP波动很大最后发现是训练数据里某些图片没有对应的txt标签模型在那些图上什么都学不到。所以训练前跑一遍ultralytics自带的校验脚本看看每张图片有没有对应的标签文件能省下很多排错时间。3.3 评估、导出与模型体积控制训练完成后先用val集做一次最终评估重点看混淆矩阵。对扑克牌项目混淆矩阵能直观暴露“哪些牌之间被模型搞混”。比如如果红心6和方块6互相混淆说明模型对花色符号的局部特征学习得不够这时候我会回去多补充这两种牌在倾斜状态下的样本而不是盲目增加所有类别的数据量。导出成部署格式的命令yolo export modelruns/poker/exp1/weights/best.pt formatonnx opset12 imgsz640导出ONNX后可以用onnxruntime做CPU推理或者在装有对应CUDA环境的机器上转TensorRT。对于这个项目我推荐一个管线训练完先导ONNX用onnxruntime直接替换torch推理能明显感觉到CPU推理速度提升而且在PyInstaller打包时还能丢掉torch依赖大幅减小程序体积。当然如果目标机器有NVIDIA显卡TensorRT的收益更明显但工程上需要适配的细节也多新手先从ONNX开始更稳妥。模型体积方面YOLOv10-S本身的权重文件大概在20MB级别如果打包时要控制体积可以只保留fp16版本精度损失很小。4. UI界面设计与功能实现4.1 界面功能怎么规划我在这类项目里一贯的原则是界面只做核心功能不要贪多。这个扑克牌识别系统我认为UI界面需要具备四个能力第一支持选择本地图片进行检测第二支持调用摄像头进行实时检测第三在显示区域绘制检测框、牌名和置信度第四把当前识别到的牌列表和数量统计展示出来方便后续逻辑处理。至于历史记录、热力图这种花哨功能我建议后期再加前期加多了只会让主流程变得难调试。技术栈上我用PyQt5因为它生态成熟、视觉风格可控而且和OpenCV配合流畅。Tkinter虽然更轻但做实时预览和自定义控件时限制太多。PySide6也可以API和PyQt5基本一致如果考虑License问题可以选PySide6。对课设、毕设来说PyQt5的资料最多遇到问题好搜索。布局上我采用左边控制区、右边展示区的经典结构左上放按钮和置信度阈值滑条右侧是一个QLabel用来显示图像底部放一个日志文本框把识别结果和时间记录进去。这个结构简单清晰后期加功能也方便。4.2 推理核心封装推理代码要独立成类不要和UI代码混在一起这样测试和后续替换模型都方便。核心推理类长这样import cv2 from ultralytics import YOLO class PokerDetector: def __init__(self, weights, labels_json, conf_thres0.4, iou_thres0.5): self.model YOLO(weights) self.conf_thres conf_thres self.iou_thres iou_thres self.labels load_labels(labels_json) def detect_frame(self, frame_bgr): results self.model.predict( frame_bgr, confself.conf_thres, iouself.iou_thres, devicecuda if torch.cuda.is_available() else cpu, verboseFalse ) detections [] for r in results: for box in r.boxes: x1, y1, x2, y2 box.xyxy[0].tolist() cls_id int(box.cls[0]) conf float(box.conf[0]) detections.append({ bbox: (x1, y1, x2, y2), label: self.labels[cls_id], conf: conf }) return detections def draw(self, frame_bgr, detections): for det in detections: x1, y1, x2, y2 det[bbox] cv2.rectangle(frame_bgr, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2) cv2.putText(frame_bgr, f{det[label]} {det[conf]:.2f}, (int(x1), int(y1) - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) return frame_bgr这个类的关键点在于模型加载放在构造方法里避免每次检测都重新加载predict的时候device参数自动选择CPU机器上也能跑返回的是一个dict列表而不是直接把绘制的图返回这样UI层可以自由决定显示原图还是标注图。4.3 摄像头实时检测的线程处理这部分是最容易出问题的。如果直接在PyQt5的UI主线程里跑摄像头循环你会发现拖动窗口都卡顿严重的时候界面会显示“未响应”。原因是主线程既要刷新界面又要做视频采集和模型推理推理又是个耗时的同步操作三者互相拥挤。正确做法是把视频采集加推理放到一个QThread里主线程只负责接收结果并显示。线程内部维护一个帧计数变量在性能不足时自动跳帧比如每处理一帧就丢掉中间三帧这样实时速度反而更流畅因为减少了排队。这里给出一个线程类的基本结构class CameraThread(QThread): frame_ready pyqtSignal(object) def __init__(self, detector): super().__init__() self.detector detector self.running True self.cap cv2.VideoCapture(0) def run(self): while self.running: ret, frame self.cap.read() if not ret: continue detections self.detector.detect_frame(frame) frame self.detector.draw(frame, detections) self.frame_ready.emit(frame) # 发出信号交给主线程更新 self.msleep(30) # 延时控制帧率 def stop(self): self.running False self.wait()在UI里把frame_ready信号连接到QLabel的更新函数即可。注意两点一是模型喂给线程之前要先加载好不要在线程的run方法里加载模型否则每次启动摄像头都要等好几秒二是多个线程不能同时调用同一个模型推理如果后续要接两个摄像头需要给模型加一个threading.Lock。5. 常见问题与排查技巧5.1 训练阶段的问题这里整理一份训练阶段的排查表现象常见原因解决办法mAP一直很低标签类别索引错乱可视化标签检查画框对照train loss正常但val loss震荡数据划分不严谨按视频片段或同牌分组划分训练速度极慢误在用CPU跑检查device参数和nvidia-smi早期loss就发散学习率过高调低lr或增加warmup检测框偏移明显标注框边缘不贴牌重新标注边界去掉手指阴影某些类别AP远低于平均类别样本不均衡过采样低频类别这里重点讲两个。第一个是“train loss正常但val loss震荡”的问题本质上是数据划分没有做好前面我提到同牌多角度要进同一个集合。第二个是“检测框偏移”如果是新标注的数据建议用训练集中的部分图片跑一次推理把预测框和真实框画在一起对比肉眼就能看出是不是标注习惯导致模型学歪了。5.2 推理阶段的问题推理阶段用户反馈最多的问题是漏检和误检。漏检的常见原因是conf阈值设太高很多牌的真实置信度可能在0.3到0.5之间你硬把阈值拉到0.7自然漏掉一片。建议先设0.25把明显的误检观察一遍再逐步往上调。误检则往往来自相似花色之间比如红色灯光下红心和方块更难区分。一个很实用的增强方案训练集里加入一些偏色的样本把红色通道往橙色和粉色两个方向都拉一拉模型对红色系的判别空间会更鲁棒。摄像头实时检测卡顿先别急着换显卡检查一下是不是在UI主线程里跑了推理或者在循环里做了全图像缩放。视频流如果能设置采集分辨率比如从默认的1920×1080降到1280×720推理速度可能提升一倍还不止而且牌面识别并不需要那么高的分辨率来做显示。5.3 UI界面和打包的问题PyQt界面常见问题是摄像头画面卡住不动。这通常是QThread里的循环被异常中断了但异常没有抛到主线程所以界面没有报错。碰到这种情况我在线程的run方法里加上异常捕获把错误信息通过信号发送到主线程显示排查起来就容易多了。打包这个问题在课设、毕设里经常遇到。PyInstaller打包时ultralytics包和torch体积巨大生成的exe经常超过1GB。我建议打包前先尝试ONNX导出把torch依赖去掉同时把模型文件放到exe外面的models目录里而不是打包进内部资源路径。这样exe体积能降到几百MB而且用户后续换模型不用重新打包。还有一个坑PyInstaller打包后的程序在别人电脑上闪退通常是因为缺VC运行库或者路径里有中文建议全程路径保持英文并且把Python环境整理干净不要装一堆无关包。6. 项目应用与后续扩展6.1 这个系统适合在哪些场景中用扑克牌识别检测系统的应用范围其实比想象中宽。最基础的是棋牌类游戏的教学和辅助分析新手用户把牌放到摄像头前系统实时告诉他这是什么牌方便学习牌型规则。也可以用于审计和统计场景比如牌局结束后快速盘点一副牌是否有缺漏。在生产质检环节印刷厂可以用它检查花色、点数是否印错、切牌位置是否偏移。对课程设计和毕业设计来说这类系统更是天然的展示项目有数据集构建、有模型训练、有UI交互、有部署打包整个链路完整答辩时很有说服力。6.2 还能往哪些方向扩展如果想在这个项目上继续迭代我认为有三个方向性价比最高。第一个方向是牌型识别检测到位之后把同一区域的牌按牌型规则聚合比如判断当前是顺子、同花还是对子。这个方向不需要改动模型写一个规则引擎就行。第二个方向是性能优化把YOLOv10-S导出TensorRT再配合多线程流水线可以做到更低的延迟适合对实时性要求高的场景。用ONNX Runtime跑CPU也很适合轻量化部署。第三个方向是大模型蒸馏如果在实际应用中YOLOv10-L精度足够但速度不够可以用知识蒸馏把大模型的能力迁移到YOLOv10-N上在几乎不掉点的情况下获得更快的推理速度。6.3 我在这个项目里的三个核心体会文章最后我分享几个在这个项目上最核心的实际体会。第一数据质量永远比模型结构重要。我试过用 YOLOv8 和 YOLOv10 分别跑同一份数据最终精度的差距远小于“数据标好了”和“数据标得马马虎虎”之间的差距。扑克牌这种细粒度分类任务尤其如此。第二工程整合的复杂度经常被低估。模型训练出好的效果只占到整个项目的一部分后面接UI、接摄像头、打包部署每一步都有各自的坑。花在数据整理和调试集成上的时间往往比训练模型更久。第三别被“最新模型”四个字忽悠。选型要看整个系统的落地环境。如果你的部署平台对 YOLOv10 的支持不成熟用 YOLOv8 一样能把这套系统做出来。技术选型的核心是用最少的成本满足需求而不是单纯追新。这些体会不是什么高深的理论但我相信能帮后来者少走很多弯路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑