石头剪刀布检测这个项目,算是目标检测入门里被问得最多的一类选题了。需求很清晰、模型选型明确、数据也不难凑,但真要把它做成一个能跑通、能演示、界面还说得过去的完整系统,坑其实不少。最近我自己把整套流程从零走了一遍,从数据集标注、YOLOv11训练、权重导出,到PyQt5界面和登录注册模块的整合,前前后后踩了不少雷,也沉淀出一些可以复用的经验。这篇就把整个项目的技术拆解、关键代码逻辑和排查过程完整记录下来,给准备做类似手势识别系统、或者想把YOLO模型接进桌面应用的朋友一个参考。无论你是刚开始接触目标检测的新手,还是已经跑通官方训练示例、卡在UI集成阶段的同学,这篇都能给你省下大量弯路。
1. 项目整体拆解:YOLOv11为什么适合手势识别
1.1 核心需求与功能模块
先把这个系统的功能边界理清楚。标题里写的是“基于YOLOv11的石头剪刀布检测系统”,但实际交付物通常包含四块内容:训练好的YOLO模型、标注好的数据集、一个带登录注册的桌面UI界面、可运行的Python源码。
其中模型负责的是“看见并判断”,也就是从摄像头画面或图片里框出石头、剪刀、布这三种手势;UI界面负责的是“让人能用”,需要支持打开摄像头实时检测、上传图片检测、展示置信度和类别,顺带还要有登录注册来区分使用用户。整个系统虽然拆成了模型和界面两个部分,但真正的工程难点恰恰在两者的衔接上,比如推理线程怎么不卡界面、检测结果怎么回调刷新到显示组件、用户登录状态如何与主界面联动,这些细节才是决定项目完成度的关键。
1.2 为什么选YOLOv11而不选老版本
很多人在选型时会纠结,为什么不用YOLOv5或者YOLOv8,非要上YOLOv11。我的看法是:YOLOv11作为较新的迭代版本,在检测精度和推理速度的平衡上做得更成熟,尤其小目标和中距离手势检测的提升是可感知的。石头剪刀布的手势在画面里所占区域往往不会特别大,尤其是在摄像头实时检测场景里,手部可能离镜头一两米远,目标框只有几十个像素,老版本的浅层特征提取往往会出现漏检。
另外从工程实现角度看,YOLOv11沿用了一套非常成熟的训练和推理工具链,很多API接口和YOLOv8保持兼容。这意味着我迁移到新版本时,几乎不需要重写检测逻辑,已有的图像预处理、NMS后处理、坐标解析代码可以直接套用。在模型体积上,轻量版模型的权重文件也不大,CPU也能跑,对部署环境的要求低,比较适合作为本科生项目或自学入门的载体。顺带提一句,在部分资料里这个版本会写成YOLOv11,也有的地方直接叫YOLO11,实际上指的是同一个系列,下载权重时认准对应文件即可。
1.3 石头剪刀布检测的三个关键难点
表面上看,三分类检测比COCO的80类简单得多,但实际上手就会发现问题并不少。
第一个难点是类别间外观相似。布和剪刀在特定角度下轮廓接近,剪刀的两指张开和布的平面展开,从某些视角拍摄后极其容易混淆;石头和攥紧的拳头在标注时也经常让标注者犹豫该不该算作同一类。这个问题的根源不在模型,而在数据和标注的一致性能不能守住。
第二个难点是光照和背景干扰。YOLO系模型对训练集分布非常敏感,如果训练数据全是在固定灯光、纯色背景下拍的,一到实际应用场景,摄像头自动白平衡、窗边逆光、背景里有人走动,检测置信度会肉眼可见地往下掉。
第三个难点是推理实时性下的帧处理逻辑。实别检测不是单帧问题,连续帧之间结果会抖动,如果不在UI层做平滑处理或渲染优化,画面里的框会一闪一闪,体验非常差。
理解这三个难点之后,后面的数据采集、训练参数和界面设计就都有针对性了。
2. 数据集准备:这是决定精度的第一关卡
2.1 数据采集怎么做最省力
很多人拿到题目第一反应是去网上找现成的石头剪刀布数据集,这当然可以,但网上下载的数据集质量参差不齐,标注框可能不贴合手势边缘,类别分布也可能严重不均衡。我这次的做法是混合方案:先下载一部分开源数据作为底料,再自己补拍一部分贴近实际场景的照片。
自己采集时有个高效率技巧:不一定非要拿单反拍,手机摄像头在固定机位拍摄视频,然后抽帧切图即可。把手机固定在支架上,分别拍摄三种手势的短视频,每段30秒左右,用OpenCV脚本从视频中按每5帧截取一帧画面,500张素材几分钟就能搞定。拍摄时尽量覆盖不同人种肤色、不同手型大小、不同手势角度,别全程只拍一个人的手,否则模型会对“特定肤色加特定手型”产生隐性依赖。
我实际采集时还额外加入了“负样本”环节,也就是拍摄一些不含任何手势的日常场景画面,比如空桌面、拿杯子、比划其他手势。负样本能让模型学会区分“有目标和没目标”,减少误检,这一步很多初学者都会忽略。负样本的比例建议控制在总数据量的20%左右,不用太多,但一定要有。
2.2 标注与目录组织
数据采集完成后,进入标注环节。标注方式我推荐直接用开源的图形化标注工具,这种工具能以YOLO格式直接导出,免去手工转换的麻烦。
标注时有一点要特别注意:类别名的统一规范。我建议将三个类别固定命名为rock、paper、scissors,并在整个标注过程中严格区分。石头的标准是五指握拳且拳眼朝上,布的标准是五指全部张开且掌心朝向可辨识,剪刀的标准是食指和中指伸出呈V形、其余三指收拢。那些“石头半变形状态”“剪刀张不开”的模糊样本,宁可删掉也不要硬标,不然会给训练过程引入标签噪声。
标注完成后的目录组织也有讲究。标准YOLO格式的目录结构是这样的:
dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml每一张图片对应一个同名txt文件,放在labels的同级目录下。txt文件每行内容为“类别ID 中心x坐标 中心y坐标 宽度 高度”,所有坐标都除以了图片宽高,归一化到0到1之间。比如一张640x480的图片里,石头框左上角在(100, 120),右下角在(300, 360),那么标注文件里对应的就是:
0 0.3125 0.5 0.3125 0.5这个换算不复杂,但手算很容易出错,所以标注工具导出后建议写个小脚本检查一遍标注坐标是否越界、类别ID是否超出范围。
2.3 数据集划分与数据增强策略
训练集、验证集、测试集的划分比例,一般用8:1:1或者9:1:0的常规比例。网上有现成的划分脚本,但需要注意一点:划分要在“数据文件层面”进行,不能只把图片目录分成train和val,而忘记labels目录也要同步移动。如果图片和标签目录结构对不上,训练时会报错找不到对应标签文件,这个问题非常隐蔽,很多新手在这里折腾半天才发现是labels少拷了。
数据增强方面,YOLOv11自带了一套在线增强策略,包括随机翻转、随机色域变化、马赛克增强等,训练时不需要额外做离线增强。但有一个原则需要记住:翻转增强要慎用。石头剪刀布的手势虽然不涉及左右语义,但克隆翻转可能导致“左手变右手”的混淆,对形状类检测影响不大,可如果叠加了旋转增强,剪刀的V形方向会被颠倒,模型会学到错误的形状关联。我实际操作时只开启了小幅度的水平翻转和色彩抖动,关闭了大幅旋转,实测精度比全增强策略要高一些。
2.4 标注验收:训练前必须检查的几件事
很多人在训练崩溃之后才回头检查数据,这个顺序应该反过来。我总结了一套训练前的快速验收流程:
第一,用可视化脚本把训练标签画回图片上,随机抽50张图肉眼检查,重点看框是否贴着手势边缘,是否存在漏标和错标。
第二,统计每个类别的样本数量。如果三个类别相差超过30%,就要优先做数据补充,而不是急着调模型参数。类别不平衡会直接导致训练出的模型偏向样本量大的那类。
第三,检查是否包含空标签文件。有些标注工具在导出时会给没有目标的图片生成空txt,训练时这类文件不会报错,但会干扰模型判断,需要直接删除。
第四,确认data.yaml中类别顺序与标注ID完全一致。这是最经典的低级错误:YAML里写的是rock、paper、scissors,标注文件里实际类别ID却是0、1、2,但对应关系写错了,导致模型把石头识别成剪刀。
3. 模型训练:从环境搭建到权重出炉
3.1 环境安装与CUDA版本踩坑
训练环境建议使用Python 3.10以上的版本。安装依赖时,官方工具库可以直接用pip安装,但这里有个常见陷阱:如果机器上已存在旧版依赖,直接安装会造成版本冲突,最典型的报错如“undefined symbol”或者“cx”开头的一堆错误,多半就是依赖版本不匹配。
我的安装顺序是这样的:
pip install torch torchvision --index-url 对应CUDA版本的源 pip install ultralytics先装PyTorch再装ultralytics,会让依赖解析更顺畅。安装完成后可以运行一个最小推理脚本验证环境,比如用官方预训练权重检测一张图片,如果这个流程跑通,说明CUDA和核心依赖都没问题。寂静排错的方式是直接用CPU跑一次,CPU能过而GPU报错,基本就是CUDA环境或GPU显存问题;GPU能过而CPU报错,则是MKL或OpenMP相关的兼容问题。
顺带一提,显存不够时不用急着换机器。训练YOLO11系列时可以把batch size调小,或者用梯度累积功能。我在一张8GB显存的消费级显卡上,用batch=8训练,迭代一次大概十几秒,完全可以接受。
3.2 数据配置文件与训练命令详解
训练前需要创建一个data.yaml文件,内容如下:
path: D:/project/rps_det/dataset train: images/train val: images/val names: 0: rock 1: paper 2: scissors注意path字段是数据集根目录,train和val填的是相对路径。这里有一个极易踩坑的点:path箭头分隔符的写法在不同操作系统上的兼容性。Windows下填绝对路径时建议统一用单斜杠或双反斜杠,很多脚本只考虑Linux路径写法,在Windows下会报FileNotFoundError。
训练命令直接使用官方CLI即可:
yolo detect train data=data.yaml model=yolo11s.pt epochs=120 imgsz=640 batch=8 device=0模型选择上,我做了一次对比实验,用nano版和small版分别训练了同样的数据。最终选择yolo11s,因为虽然nano版速度更快,但在手势边缘模糊样本上的置信度普遍低10个百分点左右。基线任务不用追求极限速度,稳一点更重要。
epochs的设置建议在100到150之间,不要盲目追求更多。我在训练到80轮之后发现验证损失已经不再下降,此时再增加轮次不仅浪费时间,还容易过拟合。训练结束后在runs/detect/目录下会生成权重文件夹,其中best.pt是验证集上表现最好的权重,last.pt是最后一轮的权重,后续部署务必使用best.pt。
3.3 训练结果评估:看哪些指标才有意义
训练完成后的评估环节,光看loss曲线下降是不够的,需要重点看几个指标的组合。精度、召回率、mAP50和mAP50-95都要逐个检查。
mAP50指的是IoU阈值为0.5时的平均精度,这个指标对手势检测来说是决定性的。如果mAP50低于0.9,说明模型在常见场景下就存在明显误检;mAP50-95则更严格,能反映框的贴合精度。还有一个容易被忽略的是预置信度曲线,它会说明不同置信度阈值下精确率和召回率的权衡。实际部署时,我通常会把置信度阈值设在0.5左右,既能过滤大多数误检,又不会把低置信度的真目标全部筛掉。
如果mAP50高但mAP50-95低,说明框定位不够精准,问题多数出在标注质量上;如果两个指标都低,先回去检查数据,别急着调模型结构。
3.4 模型导出:从pt权重到实际可用格式
训练完成后要部署到UI界面中,最简单的做法是直接加载pt权重。但为了后续性能优化,我会同时导出为ONNX格式,因为ONNX在CPU端推理时有更好的优化空间,也可以借助不同后端加速。
导出命令如下:
yolo export model=best.pt format=onnx imgsz=640导出过程中常见的报错是动态轴形状不匹配,解决方案是将imgsz明确指定为训练时的尺寸。导出后用ONNX Runtime做一次推理验证,保证导出过程没丢算子。
说实话,本项目UI界面使用PyQt5,加载onnx格式会显著加快推理速度,实测在CPU上单帧推理时间只有几十毫秒,接近实时。如果你用的是GPU机器,直接加载pt权重推理速度也足够,不需要折腾导出。
4. UI界面与登录注册模块的实现
4.1 系统整体架构:检测线程与界面显示必须分离
UI实现是整个项目里工程复杂度最高的部分,但也是最容易被初学者做砸的部分。很多人直接在主线程里调用模型推理,一卡就卡死整个界面,原因就是推理是阻塞式操作,长耗时任务会把事件循环堵住。
正确的做法是使用线程池或QThread来做推理。我采用的是在PyQt5中创建独立工作线程的方式,UI主线程负责绘制画面、接收按钮事件,工作线程负责读帧、模型推理、返回结果。推理完成后通过信号把结果传给主线程更新画面。
这里有一个细节需要重点说明:图像传输不能直接把numpy数组塞给跨线程信号。QThread里跨线程传递图像时,需要先把numpy数组拷贝一份,再通过信号传递,避免循环引用导致内存无法释放。我使用的方式也简单,在传递前msg_image = current_frame.copy()进行一次显式复制,信号读取后再把数组恢复成图像格式。
4.2 登录注册模块:SQLite存储与基础安全处理
登录注册界面是系统的入口,也是体现完整度的重要模块。用户输入用户名和密码,点击登录后跳转到主检测界面,注册时把用户信息写入本地数据库,并做基础的安全处理。
密码存储不能明文,这一点在课程项目里也是加分项。我这里采用加盐哈希的方式:随机生成一段盐值,将盐值和密码拼接后计算哈希,再存入数据库。实际代码如下:
import hashlib, os def hash_password(password: str, salt: str = None): if salt is None: salt = os.urandom(16).hex() digest = hashlib.sha256((salt + password).encode()).hexdigest() return salt, digest数据库我选择SQLite,因为不需要独立安装服务,一个文件即可存储全部数据。需要注意的一点是SQLite并发写入问题,多线程同时写入时偶尔会出现database is locked错误,解决方案是写入操作加锁,或者把数据库操作放到单独线程串行执行。登录注册场景请求量低,加一个threading.Lock就足够了。
4.3 主界面:摄像头实时检测与图片检测流程
登录成功后的主界面核心功能有两个:摄像头实时识别和图片识别。两个入口共用同一个推理核心,但处理逻辑略有不同。
摄像头实时识别的流程是:打开摄像头 → 循环读取视频帧 → 送入模型推理 → 在帧上绘制检测框和标签 → 刷新显示。这里有一个需要反复测试的细节,就是摄像头的打开方式。很多笔记本摄像头默认索引是0,但部分设备会被其他软件占用,此时打开会失败,需要加一个错误提示,让用户手动选择摄像头索引。
图片检测的流程更简单:文件对话框选择图片 → 模型单次推理 → 显示结果图。但要将推理得到的检测结果从模型输出格式转换成UI显示坐标。模型输出的是归一化的中心点加宽高格式,需要换算成图片像素坐标才能绘制。这一步要小心,不能直接把归一化坐标当作像素坐标,否则框会画偏。
4.4 检测结果的坐标解析与置信度显示
拿到模型的raw output后,数据格式通常是1xNx6的结构,其中N是候选框数量,6代表[x_center, y_center, width, height, confidence, class_id]。这个格式需要经过NMS非极大值抑制过滤重复框。官方推理接口一般已内置NMS,如果手写NMS,最简单的做法是排序置信度,然后按IoU阈值删除重叠框,阈值通常设为0.45。
过滤后只保留置信度大于阈值的框,然后遍历每个框,把坐标乘以原图宽高,得到圆整后的像素坐标。绘制时我会使用类别对应的不同颜色:石头用红色,剪刀用绿色,布用蓝色,框上方显示类别名称和置信度百分比,这样演示时直观,也给后续结果统计带来自动化基础。
5. 常见问题与排查技巧实录
5.1 训练阶段高频问题
问题一:训练时提示找不到标签文件。这个问题的原因九成是labels目录没有和images目录保持相同的子路径结构。官方工具默认要求图片路径和标签路径可以通过替换目录名找到,如果目录结构不一致就会报错。解决办法是把labels目录整体拷贝到与images同级的位置,并保持train/val子目录名称相同。
问题二:训练Loss为NaN。排查顺序是先看学习率是否过大,把学习率从默认值往下调两个数量级试试;再看数据里是否出现异常像素,有些图片在标注阶段被错误裁剪成0尺寸或包含全黑区域,这类样本要直接剔除。
问题三:训练耗时过长。如果不是追求极限精度,将imgsz从640降到416,速度会有明显提升,对检测精度的影响通常在可接受范围内。同时把workers参数设大一些,但不要超过CPU核心数的一半。
5.2 推理部署阶段高频问题
问题一:摄像头打开后画面黑屏。先检查摄像头是否被占用,最简单的方式是关闭所有可能占用摄像头的软件,比如视频会议工具;其次检查OpenCV读取到的帧是否为None,如果是None,检查摄像头索引。
问题二:CPU推理特别慢。除了换ONNX格式,还有一个实用技巧是降低推理帧率:不需要每帧都推理,每隔一帧或者每隔两帧推理一次,不推理的帧直接显示上一帧的检测结果叠加,视觉上几乎无感知,但速度能提升明显。
问题三:检测框频繁抖动。这是视频流处理里的经典问题,解决思路是加入简单的时间平滑:如果当前帧的检测结果置信度低于上一帧,就继续沿用上一帧的类别和坐标。注意保存历史状态时,坐标需要恢复为当前帧尺寸后的值,否则不同分辨率下会错位。实践下来这个简单的逻辑能让用户体验好很多。
5.3 从“能跑”到“好用”的几点经验
整个项目从跑通到真正好用,中间差的往往不是模型精度,而是几个细节体验。
第一,启动加载模型时给出明确状态提示。模型加载很耗时,如果界面上没有任何提示,用户会以为程序卡死。我在启动阶段会在状态栏动态显示“模型加载中,请稍候”,加载完成后再切换按钮的可用状态,体验提升很多。
第二,支持键盘快捷操作。比如空格暂停摄像头、Q键退出,虽然是小细节,但在演示和调试时非常好用,也给项目增添了专业感。退出的逻辑要记得释放摄像头资源和销毁线程,否则再次打开摄像头时会发现设备被占用。
第三,保留一份测试图片集。训练集目录下始终放几张包含三种手势的验证图片,每次改完界面代码、重新调试检测效果时,直接跑图片比打开摄像头省时省力,调试效率能提升一倍。
第四,配置项统一管理。把置信度阈值、模型路径、摄像头编号、窗口尺寸全部写到配置文件,程序启动时读取。这样以后换模型或调整阈值时,不需要改源码,改配置重启即可,是规范工程化的基础。
我个人在实际操作中的体会是,这类检测系统项目的最大价值不在于把训练代码跑通,而在于让你完整经历“数据工程—模型工程—软件工程”的三段流程,这三段里每一段单独拎出来都有很多坑。YOLOv11帮你省掉了大量网络结构和训练策略的过度调优时间,让你能把精力投入到真正决定项目好坏的数据质量和UI体验上。如果你正在做这个项目,我的建议是先紧着数据来,至少留出40%的时间做数据采集和标注检查,模型训练反而不用反复折腾,模型选型确定后训练一轮看多个指标,基本就稳了。后续如果有精力,还可以把系统扩展成多人对战积分模式、手势实时记录统计等玩法,这些模块都建立在当前这套检测框架之上,改造成本并不高。