ARTICLE DETAIL

资讯详情

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

YOLOv5+DeepSort车流量统计:密集场景下的跟踪与计数实战

YOLOv5+DeepSort车流量统计:密集场景下的跟踪与计数实战 简介面向计算机视觉与智能交通方向的开发者这份基于YOLOv5与DeepSort的车流量统计项目资料包专门应对密集车流场景下的车辆检测、跟踪与计数问题适合算法学习者、毕设选题者及工程落地人员快速起步。包内共117个文件以55个Python源码为核心配套20个YAML配置、7个Markdown说明、4个MP4演示视频、预训练模型权重等涵盖模型训练、目标跟踪、计数逻辑与部署配置如Dockerfile等环节压缩包约79.17MB便于离线使用与二次开发。目前已有288人学习项目结构清晰从数据处理到结果可视化均有对应模块可直接运行或按需修改。通过源码与演示视频能直观理解YOLOv5检测与DeepSort跟踪协同工作的原理并快速复现一套可适应密集交通流的统计方案。同时提供Jupyter Notebook示例与容器化环境配置方便逐步调试与部署。1. 密集车流下YOLOv5DeepSort 还够用吗这套方案能解决的问题和它的边界傍晚的城市快速路车流被压缩成一条缓慢蠕动的铁链小轿车连续变道大巴车体把后面的几辆车挡得严严实实。同一个目标在画面里被追成一串断裂的碎片ID 182 变成了 ID 193统计面板上的车流量数字跟着疯涨。这套基于 YOLOv5DeepSort 的车流量统计算法解决的就是这个场景下的流量统计问题——先由 YOLOv5 把每一帧里的车辆检测出来再由 DeepSort 在帧间把同一个车连成轨迹最后借助虚拟检测线做方向判定和计数。它适合城市快速路、高速匝道、交叉口和园区出入口的流量统计项目也能给速度估计、排队长度分析打底。需要先说清楚很多人把这类项目当成“训练完就能直接投产”的库真实项目中密集车流恰恰会把 DeepSort 的弱点全逼出来理解它的边界比跑通 demo 更重要。2. 检测、跟踪与计数盯的是同一件事YOLOv5DeepSort 的原理和分工一套车流量统计系统里检测器、跟踪器、计数器各管一段YOLOv5 负责在单帧画面里回答“哪里有车、车有多大”DeepSort 负责跨帧回答“这辆车是谁、它上一帧在哪”最后统计模块回答“它从哪条线经过、方向是什么、算不算一次通过”。很多项目把精力全花在检测精度上实际做出来才发现车流量统计的误差大头往往出在跟踪器的身份切换和计数逻辑的去重上。2.1 YOLOv5 在车流检测里的位置选它不是因为“新”先讲一个容易被忽略的事实车流量统计的检测目标相对固定就是轿车、卡车、公交车这几类场景也相对单一——固定的监控视角、固定的道路区域。这种任务对检测器的要求不是“什么都能认得”而是“在目标小、遮挡多、夜间光线差的情况下尽量少漏、少错”。YOLOv5 在这个定位下依然是很划算的选择网上搜 YOLOv5 网络结构图Backbone、Neck、Head 三段式布局很清楚改起来不费劲导出 ONNX、TensorRT 的案例也多训练部署的“后悔药”比新框架多得多。我一般会拿 YOLOv5s 或 YOLOv5m 做起步座舱摄像头分辨率如果偏低s 模型就够如果是 4K 画面截取 ROI 再送检m 甚至 l 的召回率会明显更好。近两年的 YOLOv8、YOLOv11 检测精度更高但配套的 DeepSort 整合代码、前后处理细节在网上远不如 v5 积累厚实。车流统计项目里“稳定复现”的价值大于“版本新”这是我选型的第一原则。2.2 DeepSort 的跟踪链路卡尔曼预测、级联匹配与 ReID 特征DeepSort 的核心链路分三段。第一段是卡尔曼滤波它在 8 维状态空间里维护每个目标的运动状态包括 bbox 中心坐标、宽高比、高度以及它们的速度用它预测目标在下一帧的位置。第二段是级联匹配优先给那些连续出现、轨迹稳定的目标做匹配再处理刚丢了几帧的轨迹。第三段是外观特征DeepSort 通过一个小的 ReID 网络提取车辆外观特征用余弦距离判断两个框是不是同一辆车。这三段合起来就是 DeepSort 比原始 Sort 强的地方Sort 只靠 IoU 和运动预测一旦遮挡导致框跳变就切换身份DeepSort 多了一层外观特征能扛住短时间的遮挡。但密集车流恰恰是它的压力测试——车辆外观相似度高黑色轿车和深灰轿车在 ReID 特征空间里几乎挤在一起余弦距离分不开车辆互相遮挡时卡尔曼预测的位置和真实位置偏差变大马氏距离也失效。后面避坑章会专门展开这些问题这里先记住一个结论DeepSort 不是万能的它在稀疏车流里表现优秀在密集车流里需要调参数、改逻辑。2.3 三个模块怎么分工检测输出、跟踪更新与统计入口从工程代码的角度看主循环一般长这样# 主循环检测 - 跟踪 - 统计 cap cv2.VideoCapture(video_path) while cap.isOpened(): ret, frame cap.read() if not ret: break # 1. YOLOv5 检测返回 xyxy、置信度、类别 dets detect_model(frame, conf_thres0.4, iou_thres0.5) # 2. DeepSort 更新用当前帧检测结果更新跟踪器 tracks deepsort.update(dets, frame) # 3. 统计模块用轨迹信息做跨线计数 counter.update(tracks) # 4. 绘制画面检测框、ID、计数结果 draw_result(frame, tracks, counter)这段代码是整套系统的骨架。注意 detect_model 返回的是检测框列表DeepSort 的 update 方法同时接收检测结果和原始图像因为它内部既要运动匹配也要提取外观特征。tracker 返回的 tracks 里包含每个目标的 bbox、ID 和确认状态计数逻辑只应该使用 is_confirmed 为 True 的轨迹否则刚出现一两帧的噪声框也会被计入流量。我见过有人直接拿检测框数量去当车流量的那会把同一辆车在连续帧里重复计数属于最典型的统计口径错误。3. 用 YOLOv5 训练自己的车辆检测器数据、超参数与后处理调优检测器是整套系统的底层它的召回率直接决定跟踪器能拿到多少有效输入。密集车流里最怕的是漏检一辆车被遮住两秒钟DeepSort 的轨迹就会断掉断掉再出现就会产生新的 ID计数自然翻倍。所以训练阶段的目标不是“mAP 好看”而是“在遮挡和拥挤条件下依然能稳定检出”。3.1 数据集从哪来公开数据集与自建标注的取舍做车流量统计公开数据集首选车辆目标密集、带真实交通场景的。UA-DETRAC 是常见选择它包含城市道路的车辆检测和跟踪标注画面里车流密度不小SODA 系列和 BDD100K 也覆盖城市道路夜间和恶劣天气样本相对全。直接用它们训练的问题是视角差异很多公开数据是车载视角或高点俯拍而你的项目大概率是路杆上的固定监控视角俯拍角度下车辆是“顶视侧视”混合形态直接用公开集训练出来的模型在目标小而密集时召回会掉。我一般会采用“公开集预训练 自建场景微调”的路线先用 COCO 或 UA-DETRAC 预训练权重起步再拿自己现场拍摄的 3 到 5 段视频每段抽几百帧做标注。标注工具用 LabelImg 或 X-AnyLabeling 都可以输出 YOLO 格式的 txt。目录结构按 YOLOv5 的习惯组织datasets/vehicles/ ├── images/ │ ├── train/ # 现场视频抽帧约 2000 张 │ └── val/ # 另一时段视频抽帧约 300 张 ├── labels/ │ ├── train/ # 与 images/train 一一对应的 txt │ └── val/ # 与 images/val 一一对应的 txt └── vehicles.yaml # 数据配置labels 里每行是一个目标类别 ID、归一化中心 x、归一化中心 y、归一化宽 w、归一化高 h。类别尽量控制在 car、bus、truck 三类摩托车和行人单独建类避免混在一起干扰车辆计数。标注的难点在遮挡两辆车重叠时被遮挡严重的车要不要标我的做法是“能看出完整轮廓就标只剩一条边就不标”这样可以减少训练时的噪声标签让模型把精力放在能有效检测的目标上。3.2 训练自己的数据集目录结构、data yaml 与常用超参数先看 vehicles.yaml 的写法train: datasets/vehicles/images/train val: datasets/vehicles/images/val nc: 3 names: [car, bus, truck]然后跑训练命令。YOLOv5 官方仓库的 train.py 是入口下面是我在车流项目里比较稳的一组参数python train.py \ --data vehicles.yaml \ --weights yolov5s.pt \ --img 1280 \ --batch 16 \ --epochs 150 \ --hyp hyp.scratch-low.yaml \ --patience 30这里有几个参数值得展开。--img 是训练分辨率密集车流里目标普遍小用 640 起步时小车的召回率往往不够提到 1280 能明显改善远处小车的检出但显存占用和推理时间都会上涨如果现场摄像头画面里车辆占比较高可以先从 640 训练再用 1280 微调几个 epoch。--batch 受显存限制训练 1280 分辨率时 batch 16 需要约 16GB 显存显存小就降到 8。--hyp 是 YOLOv5 超参数文件hyp.scratch-low.yaml 比较稳不要一上来就开高增强密集车流场景里 mosaic 增强会大量制造“车辆挤在一起”的训练样本反而对遮挡鲁棒性有帮助但如果你的验证集是稀疏车流mosaic 过多会让模型在真实稀疏场景下误检。超参数调优是车流项目里最像“玄学”的部分但有几个规律可以遵循学习率 lr0 保持默认的 0.01不要调大密集小目标场景下调大学习率很容易发散anchors 参数可以不开自动锚框用默认值数据增强里的 hsv_h、hsv_s 可以稍微调低因为车辆颜色是计数时不敏感的信息过度改变颜色反而让模型学到不真实的外观。3.3 推理后处理调优conf_thres、iou_thres 和 NMS 的取舍模型训完真正决定上线效果的还有检测后处理。YOLOv5 的后处理包含置信度阈值和 NMS--conf-thres 和 --iou-thres 这两个参数在密集车流里的影响比很多人想象的大。conf-thres 设得太低路牌、护栏、树影会被误检成车跟踪器会为这些假目标维护轨迹消耗算力也污染计数设得太高被遮挡的车辆尾部、夜间只露出半个车身的车就会被漏掉。iou-thres 管 NMS 的合并力度在并排行驶的车流里尤其敏感。两辆轿车并排且部分重叠iou-thres 设成 0.5 时可能被合并成一个框跟踪器只追到一个 ID流量少计一辆调到 0.3 左右重叠目标更容易被保留为两个独立框。我一般从 conf0.4、iou0.5 起步再用验证视频逐帧检查重点看三类错检护栏被当车、相邻车辆被合并、远处小车漏检。调参没有捷径抽 200 帧数出“应该检出多少车”作为基准把误检和漏检量化后再改阈值比自己盯着屏幕感觉要可靠。3.4 把检测结果交给 DeepSort从 bbox 到轨迹的最小代码训练的模型推理输出是 xyxy 格式的检测框DeepSort 的 update 方法期望的输入一般也是 xyxy 加置信度加类别信息。接口转换是新手最容易翻车的位置下面这段是我在项目里固定用的适配写法from deep_sort.deep_sort import DeepSort deepsort DeepSort( model_pathweights/osnet_x1_0_msmt17.pth, max_dist0.2, max_iou_distance0.7, max_age70, n_init3, nn_budget100, ) def detect_to_tracks(frame, results): dets [] for *xyxy, conf, cls in results: # results 是 YOLOv5 的推理输出 if int(cls) not in (0, 2, 5): # 只保留 car、bus、truck continue if conf 0.4: continue x1, y1, x2, y2 map(int, xyxy) dets.append([x1, y1, x2, y2, conf, int(cls)]) if len(dets) 0: return [] return deepsort.update(dets, frame)注意这段代码里有三个关键过滤类别过滤保证行人、摩托车不会进入车辆计数置信度过滤在跟踪前去掉低质量检测减少假轨迹deepsort.update 必须在每帧调用一次即使没有检测结果也要传空列表调用否则跟踪器内部的时间戳和轨迹状态不会推进。max_dist 是外观特征的最大余弦距离密集车流里可以放宽到 0.3但放宽后不同车辆的误匹配也会增加。n_init 控制一个目标连续出现多少帧后才被确认为正式轨迹我习惯保持 3太低会把闪烁的误检当成真车。4. 车流量统计核心落地虚拟检测线、方向判定与跨帧去重检测和跟踪都跑通之后车流量统计本身反而是整个项目里最需要想清楚的部分。统计口径是什么按车道分别计数还是加总要不要区分方向这些问题直接决定了计数逻辑的复杂度。下面我把常用做法拆开讲代码可以直接抄到自己的项目里改。4.1 三种主流统计方案对比检测线、检测区域与轨迹计数统计方式适用场景优点缺点我的建议虚拟检测线断面流量统计、方向统计实现简单方向判定直观车辆在线上停留时可能误判多数项目首选检测区域交叉口排队、区域密度能统计区域存在数量无法区分方向密集时重叠严重辅助统计不单独用轨迹计数车速估计、车道级分析信息量最大可做速度对跟踪稳定性要求高与检测线结合使用虚拟检测线的本质是在画面里画一条线段当车辆的跟踪轨迹穿过这条线时车辆方向、通过时间、车道信息都能记录下来。检测区域适合统计“某个路口排队长度”或者“区域内车辆数”但它统计不出流量——车辆在区域内停多久都算存在不形成“通过”事件。轨迹计数则是把完整轨迹作为计数依据理论上最准但在密集车流里轨迹频繁断裂会让计数严重失真。我的项目里几乎都是“检测线计数 轨迹辅助计算车速”的组合。4.2 用虚拟检测线做方向判定直线方程与跨线判断画检测线时最实用的一条经验用车辆 bbox 的底部中心点作为车辆位置而不是 bbox 中心点。原因在于透视关系摄像头俯拍时车辆整体在地面的投影是梯形bbox 底部中心最接近车辆与地面的实际接触点用它做跨线判定误差远小于用框中心。下面这段代码是检测线最小实现class VirtualLine: def __init__(self, p1, p2): # 线段端点 p1, p2 在画面像素坐标下 self.p1 p1 self.p2 p2 # 直线方程 ax by c 0 self.a p2[1] - p1[1] self.b p1[0] - p2[0] self.c p2[0] * p1[1] - p1[0] * p2[1] def side(self, pt): # pt 为 (x, y)返回值正负代表点在线的哪一侧 return self.a * pt[0] self.b * pt[1] self.c # 在画面上选一条横向检测线端点根据实际场景标定 line VirtualLine((200, 720), (1680, 720))每帧拿到跟踪轨迹后取轨迹里车辆的底部中心点计算它相对检测线是哪一侧和上一帧做比较。符号变化说明车辆跨过了线。这里有一个容易被忽略的细节车辆跨线的过程中底部中心点在某一帧可能恰好落在线上side 值接近 0此时不要立即判定跨线而是等下一帧确认符号已经翻转避免车辆在线上缓慢移动时反复触发。方向判定依赖检测线的两个端点位置。比如一条从左到右的横向线如果车辆底部中心从线的“上方”移动到“下方”说明它在画面里向下行驶从“下方”到“上方”则相反。如果你想统计对向两车道车流就画两条方向相反的检测线或者一条线两侧各计一个方向。4.3 跨帧去重与计数循环一个 ID 只算一次跨帧去重是车流量统计的核心。视频流里同一辆车出现在几十帧画面中如果每帧都计数一辆车会被重复统计几十次。依赖 DeepSort 的 ID 去重是最直接的方案每个 track 在生命周期内跨过检测线时只触发一次计数。我在代码里给每个 track 加一个 crossed_line 的标记状态跨线后立即置位轨迹销毁前不会再计。class TrafficCounter: def __init__(self, line): self.line line self.up_count 0 # 向上方向车辆数 self.down_count 0 # 向下方向车辆数 self.crossed_ids set() # 已经跨线计数的 track ID def update(self, tracks): for track in tracks: if not track.is_confirmed(): continue track_id track.id if track_id in self.crossed_ids: continue # 取 bbox 底部中心点作为车辆位置 x1, y1, x2, y2 track.to_ltrb() bottom_center ((x1 x2) / 2, y2) prev_side track.prev_line_side cur_side self.line.side(bottom_center) if prev_side is not None and prev_side * cur_side 0: if prev_side 0: # 从正侧跨到负侧 self.down_count 1 else: self.up_count 1 self.crossed_ids.add(track_id) track.prev_line_side cur_side这段代码有四个关键点。第一只有 is_confirmed 的轨迹才参与计数避免噪声轨迹污染。第二crossed_ids 用集合去重一个 ID 全生命周期只计一次停车排队时车辆在检测线上来回蠕动也不会重复计。第三prev_line_side 需要作为轨迹的状态保存每帧更新不能只靠全局变量多目标场景下每个车的侧边状态必须独立。第四计数方向用 prev_side 的符号判断prev_side 0 跨到负侧计为 down反之为 up方向定义你可以按照实际场景里的上下关系调整。这个方案的瓶颈在 DeepSort 的 ID 稳定性。如果一辆车中途 ID 切换crossed_ids 里记录的是旧 ID新车 ID 跨线时会再计一次这就是我在第 5 章重点讲的问题。项目上线前我会在计数循环里顺带记录每一辆车的跨线时刻和车道位置方便后续和人工计数做逐车比对。5. 密集车流避坑指南ID 跳变、遮挡漏检与重复计数这一章是我反复调试后积累下的踩坑记录每一条都是在真实项目里遇到过、并且能用参数和逻辑解决的。现象、原因、解决方式分开写方便你对照排查。5.1 跟踪 ID 频繁跳变外观特征失效和卡尔曼预测漂移现象同一辆车在画面里行驶时 ID 从 182 变成 193计数器被重复触发两辆并排行驶的黑车ID 在它们之间来回交换。原因密集车流里车辆外观相似ReID 特征区分度差DeepSort 依靠外观余弦距离匹配时置信度不够同时车辆近距离交错时卡尔曼滤波预测的 bbox 和真实位置偏差变大运动马氏距离也失效最终匈牙利算法把两辆车的 ID 互相匹配。这是 DeepSort 在密集场景下的先天短板。解决我一般从三方面入手。一是把 max_iou_distance 从默认 0.7 调到 0.5 左右让遮挡严重的框更不容易被错误关联二是把 max_age 适当调大70 到 100给短时遮挡的轨迹更多存活时间但注意不要超过 120否则轨迹丢失后会带着旧位置错误参与匹配三是用更强的 ReID 权重替换默认的 ckpt.t7项目里我换成过 osnet 系列的权重密集车流的 ID Switch 减少一半以上。如果场景极端密集上面这些都不够我会直接考虑 ByteTrack 这类以检测框关联为主、不依赖 ReID 的跟踪器。5.2 检测线附近漏计、重计遮挡、静止车辆与轨迹中断现象一辆小车被大巴挡住轨迹中断出现后变成新 ID跨线时被计了两次堵车时车辆长时间停在检测线上反复触发计数或者完全漏计。原因轨迹中断后 DeepSort 重新初始化轨迹新 ID 不认得旧 ID计数去重失效。静止车辆在检测线上停留时它可能在 n_init 帧内被反复确认和丢失产生重复事件或者车辆还没跨线就被判定为丢失等它再次出现时已经跨过线漏计。解决检测线的位置要尽量避开遮挡严重的区域放在车道中段而不是路口停止线附近。逻辑上我把“跨线”事件改成“进入确认区再跨线”车辆从进入检测线附近区域开始只有轨迹连续存活超过 5 帧且跨线方向一致才允许计数。对于静止车辆我会引入速度判断track 的位移速度低于阈值时不计入流量只计入排队状态。注意这需要产品定义支持车流量统计一般统计“通过断面的车辆”不是“存在车辆”。5.3 夜间、逆光与摄像头抖动检测器掉点后的连锁反应现象夜间车灯过曝车体轮廓模糊检测框时有时无逆光时车辆变成剪影检测框全丢大风或货车经过时摄像头抖动画面里所有目标的坐标整体偏移计数在检测线上反复横跳。原因检测器在低照度、过曝、逆光条件下的召回率下降跟踪器拿不到稳定的检测框输入轨迹自然断摄像头抖动相当于给所有坐标加了高频噪声跨线判定在边界附近反复触发。解决数据层面训练集里加入夜间、逆光和雨雾样本用图像增强模拟低照度和过曝比只跑白天数据有效。工程层面检测线不要贴着画面边缘给抖动留出余量在线侧边各加一条“缓冲区”只有从缓冲区外进入缓冲区再跨线才算有效可以过滤掉抖动造成的小幅坐标波动。最彻底的办法是做帧间运动补偿用 ORB 特征点估计全局平移量把坐标对齐后再送检测但这是额外工作量我只有摄像头固定不牢的项目才做。5.4 数据可信度怎么自检人工复核与误差评估现象程序给出的车流量和人工数出来的对不上但看画面又觉得程序挺合理不知道信谁。原因计数误差的来源是多级的检测漏检、跟踪 ID 切换、跨线判定时机、统计口径不一致任何一级的微小偏差在长时间统计下都会被放大。项目上线前如果没建立误差基线后续优化没有抓手。解决我的做法是取一段 15 分钟原始视频找两个人数出分方向的车流量再和程序输出做逐车比对。最终误差率控制在 5% 以内算合格超过 5% 就用比对结果定位是检测掉了还是 ID 切了。下面这段是误差评估的简化脚本def evaluate_count(manual_count, system_count): if manual_count 0: return 0.0 return abs(system_count - manual_count) / manual_count # 示例人工数出单向 100 辆程序数出 106 辆 manual 100 system 106 print(f误差率: {evaluate_count(manual, system) * 100:.1f}%)真实项目里我会把误差按方向分别统计因为不同方向的误差原因不同合在一起会掩盖问题。误差超过 5% 时优先看跟踪器输出里 ID Switch 的次数如果 ID Switch 频繁问题在 DeepSort 的参数如果 ID Switch 不多但总数仍不对再回头排查检测器的漏检检。6. 从 DeepSort 改进到多断面验证项目的进阶形态和验收方法如果你按前面的步骤跑通了计数下一步就是考虑如何让这套系统在更严苛的场景站住脚。密集车流的改进方向通常分两条线一条在跟踪器层面把 DeepSort 的 ReID 主干换成更强的模型或者直接切换成 ByteTrack 这类不依赖外观特征的跟踪器另一条在统计逻辑层面用多检测线交叉验证来校正单线误判。先讲跟踪器改进。我在一个双向六车道的项目中试过把 DeepSort 的 ReID 权重从默认 ckpt.t7 换成 osnet_x1_0_msmt17ID Switch 降低了约四成但特征提取耗时增加帧率下降 10% 左右。如果车流密度高到车辆互相贴住DeepSort 的级联匹配会频繁失手ByteTrack 反而更合适——它只用检测框和 IoU 关联低置信度框也保留参与匹配对遮挡的容忍度更高。代价是车辆快速交错时可能把两辆车缝成一个轨迹。具体选哪套我用一段包含严重拥堵的视频做评测分别跑出计数误差率再决定不凭感觉。多断面验证是我强烈建议加的一层保险。在车流方向上设置两道检测线两线相距一定像素车辆先过线 A 再过线 B只有依次经过两条线且轨迹连续的目标才进入最终计数。这样做有两个好处一是单线误判可以被第二道线纠正二是两线距离和跨线时间可以直接推算出车速送给下游做超速统计。注意两道线的间距要足够大密了没有验证意义疏了会让轨迹中断概率上升。验收阶段的最后一项是统计口径确认。做项目之前先问清楚甲方要什么是“通过量”“断面流量”还是“存在数量”高峰期的数据是否需要按 5 分钟粒度汇总夜间最低照明亮度是多少这些问题直接决定你调参的方向。我现在做新项目时会先拿一小段原始视频和两个人分别数一遍先定出允许误差再谈模型参数这套习惯帮我少走了很多弯路希望帮到你。本文还有配套的精品资源点击获取
返回列表