简介:基于YOLOv5与DeepSORT框架,融合GaitSet步态识别算法,这份源码面向“基于步态识别的多目标跨镜头跟踪算法研究”本科毕业设计课题,实现了行人检测、多目标跟踪、步态特征提取与跨镜头身份匹配的完整流程,适合计算机视觉方向学生和研究者参考。资源共343个文件,压缩后约22.23MB,其中173个Python脚本覆盖模型训练、评估、推理及工具函数,52个YAML文件定义网络结构与训练超参,另有预训练模型权重、Jupyter Notebook示例、Shell脚本、Dockerfile、Markdown/RST技术文档等,便于从零搭建环境、快速复现实验。目前已有4449人浏览学习,工程实战价值较高。从目录结构看,项目按数据处理、模型定义、训练验证、跨镜头关联等模块划分,代码注释与配置文件齐全,可直接作为毕业设计源码或算法研究底稿;通过阅读GaitSet相关实现,还能学习如何将步态识别与多目标跟踪结合,解决跨镜头场景下的身份持续匹配问题。
1. 步态识别+跨镜头跟踪:这个毕设题到底在解决什么
监控场景里最挠头的不是“有人出现”,而是“同一个人换了摄像头就被当成另一个人”。人脸在背身、遮挡、弱光画面里常常不可用,但步态作为生物特征始终在线——只要人还在走路,腿长比例、步频、摆臂幅度这些信息就够用了。这套人工智能本科毕业设计项目,核心就是基于yolov5的步态识别多目标跨镜头跟踪检测算法系统:先用YOLOv5把画面里的行人检测出来,再从人体骨架中提取步态特征作为“身份指纹”,最后在多路画面之间做目标关联,保证全局ID跨镜头不丢失。它适合有Python基础,想一次性覆盖目标检测、多目标跟踪、行人重识别三个热门方向,又不想在人脸识别红海里挤破头的计科或人工智能专业应届生。单从题目看,它把主流视觉任务串成了一条完整链路,工程量可控,展示效果也直接。
解压这类毕设包之后,典型结构不外乎五块:训练脚本、检测推理、步态特征提取、跟踪匹配、文档说明。下面直接按这套体系把原理、落点和坑一一拆开。
2. 系统怎么搭:检测、步态、跟踪三层链路的原理与选型
2.1 一条完整链路拆开看
先看数据流,这决定了你写代码时的模块划分。原始视频帧进来后,第一步是YOLOv5目标检测,输出所有行人的边界框和置信度;第二步对每个边界框裁剪人体区域,送入姿态估计网络,得到17个关键点坐标;第三步根据连续帧里的关键点序列计算步态描述子——通常是一组关节角度、躯干比例和步频;第四步在单镜头内做多目标跟踪,用框的IoU加外观相似度把相邻帧的检测关联成一条局部轨迹;第五步才是标题里的重点“跨镜头”:当镜头切换或目标消失重出现时,用步态描述子与全局ID库里的模板做相似度匹配,命中则沿用原ID,否则生成新ID。
把这条链路画在纸上,就是一个四层结构:检测层、特征层、局部跟踪层、全局重识别层。毕设答辩时这个分层本身就能讲故事,每层都对应一个可以单独验证的实验结果。运行时,摄像头输入既支持本地视频文件也支持RTSP流,推荐优先用视频文件调通逻辑,再换RTSP验证实时性。这套结构里最容易被低估的是第4步和第5步的衔接:局部跟踪维护的是“这一帧里有几个人、框在哪”,全局重识别维护的是“这些人分别是谁”,两套ID体系必须分开管理,否则镜头一切换ID就乱。
2.2 为什么偏偏是YOLOv5:从网络结构图看行人检测的取舍
YOLOv5的胜出不是因为精度碾压同行,而是因为它把工程成本压到了最低。从网络结构图看,它的Backbone是CSPDarknet,Neck是PANet,Head是三个尺度的检测头。三个输出层分别对应80×80、40×40、20×20的特征图,小目标靠浅层高分辨率的特征图,大目标靠深层语义特征。这个多尺度设计恰好命中监控场景:画面里远处的行人可能只有几十像素高,如果只靠语义层很容易漏检,P3这层80×80的特征图就是专门捞这种小目标的。
| 检测头 | 特征图尺寸 | 主要分工 | 监控场景里的角色 |
|---|---|---|---|
| P3 | 80×80 | 小目标 | 远距离行人的主力检出层 |
| P4 | 40×40 | 中等目标 | 中距离行人的主力 |
| P5 | 20×20 | 大目标 | 近距离行人和遮挡判断 |
为什么不用更新的YOLOv8或者YOLOX?纯从算法指标看它们各有优势,但毕设题目定位是“基于yolov5的系统”,不是“检测算法对比研究”。YOLOv5官方仓库文档全、issue多,普通报错几乎都能搜到现成答案,这对时间紧张的毕设季太重要了。另一个实际原因是可视化生态成熟:画PR曲线、画检测框、导出ONNX、TensorRT部署,教程一抓一大把,这些素材答辩时都派得上用场。行人检测还有个物理特性需要注意——行人通常是高大于宽的竖长条,高宽比接近2:1,而YOLOv5默认anchor是从COCO数据集上聚类出来的,和行人类别不是完全契合,这个细节放到第5章踩坑部分细说。
2.3 步态识别路线分层:姿态法、轮廓法与光流法怎么选
步态特征提取有三条常见路线:姿态关键点法、轮廓法、光流法。姿态关键点法把人体框喂给姿态网络,拿回关键点坐标再算关节角度、相对距离和步频。它的优点非常直白:特征维度低、计算快、可视化效果好,答辩时直接画出骨架图,特征的意义一目了然。轮廓法的代表是GaitSet,它把步态周期内的剪影归一化后编码成集合特征,在CASIA-B这类公开数据集上精度更高,但对数据采集要求苛刻,需要完整的步态周期,摄像头角度一变性能掉得厉害。光流法直接用视频序列的运动场做特征,理论上有种反直觉的优势——不依赖任何骨架先验,但计算量大、噪声敏感,实时系统基本不选它。
我一般劝毕设选题选姿态关键点法,核心原因是可解释性和工程量的平衡。姿态关键点本身带有明确的物理含义,“膝关节角度166度”“步频1.6步每秒”这些数值可以直接写进论文的实验分析里,而不是只给一堆抽象向量。它的计算开销也小,一张消费级显卡能跑满实时。从跨镜头角度看,姿态特征还有一个隐性优势:关节角度是围绕人体局部坐标系计算的,跟拍摄距离基本无关,球机拉近拉远不会直接把特征废掉。不过它对视角仍然敏感,正面视角和侧面视角算出来的角度分布差异明显,这个坑很常见,第5章我会给出具体的解决思路。
3. 让YOLOv5先学会“看人”:训练自己的数据集与后处理
3.1 数据准备:从COCO预训练到监控场景微调
YOLOv5训练自己数据集的第一步不是重新造轮子,而是拿COCO预训练权重起步。官方仓库里能直接下载yolov5s.pt等预训练模型,里面已经包含person这个类别。如果你的摄像头场景和COCO差异不大,直接用预训练权重做推理就够了。真正需要微调的场景是:固定机位、固定光照的监控画面里,行人的姿态和日常图片差异明显,就需要采集现场视频帧做二次训练。
标注格式是YOLO标准的.txt文件,每行一个目标,内容是“类别 中心点x 中心点y 宽度 高度”,四个坐标值都用图像宽高归一化。手工标注用LabelImg或AnyLabeling都可以,监控场景下数百张高质量帧往往比上千张杂乱图片更有效。目录结构一般按下面的方式组织:
gait_dataset/ ├── images/ │ ├── train/ # 训练图片,例如 cam1_001.jpg │ └── val/ # 验证图片 ├── labels/ │ ├── train/ # 与图片同名的txt标注 │ └── val/ └── gait.yaml # 数据集描述文件对应的gait.yaml这样写:
train: ./images/train val: ./images/val nc: 1 # 类别数,这里只保留person names: ['person']训练时只保留person一个类的好处是省去类别混淆的麻烦。如果画面里还有背包、行李箱这些经常贴着人的物体会干扰检测,可以按需加一个“person_with_bag”类,但不建议贪多。数据规模上,固定机位的监控场景200到500张微调帧就够让模型习惯新环境;如果是多机位、光照变化的场景,尽量每个机位都采一部分,避免模型产生“只认某个画面的背景”的错觉。
3.2 训练命令与超参数:epochs、batch、imgsz怎么给
训练命令不用自己写训练循环,YOLOv5仓库的train.py直接支持数据集配置入口。下面这组参数是我调试过很多次后比较稳的起点:
python train.py \ --data gait.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --patience 20 \ --cache ram \ --device 0逐项说明参数的含义:--img是训练时输入分辨率,640是速度和精度的平衡点,如果想提高小目标检出率可以提到1280,但显存和推理时间会明显上涨;--batch受显存限制,16在8GB显存上是安全值,显存紧张就降到8;--epochs给100是为了留足收敛空间,实际配合--patience 20的早停机制,模型通常在50到70轮就会停;--cache ram能显著加速训练,代价是内存占用飙升,内存不够就把它去掉。
还有个容易忽略的参数是--hyp,它控制数据增强的超参数。入门阶段直接用官方默认的hyp.scratch.yaml即可,不要去调色相、饱和度、平移这些增强幅度,否则很容易把行人拉变形,到推理阶段反而检测不稳。训练完成后看runs/train/exp目录里的results.png,重点观察验证集的mAP@0.5曲线是不是持续上升不再抖动,如果训练集mAP高但验证集mAP低,就是过拟合,回过来加数据或者调大--augment相关的正则项。
3.3 推理与后处理:置信度阈值、NMS和类别过滤
训练完best.pt,推理端要处理的问题就变了,不再是“怎么训练”,而是“怎么让检测结果稳定地喂给下游”。一个常见错误是直接把模型输出套进跟踪器,导致检测框闪烁、置信度忽高忽低,把步态特征也带偏。后处理应该包含固定动作:置信度过滤、类别过滤、NMS、以及必要的跟踪平滑。
import cv2 import torch # 加载训练好的自定义权重 model = torch.hub.load('ultralytics/yolov5', 'custom', path='runs/train/exp/weights/best.pt', force_reload=False) cap = cv2.VideoCapture('data/camera1.mp4') while True: ok, frame = cap.read() if not ok: break results = model(frame, size=640) # 只保留person类,且置信度>0.4的框 df = results.pandas().xyxy[0] df = df[(df['name'] == 'person') & (df['confidence'] > 0.4)] for _, row in df.iterrows(): x1, y1, x2, y2 = [int(row[c]) for c in ['xmin', 'ymin', 'xmax', 'ymax']] cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imshow('detect', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break这段代码里有三个值得注意的点。第一,torch.hub.load每次执行会检查缓存,反复断网或切换环境时容易出玄学问题,更稳的做法是把YOLOv5仓库clone下来放到项目目录,用本地路径导入。第二,置信度阈值0.4是经验值,太低会有大量误检框干扰跟踪,太高会把遮挡后的行人丢掉,调参时先看这个值能不能“人走到柱子后面再出来时框不丢”。第三,YOLOv5默认的iou阈值为0.45,NMS本身会把重叠严重的两个人合并成一个框,人群密集的场景可以在模型构造函数里调低到0.4,代价是可能出现同一个人的重复框,后续可视化时更容易暴露问题。
4. 跨镜头跟踪的核心:步态特征提取、ID管理与匹配策略
4.1 从姿态关键点到步态描述子
拿到行人框后,下一步是提取步态描述子。我习惯在主检测模型上并行一个姿态分支——YOLOv5官方仓库里有pose版本,能在输出检测框的同时给出17个关键点。这样不必单独搭OpenPose,省去框架间的数据格式转换。关键点坐标的精度直接决定特征质量,所以送入姿态网络的人体区域最好保留一点边缘余地,不要裁得太紧,否则四肢关键点容易被切掉。
import numpy as np # 假设kpts是单个人的关键点,shape为[17, 2],坐标已经落在人体框内 def calc_angle(p1, p2, p3): """以p2为顶点的关节角度,p1/p2/p3都是[x, y]坐标""" a = np.array(p1) - np.array(p2) b = np.array(p3) - np.array(p2) cos_theta = np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-6) return float(np.degrees(np.arccos(np.clip(cos_theta, -1.0, 1.0)))) def gait_descriptor_from_keypoints(kpts): # 左腿膝角:左髋-左膝-左踝 left_knee = calc_angle(kpts[11], kpts[13], kpts[15]) # 右腿膝角:右髋-右膝-右踝 right_knee = calc_angle(kpts[12], kpts[14], kpts[16]) # 双肩与双髋构成的躯干侧倾角,用两肩中点和两髋中点的连线算 shoulder_mid = (kpts[5] + kpts[6]) / 2 hip_mid = (kpts[11] + kpts[12]) / 2 torso_angle = calc_angle( [shoulder_mid[0], shoulder_mid[1] - 1], shoulder_mid, hip_mid ) return np.array([left_knee, right_knee, torso_angle])代码层面的关键是先想清楚哪些角度是“视角不敏感且个体差异大”的。膝关节角度和躯干倾角是首选;肩宽比髋宽这类比例特征也稳定,但对像素精度要求高;步频需要时序数据支撑,可以走一个小周期,后面单独说。实际落地时,我一般还会把左右腿角度差的绝对值加进特征,因为多数人走路有轻微不对称,这是一个便宜又有效的身份线索。
特征模板需要累积而不是用单帧。取连续10到15帧,每帧算一次描述子,最后取中位数或均值作为这个人的稳定模板。中位数比均值鲁棒,因为它能抵抗个别姿态估计出错产生的离群点。这一段在答辩时可以写成“基于滑动窗口的步态模板更新策略”,听起来也顺。
4.2 单镜头内跟踪:轨迹生成与局部ID更新
单镜头内的多目标跟踪,核心思路是每一帧拿检测框去匹配已有轨迹。最简单的做法是IoU加贪心匹配:当前帧每个检测框和上一帧所有轨迹的预测框算IoU,超过阈值的配对里选分数最高的。但纯IoU的问题是行人被短暂遮挡后框消失,回来时IoU对不上,轨迹就断了。折中的做法是再加一层外观相似度——用人体区域的颜色直方图或者直接从姿态特征里抽一个轻量特征参与匹配。
def match_detections_to_tracks(det_boxes, track_boxes, iou_threshold=0.3): """按IoU做贪心匹配,返回匹配对列表[(det_idx, track_idx)]""" matches = [] used_tracks = set() for d_idx, det in enumerate(det_boxes): best_score, best_t = 0.0, -1 for t_idx, trk in enumerate(track_boxes): if t_idx in used_tracks: continue iou = compute_iou(det, trk) if iou > best_score: best_score, best_t = iou, t_idx if best_score >= iou_threshold: matches.append((d_idx, best_t)) used_tracks.add(best_t) return matches贪心匹配不是全局最优,但胜在简单、快、好调试。如果追求效果,可以用匈牙利算法替代,实际差距在行人稀疏的监控画面里并不明显。每条轨迹要维护一个“存活状态”:连续N帧没匹配上就标记为lost,进入丢失缓冲;在缓冲期内继续用外观相似度去试探匹配,超过缓冲时间再真正删除。这样“人走到树后面3秒再出来”这种场景,局部ID就不会断裂。
4.3 跨镜头全局匹配:相似度计算与阈值策略
跨镜头匹配是这个系统里真正决定成败的一步。当局部跟踪器给出一个“新出现的目标”时,系统要先尝试在全局ID库里找“这个人之前有没有出现过”,找到就沿用ID,找不到就创建新ID。这里相似度度量我推荐余弦相似度而不是欧氏距离,因为步态描述子在不同人身高下天然存在尺度差异,余弦计算只看方向是否一致,对幅度不敏感,正好压制这种个体差异的干扰。
def cosine_similarity(a, b): """两个步态描述子的余弦相似度,返回[-1, 1]""" a = a / (np.linalg.norm(a) + 1e-6) b = b / (np.linalg.norm(b) + 1e-6) return float(np.dot(a, b)) def match_global_id(desc_new, global_templates, threshold=0.82): """在全局模板库里检索最相似的身份""" best_id, best_score = None, -1.0 for pid, desc_t in global_templates.items(): score = cosine_similarity(desc_new, desc_t) if score > best_score: best_id, best_score = pid, score if best_score >= threshold: return best_id, best_score return None, best_score # 未匹配,等待上层新建IDthreshold=0.82这类值不是拍脑袋定的。正确做法是在验证集上分别计算正样本对(同一个人不同镜头)和负样本对(不同人)的相似度分布,取两个分布分离最明显的位置作为阈值。如果正负样本分布重叠严重,说明特征本身区分度不够,一味调低阈值只会带来更多ID混用。模板也要持续更新,匹配成功后就对模板做滑动平均,让特征缓慢适应光照和姿态变化:
def update_template(pid, new_desc, alpha=0.8): global_templates[pid] = alpha * global_templates[pid] + (1 - alpha) * new_descalpha取0.8是让模板偏向历史稳定值,避免单帧噪声污染特征。这个参数影响的是“适应速度”和“稳定性”的平衡,alpha太小模板会飘逸,alpha太大则环境光照变化后模板久久不更新。毕设里可以先固定0.8,观察多镜头长时间运行后ID漂移的情况再微调。
5. 这套系统最容易翻车的5个坑:现象、原因与解决办法
5.1 镜头视角一变,同一人相似度暴跌
现象:同一个人从正面镜头走到侧面镜头,步态特征匹配分数从0.9掉到0.55,系统直接新建了一个ID,全局跟踪彻底失效。
原因:姿态关键点法对视角天然敏感。正面看到的膝关节角度和侧面完全不同,躯干倾角也会因为投影变化而失真。这不是代码bug,是特征定义层面的问题。
解决:匹配时按镜头视角分组,正面镜头只用正面模板比,侧面镜头只用侧面模板比;或者干脆在特征设计上多加入视角无关量——左右膝盖角度差的绝对值、肩髋比例、步频这类跟投影方向相关性弱的量。俯视摄像头和鱼眼镜头下姿态特征几乎不可用,这类场景要么换轮廓法,要么在采集阶段明确限制摄像头高度和朝向。
5.2 局部跟踪断链造成重复ID
现象:人走进障碍物2秒,出来后人还是那个人,但全局ID从15跳到了16。
原因:局部轨迹在遮挡期间丢失,重新出现时被当作“新目标”,直接走了新建全局ID的流程。这道流程里缺少一个关键的补漏机制:局部轨迹虽然丢了,但全局模板库里还留着这个人的特征,应该先查一次全局库再决定是否新建ID。
解决:给每条全局ID绑定一个“最近出现轨迹”,缓冲期内新目标先尝试和这些缓冲轨迹匹配;更简单的方法是把全局匹配前置——每个新目标都先过一遍全局检索,相似度高于阈值的直接沿用ID,而不是只看局部轨道断没断。这种设计能避免90%以上的重复ID问题。
5.3 多路视频时间不同步,轨迹拼接全是错位
现象:A镜头里人明明已经离开画面10秒,B镜头才出现这个人的画面,系统在两个镜头里各建了一个ID,因为特征匹配的时间窗口根本对不上。
原因:摄像头各自独立计时,视频流延迟不一致。步态是时序特征,错位之后不光匹配不成立,连步频都没法算准。
解决:单机测试时开启全帧缓存,以第一路视频的启动时间为基准做偏移校正;生产环境需要PTP或GPS授时同步,毕设里可以手动给各路视频加时间戳,读帧之前先对齐到同一时间轴。实录视频如果没做任何同步,建议干脆离线逐帧对齐再跑算法。
5.4 步频特征被帧率坑了
现象:同一人录入模板时是60fps视频,测试时是25fps视频,系统判定“不是同一个人”。
原因:步频是时间相关量,帧率不同,采样到的步态周期点数完全不同;人在镜头里走得快慢也会直接改变步频值,这是一类别的问题。
解决:步频只当辅助线索,不要让它在匹配分数里占大头;主特征用关节角度这类姿态结构量。如果一定要用步频,先把所有视频统一重采样到固定帧率,再做周期估计。另一个血泪经验是:录制模板和测试视频最好用同样规格的采集设备,省去一大堆后处理麻烦。
5.5 密集人群里的漏检把特征层饿死
现象:三四个人并排走或前后遮挡严重的地方,YOLOv5只检出一个大框,里面装着两个完整的人,姿态网络提取骨架时直接输出乱点。
原因:NMS把重叠度高的框合并了,合并后的框既不像一个人,也没法提供独立的关键点序列。
解决:模型推理时把NMS的iou参数从默认0.45调到0.4左右,减少合并;训练时在数据里刻意加入密集行人的帧;如果人群太密,最好的办法不是硬怼检测器,而是在这个场景改用“段级别”步态匹配,不要强行逐帧提取单人的姿态。密集场景本来就是检测类算法的共同弱点,不丢人,答辩里大方说明限制比藏着掖着好。
6. 怎么证明系统真能“认人”:评估指标、可视化调试与轻量化方向
系统跑起来之后,最容易出现的错觉是“看着好像跟住了”。视频演示确实重要,但它没法量化。跨镜头跟踪系统通常看三个指标:MOTA衡量检测和跟踪联合起来的总体准确率,IDF1评估ID维护的对错,Rank-1专门考跨镜头检索的首位命中率。常见的验证工具是py-motmetrics,一条命令就可以打分:
pip install motmetrics python -m motmetrics.apps.eval_motchallenge \ --groundtruths gt/ \ --tests results/指标数值要在固定数据集上才有意义。我建议你自己留出一段没参与调参的视频做测试,先跑通整个流程再回头调。三个指标的基准值不搞绝对化,但在自建小数据集上,MOTA能到60%以上、IDF1到70%以上、Rank-1到75%以上,基本说明这套系统达到了“能被评审认可”的程度。
| 指标 | 计算口径 | 经验参考值 |
|---|---|---|
| MOTA | 漏检、误检、ID切换的联合惩罚 | ≥60% |
| IDF1 | 全局ID识别正确的综合F1 | ≥70% |
| Rank-1 | 跨镜头检索首位命中率 | ≥75% |
可视化调试比指标更早看出问题。我给每个全局ID画一条“镜头时间轴”:横轴是时间,纵轴是镜头编号,每个目标的出现区间画成一条彩色的线段。ID断裂和ID混用在这张图上一眼就能看出来——线段断成两截说明轨迹丢了,两条不同颜色的线纠缠交替说明ID混得厉害。这张图放进毕设论文里,视觉效果比一堆PR曲线直接得多。
进阶方向有两个:一是导成ONNX再用TensorRT加速,把检测加姿态推到实时;二是把轮廓法跟姿态法做特征融合,正面镜头用姿态、侧面镜头切轮廓,两头互补。我自己当年做完这套系统的最大教训是:demo跑通远不等于毕设收工,指标跑一遍、时间轴图画完,隐藏的问题比代码逻辑错误多得多。从第一天起就把评估和可视化挂上,比最后补做省太多时间。希望帮到你。
本文还有配套的精品资源,点击获取