简介:本资源是一套完整的中国交通警察指挥手势识别毕业设计项目,面向计算机、人工智能、自动化等专业本科生及初学者,解决交通手势图像分类与关键点检测的实际问题,适用于课程设计、大作业及毕设参考。压缩包共34个文件,含31个Python源码(涵盖数据预处理、姿态估计模型训练、手势识别推理、结果可视化等核心模块)、1个详细README说明文档、1个Git忽略配置及1个项目演示GIF,整体仅4.42MB,轻量易部署。已有207人学习下载,项目经答辩评审获98分,全部代码调试通过并可直接运行。读者可获得从视频骨骼提取(prepare_skeleton_from_video.py)、PAFs网络构建(pafs_network.py)、手势分类模型(gesture_recognition_model.py)到端到端预测(gesture_pred.py)的全流程实现,目录结构按功能分层清晰,配套human_keypoint_pred.py与play_gesture_results.py等工具脚本显著降低学习门槛。 每年一到毕设选题季,就会有人拿着“基于Python+深度学习的中国交通警察指挥手势识别”这个题目来找我。说句实话,这题看着体面,难度也适中,很适合作毕业设计——但前提是,你得先把数据集和动态手势这两道坎迈过去。我花了大半个学期把它完整跑通,最后整理出了源码和一套自建的交警手势数据集,这篇就说说整个过程里最关键的几个决策、踩过的坑,以及答辩时真正会被问到的东西。
这题目表面上是“深度学习图像识别”,但实际上,模型结构反而是整条链路里最省心的部分。真正的功夫在数据怎么采、标签怎么定、输入形式怎么设计、以及最终如何让一个静态分类器在动态视频里稳定工作。下面按我实际推进的顺序,把这套完整方案拆开讲。
1. 这个课题真正的门槛:先想清楚“识别什么、怎么识别”
1.1 交警手势的种类与判定标准
国内交警指挥手势,常见的是八种:停止、直行、左转弯、左转弯待转、右转弯、变道、减速慢行、靠边停车。实际细分还可能把右转弯待转单独拿出来,但常规项目里按八类来做就够了。
这里有个容易被忽略的点:每个手势都是动态动作,不是一张静态图能完全表示的。比如“停止”信号,从手臂下垂到小臂向上伸直再到手掌立起,整个过程都在传达信息。如果只采最终姿态,模型看到的特征就非常局限,一旦视频里动作停在中间帧,识别就乱套了。所以数据集构建的第一原则是:把动作的全过程都录进去,起、承、转、合一个不落。
1.2 静态识别还是动态识别,要先定边界
做毕设时,最容易犯的错是一上来就想要一个“能看视频、识别手势”的完整系统,然后发现工作量根本收不住。你需要先明确输入形式:
- 单帧图片输入:把每一帧当作独立样本做分类,模型不需要考虑前后帧关系。优点是数据集好建、训练简单、便于用ImageNet预训练;缺点是答辩时容易被老师质疑“动态手势你拿静态帧做,合理吗”。
- 视频序列输入:用3D-CNN或CNN+LSTM处理连续帧,理论上更贴合真实场景,但数据量要求成倍增加,训练难度和调试成本也高不少。
我最终选择的折中方案是:静态分类模型为主,推理阶段用视频流+检测框+滑动窗口投票。这样既没有做视频分类那么重,又能在实际运行时有时间维度的纠错能力,答辩时也说得通。
1.3 这门课本身值不值得做
坦诚说,这个选题性价比不错。数据自己可以录,不用求人;技术栈主流,Python、PyTorch、YOLO、ResNet一套下来,简历上也好写;展示效果又直观,做个简单界面就能跑视频。但务必提前意识到:公开数据集几乎没有完整覆盖八种手势的,网上能搜到的相关项目,要么质量堪忧,要么类别不全,很多还是静态摆拍,做出来的模型泛化能力很差。所以,自建数据集是绕不开的环节。
2. 没有现成数据集?那就自己造一份规规矩矩的数据集
2.1 采集方案:录制视频加逐帧提取
我当时的做法是找两三位同学配合,穿上深浅不同的上衣,有条件的话戴一双白手套(实际执勤会戴白手套,也能让前景和背景区分更明显)。如果弄得到黄色反光背心,效果更好,可以显著降低衣服颜色对模型的干扰。拍摄场景选在校园空地和操场,背景相对单纯,避开真实马路,既安全又不会引入太多无关行人或车辆。
拍摄时用手机竖屏或横屏都可以,固定机位,1080p分辨率、30fps就够。重点在于:每个手势要单独录一段,时长五到十秒,动作从头到尾做完整。比如“左转弯”,要包括抬臂、伸出、收臂的整个流程,不要只摆一个标准pose。
不同角度也录一些。实测下来正面机位最好识别,侧面和斜前方容易混淆,特别是“左转弯”和“左转弯待转”,侧面看几乎只差一个手臂伸展角度。
视频录完,用OpenCV按帧抽取。不要一帧不落全存,一是有大量冗余,二是模糊帧很多。我按每秒取五帧的频率抽,再用拉普拉斯方差做简单清晰度过滤:
import cv2 import os video_path = "videos/turn_left_01.mp4" output_dir = "dataset/images/turn_left/turn_left_01" os.makedirs(output_dir, exist_ok=True) cap = cv2.VideoCapture(video_path) fps = cap.get(cv2.CAP_PROP_FPS) frame_interval = int(fps / 5) count = 0 save_count = 0 while True: ret, frame = cap.read() if not ret: break if count % frame_interval == 0: gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) laplacian_var = cv2.Laplacian(gray, cv2.CV_64F).var() if laplacian_var > 50: # 低于阈值判定为模糊帧 cv2.imwrite(os.path.join(output_dir, f"{save_count:04d}.jpg"), frame) save_count += 1 count += 1 cap.release()这里有个细节:拉普拉斯方差的阈值不能定太高,否则会把动作中间帧里手臂快速运动导致的运动模糊全部滤掉,导致数据集只剩“姿势停留帧”和“缓慢动作帧”,模型在真实视频里遇到快速挥臂就会懵。
2.2 标注策略与目录结构
标注方案直接决定后续建模方式。我对每一类手势按动作类别归档,同时额外用标注工具给交警画检测框。
先画检测框,再按手势类别归档。推荐顺序是:
- 对抽出的每一帧,用labelimg标注人形边框,标签统一叫“police”。
- 把标注后的图片按手势类别归类到不同文件夹。
这样就形成“检测+分类”两条数据线:
dataset/ ├── images/ │ ├── stop/ │ ├── straight/ │ ├── turn_left/ │ ├── turn_left_wait/ │ ├── turn_right/ │ ├── change_lane/ │ ├── slow_down/ │ └── pull_over/ ├── labels/ │ ├── police_bbox.json ├── train.txt ├── val.txt └── test.txt每个类别的样本量,我建议不低于一千张,最好到一千五到两千张。八类加起来就是一万二到一万六千张,对ResNet、MobileNet级别的模型完全够用。不要贪多,关键是质量。我自己就是每类二十段视频、每段五秒,抽完清洗后留下约一千六百张。
提示:同一段视频抽出来的帧,彼此太相似。划分训练集和验证集时必须按视频整体切分,不能把所有帧混在一起随机切,否则会出现“训练集和验证集其实来自同一段视频”的数据泄露,导致验证集指标虚高,换一段真实新视频就露馅。
2.3 数据增强,但别把左右搞反
增强我用的是albumentations,主要包括:
- 随机旋转±15度
- 亮度、对比度随机抖动
- 饱和度调整
- 随机擦除
- 高斯噪声
一个容易踩的雷是水平翻转。做分类增强时,水平翻转后“左转弯”会变成“右转弯”。如果不把对应标签同步交换,模型学到的特征就是矛盾的。我建议要么构建一个标签映射表,翻转的同时把左和右相关标签对调,要么干脆不做水平翻转,省得给自己挖坑。
3. 模型选型与整体方案设计:为什么是“检测+分类”而不是端到端
3.1 四类技术路线对比
我在动手前把主流的方案都列了一遍,简单对比:
| 技术路线 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| 静态图像分类(ResNet等) | 实现简单、预训练多、训练快 | 易受背景干扰,无法利用时序信息 | 图片输入 |
| 目标检测+分类(YOLO+MobileNet) | 抗背景干扰,多人场景可扩展 | 链路较长,需要检测框标注 | 视频输入、实际部署 |
| 姿态关键点(MediaPipe/HRNet+LSTM) | 更关注人体动作本身,对服饰变化鲁棒 | 对遮挡敏感,小模型精度欠缺 | 通用动作识别 |
| 视频分类(3D-CNN/TSM) | 充分利用时序上下文 | 数据需求大、训练慢、调试复杂 | 长视频动作识别 |
决定方案时,我考虑了两点:第一,实际场景中交警在画面里通常只占一小块区域,如果直接整图分类,模型会把大量注意力花在背景上,得不偿失;第二,毕设周期有限,视频分类方案训练成本高,调参复杂,一旦翻车很难补救。
3.2 我最终采用的pipeline
最终方案是两阶段:
- 用YOLOv8n训练一个“police”检测器,负责在画面中框出交警。
- 对检测框区域裁剪、缩放、归一化,送入一个MobileNetV3-Large分类器做手势分类。
推理时,整条链路是“视频帧 → YOLO检测交警 → 裁剪最大检测框 → 手势分类 → Softmax → 滑动窗口投票”。
YOLOv8n本身很小,检测交警这种大目标绰绰有余。分类器用MobileNetV3是因为它轻量、运行快,准确率也不输ResNet50太多。如果对精度要求更高,可以换成ResNet50,代价是单帧推理慢个两三毫秒,倒也不会影响实时性。
关键代码如下:
import cv2 import torch from torchvision import transforms from ultralytics import YOLO detector = YOLO("runs/detect/police/weights/best.pt") classifier = torch.load("checkpoints/mbv3_large_best.pth", map_location="cpu") classifier.eval() cls_names = ["stop", "straight", "turn_left", "turn_left_wait", "turn_right", "change_lane", "slow_down", "pull_over"] mean = [0.485, 0.456, 0.406] std = [0.229, 0.224, 0.225] transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize(mean, std) ]) def predict_frame(frame): results = detector(frame, verbose=False) boxes = results[0].boxes if len(boxes) == 0: return None # 取面积最大的检测框 areas = (boxes.xyxy[:, 2] - boxes.xyxy[:, 0]) * (boxes.xyxy[:, 3] - boxes.xyxy[:, 1]) idx = areas.argmax() x1, y1, x2, y2 = boxes.xyxy[idx].tolist() crop = frame[int(y1):int(y2), int(x1):int(x2)] crop = cv2.resize(crop, (224, 224)) crop = crop[:, :, ::-1] # BGR转RGB tensor = transform(crop).unsqueeze(0) with torch.no_grad(): logits = classifier(tensor) prob = torch.softmax(logits, dim=1) cls_id = prob.argmax().item() return cls_names[cls_id], prob.max().item()3.3 环境配置与工程组织
环境我用的是Ubuntu 22.04,Python 3.8以上,PyTorch 2.x,ultralytics库装好就行。Windows也能跑,但GPU版本驱动和CUDA版本最好提前对好,Ubuntu下相对省心。CPU也能训练,只是MobileNetV3-Large在64G内存的机器上训练一个epoch要比较久,建议有NVIDIA显卡,哪怕是GTX 1660 Super这种4G显存都够。
工程结构按模块划分:
traffic-police-gesture/ ├── data_prepare/ │ ├── extract_frames.py │ └── split_dataset.py ├── train/ │ ├── train_detector.py │ ├── train_classifier.py │ └── eval.py ├── inference/ │ ├── predict.py │ └── smooth.py └── checkpoints/这样训练、评估、推理分开,答辩演示和后续扩展都方便。
4. 训练过程与关键参数:从ResNet到MobileNet的反复横跳
4.1 第一阶段:纯分类模型的基准测试
我最早先用ResNet50做了个baseline,直接在整图上分类。训练配置是:ImageNet预训练权重,AdamW优化器,初始学习率1e-3,weight decay 1e-4,batch size 32,输入尺寸224×224,训练约30个epoch,早停法收敛。
结果验证集准确率在86%-88%左右。乍看还行,但一看混淆矩阵就发现,错误高度集中在“turn_left”和“turn_left_wait”之间,以及“slow_down”和“pull_over”之间。这两个方向都有点模棱两可,尤其左转和左转待转,开合角度接近,单帧区分确实难。
接着我把整图分类换成“先检测后分类”的流程再测,验证集准确率直接跳到93%左右。差异基本来自背景干扰被排除掉了。整图模型学了很多背景特征,换一个场景就性能下降;裁剪后模型只能看到人和手臂,学到的特征更接近手势本身。
4.2 第二阶段:模型轻量化与精度平衡
检测器YOLOv8n训练起来很快。交警这种目标在画面里明显,标签单一,训练50个epoch,mAP能到0.95以上。
分类器我从ResNet50换成了MobileNetV3-Large,精度几乎没掉,推理速度快了一截。换模型时要注意:两个模型的输入预处理完全一致,ImageNet的mean和std都是[0.485, 0.456, 0.406]和[0.229, 0.224, 0.225],不能混用。
我又把分类器输入从224×224提到320×320,准确率涨了大约1.5个百分点。对大一点的检测框区域来说,高分辨率确实保留了更多手势细节。代价是推理速度慢几毫秒,无伤大雅。
4.3 时序平滑,把静态模型变成可用的动态系统
即使分类准确率到了95%,单帧预测在视频里仍然会抖。原因很简单:某些中间帧本身太像别的类别。我用一个长度为7的滑动窗口做多数投票,效果立竿见影,视频测试的明显误判从约10%降到了2%以内。
from collections import deque, Counter history = deque(maxlen=7) def smooth_predict(frame): result = predict_frame(frame) if result is None: return None history.append(result[0]) counter = Counter(history) return counter.most_common(1)[0][0]如果连续七帧里五帧是“turn_left”,一帧是“turn_right”,一帧是“stop”,最终结果就是“turn_left”。这种策略虽然简单,但对“动作中间帧模糊”的鲁棒性提升非常大。
最终测试集评估指标:整体准确率94%左右,加权F1约0.94。这个成绩在毕设场景下够看了,重点是整个方案稳定、可复现、可说清。
5. 真实场景测试中的坑:不是训练完了就天下太平
5.1 背景干扰:检测到多个人的处理策略
在真实视频里,画面中可能同时出现多人。YOLO会把所有人都框出来,甚至把路人的手臂当成交警手势。我的做法是取面积最大的police检测框,因为实际场景中交警通常处于画面中心或较近位置。如果两个框大小接近,则取置信度高的那个。
这个策略在绝大多数单警场景下够用。如果你要处理多交警同时出现的情况,那就得再加一道逻辑,比如按区域或优先级分配,那已经超出毕设范围了。
5.2 相似手势的静帧歧义
“左转弯”和“左转弯待转”这两类,即使在人工标注时也容易犹豫。除了增加样本外,我在采集时专门让模拟者把动作放慢,多做几个过程过渡帧,尽量让模型学到更多中间状态。但坦率说,单帧静态模型无法彻底解决这个歧义。要彻底解决,得上时序模型。毕设答辩时,这个点老师大概率会问到,我建议如实说清局限并给出改进方向,比遮掩更稳。
5.3 训练集和验证集划分不当
前面说过,按视频切分是铁律。我一开始图省事,把所有帧混在一起随机划分,验证集准确率高达98%,结果换一段肉眼可见的简单视频,预测一塌糊涂。查了很久才发现,训练集里包含了很多验证集视频的相邻帧,模型几乎是“背题”考的。之后改成按视频切分,指标立刻真实了很多。
5.4 通道顺序和推理环境导致性能骤降
一个特别常见的低级错误:用OpenCV读视频,图片是BGR通道顺序,模型训练时用的是RGB。如果推理代码忘了做BGR到RGB的转换,性能会掉得离谱,而且不易察觉。我自己踩过这个坑,一度以为模型训练有问题,最后发现是通道顺序反了。建议在推理函数里固定写一次frame[:, :, ::-1],别依赖外部传入。
还有部署时,模型跑在GPU上,输入tensor的dtype要统一。PyTorch里如果你用CPU推理,float32和float64混在一起也会报错。干脆在所有入口统一用torch.float32。
5.5 光照差异与拍摄设备差异
我最初的数据有一部分是阴天拍的,一部分是傍晚拍的,结果模型在强光下测试时,会把“停止”误判成“减速慢行”。后来做了亮度抖动增强,并在采集中尽量覆盖不同时段的自然光,问题明显缓解。
如果条件允许,用两台不同手机各录一部分数据,让网络不要过拟合到某一种摄像头色彩倾向。这一点对实际使用非常关键。
6. 答辩前必须打通的几个问题:别让老师问住你
6.1 “为什么用YOLO+分类,而不是端到端手势识别网络?”
这是一个必然被问的问题,要能答出设计权衡。
- 端到端视频分类需要大量带动作标签的连续视频样本,数据规模动不动上万段,毕设阶段很难自建到可用量级。
- 检测+分类把复杂任务拆成两个简单子问题:检测器负责“人在哪里”,分类器负责“手在做什么”,每个子问题都能独立优化、独立评估。
- 推理时可解释性更强,分类错误时我可以查看是检测框偏了还是分类器判断错了,调试起来高效很多。
6.2 “数据集是怎么构建的?有多少张?如何标注?”
围绕自采视频、逐帧提取、清晰度过滤、类别归档、检测框标注五步展开。清晰表达数据总规模:八类约一万三千张,其中每个类别约一千六百张,训练验证测试按8:1:1切分,并按视频整体切分避免数据泄露。检测框用labelimg标注,标签为police。
6.3 “模型是怎么处理动态手势的?”
如实承认骨干模型是单帧分类,但推理阶段引入了滑动窗口多数投票,利用时间一致性来降低单帧误判。并且数据采集时覆盖了动作全过程,这本身就是一种隐式的时序信息。如果想进一步改进,可以替换为LSTM或TSM结构。
6.4 “实时性怎么样?”
给出可验证数据:GTX 1660 Super上,YOLOv8n单帧约8ms,MobileNetV3-Large约3ms,加上预处理和投票逻辑,整体约20ms,能达到50fps左右。如果没有GPU,CPU模式下大约是200ms到300ms一帧,离线处理视频完全够用。
6.5 “为什么准确率没有做到99%?”
可以从数据集难度和相似手势两个层面回答。误差主要来自“左转弯”与“左转弯待转”在特定视角下的静帧形态接近,以及极少数光照过暗的视频帧检测框偏移。这不是模型bug,而是单帧信息本身的局限性。如果要做更高精度,需要更精细的时序建模和更大的数据规模。
结尾:一点个人体会
做完这个项目,我最大的感受是:深度学习的模型结构只要选定一个主流方案,调参再多也难有大突破,反而是数据采集和数据处理的质量决定了项目上限。这个题目真正的价值,在于把数据工程、模型训练、推理部署、误差分析这些环节完整串了一遍,每一环都会出问题,也只能靠实打实地排查才能解决。最后整理源码和数据集的时候,我把整个流程固定成了脚本,保证以后换一批新数据也能一键复现。后来再有人拿这个题做毕设,我基本都让他们直接按这个链路走,省下来的时间,多去跑几组实验、把混淆矩阵琢磨透,比什么都值。
本文还有配套的精品资源,点击获取