ARTICLE DETAIL

资讯详情

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

基于YOLOv5和DeepSORT的车辆行人追踪计数实战解析

基于YOLOv5和DeepSORT的车辆行人追踪计数实战解析 简介基于YOLOv5与DeepSort的车辆行人追踪计数项目是一套面向毕业设计、课程设计和期末大作业的完整可运行源码。项目将YOLOv5目标检测与DeepSort多目标跟踪有机结合能够对视频或实时画面中的车辆和行人进行精准识别、连续跟踪与自动计数代码中附有详细注释逻辑清晰新手根据使用说明即可完成环境配置和部署。资源包共包含七十八个文件主要涉及五十个Python脚本、六个YAML配置文件、预训练权重文件、说明文档以及测试视频等整体大小约八十二点六九兆字节目录模块划分明确便于按需调用和二次开发。目前该项目已有两百四十六人学习下载适合需要快速搭建智能监控、交通流量统计等应用场景的开发者参考。整体经过调试稳定界面简洁既能直接作为毕业设计演示也可在此基础之上扩展车型识别、越界报警等功能具有较高的实用价值与完成度。1. 基于YOLOv5Deepsort实现车辆行人追踪和计数项目源码毕设为什么值得盯住这条链路“基于YOLOv5Deepsort实现车辆行人追踪和计数项目源码”这个标题看起来只是把一个仓库名写进了论文封面但它实际上覆盖了视频分析项目里最完整的一条链路视频进、框出来框变成轨迹轨迹变成计数值。用YOLOv5做单帧目标检测用Deepsort把同一个车辆的检测结果连成一条轨迹最后基于轨迹去统计进出方向和数量。这个组合最适合两类人一是毕业设计需要可演示、可量化效果的开发者二是已经有了检测基础、想做计数的从业者。它解决的问题很具体——单帧检测本身无法回答“这一辆车是不是刚才那一辆”只有加追踪才能得到不重不漏的计数。在开源社区里围绕这套组合的轮子已经很多但大多数“源码包”只是把两个模型拼在一起关键的计数逻辑和参数配置反而没人讲。下面重点讲清楚的关键点就是把你从“能跑通”拉到“能解释为什么跑通、参数为什么这么设”的状态。2. 把检测层先做稳用YOLOv5跑通车辆行人的识别与后处理检测层是整个计数项目的地基。计数器看似依赖后端的追踪和跨线逻辑但绝大多数计数误差的源头都在检测层漏检导致轨迹断掉错检导致track_id乱跳。先把YOLOv5的输出吃透DeepSORT才不会跟着翻车。2.1 最小可用命令一条视频直接让YOLOv5输出车辆行人框拿到项目源码后第一步不是打开IDE读代码而是先让模型对一段车辆视频产生逐帧检测结果。在YOLOv5的源码根目录下最直接的命令是python detect.py --weights yolov5s.pt --source ./test_video.mp4 --conf-thres 0.35 --iou-thres 0.45 --classes 0 2 5 7 --save-txt这段命令的要点在参数上--conf-thres 0.35把置信度阈值从默认的0.25提到0.35先过滤掉一批误检--iou-thres 0.45控制NMS的并交比阈值阈值越小重叠框被合并得越彻底--classes 0 2 5 7是COCO数据集中本次要用的类别索引0代表person2、5、7分别代表car、bus、truck如果只需要轿车和行人可以直接写0 2。--save-txt会把每帧检测结果落到label文件里这比盯终端打印更容易定位问题。跑完命令需要理解一件事detect.py只是做单帧目标检测它没有能力知道“上一帧的框和这一帧的框是不是同一辆车”。这也是为什么不能只靠YOLOv5做计数——车辆一旦重叠、遮挡单帧检测结果就互相隔离。当要在自己的代码里集成而不是用命令行时常见做法是在项目内部实例化模型循环读取视频帧推理把检测结果组织成数组供后续追踪使用。2.2 检测结果的正确读法xyxy、置信度与类别索引缺一不可在项目源码里检测结果进入DeepSORT之前通常要过一道后处理转换。这个转换就是“yolov5后处理”在毕设中最常见的形态。YOLOv5官方推理接口返回的results对象在调用results.xyxy[0]后得到的是一个NumPy数组每行格式为[x1, y1, x2, y2, confidence, class_id]。我一般会先做一个过滤函数把不需要的类别和不稳定的低置信度框剔掉import numpy as np import torch # 加载本地权重sourcelocal 表示从当前源码目录加载模型 model torch.hub.load(., custom, pathyolov5s.pt, sourcelocal) model.conf 0.35 model.iou 0.45 # 只保留 person(0), car(2), bus(5), truck(7) model.classes [0, 2, 5, 7] def parse_detections(results): 把YOLOv5检测结果转换成追踪层要的格式 det results.xyxy[0].cpu().numpy() if len(det) 0: return np.empty((0, 6)), np.empty((0, 4)) # 按置信度排序确保高分目标排在前面 det det[det[:, 4].argsort()[::-1]] return det[:, :6], det[:, :4]参数说明在这里。model.conf和model.iou是YOLOv5模型对象的属性推理时内部直接使用不需要每次传参。det[:, :4]是四个坐标det[:, 4]是置信度det[:, 5]是类别索引。为什么我要按置信度排序因为DeepSORT的级联匹配内部会优先处理置信度高的目标让高分目标先抢占轨迹能减少低分误检对ID稳定性造成的影响。检查代码运行效果有一个硬性指标同一辆车的检测框在连续30帧里要至少稳定出现25帧。如果只有15帧说明置信度阈值太高或者模型本身对远距离小目标不敏感这种情况不要急着调DeepSORT参数回去看检测层才是正路。2.3 让模型适配自己的场景训练自己的数据集时超参数怎么定很多毕设数据的场景和COCO差距不小比如校园门口、夜间停车场、无人机俯视视角。在这些场景里直接用官方yolov5s权重会出现明显的漏检。如果项目源码里提供了原始标注建议还是训练一下。“yolov5训练自己的数据集”流程本身不复杂用labelImg或labelme标注Pascal VOC格式转成YOLO的txt格式然后准备一个data.yamltrain: ./dataset/images/train val: ./dataset/images/val nc: 2 names: [person, vehicle]这里的nc和names必须和标注类别完全一致。如果只分行人和车辆两类把所有车辆统一标成vehicle可以大大降低训练难度。训练命令里我习惯用--img 640 --batch 16 --epochs 100 --hyp hyp.scratch-low.yaml前三个参数不新鲜关键是超参数文件。默认的hyp.scratch-low.yaml已经够稳不要一上来就改学习率真正影响车辆行人项目的yolov5超参数是fl_gammafocal loss系数和mosaic马赛克增强。在夜间场景和密集人群下把fl_gamma从默认值调大到2.0小目标召回率会有可感知的提升。对计数项目只需要盯住recall这个指标不用管map精度多刷了零点几。检测层的作用是追踪和计数解决的是“同一目标是谁”检测解决的是“有没有目标”。检测层没做稳后面所有ID逻辑都是在错误的前提下盖楼。3. 追踪层如何接管检测框DeepSORT的卡尔曼滤波、级联匹配与参数调节单帧检测已经输出框了为什么还要追踪因为视频分析里的“同一个目标”这个概念单帧根本不存在。DeepSORT的作用是把每一帧检测结果按“身份”连接起来为每个目标分配一个稳定ID。车辆行人追踪和计数的成败本质上是让这个ID在目标整个生命周期内保持不变。3.1 DeepSORT在项目管线里的位置检测结果如何变成稳定ID我见过不止一个毕设项目把检测结果和追踪结果混淆把当前帧每个框的xyxy传入DeepSORT的update函数拿回来一堆tracks然后把tracks画在帧上。方向是对的但中间有坑。DeepSORT期望的输入不只是坐标还要类别、置信度以及当前帧原图。原图是用来裁剪目标区域、提取外观特征向量的。from deep_sort import DeepSort deepsort DeepSort( model_pathckpt.t7, # ReID外观特征提取权重的路径 max_dist0.2, # 外观特征余弦距离阈值 max_iou_distance0.7, # 追踪框和检测框的IOU距离阈值 max_age40, # 目标丢失后轨迹保留帧数 n_init3, # 连续匹配成功几帧后确认新ID nn_budget100 # 每个轨迹保留外观样本的数量 ) # 解析第2章得到的检测结果 # det[:, :4] 是 xyxy 坐标det[:, 4] 是置信度det[:, 5] 是类别ID x1, y1, x2, y2 det[:, 0], det[:, 1], det[:, 2], det[:, 3] # DeepSORT实际使用的是中心点坐标加宽高 bbox_xywh np.column_stack([ (x1 x2) / 2.0, (y1 y2) / 2.0, (x2 - x1), (y2 - y1) ]) confidences det[:, 4] class_ids det[:, 5].astype(int) tracks deepsort.update( bbox_xywh, confidences, class_ids, frame_original # 当前帧BGR原图不能传空图 )这里最需要强调的不是坐标格式而是frame_original。很多复现到一半的人发现追踪效果和作者视频差很远一对比代码才发现传进update的是一张经过letterbox填充后的归一化图特征提取器输入的不是实际目标追踪效果自然不行。代码里max_dist控制外观特征匹配的容忍度值越小要求两张图越像max_age控制一条轨迹丢失后保留的最长帧数车辆场景40帧是在普通路口视频里比较稳的起点n_init3表示新目标要连续3帧都匹配成功才被确认等于防止单次误检直接获得正式ID。3.2 决定ID稳不稳的三个参数max_dist、max_age和n_init卡尔曼滤波在DeepSORT里做的事情很简单用历史运动状态预测目标下一帧的位置。匹配阶段用预测位置与实际检测框算IOU和外观余弦距离放进代价矩阵匈牙利算法在这个矩阵上找全局最优匹配。这就是ID能跨帧持有的原因。调max_age时实际是在决定“预测”能撑多久。初学者最常犯的错是同样一套参数用在所有场景。车辆行人的运动速度、遮挡程度、重叠频率差别很大参数必须分开调。这里有一个典型参数表是我在一个路口录像上反复试过的起始值参数车辆场景行人密集场景说明max_dist0.30.5行人穿着相似度高阈值太低会频繁丢轨迹max_iou_distance0.50.7密集人群重叠多IOU阈值要放宽max_age25~4050~60车辆被遮挡恢复后位置变化大轨迹不宜保留过久n_init32行人目标小检测不稳定少等一帧确认更顺先把这组参数套进去跑一遍再按现场微调。注意一个原则调参的顺序永远是先调检测置信度再调max_age最后才调max_dist。因为检测漏了会导致轨迹断max_age是给断了的轨迹一个补救窗口max_dist则是最后一道外观把关。如果先动了max_dist很难分辨计数改善到底来自检测还是追踪。3.3 DeepSORT改进方向用外观ReID特征解决遮挡后的身份变化“deepsort改进”在毕业设计里几乎是必问加分项不是没理由的——原版DeepSORT在新版PyTorch上加载权重时常有问题而且ReID特征模型本身对行人训练得多对车辆外观的区分能力一般。两辆同型号、同颜色的车并排走过外观特征几乎一样仅靠外观匹配是不够的。常见做法是更换更强的ReID模型或通过增大nn_budget让特征队列保留更多历史外观。但我的经验是毕设里最划算的改进不是换网络而是在前端过滤检测噪声把第2章里的conf阈值从0.25提到0.4并做一次轻量NMS就能减少一半的ID跳变。追踪器的好坏和输入质量强相关权重网络只是兜底。不要为了改进而改进先用参数把现有链路压榨干再考虑替换模型。4. 从追踪到计数跨线判定、去重与结果的落地实现如果只是追踪项目只能演示“框跟着人走”。毕设里要有数字输出就得把轨迹变成计数。计数逻辑通常放在追踪返回tracks之后、画框之前因为每一帧拿到的tracks里带有当前track_id和经过卡尔曼滤波平滑后的中心点这是进行跨线判断的最小输入。4.1 跨线计数的判定逻辑用叉积符号检测轨迹是否穿过虚拟线虚拟线是计数项目最常用的方案在画面上定义一条线段作为“门槛”。这条线通常划在路口或门禁位置方向和车辆行进方向垂直。跨线判定我一般用叉积符号变化而不是求点到直线距离后直接比较。原因很简单车辆直线行进时轨迹点可能离虚拟线很近但没有穿过去距离法会误报。叉积法根据点在线的哪一侧来做判断只有当两侧发生翻转时才认为是一次穿越更自然。import numpy as np LINE_START (320, 540) # 虚拟线起点图像像素坐标 LINE_END (960, 540) # 虚拟线终点 def cross_value(point, line_start, line_end): 点在线段哪一侧返回叉积值 x, y point xa, ya line_start xb, yb line_end return (xb - xa) * (y - ya) - (yb - ya) * (x - xa) def has_crossed(prev_point, curr_point, line_start, line_end): 上一帧和当前帧中心点分布在线的两侧说明发生了穿越 c1 cross_value(prev_point, line_start, line_end) c2 cross_value(curr_point, line_start, line_end) # 当前帧刚好落在线上的情况等下一帧再看 if abs(c2) 1e-6: return False return c1 * c2 0逻辑说明cross_value计算的是向量叉积的z分量正负号用于区分点在线段的哪一侧绝对值大小用于衡量点到线的距离。函数里唯一要注意的是abs(c2) 1e-6的兜底轨迹点恰好压在虚拟线上时当前帧不判定等下一帧位置明确后再比较避免反复横跳。这里有一个隐形的调参点绘制虚拟线时需要按原视频分辨率来定坐标不要拿letterbox之后的尺寸去标。因为检测框坐标已经映射回原图虚拟线也要在原图空间画否则坐标错位会导致计数的穿越点系统性偏移全部计数朝一个方向散开。验证方法也很简单画线出图时把线叠加到第一帧视频上放大检查线的位置是不是真的横跨了目标打算走的路面。4.2 解决“重复计数”用Track ID加时间窗口锁定计数状态重复计数是这类项目被答辩老师问得最多的一个点。同一个track_id穿过虚拟线只能计一次这个逻辑大家都懂代码也简单但实际场景里麻烦的是一辆车停了又走、走了又停ID还在账已经记了。如果只是死板地维护一个counted_ids set()目标ID被清空后再出现比如离开画面很久重新接入复用相同ID时就会被当成旧目标造成漏计数。所以我的方案是用时间戳做去重count_history {} # track_id - (last_count_frame, direction) def try_count(track_id, frame_idx, direction): 只有当前ID没有在本轮计过数而且没有被长时间锁定时才允许计数 if track_id in count_history: last_frame, last_dir count_history[track_id] # 同一ID在120帧内不重复计数防止线附近来回震荡 if frame_idx - last_frame 120: return False # 如果方向和上一次完全相同说明只是抖动不重复计数 if last_dir direction: return False # 超过120帧没有再次出现或方向已变化允许重新计数 count_history[track_id] (frame_idx, direction) return True这个函数的作用不只是去重它还顺带处理了方向合并同一个ID先朝A方向计了一次之后如果反向穿越按业务需求可能算第二次。这里的120帧是时间窗口参数在30fps下对应4秒。停车场出入口前走走停停的车辆4秒内不会重复计数但真的掉头折返超过4秒后就会被接受。参数值可以按场景调监测拥堵路口时我会放大到300帧而人流量统计场景120帧足够。计数方向的判定由叉积变化的符号决定c1 0 and c2 0可以记为一个方向反之为另一个方向。4.3 计数结果怎么落地OpenCV叠加、CSV和JSON导出有了一套跨线判定下一步是把计数值输出出来。毕设里通常要求两个形式画面上的实时数字和后处理用的结构化数据。OpenCV叠加是逐帧做的放在画框的同一位置CSV/JSON导出则要同时带上track_id和时间戳方便后面做人工校验。import csv import json from datetime import datetime # 叠加显示 cv2.putText(frame, fIN: {count_in} OUT: {count_out}, (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 255, 0), 2) # 事件级导出一行代表一次有效穿越 with open(count_log.csv, a, newline) as f: writer csv.writer(f) writer.writerow([frame_idx, track_id, direction, datetime.now().isoformat()]) # 帧级别汇总用于事后分析 summary { frame: frame_idx, car_in: count_in, car_out: count_out, person_in: count_in_person, person_out: count_out_person }这段代码里最容易被忽略的是时间戳答辩时老师经常问“你的计数怎么证明是对的”一份带帧号、track_id、方向的日志就是证据。CSV按事件导出的好处是每一行只对应一次穿越不用在几千行帧级数据里捞数JSON则是给后续可视化脚本用的。实际项目中建议用“事件级CSV 帧级JSON”双写CSV给人看JSON给程序读。注意上面那段CSV写入是示例流程真正运行时要确保try_count返回True之后再写这一行不要在每帧结束时遍历全部count_history否则会把同一事件重复写多次。5. YOLOv5DeepSORT避坑指南5个导致计数翻车的常见问题与排查追踪计数项目能跑通的源码很多能在不同场景下稳定计数的很少。下面5条是我认为最典型的踩坑记录按现象、原因、解决的顺序说每一条都可以直接对号入座。5.1 一个行人被数成三个人ID跳变引发重复计数现象视频里一个人从画面左侧走到右侧输出total从1直接跳到3检查跟踪log发现同一瞬间出现的track_id在12、14、15之间来回切换。原因行人在行走过程中展腿、背包或衣物遮挡会导致检测框出现间歇性丢失。检测一旦丢帧DeepSORT的轨迹在n_init判定失败后死亡下一次检测出现时重新创建了新ID同一真实目标因此被数了三次。解决先回到检测层把conf_thres从默认0.25提高到0.4稳定单帧召回再把DeepSORT的n_init从3降到2让新ID更快建立最后把max_age加大到50帧左右给检测抖动预留缓冲。按这个顺序排查大多数ID跳变问题在参数阶段就结束了不需要动模型。5.2 停在计数线附近的车被反复1轨迹点在线上震荡现象一辆车在停止线前减速停车车辆并没有真正穿过虚拟线计数值却在两秒内从1涨到5。原因车的中心点恰好压在虚拟线附近帧间抖动使中心点在线的两侧来回跳动叉积符号反复翻转跨线判定被连续触发。解决两条路一起走。第一try_count里加时间窗口同一ID在120帧内只能最多计一次第二在has_crossed前加一个最小位移检查只有当前帧中心点相对上一帧移动超过比如8像素才认为是真实运动状态静止抖动被过滤。位移阈值要用原图像素尺度按视频分辨率上下调整。5.3 环境配置的玄学ReID权重加载报错与依赖版本冲突现象conda创建了Python 3.8环境torch也装好了yolov5命令行能正常出框但运行到deepsort.update()时torch.load()报权重文件格式错或者sklearn版本对不上直接卡死。原因很多DeepSORT源码实现停留在torch 1.x时代新装torch 2.x在加载旧checkpoint时会因为权重序列化格式变化报错另外sklearn从1.2开始废弃了部分旧接口追踪器内部调老接口会抛异常。这就是“yolov5环境配置”里最隐蔽的一层。解决把环境固定为python 3.8 torch 1.13 scikit-learn 1.0的兼容组合。如果项目已经在新版torch上跑通了yolov5就不必把两个模型放进同一个环境可以用两个虚拟环境分别跑再用文件或消息队列交接检测结果。在树莓派或Windows上做开发时尤其值得这样隔离避免一次牵动所有依赖。5.4 目标消失很久再出现max_age不是越大越稳现象一辆车在画面外停了8秒后重新进入路口系统依旧沿用它原来的track_id把这次进入当成了再一次穿过计数线结果方向统计错乱。原因max_age设到200意味着轨迹在目标消失后还能存活200帧。车辆重新出现时外观ReID特征匹配评分和预测位置恰好过阈值老ID被“复活”最终它和真实的新目标占用了同一次计数机会。解决把车辆场景的max_age收到25~40帧行人场景50~60帧。原因很朴素车辆在路口被遮挡的时间通常不超过1秒离开久了身份本来就该重建。判断max_age是否过大有一个快速办法——把消失再出现那一小段单独剪出来观察复活老ID后轨迹中心点是否突然跳了一大段距离如果是调小max_age即可。5.5 部署到树莓派5这类边缘设备时帧率大跌后处理是隐形瓶颈现象在PC上跑25fps的计数程序部署到树莓派5之后掉到2fps车辆已经过了线计数画面还没跟上。原因模型本身确实慢但更重要的瓶颈在Python侧。YOLOv5每次前向的letterbox填充、NMS以及逐帧把批量结果从GPU搬回CPU再做后处理这些开销在边缘设备上没有GPU加速时被完全放大导致吞吐量骤降。解决面向边缘部署时先把--img从640降到320再把--weights换成yolov5n把类别过滤从运行时过滤提前到模型解析后立刻截断只保留person和车辆然后将模型导出为TensorRT或ONNXONNX在树莓派上可以配合ONNX Runtime CPU后端运行。还有一个很有效的操作关掉YOLOv5的detect.py入口改为直接调用模型推理接口避免每次重新创建结果对象带来的零碎开销。这套做下来帧率通常能翻倍计数结果虽然还会滞后半秒但不再出现目标过线后才记录的事件。6. 让计数值经得起复核手工验证方法、轨迹尾迹调试与一条实用技巧毕设答辩时老师大概率会问“你这计数准吗”。代码里没有置信区间所以要自己先做人工校验并把校验手段带进演示流程。6.1 用一段30秒视频做人工对照记录帧号、方向与ID选一段目标不太密集、能一眼数清的视频放慢到0.5倍速人肉记录一次穿越对应的帧号、方向、ID与程序导出的CSV比对。不要只对数要对ID数值对得上但ID对不上说明系统在靠错误补偿得到正确总数后面换场景大概率翻车。6.2 把轨迹尾迹画出来一秒定位断点调试阶段在每帧上画出每个track_id最近15帧的中心点连线可以很快发现ID在哪个位置断开、又在哪里重新建立。这个方法比盯着一串控制台日志直观得多。6.3 实用技巧对中心点做轻量平滑抑制抖动smooth_pos {} def smooth_center(track_id, cx, cy, alpha0.3): 对追踪返回的中心点做指数平滑供跨线判定使用 if track_id not in smooth_pos: smooth_pos[track_id] (cx, cy) return cx, cy px, py smooth_pos[track_id] sx alpha * cx (1 - alpha) * px sy alpha * cy (1 - alpha) * py smooth_pos[track_id] (sx, sy) return sx, sy这个平滑在计数判定的上游做只影响轨迹中心点不影响检测框坐标。我的习惯是新场景先不看精度先看连续30秒内每辆车的ID是否能全程稳定走完ID稳定了再谈计数。用这套方法调过的项目基本没有在答辩现场翻过车。希望帮到你。本文还有配套的精品资源点击获取
返回列表