ARTICLE DETAIL

资讯详情

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

YOLOv5+DeepSort车流量统计:密集车流下稳定计数的实践指南

YOLOv5+DeepSort车流量统计:密集车流下稳定计数的实践指南 简介基于YOLOv5与DeepSort的车流量统计算法项目源码包面向智慧交通、视频监控及多目标计数方向的开发者与学生重点解决密集车流场景下的车辆检测、跟踪与跨线计数问题。压缩包共117个文件包括55个Python脚本、20个YAML配置、7个Markdown文档、7个YAML环境配置以及PyTorch权重、Notebook、Shell部署脚本、Dockerfile和演示MP4/GIF等整体约79.17MB目录结构清晰便于按功能模块定位代码。Python脚本覆盖车辆检测、DeepSort跟踪与跨线计数完整流程YAML文件用于模型参数与数据配置PTH权重可直接加载实测Notebook适合调试与可视化Dockerfile和Shell脚本可快速搭建部署环境工程化程度较高兼顾研究与落地需求。目前已有288人学习下载适合需要直接获取可运行算法并理解工程化实践的读者。资源另附使用手册、演示动画和测试视频可对照效果快速验证也便于在真实交通录像上做二次开发与算法调优。1. 车流量统计与YOLOv5DeepSort这个组合到底解决什么问题交通口的视频里数车听起来是件小事真正做过的人才知道坑有多深车多的时候互相遮挡车慢的时候在画面里停很长时间一旦目标漏检几帧统计口径就全乱了。常见做法是在画面里画一条线车过线加一这个方案车少时勉强能用一旦进入密集车流检测框断断续续、ID频繁跳变计数结果根本没法交付。这也是“车流量统计-基于YOLOv5DeepSort实现的车流量统计算法-可适应密集车流场景”这类项目标题真正想解决的痛点不是把框画出来而是在遮挡、拥堵、并行的条件下仍然保持一个稳定的计数输出。YOLOv5负责把每辆车检测出来DeepSort负责让同一辆车在整个画面里始终是同一个ID两者配合车流量统计才能从“能跑”变成“能信”。这套思路适合做交通监控、园区出入统计、城市路口流量分析的人直接复现下面按我自己的落地顺序拆。2. 先拆方案检测、跟踪、计数三件事的分工与选型理由2.1 YOLOv5在车流量统计里的角色边界只出框不负责追车车流量统计的第一步是把“车”这个对象从画面里找出来。YOLOv5在项目里承担的职责明确每一帧输入图像输出若干个检测框每个框带类别和置信度。常见实现里YOLOv5在视频帧上产生的输出会经过一次置信度过滤和非极大值抑制最终进入跟踪器的只有那些分数达标的框。实际动手时很多入坑的人会想当然地认为“检测准了计数就准”这个认知在高速场景还说得过去但城市道路和路口场景很快会被打脸。车辆长时间停留在画面内摄像头视角低导致的遮挡车灯在夜间的光晕都会让YOLOv5的输出产生断裂。我有一次在夜间路口做测试一个小轿车的检测框在连续10帧里消失又出现3次如果直接用检测帧去计数一辆车能贡献三个统计量。一个实操上很有用的做法是把检测部分和统计逻辑解耦。检测只是上游单独调YOLOv5的精度意义不大真正决定车流量统计准确率的是检测框是否稳定地送进跟踪器。我一般会在YOLOv5的检测输出后追加一个统一的封装把每一帧的检测框整理成[x1, y1, x2, y2, score, class]的格式再传给DeepSort。这看起来是多余的转换层但后续换其他检测模型、调整输入尺寸时反而省事。2.2 DeepSort在密集车流里补上的一课遮挡时的ID保持单一帧检测的局限在于没有记忆。YOLOv5看到的是“这一帧哪些位置有车”但看不到“上一帧这辆车在哪儿”。DeepSort就是为了补这个时间维度存在的它对每一帧的检测框做外观特征提取同时用卡尔曼滤波预测上一帧每个目标在本帧的位置然后通过级联匹配和IOU匹配把检测框和已有轨迹关联起来。在车流量统计的语境里DeepSort的价值不在追得有多快而在ID保持得有多稳。密集车流中一辆车被大车遮挡几秒钟是常态如果跟踪器在遮挡结束后给这辆车换一个编号计数逻辑很快就会被搅乱。DeepSort的做法是用ReID特征做表观匹配遮挡前存下来的特征和遮挡后重新出现的框如果足够接近就延续原来的轨迹ID。这里要给一个实际的选型建议如果你手里的视频是高速路段车速快、遮挡少只用纯IOU关联的简单跟踪器也能对付但如果是路口、匝道、园区入口这类典型密集车流场景DeepSort的级联匹配和特征重关联机制就不是可选项而是必选项。原因很直接——车流量统计项目交付时领导第一个问的问题就是“为什么这辆车数重了”ID跳变是重数的第一大来源。2.3 两种统计口径虚拟检测线方案与检测区域方案完成检测和跟踪之后车流量统计的“统计”发生在哪个环节是方案设计上必须先回答的问题。项目标题里没有写明这一点但从业者常见的做法有两种。第一种是虚拟检测线。在画面中画一条横向或纵向的线给每个跟踪目标记录上一点的历史轨迹当轨迹与检测线相交时计数加一。这是目前车流量统计项目里最常用的方案优点是逻辑直观缺点是计数结果受线的位置影响很大线放得太靠近安装杆下方大车车头刚进画面就触发计数时间与真实过车时刻会有偏差。第二种是检测区域方案。在画面中划定一个区域目标进入区域时计数加一离开区域时再计数一次。这种方案适合统计“区域内当前有多少车”比如停车场、园区出入口不太适合“某一方向通过了多少车”的流量口径。我自己的习惯是两种都实现用一个配置文件切换。原因在于交管项目的口径经常事后调整有的要“北向南直行流量”有的只要“进出景区总量”视频素材相同换个计数逻辑就能适配省得重新走一遍标定流程。计数逻辑的调整只涉及约20行代码而重新采集和标注视频数据的成本远超这个数。3. 搭建并跑通YOLOv5DeepSort车流量统计的最小流程3.1 环境准备Python环境、PyTorch版本与CUDA对应关系最常见的入坑第一步是环境配不通。项目标题虽然没写环境要求但YOLOv5DeepSort这套组合在实战里99%跑在PyTorch上第一步就是把环境和模型权重准备好。我的建议是别用系统的 base 环境直接用 conda 新建一个环境Python 版本选 3.8 或 3.9。这里有个容易忽略的细节DeepSort 的 ReID 特征提取部分如果用了 torchreid 或者某些旧版本依赖在 Python 3.10 以上会遇到 API 不兼容的问题所以先把版本降下来后面始终能省心很多。创建环境的命令一般这样写注意把 CUDA 版本先确认好先执行nvidia-smi看驱动支持的最高 CUDA再选择对应 PyTorch 版本。conda create -n traffic python3.9 -y conda activate traffic pip install torch1.13.1 torchvision0.14.1 --index-url https://download.pytorch.org/whl/cu117逻辑说明conda 创建隔离环境是为了避免把机器上的其他项目搞乱Python 3.9 是一个兼容性窗口。PyTorch 1.13.1 是这套组合里比较稳定的版本配合 CUDA 11.7 的预编译包既能用 GPU 加速又不会像 PyTorch 2.0 那样引入编译期的不确定性。这里要特别提醒一下--index-url参数。很多人在这一步习惯用默认的pip install torch然后陷入 CPU 版本的泥潭——代码也能跑但视频推理速度只有 1~2 FPS到了车多的路口跟踪器根本来不及关联ID 跳变得一塌糊涂。GPU 版本和 CPU 版本在安装命令上的区别只有上面这一行 URL 参数但换来的性能差距接近 20 倍。3.2 模型权重准备与输入视频的预处理环境就绪后要准备好目标检测权重。标题里说的是 YOLOv5实际落地时你面临两个选择用官方预训练权重在 COCO 上训练包含 car、bus、truck 等类别还是用自己标注的数据微调后的权重。第一轮建议直接用官方权重把流程跑通原因很简单先让整个链路工作起来再讨论精度。视频输入也有讲究。交通摄像头输出的视频常见有 25 FPS 和 30 FPS 两种码率高的时候直接用 OpenCV 逐帧读会有大量丢帧。一个容易被忽略的工作是先用 ffmpeg 检查视频的编码格式和帧率ffprobe -v error -select_streams v:0 -show_entries streamwidth,height,r_frame_rate -of csvp0 traffic_video.mp4逻辑说明ffprobe是查看视频元信息的工具这条命令只关心分辨率尺寸和帧率输出如1920,1080,25/1。拿到这组数字之后才能确定后续跟踪器的时间参数设置。25 FPS 的视频卡尔曼滤波的位置预测步长每帧是 0.04 秒如果输入视频实际是 18 FPS 但代码只按 25 算DeepSort 的轨迹预测会产生系统性偏差表现为 ID 切换率升高。3.3 一次可跑通的完整推理把检测结果喂给跟踪器接下来是核心环节。下面这个代码块是我在多个车流量统计项目里反复使用的骨架它把 YOLOv5 的检测输出转换成 DeepSort 需要的输入格式并完成跟踪结果的输出import cv2 import torch import numpy as np # 加载 YOLOv5 模型本地权重或官方权重均可 model torch.hub.load(ultralytics/yolov5, custom, pathweights/best.pt, force_reloadFalse) # 从 deep_sort 导入跟踪器 from deep_sort.deep_sort.tracker import Tracker from deep_sort.deep_sort import nn_matching from deep_sort.application_util import preprocessing # 构造 DeepSort 的最近邻匹配器max_dist 控制外观特征的容忍范围 metric nn_matching.NearestNeighborDistanceMetric( cosine, max_dist0.2, max_age80) tracker Tracker(metric) # 只保留车相关的类别2 是 car5 是 bus7 是 truck vehicle_classes {2, 5, 7} cap cv2.VideoCapture(traffic_video.mp4) fps cap.get(cv2.CAP_PROP_FPS) width int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) writer cv2.VideoWriter(output.mp4, cv2.VideoWriter_fourcc(*mp4v), fps, (width, height)) while cap.isOpened(): ret, frame cap.read() if not ret: break # YOLOv5 推理返回结果是带置信度和坐标的 DataFrame results model(frame, size1280) dets results.xyxy[0].cpu().numpy() # 过滤车辆类别并组织成 [x1, y1, x2, y2, score, class] 格式 bboxes [] for det in dets: x1, y1, x2, y2, conf, cls det if int(cls) in vehicle_classes and conf 0.4: bboxes.append([x1, y1, x2, y2, conf, int(cls)]) bboxes np.array(bboxes) if bboxes else np.empty((0, 6)) # 转成 DeepSort 需要的形式直接传检测框和分数 trackers tracker.update(bboxes) # 绘制跟踪结果并输出 for track in trackers: if track.is_confirmed(): x1, y1, x2, y2 map(int, track.to_tlwh()) cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, fID:{track.track_id}, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) writer.write(frame) cap.release() writer.release()逻辑说明这个骨架里有两个决定项目成败的细节。第一是max_age80它表示一个目标在连续 80 帧内没有匹配到检测框时才会被删除。密集车流中一辆车被遮挡 20~30 帧是常态如果max_age默认是 30遮挡超过 1 秒后目标直接消失之后重新出现又变成一个新车同一辆车就这样数成了两辆。第二是nn_matching.NearestNeighborDistanceMetric的max_dist0.2它决定了外观特征匹配的阈值上限设得太低同一辆车换个角度就被判成新目标设得太高不同车之间的区分会变差。一般来说 0.2 到 0.3 是密集交通场景下比较稳的经验区间。4. 车流量统计算法的4个必调参数从检测置信度到跟踪阈值4.1 检测置信度阈值0.25与0.4之间为什么差出一个数量级的误报YOLOv5 的 detect 阶段有一个conf-thres参数很多人直接沿用默认的 0.25。这个参数在通用目标检测里问题不大但在车流量统计场景里它直接决定你的计数可信度。交通摄像头的特点是居高临下车辆在画面里往往较小侧面车身特征不完整。置信度 0.25 意味着你对“这是车”的判断很宽松于是路边广告牌上的汽车图案、桥梁阴影里形状相似的物体、甚至树影的晃动都会成为检测结果。这些误检框进入 DeepSort 后会形成轨迹再过一次虚拟检测线给出一辆不存在的车。我在实战中一般把置信度阈值调到 0.4 起步。这不是拍脑袋而是因为跟踪器的max_dist和检测置信度存在耦合关系低置信度框的特征本身不可靠匹配器接收这些噪声送入卡尔曼滤波后预测位置会出现抖动进而影响后续若干帧的关联稳定性。一句话总结宁可漏掉一些模糊的小目标也不要让跟踪器被假目标带偏。4.2 DeepSort的max_age与min_confidence密集遮挡场景下的关键权衡min_confidence参数控制的是检测框进入跟踪器的前置门槛。DeepSort 源码中每个检测框会有一个置信度分数低于min_confidence的框在匹配阶段会被标记为低置信度只有匹配了后续帧中的高置信度框才算可靠目标。这个参数的调节逻辑和检测阈值要配合检测端已经过滤到 0.4 的话跟踪端可以把min_confidence设到 0.3 左右形成一个“检测严格、跟踪适度宽松”的组合。max_age前面说过是目标失配后的存活帧数。这里补充一个重要边界max_age不是越大越好。在路口场景一个目标如果连续 100 帧没有匹配多半是已经驶离画面卡尔曼滤波的预测位置已经偏离真实位置很远了。留一个僵尸轨迹在跟踪器里不但浪费算力还可能在某帧检测框恰好出现在预测位置附近时被误关联造成 ID 接力——真实的新车继承了老轨迹的旧 ID计数逻辑随后把这个 ID 统计成旧车数字就乱了。经验区间是 50~90。4.3 检测框宽高比过滤把路灯杆和行人误检挡在计数之外密集车流项目里最容易被忽略的一个参数是检测框的宽高比。YOLOv5 模型输出的类别里有 person但项目交付时只关心车辆。很多人的做法是在类别过滤时只保留 car、bus、truck以为这样就干净了却忽略了车类误检的一个典型来源竖向结构物。路灯杆、电线杆、交通标志牌的立柱在特定光照下会被模型高置信度地识别成车辆——因为车辆数据集中确实存在大量竖向的卡车侧面照。这类误检框有个共性特征宽高比集中在 0.2 到 0.4 之间而真实车辆的宽高比极少低于 0.6。加入一道宽高比过滤误检率会立竿见影地降下来。# 在检测结果进入跟踪器之前增加宽高比的筛选 filtered [] for det in bboxes: x1, y1, x2, y2, conf, cls det w, h x2 - x1, y2 - y1 if h 0 and 0.6 w / h 3.0: filtered.append([x1, y1, x2, y2, conf, cls])逻辑说明这个过滤要和置信度过滤一起做顺序放在置信度之后。值得说明的是为什么下限是 0.6 而不是 0.5公交车、货车在拐弯时会有短暂侧倾但正常行驶车辆最窄的宽高比也在 0.7 附近。下限调到 0.6 是为了留出余量。如果场景里有摩托车和电瓶车可以再放宽到 0.4但那样就需要额外的面积阈值来兜底。4.4 视频帧率与坐标缩放为什么同一份代码在两个视频上结果完全不同最后一个参数最容易翻车输入视频的分辨率。YOLOv5 推理时size1280会把画面等比缩放后再检测输出坐标会映射回原图尺寸这没问题。但 DeepSort 的卡尔曼滤波是在原图坐标下做位置预测的如果两个视频一个 1080p 一个 720p同一套max_age和同一套检测框尺寸跟踪器的行为不会完全一致。原因在于卡尔曼滤波的噪声参数meas_noise等内部配置是按像素坐标空间设定的1080p 视频里一辆车的位移是 40 像素720p 里是 27 像素相对噪声的占比不一样。常见做法是输入视频先统一缩放我一般会把所有视频都缩放到 1280 像素宽再送入流程。另一个是往下做部署时如果目标硬件是树莓派 5 这类低算力平台这个统一缩放也方便携带因为检测和跟踪都用相同分辨率的图片后续优化直接针对固定尺寸做不用再考虑多尺度适配问题。5. 车流量统计实战中的5个高频避坑点从漏检到计数翻倍5.1 只训练小车导致大车被撕裂成两段现象画面里一辆厢式货车从远处驶来时YOLOv5 在连续十几帧里输出两个框分别命中车头和车身DeepSort 把两个框当成两个独立目标。原因训练数据集中大型车辆样本太少模型学到的锚框尺寸集中在轿车尺度上遇到超长货车就同时激活两个相邻锚点没有合并成一个整体。车流量统计里这是最隐蔽的漏检因为模型明明检测到了车跟踪却把一辆车计成了两辆。解决采集数据时专门加大客、货车的占比或者在后处理阶段对“同方向、上下位置接近且宽高比互补”的两个框做合并。我自己的做法是给数据增加一个超长车拼接的预处理把连续帧的货车框先合并再送跟踪器这个方法在匝道场景效果明显。5.2 夜间车灯导致检测框抖动ID反复切换现象白天跑得好好的模型到了晚上 ID 切换率直接翻倍计数结果偏大。原因夜间车辆的可见特征大幅减弱模型主要依赖车灯作为线索而车灯成对出现检测框时而框住整个车、时而只框住一个灯。框的坐标不稳定会导致外观特征提取区域变化DeepSort 的余弦距离随之波动同一辆车在不同帧被判成不同目标。解决夜间的第一解决手段是在采集端补数据把夜间视频片段混入训练集模型会学到灯组和车身轮廓的组合特征。如果短期内没有数据一个临时但有效的方案是调低max_dist并调高max_age让特征匹配更宽松的同时保留更长记忆。两者搭配能让夜间 ID 切换率下降大约一半。另外可以把锐化处理加在推理输入之前增强车身边缘和尾灯轮廓的对比度。5.3 车辆排队缓行时计数偏少现象路口红灯排队时队伍缓慢向前移动统计结果远远小于实际通过车辆数。原因排队场景下车速极低每辆车在连续帧中的位置变化可能只有 2~3 像素这小于卡尔曼滤波的预测步长。跟踪器在位置预测和检测框匹配之间产生歧义多个目标位置挨得很近IOU 匹配失效。同时缓慢移动的目标在虚拟检测线上来回摆动轨迹可能穿线两次或始终未达到触发条件。解决计数触发从“轨迹跨越检测线”改成“检测框中心点首次进入检测线的持续状态”。简单说就是目标中心进入线上方区域并保持连续 3 帧以上时才计数避免车辆前后摆动引起的重复触发。这个改动在排队场景价值很大因为低速时中心点比轨迹更稳定。5.4 大车遮挡小车导致出现漏计现象一辆公交车和小轿车并排行驶公交车从画面中间穿过小轿车被遮挡了约 1 秒。公交车过去后小轿车在跟踪器里已经变成一个全新的 ID如果此时小轿车恰好压到检测线计数等于把同一辆车又数了一遍。原因之前提到的max_age如果设置得太小目标在遮挡期间就被遗忘了背后的机制在 DeepSort 里其实分为两步先是遮挡期间特征匹配中断然后是 ID 的重新初始化。这两步之间的时间窗口正好覆盖了车辆通过检测线的时机撞在一起就漏计了。解决把max_age提高到 70 以上。再从参数层面补充一个保障n_init参数目标在确认之前需要连续匹配的帧数不要设得太高否则车辆出遮挡后要经历较长时间才能恢复确认态而这段恢复期可能已经越过计数线。5.5 摄像机视角引起的计数偏差现象同一个路口A 摄像头统计结果和人工计数差距在 5% 以内B 摄像头却差到 20%。原因B 摄像头安装角度过斜画面中车辆的行驶轨迹和检测线夹角过小车辆从线的一端“蹭”过去而不是“穿”过去触发时刻变得很不稳定。而 A 摄像头是标准的顶装视角轨迹和检测线接近垂直每次过车都有干净的交叉动作。解决安装条件受限时把虚拟检测线改成检测区域。区域方案不再依赖“轨线与轨迹交叉”这个强约束而是看检测框中心点是否进入区域边界对斜视角的容忍度高很多。另一个折中方案是画两条平行检测线车辆依次触发两条线才算一次有效通过能消除大部分边缘擦碰导致的误计。6. 密集车流下让计数结果更可信的三个验证技巧项目推进到最后最难的不是让代码跑通而是让计数结果经得起质疑。这里分享三个我在交付前后经常做的验证手段能帮你在被质疑之前就把问题暴露出来。第一个是轨迹回放验证。跟踪器每帧生成的轨迹数据不要只停留在画面上而是以 JSON 行格式落盘每行记录帧号、track_id、检测框坐标和中心点。跑完一个视频后用脚本挑出那些横跨检测线但计数标志异常的轨迹逐帧回放。这一步能快速定位是检测断裂还是跟踪 ID 切换效率远比肉眼盯完整视频高。第二个是人工抽帧核对。取一个 10 分钟的片段等间隔抽 5 个 1 分钟的小段人工计数后和统计结果做对比。对比时按“过线计数”和“区域存在数”分别核对能确认统计口径和人工直觉是否一致。我做过的一个真实案例里人工计数 47 辆算法输出 51 辆排查后发现是监控杆的影子在特定时段被识别成车这个误检在白天抽帧里完全看不出来——因为影子只在午后出现所以验证片段要覆盖多个时段。第三个是过车日志的对账。在有信号机的路口可以把绿灯放行时段和计数时间戳对齐。绿灯亮起后前 3 秒内应该产生一个放行波次的集中计数如果算法输出的时间分布和信号灯周期没有相关性说明计数事件发生的时间点与真实过车时刻有系统性偏差问题多半出在检测线的位置而不是跟踪环节。这种时间维度的交叉验证比只看总数更能说明系统是否可靠。这套方案里模型选 YOLOv5 的s或者m尺寸就够用x尺寸在密集场景下 FPS 掉得厉害性价比并不高。实际部署到树莓派 5 这类设备上时我会先把输入缩到 640 宽把max_age降到 50再用 OpenCV 的CAP_PROP_BUFFERSIZE控制输入缓冲这样能保证推理线程不追着采集线程跑。和所有视觉项目一样车流量统计做出来容易做准需要反复磨参数和排查异常希望这些踩过的坑能帮到你。本文还有配套的精品资源点击获取
返回列表