
简介基于YoloV5的手语识别系统是一份可运行、可扩展的目标检测工程面向需要快速掌握手势识别技术的开发者和学生解决从数据标注到实时推理的全链路问题。压缩包共包含181个文件包体大小约49.17MB涵盖75组XML标注与对应JPG手语图片、12个proto自定义算子、2份config训练配置、2个ipynb演示脚本以及训练完成的pb模型权重和checkpoint存档另备有环境配置工具和压缩模型包基本覆盖模型训练与部署所需的关键部件。资源内的目录结构清晰数据、配置、代码和模型分层存放便于按需取用。目前已有640人学习下载适合作为YoloV5迁移学习的基线项目。读者可参考其数据处理方式、模型调参思路和推理流程快速复现手语识别实验也可替换标注数据来扩展到其他手势词汇或相近目标检测任务。1. 手语识别不是语音识别的替代品为什么 YoloV5 是快速可行的切入点“基于YoloV5的手语识别系统”听起来像是一个完整的翻译产品实际工程里它更接近“目标检测 时序投票”的组合先用 YoloV5 把画面里的手部区域框出来再对框内的手势做分类。之所以选 YoloV5 而不是直接上视频理解模型是因为它的生态最省心——yolov5 环境配置、训练、部署的教程足够多遇到问题几乎都有现成答案而手语识别的瓶颈根本不在模型结构而在数据标注和手指遮挡。这个方案适合手里已经有手语视频素材、想在 PC 或树莓派上快速跑出识别原型的工程师不太适合想直接拿到“视频进文字出”完整产品的人。2. 数据与标注让手语识别系统跨过第一道坎手语识别系统跑不通八成不是模型问题而是数据集没做好。YoloV5 对数据的要求很明确每张图片对应一个 txt 标注文件里面记录类别编号和归一化后的中心点坐标、宽高。很多人拿公开数据集下完就训发现 mAP 惨不忍睹其实是因为那些数据集根本不是为 YoloV5 准备的需要先转换。2.1 公开数据集的收集与筛选公开的 MSASL、WLASL、NMFs-CSL 这些手语数据集大多以视频或骨骼关键点形式发布不附带 YOLO 格式的框。直接拿来训练 YoloV5 之前要先把视频抽帧、然后逐帧标注这一步的工作量比训练本身大得多。我的做法是第一版只挑 5~10 个动作差异足够大的手势比如“你好”“谢谢”“停下”“喝水”“请”类别太多时手指形态相近模型很容易混淆。筛选素材时注意背景和光线。如果打算后续部署到树莓派 5 上最好在类似机位的画面里采集否则训练集是在复杂室内背景部署时遇到白墙亮光误检率会立刻上来。一个朴素标准是每个手势保留 500~1000 帧不连续画面覆盖不同角度、远近和肤色但不要贪多连续帧之间的差异太小喂进去只会增加训练时间。2.2 把视频帧做成 YoloV5 训练数据标注格式与目录结构拿到视频后先抽帧。手语动作变化快连续相邻帧高度相似抽帧既能减小数据集体积也能让模型不那么容易被某一帧带偏。我一般每 15 帧抽一帧动作特别快的场景改成每 5 帧。# 每 15 帧保留 1 帧减少相似样本保留画面完整性 mkdir -p hand_frames ffmpeg -i hand_video.mp4 -vf selectnot(mod(n,15)),setptsN/FRAME_RATE/TB -vsync vfr hand_frames/frame_%04d.jpgselect 过滤器里的 n 表示帧序号not(mod(n,15)) 的意思就是只保留帧序号能被 15 整除的帧。setptsN/FRAME_RATE/TB 用来重写时间戳避免输出视频的帧率信息出现跳变不加 -vsync vfr 的话ffmpeg 会把抽出的帧重新按原帧率排列产生大量重复帧。如果抽出来的图模糊多半是原视频帧率太低或者手部运动过快把 15 改成 5 或者 3 即可。抽完帧以后用 LabelImg 之类工具标注。注意 YoloV5 的标签格式是每一行“类别编号 x_center y_center width height”坐标必须是归一化后的 0~1 小数。LabelImg 选择 YOLO 模式保存后会直接生成同名的 txt 文件。我踩过的坑是txt 里的坐标偶尔出现负值或大于 1 的值多半是标注时拖框出界了训练前要清洗一遍。接下来把图片和标签按 YoloV5 约定的目录结构排好import os, random, shutil # 建立 YoloV5 需要的 train/val 下的 images、labels 目录 for split in [train, val]: for sub in [images, labels]: os.makedirs(fhand_dataset/{split}/{sub}, exist_okTrue) frames sorted(os.listdir(hand_frames)) labels { os.path.splitext(f)[0]: f for f in os.listdir(labels) } random.shuffle(frames) split_idx int(len(frames) * 0.8) for i, f in enumerate(frames): split train if i split_idx else val base os.path.splitext(f)[0] shutil.move(fhand_frames/{f}, fhand_dataset/{split}/images/{f}) if base in labels: shutil.move(flabels/{labels[base]}, fhand_dataset/{split}/labels/{labels[base]})这段脚本做的事情是建立 train/val 两个集合各自包含 images 和 labels 子目录再把图片随机按 8:2 划分并带上同名的标注文件。注意它只移动有标注的 txt没有标注的图片要么重新标注要么删除否则训练时 YoloV5 会跳过没有标签的图片白白浪费显存。划分完以后在 hand_dataset 目录下新建一个 hand.yaml# YoloV5 数据描述文件路径支持相对或绝对路径 train: hand_dataset/train/images val: hand_dataset/val/images nc: 5 names: [hello, thanks, stop, drink, please]这个 yaml 是 YoloV5 训练时的入口之一。train 和 val 指向图片目录即可YoloV5 会自己去同级 labels 目录找同名 txt。nc 必须和实际标注的类别数量一致names 顺序也要和标注 txt 里的类别编号一一对应顺序错了训练出来的模型就完全不可用。3. 用 Conda 配置环境并训练自己的数据集从配置到调参数据准备好以后就开始进入 yolov5 环境配置和训练环节。环境这步看着简单实际消耗的时间往往比训练还长因为 torch、torchvision、opencv 版本匹配在电脑上很容易翻车。我习惯先用 Conda 隔离出独立环境避免把系统 Python 搞乱。3.1 YoloV5 环境配置Conda 创建 GPU 环境的完整命令# 创建 Python 3.8 环境命名 yolo避免与系统其他项目冲突 conda create -n yolo python3.8 conda activate yolo # 安装 GPU 版 PyTorchpytorch-cuda11.8 与 NVIDIA 驱动对应不是系统 CUDA conda install pytorch torchvision torchaudio pytorch-cuda11.8 -c pytorch -c nvidia # 进入 yolov5 源码目录后安装剩余依赖 pip install -r requirements.txtconda create -n yolo python3.8 这行里的 python 版本选择有讲究YoloV5 的 6.x 分支在 Python 3.8 上验证最充分3.10 以上版本容易出现 numba 或 pycocotools 编译报错。pytorch-cuda11.8 是 PyTorch 自带的 CUDA 库版本它只要求你的 NVIDIA 驱动支持 11.8 以上而不要求你手动装过 CUDA Toolkit。安装完成后建议先跑一句 python -c import torch; print(torch.cuda.is_available())确认输出 True 再继续。如果这一步报错最常见的是 opencv 版本冲突。YoloV5 的 requirements.txt 里指定了 opencv-python4.1.2但 Conda 在安装其它包时可能顺手把 opencv 降级。解决方式是装完 requirements 后再强制补装一次 opencv-python-headless。用 Conda 而不是直接 pip install 全部依赖的好处是torch 的二进制包在 Conda 里已经绑定了 CUDA 运行时不会出现“torch 是 CPU 版但你以为在用 GPU”这种黑匣子问题。3.2 训练自己的数据集超参数与后处理参数怎么调环境就绪后训练命令长这样python train.py \ --data hand_dataset/hand.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --hyp hand_hyp.yaml \ --cache--data 指向数据描述文件--weights yolov5s.pt 是官方在 COCO 上预训练过的权重用它做初始权重相比从头训练收敛快很多对只有几百张图的手语数据集尤其重要--img 640 表示输入分辨率手语里的手指属于小目标分辨率不能太低但 1280 在普通显卡上显存会爆640 是平衡点--batch 16 要根据显存调整显存不够就降到 8 或 4batch 太小会导致梯度不稳定--epochs 100 对小数据集足够如果验证集 mAP 在 60 轮以后还在涨可以续跑--hyp 指定超参数文件--cache 把图片预加载进内存能减少训练时从磁盘读图的等待。手语识别和通用目标检测不一样的地方在于超参数设置。官方默认的 hyp.scratch-low.yaml 里 mosaic 增强是 1.0意思是每张训练图都由 4 张图拼接而成。mosaic 对小目标检测有提升但手语场景里手指区域占比小拼接后手指被切掉的概率很高模型学到的是“半截手指”推理时反而丢框。我的做法是单独建一个 hand_hyp.yaml把 mosaic 关掉或降到 0.2# 手语识别建议改动的超参数关闭破坏小目标的增强 mosaic: 0.0 degrees: 0.0 translate: 0.2 scale: 0.5 hsv_h: 0.015 hsv_s: 0.5 hsv_v: 0.4mosaic 设为 0.0 后训练每轮看到的图片都是完整的一帧手部不会被裁掉。degrees 控制旋转增强手语手势的朝向有一定变化但旋转角度太大反而会把手势语义扭曲所以直接设 0。translate 平移和 scale 缩放保留较低强度因为实际部署时手部位置不会总在画面正中。hsv 色彩增强对光照变化有帮助但不要调太高否则肤色失真。训练过程中的后处理参数同样值得提前理解。YoloV5 推理时默认 conf_thres 是 0.25iou_thres 是 0.45。手语识别系统我一般把 conf 提到 0.5 以上因为背景里偶尔会出现另一只手或人脸误判成手势置信度高一点宁缺毋滥iou 阈值则可以适当降低到 0.3让 NMS 不要过早合并相邻的候选框免得两个相似手势被压成一个。这个经验和通用检测相反通用检测通常对 iou 更敏感手语因为手指框本身很小、位置抖动大需要给后处理一点冗余。训练时看 terminal 输出的 mAP 和 loss 曲线如果 mAP0.5 能到 0.85 以上说明数据没有大问题接下来要解决的就是部署。4. 部署到树莓派 5 前先看这 4 个避坑点很多人在电脑上把模型训出来以后满怀信心往树莓派 5 上一放结果帧率不到 5检测框还来回抖。这一章把我在部署里遇到最多的四个问题按“现象、原因、解决”写清楚省得再踩一遍。4.1 现象训练正常但检测框在手指部分乱跳损失曲线已经收敛但在实时画面上框住手的矩形框每帧都在抖甚至从手背跳到手指尖再跳回手背。原因通常有两个一是训练时开了 mosaic模型对小目标的手势特征不够稳定二是输入分辨率设得太高导致手部细微抖动被放大。解决方式是在手语数据集上关闭 mosaic并把 --img 从 640 先降到 480 试试。分辨率降低后检测框抖动明显减少代价是远距离手部检出率变低。实际部署时可以用“暂停输出直到框连续 N 帧稳定再判定”的方式效果比单纯调阈值更直接。4.2 现象模型在电脑上 60 帧部署到树莓派只有 3 帧直接拿 PyTorch 模型在树莓派上跑CPU 会一直满载但帧率依然上不去。原因很简单PyTorch 的运行时在树莓派上没有针对 CPU 做太多优化而且默认把整个模型加载进内存推理时的矩阵运算开销太大。解决方法是先把模型导出成 ONNX再用 onnxruntime 推理。导出命令在 yolov5 源码目录里执行python export.py --weights best.pt --include onnx --opset 12导出时要确认输入尺寸固定比如 640x640避免后续部署时动态尺寸影响效率。树莓派 5 上安装 onnxruntime 后同一模型推理速度通常能比 PyTorch 快 3 倍以上。如果还想继续压帧率把 --img 降到 320 或 416或者用树莓派的 NPU 做加速不过那要额外适配不是必须。先用 ONNX 跑通再决定要不要继续优化。4.3 现象转 ONNX/TorchScript 后精度突然下降同一个模型在 PyTorch 里 mAP 有 0.88导出 ONNX 后实测识别率明显变差。原因基本都出在预处理不一致上。YoloV5 训练时使用 letterbox也就是把图片等比缩放后填充到固定尺寸保持宽高比不变。部署时如果直接 cv2.resize 到 640x640手部轮廓会横向或纵向被拉伸模型自然认不出来。解决方式很简单部署代码里也调用 YoloV5 源码中的 letterbox 函数先缩放再填充再把输入数据除以 255 做归一化。填充色用 114这个值是 YoloV5 训练时的默认值改了也会掉点。4.4 现象识别手语视频中的动作变化总是晚半拍逐帧检测模型本身没有时序记忆手语从“你好”切到“谢谢”的过程中中间过渡帧可能会被识别成两者之一输出结果在两种手势之间跳来跳去。很多人的第一反应是加大置信度阈值但实际上更有效的办法是加一个滑动窗口投票。给一个非常轻量的实现from collections import deque votes deque(maxlen5) def decide(cls): votes.append(cls) # 最近 5 帧里至少 3 帧输出同一类别才认为手势切换完成 if votes.count(cls) 3: return cls return Nonevotes 保存最近 5 帧的检测类别decide 函数要求同一个类别至少出现 3 次才返回结果。在 30fps 的视频流里5 帧窗口对应约 0.17 秒的延迟用户体感基本无感却能把手语切换动作中的抖动滤掉。如果部署在低帧率设备上把窗口缩小到 3 帧、要求 2 帧一致否则延迟会累加。5. 用 YoloV5 后处理接口做手语级联筛选一个实测技巧当模型在树莓派上跑通之后你会发现真正的难点已经不在模型的 mAP而在于如何把手语手势从大量误检中挑出来。手语识别系统里画面中经常出现人脸、手臂、背景里的其他手YoloV5 后处理里只调阈值是远远不够的。我习惯在得到检测框以后再做一道级联筛选用框的几何特征过滤明显不合理的结果。# 在 YoloV5 推理结果上继续做规则筛选过滤细长误检与低置信度框 for det in results.xyxy[0]: x1, y1, x2, y2, conf, cls det.tolist() w, h x2 - x1, y2 - y1 if h w * 2.5: continue if conf 0.55: continue print(f手势类别 {int(cls)}置信度 {conf:.2f})这里 h w * 2.5 是在过滤竖直方向的细长框比如手指尖或手臂的一部分被框住的情况。手语手势的框一般来说接近正方形如果你的标注框本来就是横向的这个比例要按你自己的数据集调整。置信度阈值 0.55 是一个起点实际部署中如果误检率还高可以调到 0.65如果漏检多了降到 0.45 也没问题。验证这个筛选是否有效我建议准备一段 15 秒左右、包含“你好”“谢谢”“停下”三个手势、中间穿插说话和走动画面的视频跑一遍统计每个手势的命中帧数和切换延迟。命中帧数要高于 70%切换延迟不超过 0.3 秒才算能交给业务侧使用。当年我刚开始做时只调 conf_thres 不管框的几何特征误检率怎么都压不下来后来把这一步加进后处理管线误检率直接降到 3% 以下。这个小习惯一直保留到现在希望帮到你。本文还有配套的精品资源点击获取