ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于PyTorch的交警手势识别:8类手势分类实战与部署

基于PyTorch的交警手势识别:8类手势分类实战与部署 简介本资源面向深度学习入门者与计算机视觉开发者提供一套基于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, transformNone): 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, transformtrain_tf) loader DataLoader(ds, batch_size32, shuffleTrue, num_workers4)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_size0.2, stratifylabels, random_state42)stratifylabels是关键参数它保证切分后验证集里 8 类比例和原始一致。不加这个参数随机切分可能让某个小类别在验证集里一张都没有评估指标就成了玄学。random_state固定住方便复现和对比不同模型。3. 模型选型与训练循环从 ResNet 到 8 分类输出数据管道通了接下来是模型和训练。这类手势识别项目通常不会从零设计网络而是拿预训练 backbone 改分类头收敛快、精度稳。选型理由和训练细节决定了你能不能在一张普通显卡上跑出可用结果。3.1 为什么用预训练 ResNet 而不是自己搭 CNN8 类手势、每类样本量通常几百到几千这个规模从零训练一个深层 CNN 很容易过拟合验证集精度上不去。常见做法是加载 ImageNet 预训练的 ResNet18 或 ResNet50把最后的全连接层换成 8 输出。ResNet18 参数量约 1100 万显存占用低适合低显存运行模型ResNet50 精度略高但显存翻倍。我一般先用 ResNet18 跑通全流程确认数据和标签没问题再换大模型对比。import torch import torch.nn as nn from torchvision import models def build_model(num_classes8, backboneresnet18, pretrainedTrue): if backbone resnet18: model models.resnet18(weightsmodels.ResNet18_Weights.DEFAULT if pretrained else None) in_features model.fc.in_features else: model models.resnet50(weightsmodels.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)weightsDEFAULT是新版 torchvision 的写法老版本用pretrainedTrue两者别混用否则报参数冲突。替换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_decay1e-4) scheduler CosineAnnealingLR(optimizer, T_max30) 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(fepoch {epoch1}, loss {running_loss/len(loader):.4f})分组学习率是这套代码的核心backbone 用 1e-4 微调分类头用 1e-3 快速学习。AdamW比Adam的权重衰减更规范weight_decay1e-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_namesds.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, dim1) 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 size1632越大越吃显存影响 BN 统计置信度阈值0.50.7高阈值减少误报低阈值减少漏报num_workers04Windows 设 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_version12)dynamic_axes让 batch 维度可变部署时能一次处理多张图。opset_version12兼容性较好版本太高部分推理引擎不认。导出后务必用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, normalizetrue) plt.imshow(cm, cmapBlues) plt.colorbar() plt.xticks(range(8), ds.classes, rotation45) plt.yticks(range(8), ds.classes) plt.xlabel(Predicted); plt.ylabel(True) plt.tight_layout(); plt.savefig(cm.png, dpi150)normalizetrue按真实类别归一化这样每行加起来是 1能直接看出某类被误分到哪去的比例。如果「左转弯待转」有 40% 被预测成「左转弯」那就针对这两类补数据或者设计一个二阶段分类器专门区分它们。另一个技巧是测试时增强TTA对同一张图做几种不改变语义的变换把预测概率平均。手势识别里可以用多尺度缩放比如 224 和 256 各跑一次取平均通常能涨 12 个点代价是推理耗时翻倍。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), dim1)) avg torch.stack(probs).mean(0) conf, idx avg.max(1) return classes[idx.item()], conf.item()TTA 的前提是模型对尺度变化鲁棒如果训练时只用了 224直接上 256 可能反而掉点所以要先在小验证集上试。这套资源里如果带了训练好的模型建议先原样跑一遍 baseline记下准确率再逐项加 TTA、加权重、换 backbone每次只改一个变量才知道是哪一步起了作用。从那以后我每次拿到新的分类数据集都强制先跑一遍混淆矩阵再谈调参因为大部分精度问题根本不在模型而在数据和标签。希望这套交警手势识别资源能帮你把 PyTorch 多分类的完整链路走通少走几个我当年踩过的弯路。本文还有配套的精品资源点击获取
返回列表