news 2026/10/22 13:49:17

基于YOLOv5/v8/v10的石头剪刀布手势识别系统实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于YOLOv5/v8/v10的石头剪刀布手势识别系统实战

前一阵子接到一个小需求:在摄像头画面里实时识别石头、剪刀、布三种手势,并把结果直接显示在画面上。需求听起来不复杂,但真做起来还是有不少细节容易翻车。我当时第一版想的是用图像分类网络,后来很快改成了方案,换成基于YOLOv5/v8/v10的静态手势识别系统。这篇文章就把完整过程写下来:为什么要用目标检测、数据集怎么准备、三个版本怎么选、训练推理踩了哪些坑,希望给正在做类似项目的人一条可参考的完整路线。

如果你跟我一样,之前没怎么碰过手势识别,只想快速跑通一个能用的“石头剪刀布”Demo,那这篇文章适合你。我会尽量把每个选择的理由都说清楚,包括模型对比、数据集组织、训练参数和部署细节,而不是丢给你几行命令就跑。毕竟踩坑的经验,往往比成功跑通的命令更值钱。

1. 石头剪刀布为什么会用目标检测,而不是直接上分类网络

1.1 分类模型只能回答“是什么”,回答不了“在哪里”

很多人第一次接触视觉任务,会先想到图像分类。流程也很经典:把图片居中、裁剪、缩放到类似224x224的尺寸,交给 ResNet 或 MobileNet 这类分类网络,输出三类概率。如果只是拿别人处理好的“手势证件照”做离线分类,这个思路完全没问题。

但一到真实场景就露馅了。摄像头画面里,手不会乖乖站在正中间,可能偏左上,可能很靠近镜头,也可能画面里同时出现两只手。分类网络的输入是固定尺寸的整张图,它天然没有“目标在哪”的概念。你只能在外部想办法把手部区域切出来,再送进分类器。这就像你先要有人帮你把照片里的人裁剪好,分类网络才能告诉你这人是男是女;一旦没人裁剪,它就懵了。

用肤色分割去定位手部是个老办法,但同样脆弱。手部肤色在复杂背景前、偏暗环境下很容易跟其他物体混在一起,哪怕你上了高斯滤波、形态学开闭操作,效果也不够稳。我试过在办公室日光灯下用这个方案,只要身后有块木色桌板,分分钟把桌子也当成手的一部分。

1.2 目标检测带来的实际收益:定位、多目标、抗干扰

后来我换成 YOLOv5/v8/v10 这套目标检测方案,相当于一口气把“手在哪里”和“这是什么手势”两个问题同时解决了。模型直接输入整张摄像头画面,输出每个手部目标的边界框,以及对应的类别(石头、剪刀、布)和置信度。

这样做有几个非常实际的好处:

  • 不需要额外的手部裁剪模块,检测框本身就是裁剪结果;
  • 画面里出现多个人、多只手也能一帧处理完,分类网络做不到;
  • 对光照、复杂背景的鲁棒性,远好于肤色分割+分类器组合;
  • 后续如果想扩展“谁出拳赢了”这种多人玩法,或者加一个“无手势”负样本类别,目标检测的框架可以直接复用,不用推倒重来。

石头剪刀布只涉及三类目标,检测头很轻松,几乎不会有类别数量太多导致的模型压力。而且 YOLO 系列模型对小目标的检测能力一直在提升,手部在普通摄像头画面里通常占的比例不小,算是一个很好的入门项目。

如果你已经决定用目标检测,那下一个问题就是:数据和模型到底怎么准备。这也是整个项目里最容易返工的部分。

2. 数据准备:公开数据集打底,自采集补短板

2.1 数据从哪来:公开数据和自采数据如何组合

做目标检测,数据比别人想象得更重要。模型结构再好,如果训练数据里“剪刀”只有几十张,“布”有几百张,最后结果一定偏得厉害。

我当时的做法是:找一个开源的手势数据集,筛选出石头、剪刀、布三类图片,大概凑了两千张作为基础;然后自己用手机和摄像头补拍了一些照片,主要覆盖不同距离、不同角度、不同光照下的手势。总数没必要盲目追到上万,对三类简单手势来说,三千到五千张质量比较高的图片已经足够打底了。

公开数据集的好处是省时间,但坑也很明显。很多开源数据集里的手势是卡通图、符号图,或者手部只占画面很小一部分,这些图直接拿来做目标检测训练效果很不理想。必须人工清洗一遍,把那些模糊、遮挡严重、标注完全对不上的图过滤掉。

自采集数据则是最能补短板的一步。我补拍时有意覆盖这些场景:手在画面左侧、右侧、靠近镜头、离镜头远、握拳角度偏一点、剪刀手势比得不标准等等。因为这些“不完美”的样本,恰恰是实际部署时最容易出现的真实情况。实测下来,加入这批自采集数据之后,摄像头实时识别稳定性明显提升。

2.2 YOLO格式标注:txt标签的坐标换算逻辑

目标检测训练需要标注文件,YOLO 系列用的是 txt 文本格式。每一行代表一个目标,格式是:

class_id x_center y_center width height

这四个位置信息全都是归一化后的相对值,取值范围在 0 到 1 之间。具体计算方式其实是小学算术题:

假设一张图宽 640、高 480,一个手部框的左上角是 (200, 150),右下角是 (500, 450)。那么框宽是 300,高是 300,中心点坐标是 (350, 300)。归一化后:

  • x_center = 350 / 640 = 0.546875
  • y_center = 300 / 480 = 0.625
  • width = 300 / 640 = 0.46875
  • height = 300 / 480 = 0.625

所以这一行就是:

0 0.546875 0.625 0.46875 0.625

其中 0 是石头这个类别的 id。类别 id 从 0 开始排,对应 data.yaml 里的 names 列表顺序。

标注阶段可以直接用开源标注工具,画框后导出成 YOLO 格式。如果你拿到的数据集是 COCO 或 VOC 格式,网上也有不少转换脚本,但转换后一定抽查一版,重点看框的中心点是否对得上目标。我之前就因为转换脚本的坐标是从左上角继续累加而不是取中心点,导致模型学了很久都学不到位置,后来才发现标签全部偏了半个框。

2.3 数据增强怎么做才不破坏手势语义

YOLO 系列训练默认带着一套增强策略,比如 Mosaic、随机翻转、HSV 色彩扰动、缩放平移、旋转等。对石头剪刀布这个项目来说,左右翻转是安全的,因为“石头”翻一下还是握拳,“剪刀”翻一下还是两指张开,“布”翻一下也不影响语义,所以可以放心用。

但我要提醒一句:如果你以后做“左手指向、右手指向、举手、OK手势”这类带方向或语义差异化更强的手势,左右翻转就会非常危险。举个最简单的例子,“竖大拇指朝上”和“竖大拇指朝下”翻转后语义直接相反。石头剪刀布只是恰好不怕翻转,不意味着所有手势都不怕,这是一个很容易被忽略的增强陷阱。

类别平衡同样要重视。我刚开始训练时没有刻意均衡,结果“布”图片只有“石头”的三分之一,训练出来的模型对“布”的召回率惨不忍睹。后来我把三类数据量尽量对齐,又把“布”的样本通过采集补充到接近其他两类的水准,情况立刻好转。

3. YOLOv5、v8、v10到底差在哪,我三版都跑了一遍

3.1 三个版本的模型结构差异

这三个版本虽然都出自同一个 YOLO 家族,但思路差别不小。

YOLOv5 是经典中的经典,用的是 anchor-based 检测头。它需要预先在数据集上聚类出若干个默认框,再基于这些 anchor 做回归。这东西成熟稳定,社区资料多、遇到的坑基本都能搜到解法,但它身上背着不少历史包袱。

YOLOv8 换成了 anchor-free 方案,不再需要预设 anchor,而是直接让模型预测每个目标中心点的位置和宽高。它的主干结构也做了替换,引入了一些效率更高的模块。整体给我的感觉是:精度和速度都比 v5 更进一步,而且很多工具链天然支持,不用额外适配。

YOLOv10 是升级最明显的一版,追求端到端。v5 和 v8 在推理时都需要 NMS(非极大值抑制)来做候选框去重,这其实就是推理链路里的一道额外工序。v10 通过一些训练和结构上的改动,直接免去了 NMS 后处理,模型输出的结果更干净,部署代码也更短。对实时摄像头推理来说,少一个后处理步骤,延迟更低、管道更简洁。

3.2 同批数据下的实测对比

为了不靠感觉选型,我把同一份数据分别丢给 v5s、v8s、v10s 训练,图片尺寸 640,训练 50 轮,在同一台装了 8G 显存消费级显卡的机器上跑。下面这组数据是我这次训练的实测结果,不同环境、不同随机种子下会有波动,但整体趋势可以参考。

版本参数量(约)模型体积(约)训练耗时mAP50单帧推理耗时
YOLOv5s7M14MB42分钟96.3%11.2ms
YOLOv8s11M21MB45分钟98.1%12.5ms
YOLOv10s8M16MB38分钟97.5%9.8ms

简单解读一下:三者对石头剪刀布这种类别少、目标不算小的任务,精度差距没有想象中那么大。v8 略胜在 mAP,v10 胜在推理速度,v5 虽然各项不太突出,但也没有破绽,胜在省心和资料多。

3.3 选型建议:从入门到落地

如果你跟我一样属于“先跑通再优化”的类型,我的建议是分场景看。

  • 刚开始学,想搞懂整个流程:选 YOLOv5。它的文档最全,网上教程密度也最高,遇到环境报错或者参数问题,搜索一下基本都有答案。
  • 想要一个精度和速度综合最好的版本:选 YOLOv8。训练、导模型、做推理,整个链路成熟,踩坑成本低。
  • 想直接做端到端低延迟部署,比如在边缘设备上跑实时识别:选 YOLOv10。省掉 NMS 之后代码干净,推理管道的逻辑少一环。
  • 如果你的开发板算力很弱,可以选三个版本的 n 型号。我自己也对比过轻量版,精度会有下降,但手势识别这种任务通常还能接受。

4. 训练前的环境与配置,以及训练命令详解

4.1 环境安装和版本匹配

训练环境的坑通常不在“安装”这一步,而在“版本匹配”。

我的建议是 Python 用 3.9 或 3.10,PyTorch 用 1.13 以上。官方仓库的 requirements 文件会列出具体依赖,安装的时候照做即可。如果你用的是 NVIDIA 显卡,记得先装好对应 CUDA 版本的 PyTorch,而不是先装 CPU 版再后面后悔。安装时可以用本地镜像源加速,但最好不要把源全换掉,否则后续装其他包可能反而踩坑。

装完后,最好先跑一个官方预训练权重的小推理验证环境通不通。直接拿一张不存在的手势图推理一次,能输出结果说明环境基本没问题。省得后面辛辛苦苦训练完,才发现是推理阶段环境没配对。

4.2 data.yaml与模型yaml的修改

准备训练前要改两个 yaml 文件。第一个是数据集配置文件,内容非常简单,地址、类别数、类别名,缺一不可:

train: dataset/train val: dataset/val nc: 3 names: 0: rock 1: scissors 2: paper

这里最容易踩坑的是路径问题。train 和 val 路径最好写绝对路径,尤其当你的命令是在项目根目录之外执行的。如果目录结构是images/train和labels/train并列,YOLO 会自动找同名标签;如果图片和标签放在不同名字的目录里,就需要用配置文件里更明确的路径写法,否则训练时八成会报“label 找不到”。

第二个是模型配置文件。比如 v8 系列的模型 yaml,里面写的是网络结构的 depth、width 等超参。你只需要把nc改成 3 就行。要注意的是,如果你用了官方预训练权重,模型 yaml 的 nc 会被权重文件里的分类头覆盖,不需要自己在 yaml 里手动改;但如果想在没有任何预训练的情况下从头训练,就要把 yaml 里的 nc 改对,并且训练命令里不要传带权重的模型文件。

4.3 训练命令参数逐项说明

不同版本训练命令略有区别。以 v5 为例,命令是:

python train.py --data data.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 50 --cache

v8 的命令风格更偏向用子命令:

yolo train data=data.yaml model=yolov8s.pt imgsz=640 batch=16 epochs=50

v10 也是类似的风格,只是模型文件换成 yolov10s 相关权重。

这里挑几个关键参数解释一下:

  • --img 640:训练图片尺寸。不要为了省时间改成 320,除非你的实际部署场景就是 320。手部细节在缩小后很容易丢失,我吃过亏,后面专门讲。
  • --batch:批量大小。结合显存来定,8G 显存跑 s 系列,batch 16 比较从容。如果显存不够,优先调小 batch,而不是调小图片尺寸。
  • --cache:把图片缓存到内存里,能显著加快训练读取速度。但如果你的内存只有 16G 或更小,建议不启用了,否则容易内存溢出。

训练图尺寸最好和部署时的输入尺寸保持一致。很多人训练用 640,部署时为了省算力改成 320,结果精度猛掉,还一度以为是模型坏了。

5. 训练过程怎么判断“正常”,结果怎么对比

5.1 训练日志里的关键指标

训练不是把命令跑完就完事,要学会看日志判断模型是否正常。YOLO 训练时会打印 box_loss、cls_loss、dfl_loss 和验证集指标。

我先说个通俗的理解:box_loss 负责边界框定位,cls_loss 负责类别判断。两者的共同特征是,训练初期下降明显,随着轮次增加逐渐变缓。如果训练 loss 一直平坦没有下降,别怀疑数据,先检查是否没传预训练权重、学习率是不是设得离谱。

更重要的是看验证集的表现。训练过程中 mAP50 和 mAP50-95 会打印出来。mAP50 表示 IOU 阈值在 0.5 情况下的平均精度,对石头剪刀布这种项目来说,最终稳定在 95% 以上是比较正常的水平。如果 mAP50 一直在低位徘徊,大概率是数据集有问题,而不是模型不行。

还有一个容易忽略的点:过拟合。如果训练 loss 持续下降,但验证集指标在第 20 轮就开始下滑,说明模型开始死背训练集了。石头剪刀布手势类别少、图片多样性强,发生的概率不算高,但如果你偷懒用了很小的数据集,就会遇到。这时候优先补数据,而不是盲目减少训练轮次。

5.2 三版模型收敛速度与精度小结

从我这轮的实测来看,v5s 收敛略微偏慢,大概到第 35 轮才完全稳定;v8s 稳定得很快,第 25 轮附近 mAP50 就已经很高;v10s 收敛速度总体和 v8 接近,但因为它免了 NMS,后续验证时的流程更简洁。

最终三个版本的 mAP50 都在 96% 到 98% 之间,说实话差距非常小。这说明石头剪刀布这个任务本身的瓶颈不在于模型多强,而在于数据覆盖够不够、场景是否贴近真实部署。你与其纠结选 v5、v8 还是 v10,不如把精力花在把数据集做好、把训练配置调对。

我在测试时还做过一个直接对照:用同一张包含“剪刀”和“布”两只手同时出现的图片分别让三个模型推理。v5 偶尔会漏掉其中一只手的框,v8 相对稳健,v10 在置信度设置得当的情况下也能同时框住。这种多目标场景,正是分类模型做不到、但目标检测框架擅长的地方。

6. 从离线图片到摄像头实时识别

6.1 最简推理脚本

训练完成后,runs/detect/train/weights/目录下会有last.pt和best.pt,用best.pt来推理。v8 和 v10 的推理代码非常简洁:

from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") results = model.predict("test.jpg", conf=0.5) results[0].show()

这段代码会输出检测框、类别和置信度,并在窗口中展示结果。如果只是想快速验证单张图片,这一步就够了。v5 的推理逻辑稍微传统,常见的方式是用命令行或编写加载模型脚本,但思路一致。

6.2 实时识别与帧去抖

离线验证过后,就可以上摄像头。下面是我用的实时识别流程,大体结构稳定:

import cv2 from ultralytics import YOLO model = YOLO("best.pt") cap = cv2.VideoCapture(0) fps_counter = 0 while True: ret, frame = cap.read() if not ret: break results = model.predict(frame, conf=0.5, verbose=False) annotated = results[0].plot() cv2.imshow("Rock Paper Scissors Detection", annotated) if cv2.waitKey(1) & 0xFF == ord("q"): break cap.release() cv2.destroyAllWindows()

这里有个细节:OpenCV 读进来的是 BGR 格式,而 YOLO 内部会按 RGB 处理,你不用手动转换。只用把原始帧传给predict,再拿results[0].plot()画框即可。

实时识别时最烦的是闪烁。某一帧把剪刀误判成布,下一帧又恢复成剪刀,显示结果就会疯狂跳动。我用的去抖方法是维护一个最近 5 帧的类别列表,取众数作为当前最终结果;或者更简单一点,让同一个类别连续出现 3 帧才更新输出。实测下来,去抖后的结果稳定得多,体验也自然。

6.3 导出ONNX并加速部署

如果要在没有 PyTorch 环境的目标机器上跑,通常会把模型导出成 ONNX。v8 和 v10 的导出命令是:

yolo export model=best.pt format=onnx

v5 则用同目录下的export.py脚本。导出成功后可以看到一个best.onnx文件,用 ONNXRuntime 加载即可推理。

v10 在导出后的最大优势是,因为它的检测流程已经免去了 NMS,你拿到的输出直接是最终的框和类别,解析结果非常容易。v8 和 v5 导出时通常还要跟着一个 NMS,这就是为什么我说 v10 在部署阶段“管道更短”。如果你的目标平台是嵌入式设备,还可以进一步把 ONNX 转成对应硬件加速格式,能明显提升帧率。

7. 踩坑记录与调优经验

7.1 “布”被误判成“石头”的排查链路

这个坑是我遇到的最典型的误检问题。现象是:在正常距离下,手掌张开的“布”偶尔会被框成“石头”,置信度还不低。我没有直接去改模型,而是先做了三步排查。

先看验证集里“布”这个类别的 PR 曲线和 AP 值,确认是否整体偏低。接着找出具体的误检图片,把模型输出的预测框和原图叠在一起看。最后发现一个规律:被误判的图片里,“布”手势离镜头都比较远,手在整张图中占的比例很小,特征不明显。

根因有两方面。一是“布”的样本数量偏少;二是训练和推理尺寸都用了 640,但“布”在远距离时本质上已经接近小目标,模型识别起来自然会吃力。解决方式:补充远距离的“布”样本,同时把置信度阈值从 0.5 稍微提到 0.6,过滤掉犹豫结果。改完之后,“布”的误判率明显下降。

7.2 “剪刀”和“布”边界混淆的处理

剪刀的手势是两指张开,布是五指张开。从远处看,两者轮廓都非常接近,特别容易混淆。

我先检查了标注,发现一个隐藏问题:很多数据集里的“剪刀”标注框紧贴两指,但同样的手势换个角度,两指间隙看起来就不是特别明显,模型学到的是模糊特征。解决方式是重新统一标注标准:剪刀必须两指有明显分离,布则五指均匀张开。如果标注框太紧,手部的尺度和形变信息会被裁掉,模型很难学出稳定的特征。

另外我在增强配置里刻意增强了缩放范围,让模型见过更多不同大小的“剪刀”样本。这听起来不起眼,但对这类类间差异极小的任务,很管用。

7.3 光照背景变化导致的漏检

训练时大量图片都是均匀光照、单一背景,到了办公室的日光灯环境直接开始漏检。这个问题的根源在于数据分布和真实部署场景不一致。

我的解决办法分两层。第一层是在训练数据中主动混入一些复杂背景、逆光、偏暗的图片,数量不用太多,但要让模型见过这些情况。第二层是增强时加大 HSV 扰动的强度,让颜色通道有更多变化,模型就不会死记某个特定的肤色和背景组合。

需要提醒的是,肤色在不同色温下变化非常大。摄像头白平衡不准的时候,手部肤色可能偏蓝或偏黄。如果训练时没有做过色彩扰动,检测器很容易把偏蓝的手漏掉。经过 HSV 增强后,这个问题会小很多。

7.4 训练与推理分辨率不一致的退化

这是我中途犯过的低级错误。训练时用的是 640,部署到摄像头时为了省时间手动把推理尺寸调成了 320,结果精度肉眼可见地下降,漏检、误检全都冒出来了。

原因不复杂:模型在 640 分辨率下学会的特征尺度,在 320 输入下对不上号了。手部细节和边界框分布都发生了变化,模型自然要重新适应。如果你在乎性能,想用 320 推理,那就训练时也用 320,保持输入分布一致;如果训练用的 640,推理就别贪快改成 320。

最后再分享一个小技巧:我最终选择的是 YOLOv10s,导出成 ONNX 后在整个目标平台的部署流程简化了很多,去 NMS 的端到端输出让代码更短。但如果你是第一次做这类项目,我个人更推荐先用 v8s 跑通完整链路,等真正有了部署需求再切到 v10。实际训练数据不够时,我还在摄像头前录了一段自己出拳的视频,按帧抽取生成了一批带真实背景的多样本,明显提升了实际场景里的鲁棒性。做手势识别,很多时候花在数据和场景上的时间,比调模型结构更值钱。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 13:48:14

Qt+MySQL门诊预约系统源码实战:从环境搭建到功能扩展

简介:本资源为基于Qt与MySQL的医院预约管理系统完整项目包,面向计算机相关专业的在校学生、教师及企业开发者,可用于毕业设计、课程设计、作业提交或项目初期立项演示。项目已通过导师指导与答辩评审,获得95分高分,代码…

作者头像 李华
网站建设 2026/10/22 13:47:06

豆包AI助手实战指南:从对话机器人到智能生活多面手

豆包AI助手火了这么长时间,我身边不少朋友都在用,但真正把它用成“智能生活多面手”的人其实不多。多数人还停留在随手问两句、让它写个小作文的阶段,实际上豆包能干的事情远不止这些——从日常问答、写作辅助,到行程规划、学习辅…

作者头像 李华
网站建设 2026/10/22 13:46:29

提示工程持续部署:从版本化到灰度回滚的完整策略

最近在搭提示工程持续部署机制的时候,我被问得最多的一个问题特别直接:Prompt也要做持续部署?不都是改完直接上线看效果吗?这个问题的背后,其实是很多团队仍然在用“改代码、跑测试、发版”那套传统CD思路来管理人机交…

作者头像 李华
网站建设 2026/10/22 13:46:02

基于Python与OpenCV的智能交通信号灯动态配时优化系统实现

城市路口那套红绿灯,大多数时候是“固定配时”——不管东边堵成什么样,绿灯该亮几秒还是几秒。明明直行车流已经排到上一个路口了,绿灯还得等几十秒才轮到你。这套基于Python与OpenCV的智能交通信号灯优化系统,就是干这个的&#…

作者头像 李华
网站建设 2026/10/22 13:45:36

Kubernetes生态演进全解析:从容器运行时到GitOps的架构变迁

1. 从“能跑起来”到“跑得稳”:K8s生态的认知拐点 刚接触容器编排那会儿,我以为把应用打包成镜像、写个Deployment、暴露一个Service,就算“掌握Kubernetes”了。后来在生产环境里滚了几年才发现,那顶多叫“会用kubectl”&#x…

作者头像 李华
网站建设 2026/10/22 13:42:58

Linux字符设备驱动开发:设备号申请、cdev注册与设备节点创建详解

关注 Linux 驱动开发的朋友,应该都绕不过“字符设备”这个坎。字符设备驱动是驱动开发里最基础也最高频的类型,从按键、触摸屏到传感器,底层基本都是这套框架。而我写这篇文章的原因很直接:不少刚入门的开发者,写完 he…

作者头像 李华