简介:基于YoloV5的手语识别系统项目包,面向计算机视觉、无障碍交互开发者和学习者,解决手部关键特征定位与手语词汇实时分类问题。包内共181个文件,压缩后约49.17MB,主要包含75组图像与XML标注、模型配置与训练权重、Python脚本及Jupyter Notebook等;XML标注用于训练目标检测模型,配置与权重文件可直接加载推理,Notebook则方便分步复现实验流程,整体目录结构清晰,便于按需检索。该资源配套详细的项目解读,从预处理、特征提取、模型训练到识别应用均有说明,可支撑教学演示、课程设计或手势交互场景的二次开发。已有640人学习下载,适合需要YoloV5具体落地方案的研究者和开发者参考实践。
1. 把“找手”和“认手型”分开,手语识别才能落地
在摄像头画面里做手语识别,最容易踩的坑不是算法选型,而是把“识别手语”理解成“识别整个人”。手语的有效信息集中在手掌、手指和手腕,背景、手臂晃动、人物移动都是噪声。YoloV5解决的是第一步:在画面里定位到手,并给出一个类别置信度,把这个检测结果当作整条识别流水线的输入,比用图像分类直接对全图打分要可靠得多。
手语识别系统真正适合的技术路径是单帧检测加后续时序处理。YoloV5的COCO预训练权重已经学会了通用的物体特征,迁移到“手掌”这种小目标上收敛很快;用几百张自采数据就能得到一个可演示的原型。本文面向准备自己采集数据、训练模型并最终部署到边缘设备(比如树莓派5)的工程师,讲清楚数据、训练、部署这条线,以及每步的取舍。
2. 手语数据采集与YoloV5环境配置:先凑齐能训练的样本
2.1 手语视频拆帧与Yolo标注的细节
手语识别的训练数据不推荐去网上下载现成图片集。手语的表达存在地域差异,不同人打同一手势的节奏和形态都不一样,网图很难覆盖实际使用场景。常见做法是自己录制视频再拆帧。录一段手势视频,用ffmpeg按固定帧率抽帧:
ffmpeg -i sign_hello.mp4 -vf "fps=15,scale=640:-1" frames/frame_%04d.jpgfps设成15而不是30,是因为手语动作中手掌形变较慢,每秒15帧已经能覆盖完整轮廓;30帧会产生大量重复相近帧,标起来浪费人力。scale=640:-1把画面缩放到640宽,这一步可以提前模拟YoloV5训练时的输入尺度,让标注框更接近后续实际训练分布。
标注工具用LabelImg,输出格式选YOLO。标注框要贴着手指尖和手腕,不要框到小臂。手语识别里最常见的标注错误是把手掌下方的小臂一起框进去,引入多余纹理干扰。建议每个类别采集30到60段不同角度、不同距离的视频,并保证背景有深浅变化。数据总量不需要像通用目标检测那样动辄上万张,手部特征在同一个人身上相对稳定,几百张已经能启动训练。注意手语词汇中静态手型占多数,动态词汇(例如“谢谢”这类有明显轨迹变化的)在单帧检测里根本无法区分,这类数据要么不纳入一期,要么给后续时序模型单独准备。
2.2 用conda固定YoloV5训练环境,避免依赖漂移
环境配置是复现的第一道坎。YoloV5对依赖版本敏感,尤其是numpy和opencv在版本升级后经常出现推理结果异常。我一般用conda独立环境隔离项目:
conda create -n sign_yolov5 python=3.9 -y conda activate sign_yolov5 pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simplerequirements.txt从YoloV5官方仓库里拿。如果你用的是ultralytics的整合版,安装最省事:
pip install ultralytics注意PyTorch的安装方式。训练机有NVIDIA显卡时,先到PyTorch官网挑对应CUDA版本的安装命令再装torch和torchvision,最后装其他依赖;训练机上只有CPU时不要指望训练速度,手语数据集即使只有几百张图,CPU上跑100个epoch也够等。
装完环境后用官方预训练权重快速验证环境正常:
python detect.py --weights yolov5s.pt --source data/images/bus.jpg这条命令会下载yolov5s.pt并跑出一张带检测框的图片。若这一步报错,优先检查torch和torchvision版本是否匹配,以及opencv是否正常。
2.3 数据集目录结构与data.yaml配置
YoloV5要求图片和标签分开存放,用data.yaml描述数据集路径:
path: /home/user/hand_sign train: images/train val: images/val nc: 10 names: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']path建议写成绝对路径,比相对路径稳。目录结构固定如下:
hand_sign/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yamltrain和val的比例按80/20切。每个jpg对应一个同名txt标签,里面每行是“类别序号 x_center y_center width height”,四个坐标值都归一化到0到1之间。LabelImg会自动生成这个格式,不用手写。
这里有个容易踩的坑:标签类别序号必须和data.yaml里的names顺序一致。比如names里第3项是‘2’,那么标签文件里类别序号为2的框代表的是‘2’这个手势,而不是数字2。如果你中途调整过names顺序,必须回头改标签文件,否则训练不会报错,但识别结果全部错位。
3. 训练YoloV5手语识别模型与超参数调整
3.1 选yolov5s还是yolov5n:小目标的网络结构考量
训练手语模型前先理解YoloV5网络结构图的基本盘。Backbone是CSPDarknet,负责从输入图像中提取多层特征;Neck是PANet,把浅层的位置信息和深层的语义信息融合;Head部分输出三个尺度的检测结果,分别对应小、中、大目标。
手语识别中“手”在画面里属于中偏小目标,依赖大尺寸特征图。官方提供yolov5n、yolov5s、yolov5m、yolov5l这几种尺寸,n和s的Backbone更薄,推理更快;m和l精度更高但开销大。对于静态手型识别,yolov5s是性价比起点;如果目标是部署到树莓派5这类ARM设备,建议直接用yolov5n训练,减少后续模型转换时的精度损失。
3.2 YoloV5训练命令与超参数表
在提前准备好的conda环境里执行训练:
python train.py \ --data hand_sign/data.yaml \ --cfg models/yolov5s.yaml \ --weights yolov5s.pt \ --epochs 100 \ --batch-size 16 \ --imgsz 640 \ --hyp hyp.scratch-low.yaml五个关键参数说明:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| epochs | 100 | 小数据集100轮足够;超过200轮容易过拟合,除非每轮都做增强 |
| batch-size | 16 | 显存不够就调成8;batch-size减小时初始学习率也应同步调低 |
| imgsz | 640 | 训练输入分辨率;手部小目标用640,不要降到416以下 |
| hyp | hyp.scratch-low.yaml | 基础增强策略,手语不建议上high增强 |
| weights | yolov5s.pt | 使用COCO预训练权重做迁移学习 |
超参数表是YoloV5调优的核心。打开hyp.scratch-low.yaml,重点关注下面几个值:
| 超参数 | 默认值 | 手语场景建议 |
|---|---|---|
| lr0 | 0.01 | 迁移学习时可保持,收敛快 |
| warmup_epochs | 3.0 | 前3轮学习率从小往上升,防震荡 |
| fliplr | 0.5 | 左右翻转增强,手语里可能导致左右手混淆,改为0 |
| degrees | 0.0 | 旋转角度,0到5足够应对摄像头晃动 |
| hsv_h | 0.015 | 色相扰动,手语肤色变化小,保持默认 |
| mosaic | 1.0 | 四图拼接增强,对小目标有效,但标签多时显存占用高 |
fliplr是手语识别里必须关注的参数。6和7这类手势在左右翻转后视觉形态基本不变,但类别含义完全不同;如果训练集里同时存在左右手习惯的手势,建议把fliplr直接设为0。degrees和perspective同理,手语拍摄基本是正面平视,过度旋转增强只会让模型学到不存在的视角。
3.3 训练日志解读:判断过拟合与欠拟合
训练完成后看runs/train/exp/下的results.png和混淆矩阵,不要只看loss。手语模型的重点指标是验证集上的precision和recall:
- precision高、recall低,说明模型把框打得保守,手部区域大量漏检,需要补充不同角度和比例的训练样本;
- precision低、recall高,说明模型框了一堆背景噪声,需要检查标签框是否贴手太宽;
- 两个都低,首先怀疑标注错位和类别数量不均衡,而不是调超参。
如果训练到后期loss在训练集上持续下降、验证集loss反而回升,这是过拟合的明确信号。手语数据量少时尤其明显,应对策略是减少epochs到原先的2/3,或者提高mosaic增强的样本权重,不要盲目加数据。
模型保存为runs/train/exp/weights/best.pt,后续导出都用这个文件而不是last.pt。
4. 部署YoloV5模型到树莓派5:边缘端实时推理
4.1 导出ONNX模型并固定opset版本
树莓派5上不会直接跑PyTorch模型,PyTorch的运行时和算子实现在ARM上太重。常规路线是先把best.pt导出为ONNX,再通过ONNX Runtime在树莓派5上推理。导出命令:
python export.py \ --weights runs/train/exp/weights/best.pt \ --include onnx \ --opset 12 \ --simplifyopset 12是一个稳定且兼容性好的算子集版本,树莓派5上的ONNX Runtime对它的支持完整。simplify参数会移除计算图冗余节点,减少模型体积和推理时间。
导出后用检查工具确认输出名和shape:
python -c "import onnx; m=onnx.load('best.onnx'); print([o.name for o in m.graph.output])"手语模型输入一般是“images:1x3x640x640”,输出是“output0:1x25200x15”。25200是三个尺度特征图上的anchor数量总和,15代表“4个坐标 + 1个置信度 + 10个类别”。看到这个输出就能确认导出正常。
4.2 树莓派5上的运行环境与推理代码
树莓派5安装运行依赖:
python -m venv ~/sign_env source ~/sign_env/bin/activate pip install onnxruntime opencv-python-headless numpy推荐使用opencv-python-headless而非标准opencv-python,前者不携带GUI依赖,在无桌面环境下启动快。ONNX Runtime用CPU版即可,树莓派5没有可用的GPU加速后端。
推理主力代码:
import cv2 import numpy as np import onnxruntime as ort import time session = ort.InferenceSession("best.onnx", providers=["CPUExecutionProvider"]) input_name = session.get_inputs()[0].name input_shape = session.get_inputs()[0].shape # [1, 3, 640, 640] def letterbox(img, size=640): h, w = img.shape[:2] r = min(size / h, size / w) new_w, new_h = int(w * r), int(h * r) resized = cv2.resize(img, (new_w, new_h)) canvas = np.full((size, size, 3), 114, dtype=np.uint8) canvas[0:new_h, 0:new_w] = resized return canvas, rletterbox将原图等比例缩放并填充灰色边框,保持宽高比不变。填充色114是YoloV5训练时的默认像素值,推理时不要随意改成黑色或白色,会影响检测精度。
预处理与后处理:
def preprocess(img): img, r = letterbox(img, 640) img = img[:, :, ::-1].transpose(2, 0, 1) # BGR转RGB并变为CHW img = np.ascontiguousarray(img, dtype=np.float32) img /= 255.0 img = np.expand_dims(img, axis=0) return img, r def postprocess(output, r, conf_thres=0.35, iou_thres=0.45): output = output[0] # shape [25200, 15] boxes, scores, class_ids = [], [], [] for row in output: class_scores = row[5:] max_score = class_scores.max() if max_score < conf_thres: continue x, y, w, h = row[:4] boxes.append([(x - w/2) / r, (y - h/2) / r, (x + w/2) / r, (y + h/2) / r]) scores.append(float(max_score)) class_ids.append(int(class_scores.argmax())) if not boxes: return [], [], [] # NMS用cv2.dnn.NMSBoxes做,置信度阈值和IoU阈值分开控制 idxs = cv2.dnn.NMSBoxes(boxes, scores, conf_thres, iou_thres) ...conf_thres设为0.35是经验值。手语检测中手部与背景对比度低,阈值太高容易漏检;低于0.3又会产生大量误报,适合在摄像头画面里保留一个手势时使用0.35到0.4之间。
摄像头循环:
cap = cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) while True: ret, frame = cap.read() if not ret: break start = time.time() input_data, r = preprocess(frame) output = session.run(None, {input_name: input_data})[0] boxes, scores, ids = postprocess(output, r) for box, score, cls_id in zip(boxes, scores, ids): x1, y1, x2, y2 = [int(v) for v in box] cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, class_names[cls_id], ...) cv2.imshow("sign", frame) if cv2.waitKey(1) == ord("q"): break识别结果直接输出为中文名会有字体渲染问题,建议类别名在训练时就使用英文,推理时再通过映射表转成中文显示。
4.3 树莓派5上的速度优化参数
树莓派5实测数据参考:输入640的yolov5s在CPU上大约6到10 FPS,输入416的yolov5n大约12到18 FPS。手语识别场景里,识别结果不需要每帧都刷新,帧率维持在10 FPS以上就能保证交互不卡顿。
优化方向有四个;一是把imgsz降到416,损失一点小目标精度换将近一倍帧率;二是把后处理中的循环向量化,减少Python层开销;三是打开ONNX Runtime的图优化,它默认开启,不需要额外配置;四是如果使用树莓派5的官方摄像头,优先用libcamera而不是OpenCV的V4L2后端。
5. 视频回放验证与误识别排查的三个具体技巧
5.1 用录屏回放而不是实时盯屏判断结果
实时看摄像头窗口很难注意到单帧闪断。正确做法是先把识别结果逐帧写入视频文件,然后回放对照:
fourcc = cv2.VideoWriter_fourcc(*"mp4v") writer = cv2.VideoWriter("result.mp4", fourcc, 10, (640, 480)) # 每帧推理后写入 writer.write(frame),结束后再统一播放回放时重点观察频繁出现的标签跳变。比如某个手语手势在连续几帧内从“1”跳到“7”又跳回“1”,这不是模型不稳定,而是这两个手势的形态特征在某个角度下高度相似。处理办法是给输出结果加一个简单的滑动窗口滤波,取最近5帧中出现次数最多的类别作为当前识别结果。
5.2 手语识别独有的三类误测:翻转、肤色与动态词汇
第一类是翻转类误测。YoloV5训练的fliplr增强会让模型把左右手镜像当成同一物体,如果数据集同时存在“6”和“9”这类仅靠朝向区分的数字,就会产生系统性混淆。处理方法是在训练前统计类别间的混淆矩阵,对混淆度高的类别对重新检查标注框是否贴合指尖。
第二类是肤色与背景融合。当手部颜色和衣服或墙面色调接近时,检测框会漂移到整个手掌的一半。检查方式是把训练集中的暗光照片单独筛出来看avg置信度,低于0.3就要补充光照样本,而不是调低conf_thres。
第三类是动态手语词汇。YoloV5本身是单帧检测器,对于“谢谢”“请”这类包含动作轨迹的手语,单帧识别天然不适用。如果你想识别这类词汇,正确的做法是在YoloV5外面套一个滑动窗口加LSTM的分类器,把连续30帧的手部检测框序列作为输入;单帧模型只负责提供定位,不负责对动态词汇下结论,这个边界要在系统设计时就讲清楚。
5.3 检查手部在画面中的像素占比
最后给一个可复用的检查脚本,统计训练集中所有标注框占图片面积的百分比,用来判断手部是否属于小目标:
python analyze_annotations.py --labels hand_sign/labels/train脚本统计所有标签文件中每一行width和height的平均值,如果平均框宽高低于图片尺寸的10%,说明目标过小。此时优先把训练输入分辨率提高,或者在数据采集阶段把摄像头靠近手部。记住,YoloV5能识别的精度上限由训练数据里目标的像素密度决定,单纯调超参数救不了像素不足的问题。
本文还有配套的精品资源,点击获取