☰
基于PyTorch的交警手势识别:8类手势分类实战与部署
2026/10/2 20:07:54 网站建设 项目流程

简介:本资源面向深度学习入门者与计算机视觉开发者,提供一套基于PyTorch实现中国交通警察8种指挥手势识别的完整项目方案,可用于智能交通场景下的手势自动化识别学习与复现。压缩包共34个文件,约4.42MB,以31个Python脚本为核心,涵盖模型定义、训练循环、评估预测与数据处理流程,另含2个Markdown说明文档和1个演示GIF,便于快速理解项目结构与运行效果。项目围绕手势识别数据集、卷积神经网络模型、训练与推理代码及详细操作步骤展开,涉及关键点检测、姿态估计、数据增强与模型优化等模块,读者可据此掌握从数据预处理到模型部署的完整链路。目前已有1345人学习下载,适合希望以实战方式理解PyTorch视觉识别任务、并需要可运行参考代码的开发者。

1. 从一段路口监控说起:这套交警手势识别资源到底能干什么

路口监控里,交警站在车流中央打手势,左转待转、直行、变道、停止,动作幅度不大,背景还全是车和人。想用视觉模型把这 8 类指挥手势实时分出来,难点从来不在模型结构本身,而在数据怎么标、类别怎么分、训练怎么不翻车。这份基于 PyTorch 的中国交通警察指挥 8 种手势识别资源,打包了源码、数据集、训练好的模型和一份详细项目说明,属于那种拿到手就能跑、跑完还能改成自己场景的完整工程包。

它解决的是「从零搭一套手势分类流水线」这件事:数据读取、增强、模型定义、训练循环、评估、推理脚本一条龙。适合两类人——一类是刚学完 PyTorch 基础、想找一个真实多分类项目练手的新手,跟着步骤能复现出可用的识别结果;另一类是做智能交通、安防或边缘部署的从业者,想拿现成数据集和 baseline 快速验证自己的改进思路。下面我按「资源是什么 → 怎么跑起来 → 参数怎么调 → 坑在哪」的顺序拆一遍,重点放在能抄作业的代码和参数上。

2. 拆开压缩包:8 类手势的数据集结构与 PyTorch 读取方案

拿到资源先别急着python train.py,把目录结构和数据格式看清楚,后面调参和排错才有依据。这类手势识别项目通常按类别分文件夹存放图像,配合一个train/val划分或 CSV 索引文件。理解数据组织方式,直接决定你写 Dataset 时怎么取路径、怎么做标签映射。

2.1 目录结构与类别定义

常见做法是dataset/下按 8 个手势类别各建一个子目录,目录名就是类别名,PyTorch 的ImageFolder能直接吃这种结构。8 类手势一般对应:停止、直行、左转弯、左转弯待转、右转弯、变道、减速慢行、车辆靠边停车。实际类别名以资源里的文件夹为准,不要自己臆造,训练前先ls一遍确认。

# 查看数据集目录结构,确认类别文件夹数量 find dataset -maxdepth 2 -type d | sort # 统计每个类别的图片数量,判断是否存在类别不平衡 for d in dataset/*/; do echo -n "$d: "; ls "$d" | wc -l; done

第一段命令列出两级目录,确认 8 个类别文件夹都在;第二段循环统计每类图片数。如果某类只有几十张、另一类上千张,就是典型的类别不平衡,后面训练要加权重或重采样,否则模型会偏向多数类,少数手势几乎识别不出来。

2.2 用 Dataset 和 DataLoader 接管数据流

不依赖ImageFolder的隐式行为,自己写一个 Dataset 更可控,尤其是要做自定义增强或按 CSV 读取时。下面这段是这类项目里最通用的写法,直接可抄。

import os from PIL import Image from torch.utils.data import Dataset, DataLoader from torchvision import transforms class GestureDataset(Dataset): def __init__(self, root, transform=None): self.samples = [] self.classes = sorted(os.listdir(root)) # 类别名排序,保证标签稳定 self.class_to_idx = {c: i for i, c in enumerate(self.classes)} for c in self.classes: c_dir = os.path.join(root, c) for fname in os.listdir(c_dir): if fname.lower().endswith(('.jpg', '.png', '.jpeg')): self.samples.append((os.path.join(c_dir, fname), self.class_to_idx[c])) self.transform = transform def __len__(self): return len(self.samples) def __getitem__(self, idx): path, label = self.samples[idx] img = Image.open(path).convert('RGB') # 统一转 RGB,防止灰度图报错 if self.transform: img = self.transform(img) return img, label train_tf = transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(), # 手势左右翻转要谨慎,见避坑章 transforms.ColorJitter(0.2, 0.2, 0.2), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) ds = GestureDataset('dataset/train', transform=train_tf) loader = DataLoader(ds, batch_size=32, shuffle=True, num_workers=4)

classes用sorted排序是为了让标签索引在训练和推理时一致,否则换台机器类别顺序变了,预测结果全错位。convert('RGB')是血泪经验,数据集里混进灰度图或带 alpha 通道的 PNG 时,不转直接进网络会报通道数不匹配。Normalize用的是 ImageNet 均值方差,因为后面多半用预训练 backbone,保持一致才能吃到预训练权重的红利。num_workers在 Windows 上建议设 0 或 2,设大了容易卡在启动阶段。

2.3 训练集与验证集的划分策略

如果资源只给了一个总目录,需要自己切分。按 8:2 分层抽样,保证每个类别在验证集里都有样本。

from sklearn.model_selection import train_test_split all_samples = ds.samples labels = [s[1] for s in all_samples] train_s, val_s, _, _ = train_test_split( all_samples, labels, test_size=0.2, stratify=labels, random_state=42)

stratify=labels是关键参数,它保证切分后验证集里 8 类比例和原始一致。不加这个参数,随机切分可能让某个小类别在验证集里一张都没有,评估指标就成了玄学。random_state固定住,方便复现和对比不同模型。

3. 模型选型与训练循环:从 ResNet 到 8 分类输出

数据管道通了,接下来是模型和训练。这类手势识别项目通常不会从零设计网络,而是拿预训练 backbone 改分类头,收敛快、精度稳。选型理由和训练细节决定了你能不能在一张普通显卡上跑出可用结果。

3.1 为什么用预训练 ResNet 而不是自己搭 CNN

8 类手势、每类样本量通常几百到几千,这个规模从零训练一个深层 CNN 很容易过拟合,验证集精度上不去。常见做法是加载 ImageNet 预训练的 ResNet18 或 ResNet50,把最后的全连接层换成 8 输出。ResNet18 参数量约 1100 万,显存占用低,适合低显存运行模型;ResNet50 精度略高但显存翻倍。我一般先用 ResNet18 跑通全流程,确认数据和标签没问题,再换大模型对比。

import torch import torch.nn as nn from torchvision import models def build_model(num_classes=8, backbone='resnet18', pretrained=True): if backbone == 'resnet18': model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT if pretrained else None) in_features = model.fc.in_features else: model = models.resnet50(weights=models.ResNet50_Weights.DEFAULT if pretrained else None) in_features = model.fc.in_features model.fc = nn.Linear(in_features, num_classes) # 替换分类头为 8 类 return model device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = build_model(8, 'resnet18').to(device)

weights=DEFAULT是新版 torchvision 的写法,老版本用pretrained=True,两者别混用否则报参数冲突。替换model.fc后,新分类头是随机初始化的,训练时建议给 backbone 设小学习率、给分类头设大学习率,避免预训练特征被一开始的大梯度冲垮。

3.2 训练循环与关键超参数

训练循环本身不复杂,关键是损失函数、优化器和学习率调度怎么配。多分类用交叉熵,类别不平衡时加weight参数。

import torch.optim as optim from torch.optim.lr_scheduler import CosineAnnealingLR criterion = nn.CrossEntropyLoss() optimizer = optim.AdamW([ {'params': [p for n, p in model.named_parameters() if 'fc' not in n], 'lr': 1e-4}, {'params': model.fc.parameters(), 'lr': 1e-3}, ], weight_decay=1e-4) scheduler = CosineAnnealingLR(optimizer, T_max=30) for epoch in range(30): model.train() running_loss = 0.0 for imgs, labels in loader: imgs, labels = imgs.to(device), labels.to(device) optimizer.zero_grad() outputs = model(imgs) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() scheduler.step() print(f'epoch {epoch+1}, loss {running_loss/len(loader):.4f}')

分组学习率是这套代码的核心:backbone 用 1e-4 微调,分类头用 1e-3 快速学习。AdamW比Adam的权重衰减更规范,weight_decay=1e-4抑制过拟合。CosineAnnealingLR让学习率按余弦曲线下降,T_max设成总 epoch 数,训练后期自动收小步长。如果验证集精度几个 epoch 不涨,先别怀疑模型,去看学习率是不是太大导致震荡。

3.3 验证与指标观察

训练时同步跑验证集,盯住准确率和混淆矩阵,比只看 loss 有用得多。

from sklearn.metrics import classification_report def evaluate(model, val_loader): model.eval() preds, gts = [], [] with torch.no_grad(): for imgs, labels in val_loader: imgs = imgs.to(device) out = model(imgs) preds.extend(out.argmax(1).cpu().numpy()) gts.extend(labels.numpy()) print(classification_report(gts, preds, target_names=ds.classes)) evaluate(model, val_loader)

classification_report会给出每类的 precision、recall、f1。手势识别里最容易混的是「左转弯」和「左转弯待转」,如果这两类 f1 明显低,说明特征区分度不够,要么加数据,要么在增强上做文章。model.eval()和torch.no_grad()必须加,否则 BatchNorm 和 Dropout 行为不对,验证结果不可信。

4. 推理部署与参数调优:让模型在真实图片上跑起来

训练完拿到权重只是半程,真正落地要能对单张图、一批图甚至视频流做推理。这一章讲推理脚本怎么写、阈值和输入尺寸怎么定,以及怎么把模型导出成部署友好的格式。

4.1 单图与批量推理脚本

推理脚本要复用训练时的预处理,否则输入分布不一致,精度会掉一大截。

from PIL import Image import torch.nn.functional as F infer_tf = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) def predict(img_path, model, classes): model.eval() img = Image.open(img_path).convert('RGB') x = infer_tf(img).unsqueeze(0).to(device) # 增加 batch 维度 with torch.no_grad(): logits = model(x) prob = F.softmax(logits, dim=1) conf, idx = prob.max(1) return classes[idx.item()], conf.item() label, score = predict('test.jpg', model, ds.classes) print(f'{label} {score:.3f}')

推理时的Resize和Normalize必须和验证集完全一致,训练用了RandomHorizontalFlip这类随机增强,推理时不能带。unsqueeze(0)补上 batch 维度,因为模型期望输入是[N, C, H, W]。softmax后取最大概率,conf低于 0.6 时建议人工复核,别硬信模型输出。

4.2 输入尺寸与置信度阈值怎么定

输入尺寸不是越大越好。224×224 是预训练模型的默认尺寸,改成 320 或 448 可能涨点,但显存和推理耗时同步上升。低显存运行模型时,优先保 224,把 batch size 压到 8 或 16。置信度阈值要按业务定:安防场景宁可漏报不可误报,阈值设 0.7 以上;辅助提示场景可以放宽到 0.5。

参数常用值影响
输入尺寸224×224与预训练一致,显存友好
batch size16~32越大越吃显存,影响 BN 统计
置信度阈值0.5~0.7高阈值减少误报,低阈值减少漏报
num_workers0~4Windows 设 0 更稳

4.3 导出 ONNX 做跨平台部署

想在 C++ 或边缘设备上跑,导出 ONNX 是常见做法。

dummy = torch.randn(1, 3, 224, 224).to(device) torch.onnx.export( model, dummy, 'gesture.onnx', input_names=['input'], output_names=['output'], dynamic_axes={'input': {0: 'batch'}, 'output': {0: 'batch'}}, opset_version=12)

dynamic_axes让 batch 维度可变,部署时能一次处理多张图。opset_version=12兼容性较好,版本太高部分推理引擎不认。导出后务必用onnxruntime跑一张图,和 PyTorch 输出对比,误差在 1e-3 以内才算导出成功,这一步是后悔药,不做后面部署出问题很难定位。

5. 避坑与常见问题排查:这些翻车点我替你踩过了

这套流程跑通不难,难的是中间各种报错和精度异常。下面 5 条是这类手势识别项目里出现频率最高的坑,按「现象 → 原因 → 解决」写清楚。

5.1 现象:训练 loss 不降,准确率停在 12.5% 左右

原因:8 分类随机猜的准确率就是 12.5%,说明模型没学到东西。最常见的是标签错位——classes排序和标签映射不一致,或者数据加载时路径和标签对不上。其次是学习率过大,梯度爆炸把权重打飞。

解决:先打印一个 batch 的图片和标签,肉眼确认图和标签对应;再把学习率降到 1e-4 试跑几个 epoch。如果 loss 还是平的,检查Normalize是否和预训练权重匹配。

5.2 现象:验证集精度很高,实际推理全错

原因:训练和推理的预处理不一致。训练时用了RandomHorizontalFlip,推理时忘了去掉,或者推理脚本的Resize尺寸和训练不同。另一个隐蔽原因是类别顺序:训练时sorted得到一种顺序,推理时用os.listdir得到另一种顺序,标签整体错位。

解决:把预处理管道抽成一个函数,训练和推理共用;类别列表存成classes.json,训练和推理都从同一个文件读,杜绝顺序漂移。

5.3 现象:显存不足,报 CUDA out of memory

原因:batch size 太大、输入尺寸太大,或者验证时忘了torch.no_grad()导致计算图累积。

解决:先把 batch size 减半,再把输入尺寸降到 224。验证和推理阶段必须包在torch.no_grad()里。如果还不行,用梯度累积模拟大 batch,或者换 ResNet18 这种轻量 backbone。

5.4 现象:某个手势类别几乎识别不出来

原因:类别不平衡,小类别样本太少,模型偏向多数类。也可能是该类手势本身动作相似度高,特征区分度不够。

解决:给CrossEntropyLoss加weight参数,按类别频率倒数设权重;或者对小类别做重采样。增强上针对小类别多做旋转、缩放,但左右翻转要谨慎——交警手势有方向性,翻转后「左转弯」变「右转弯」,标签就错了。

5.5 现象:Windows 上 DataLoader 卡死或报多进程错误

原因:num_workers大于 0 时,Windows 的进程启动方式和 Linux 不同,容易在脚本入口处递归启动。

解决:把训练代码放进if __name__ == '__main__':保护块,num_workers设 0 或 2。这是跨平台跑 PyTorch 的老问题,不是代码逻辑错。

6. 进阶技巧:用混淆矩阵反推数据问题,把精度再抬一档

跑通全流程后,想再涨点,别急着换更大的模型,先看混淆矩阵。我一般会画一张 8×8 的归一化混淆矩阵,横轴预测、纵轴真实,对角线越亮越好。哪两个类别之间亮,就说明模型分不清它们。手势识别里「左转弯」和「左转弯待转」、「减速慢行」和「停止」经常互相混,这不是模型容量不够,是数据本身边界模糊。

import numpy as np import matplotlib.pyplot as plt from sklearn.metrics import confusion_matrix cm = confusion_matrix(gts, preds, normalize='true') plt.imshow(cm, cmap='Blues') plt.colorbar() plt.xticks(range(8), ds.classes, rotation=45) plt.yticks(range(8), ds.classes) plt.xlabel('Predicted'); plt.ylabel('True') plt.tight_layout(); plt.savefig('cm.png', dpi=150)

normalize='true'按真实类别归一化,这样每行加起来是 1,能直接看出某类被误分到哪去的比例。如果「左转弯待转」有 40% 被预测成「左转弯」,那就针对这两类补数据,或者设计一个二阶段分类器专门区分它们。

另一个技巧是测试时增强(TTA):对同一张图做几种不改变语义的变换,把预测概率平均。手势识别里可以用多尺度缩放,比如 224 和 256 各跑一次取平均,通常能涨 1~2 个点,代价是推理耗时翻倍。

def predict_tta(img_path, model, classes, sizes=(224, 256)): model.eval() img = Image.open(img_path).convert('RGB') probs = [] for s in sizes: tf = transforms.Compose([ transforms.Resize((s, s)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) x = tf(img).unsqueeze(0).to(device) with torch.no_grad(): probs.append(F.softmax(model(x), dim=1)) avg = torch.stack(probs).mean(0) conf, idx = avg.max(1) return classes[idx.item()], conf.item()

TTA 的前提是模型对尺度变化鲁棒,如果训练时只用了 224,直接上 256 可能反而掉点,所以要先在小验证集上试。这套资源里如果带了训练好的模型,建议先原样跑一遍 baseline,记下准确率,再逐项加 TTA、加权重、换 backbone,每次只改一个变量,才知道是哪一步起了作用。

从那以后我每次拿到新的分类数据集,都强制先跑一遍混淆矩阵再谈调参,因为大部分精度问题根本不在模型,而在数据和标签。希望这套交警手势识别资源能帮你把 PyTorch 多分类的完整链路走通,少走几个我当年踩过的弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询