ARTICLE DETAIL

资讯详情

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

YOLOv5+DeepSORT实现车流量统计与轨迹显示实战解析

YOLOv5+DeepSORT实现车流量统计与轨迹显示实战解析 简介目标检测与多目标跟踪是计算机视觉中相辅相成的核心技术。目标检测负责在单帧图像中定位物体而多目标跟踪则在时序上关联不同帧中的同一目标赋予其稳定ID。以YOLOv5为代表的检测器与DeepSORT为代表的跟踪器组合能够构建高鲁棒性的视觉分析流水线解决车辆计数、轨迹还原等复杂问题。在智慧交通场景中通过虚拟计数线判断穿越方向结合卡尔曼滤波和匈牙利算法实现ID关联即可实现车流量统计与轨迹显示。本文以真实项目为背景详细解析YOLOv5DeepSORT的工程配置、核心代码、调参经验与常见坑点为相关开发者提供可直接落地的参考。 我去年接了个路口的车流量统计需求要统计来往车辆数量同时能在视频里实时看到车辆的移动轨迹。刚开始第一个方案是用纯背景差分加轮廓检测结果树影一摇、光照一变计数直接乱套后来才换成YOLOv5做检测、DeepSORT做跟踪的组合方案配合虚拟计数线和轨迹记录效果才真正稳下来。这套“YOLOv5DeepSORT车流量统计轨迹显示”的方案本质上是把一个单帧目标检测模型和一个多目标跟踪算法串成一条流水线YOLOv5负责“找到车”DeepSORT负责“认住车”统计逻辑负责“数车”轨迹模块负责把每辆车的行进路径画出来。几行Python代码就能实现一个准实时的车流统计系统而且大部分逻辑都有现成开源实现可以参考。这篇文章我会把整个项目的设计思路、核心代码片段、常见的坑和调参经验一次性说清楚适合正在做智慧交通、安防监控或者想入门目标跟踪的读者参考。1. 整体设计与技术方案选择1.1 为什么是YOLOv5搭配DeepSORT而不是单模型搞定一切先明确一个核心问题单纯的目标检测模型每一帧都是独立推理的它不知道当前这辆车和上一帧那辆车是不是同一辆。如果你只用一个检测器来数车最朴素的办法是数画面里出现的“目标框”但这样做的结果就是同一辆车连续出现在100帧里你就数出来100次统计毫无意义。所以需要一个“检测跟踪”的架构检测负责每帧找出所有车辆的位置跟踪负责把这些位置按时间关联起来给每辆车分配一个唯一的ID。只要ID不丢就能判断“这辆车是从哪进来的、从哪出去的、是不是已经数过了”。YOLOv5做检测DeepSORT做跟踪是目前工程上性价比很高的组合。虽然现在有YOLOv8配合ByteTrack、BoT-SORT这类更新的方案但YOLOv5DeepSORT的代码成熟度、资料丰富度、部署到嵌入式设备的兼容性都很好适合作为第一套上手的系统。1.2 整体流水线检测、跟踪、统计、可视化四段式第1段视频帧输入。打开视频文件或者接入RTSP/RTMP摄像头流用OpenCV逐帧读取。第2段目标检测。YOLOv5对每一帧做推理输出所有检测框。框的格式一般是(x1, y1, x2, y2, confidence, class_id)。这里只保留类别为车的目标包括car、bus、truck、motorcycle减少非车辆目标的干扰。第3段目标跟踪。把当前帧的检测框喂给DeepSORT它会分别做“预测”和“更新”两步输出每个跟踪目标的中心点坐标、跟踪框以及ID编号。DeepSORT内部保存了每个ID的历史运动信息所以即使某辆车临时被遮挡一帧也能靠运动预测保持ID不丢。第4段统计与可视化。拿到跟踪结果后做两件事一是判断车辆的行驶方向看它是否穿越计数线穿越了就对应方向计数1二是把中心点连成轨迹线用不同颜色区分不同ID的车辆实时画到视频画面里。从代码上讲这四段可以写在同一条Python脚本里每帧跑一个循环。项目结构上我习惯拆成几个文件detector.py负责检测tracker.py负责DeepSORT封装counter.py负责计数逻辑draw.py负责画框画轨迹主程序main.py负责串联整个流程。2. 环境准备与核心依赖搭建2.1 从零搭建YOLOv5运行环境含CUDA版本选择YOLOv5跑在PyTorch上而PyTorch依赖CUDA加速。以前经常有人下载了带GPU的PyTorch包结果本机没有NVIDIA显卡驱动报错找不到CUDA设备也有人显卡驱动太老匹配不上新版PyTorch要求的CUDA版本导致训练直接崩。我推荐先确认三件事显卡型号、驱动支持的CUDA版本、Python版本。对应关系大致如下PyTorch版本CUDA要求Python版本建议适用场景PyTorch 1.8~1.10CUDA 11.1或11.33.7~3.9老显卡、旧项目PyTorch 1.12~2.0CUDA 11.6~11.83.8~3.10大部分常规显卡PyTorch 2.1CUDA 12.13.10~3.12新显卡、新特性安装的时候建议用conda建一个独立环境避免Python包版本冲突conda create -n traffic python3.9conda activate trafficpip install torch torchvision --index-url按你CUDA版本去PyTorch官网复制对应的安装命令pip install -r requirements.txtYOLOv5项目里自带依赖清单这里有个小技巧如果显卡内存只有4GB就不要装batch_size为16的默认配置推理时也尽量把imgsz设为640达不到再降。如果机器显存实在不够还可以用paddlepaddle版本或者OpenVINO导出的IR模型在CPU上也能跑起来。2.2 权重文件与项目目录的准备工作YOLOv5检测部分需要权重文件最简单的办法是直接用官方预训练权重yolov5s.pt。如果场景比较特殊可以后续用自己标注的数据微调。我这边是先用官方权重跑通流程再去训练一个专门识别特定车型的数据集。DeepSORT这边需要两个东西深度特征提取模型权重比如ckpt.t7和DeepSORT的源码目录。现在GitHub上有很多整合好的仓库比如“mikel-brostrom/Yolov5_StrongSORT_OSNet”这类已经把YOLOv5和DeepSORT集成好了。自己搭也不难主要工作就是把DeepSORT的tracker部分作为一个包放进你的工程目录。最后项目目录大致长这样traffic_counter/ ├── main.py # 主流程 ├── detector.py # YOLOv5封装 ├── deep_sort/ # DeepSORT跟踪器 │ ├── tracker.py │ ├── deepsort.py │ └── model_data/ │ └── mars-small128.pb ├── weights/ │ └── yolov5s.pt ├── videos/ │ └── test.mp4 └── output/ └── result_video.mp42.3 视频输入源的三种常见接法车流统计的视频来源通常有三种本地录制好的视频文件、网络摄像头RTSP流、USB摄像头。代码上统一用OpenCV的VideoCapture处理但每种有一些参数差异。本地视频文件直接capture cv2.VideoCapture(test.mp4)简单稳定适合先跑通流程。RTSP流比如海康/大华摄像头输出的RTSP地址。注意OpenCV拉取RTSP时会因为网络抖动丢帧需要做缓冲队列或者丢帧处理。实测中如果连续几帧读取失败重启capture比等待重连更靠谱。USB摄像头capture cv2.VideoCapture(0)要注意分辨率设置很多USB摄像头默认输出640x480如果设置成1920x1080反而会卡顿。3. 核心原理与关键代码拆解3.1 YOLOv5目标检测从输入到输出的完整处理链YOLOv5的推理接口很简洁加载模型后直接传帧进去返回的结果再用官方utils里的函数解析成坐标数组。每个检测结果包括bounding box坐标、置信度和类别ID。在车流量统计这个场景里需要对检测结果做两层过滤第一层过滤掉置信度低于阈值的框比如conf_thres0.3第二层过滤掉类别不是车辆的检测框。因为在路口画面里经常会有行人、自行车、交通标志如果不做类别过滤这些目标会被DeepSORT跟踪然后干扰计数逻辑。关于置信度阈值我自己的经验是对于白天光线充足的视频0.3~0.45就够了对于夜间或者画面模糊的视频阈值需要降到0.2左右不然漏检会很严重。同时注意YOLOv5的nms非极大值抑制默认参数在密集场景下可能会把相邻很近的两辆车合并成一个框这种情况可以适当调低iou_thres到0.4保留更多候选框。3.2 DeepSORT跟踪的核心机制级联匹配、卡尔曼滤波、运动特征DeepSORT的原理可以拆成三个关键词预测、关联、更新。预测用的是卡尔曼滤波。每一帧它会基于上一帧的位置和速度预测当前帧目标应该在哪里。这里有点像一个物理题一辆车在t-1时刻位于(x, y)速度为(vx, vy)那么t时刻的预估位置就是(xvx yvy)。卡尔曼滤波比这个简单线性外推更聪明的是能同时估计系统的噪声和测量噪声在观测不到目标时也能维持一段时间的位置估计。这就是为什么目标被短暂遮挡时DeepSORT依然能维持ID。关联用的是匈牙利算法匹配检测框和已有的跟踪轨迹。匹配的依据有两个一是运动信息的马氏距离就是当前预测的位置和实际检测到的位置差多少二是外观特征向量的余弦距离就是当前检测到的车辆外观特征和轨迹里记录的外观特征有多接近。DeepSORT的“Deep”就体现在它会用一个CNN网络提取每个检测框的外观特征向量这个向量对车辆颜色、车型比较敏感所以即使车辆运动模型预测得不太好只要外观特征匹配得上也能正确关联。更新就是把匹配上的检测框位置赋给对应的轨迹更新卡尔曼滤波器的状态。如果某个轨迹连续好多帧没有匹配到任何检测框就被判定为“丢失”删除这个ID这样它占用的ID就被释放给新出现的车辆。3.3 车流量统计的两种常见逻辑虚拟线圈与穿越计数线车流量统计在工程上有两种经典做法各有利弊。第一种是虚拟线圈法。在画面里画一块固定区域比如一个矩形或者一条细长条当目标中心点进入这个区域时该区域计数1。它是“区域计次”的思路实现简单但对遮挡和重叠比较敏感如果两辆车同时在区域里系统可能只检测到一个目标统计数量就会偏低。适合摄像头架设高度比较高、车流比较分散的场景。第二种是穿越计数线法。在画面中画一条虚拟的线或者一个窄条区域只检测目标中心点是否跨过这条线同时记录跨线的方向。判断穿越方向的常见逻辑是比较目标在当前帧和上一帧的中心点位置如果上一帧在线的一侧当前帧在线的另一侧就说明它穿越了再根据是左侧到右侧还是右侧到左侧判断方向。对于双向车道场景我建议用穿越计数线法因为它天然支持方向和双向统计。假设在画面中设置两条计数线一条用于记录进入方向一条用于记录驶出方向每辆车的ID只会被计数一次之后即使目标一直在画面里也不会重复计数。这是通过“是否已经计数过”的标记实现的每辆被DeepSORT赋予唯一ID的车在第一次穿越计数线时才会计数之后把这个ID加入已计数集合。这里有一个值得注意的边界条件如果车辆斜着穿过了计数线的端点区域中心点可能没有严格穿过线但实际上车已经过了一半。解决方法是把计数区域做宽一点用一个较扁的矩形来判断而不是一条数学意义上的线。例如计数线高度设为2个像素矩形宽度覆盖车道宽度。3.4 轨迹显示设计实时轨迹线与历史轨迹记录轨迹显示有两种做法一种是只画出当前帧所有跟踪目标最近N帧的中心点连线像彗星尾巴一样能直观看到目标当前的运动方向另一种是保存每辆车从出现在画面里到消失的全部点列画成一条完整的历史轨迹方便事后回溯。第一种做法实现起来很简单对每个目标维护一个deque最多保存15个坐标点每帧把最新的中心点push进去然后把deque里的点连成线。因为deque是先进先出的老的点会被自动挤掉所以尾巴长度固定看起来很干净。第二种做法需要把轨迹数据保存到内存或者本地JSON文件里每帧给每个ID追加点坐标。注意目标ID可能会被DeepSORT释放并重新分配给其他车辆所以轨迹存储时建议加上每个ID的起止时间戳这样回溯时候能区分开不同时间段里同ID的车辆。4. 实操过程与性能调优4.1 完整部署运行步骤从视频到统计结果完整的运行过程可以分成下面几个阶段第1步准备测试视频。如果没有现成的交通路口视频可以去下载公开的交通监控数据集或者用手机在过街天桥上拍一段车流画面。视频分辨率建议1280x720以上帧率不要低于15fps不然跟踪效果会打折。第2步加载模型并初始化跟踪器。在main.py里先加载YOLOv5模型再创建DeepSORT跟踪对象。DeepSORT的初始化参数比较多很多人会忽略max_cosine_distance这个参数它控制外观特征匹配的阈值。阈值太大容易把不同的车匹配成同一个ID阈值太小同一辆车因为反光、角度变化又会被拆成多个ID。实测下来默认的0.2~0.3区间是比较合理的。第3步读取第一帧并画计数线。计数线位置不要放在画面最边缘因为目标在边缘时检测框可能不稳定也不要放在画面中央尽量选择车辆即将驶入或驶出的位置这样能留出足够的时间做目标跟踪保证ID稳定。第4步循环推理与跟踪。while循环里每次读取一帧先调用YOLOv5检测再把检测结果转换为DeepSORT需要的格式格式是[x1, y1, x2, y2, score, class_id]然后调用tracker.update()得到跟踪结果。这一步是整个系统最耗时的部分两个模型都要前向推理所以性能优化主要做在这一层。第5步统计判断与可视化。针对每个跟踪目标取中心点判断是否穿越计数线判断方向并累加计数。同时画出目标的框、ID号、中心点轨迹把当前总车辆数等文字叠加到画面左上角。第6步保存输出视频。用cv2.VideoWriter把带标注的帧写入输出文件注意编码参数要匹配输入视频的分辨率和帧率。4.2 核心代码片段计数线穿越方向判断下面这段代码是穿越计数线法里最核心的判断逻辑我简化了直接可用的版本def is_cross_line(before_point, after_point, line_y): 判断目标是否从下往上穿越水平线 line_y 返回 True 表示发生了穿越 if before_point[1] line_y and after_point[1] line_y: return True return False def check_direction(before_point, after_point, line_y): # 从下往上: 进入方向 if before_point[1] line_y and after_point[1] line_y: return in # 从上往下: 驶出方向 elif before_point[1] line_y and after_point[1] line_y: return out return None对于垂直方向的计数线比如画面从左往右行驶的车判断逻辑是一样的只是把纵坐标换成横坐标。4.3 性能优化追求实时性必须做的几件事如果视频分辨率1920x1080、帧率30fps直接跑YOLOv5s加DeepSORT在普通GTX 1660显卡上大概只能达到12~18fps达不到实时。想要流畅运行推荐按优先级做这几件事。首先降分辨率。把每一帧的尺寸缩放到640x640再送入检测器。YOLOv5内部本来就会把输入缩放到640但如果直接喂原图OpenCV的resize和模型内部的letterbox会重复工作浪费时间。更关键的是如果喂1920x1080的图检测耗时比640x640高出接近一倍而精度提升有限。其次跳帧处理。如果检测卡的太严重可以每两帧做一次检测中间隔的那一帧用DeepSORT的预测结果代替检测结果。这样跟踪的ID稳定性会有一定下降但整体帧率能接近翻倍。第三启用半精度推理。YOLOv5里设置model.half()把模型切到FP16推理显存占用减半速度提升约30%。注意某些老显卡FP16推理不稳定如果出现nan检测框就退回FP32。最后如果是部署到Jetson Nano这类嵌入式设备不要用原版YOLOv5改用TensorRT加速的YOLOv5DeepSORT的余弦距离计算部分也可以换用向量化实现能显著降低耗时。4.4 DeepSORT参数调优不同场景下的经验推荐参数名默认值白天路口夜间/雨天高速场景max_cosine_distance0.20.20.30.15max_iou_distance0.70.70.80.7max_age30305020n_init3353nn_budget505010030参数背后其实对应了不同的物理场景逻辑。夜间车辆外观特征提取质量下降特征距离普遍偏大max_cosine_distance就得放宽车辆经常被路灯杆、树木完全遮挡max_age要放大否则ID频繁切换计数会严重偏高。高速公路上车辆位置变化极快tracking必须反应灵敏max_age反而要调小n_init要设成3保证快速确认新目标。这个表格是从多次适配里总结出来的经验值而不是绝对的标定结果。实际项目中可以先跑一段短测试视频看ID切换率和计数误差再反推参数。我通常的检查指标是如果同一个目标在画面里ID频繁跳变优先调大max_cosine_distance和max_age如果车辆还没出画面就被判定丢失优先调大n_init。5. 常见问题、坑点与排查技巧5.1 ID切换导致重复计数的经典问题最常遇到的坑是ID切换。同一个真实车辆因为外观特征匹配不上或者目标被完全遮挡了几帧ID从12变成了37。这时候因为12号车已经计过数了新出现的37号车穿越计数线时又会被当场计一次导致重复计数。排查的时候可以把每个ID的计数时间打印出来比对视频回放找到ID切换发生的时刻。解决方案通常有这几个方向第一提高检测的稳定性减少漏检这是根本第二增大max_age给被遮挡目标更长的等待时间第三调整计数线位置让计数线尽量靠近画面中段不要靠边缘因为画面边缘目标检测置信度低、容易出现飞框。还有一个思路是加入寿命校验一辆车从第一次出现到穿越计数线至少经过了N帧如果某ID出现不到3帧就穿越计数线视为误检目标不计入统计这个方法能过滤掉大部分闪跳的检测框。5.2 车辆被漏检的几种来源与应对漏检的原因往往是目标太小、目标被遮挡、光照剧烈变化。远距离的车在画面里可能只有10x10像素YOLOv5几乎肯定检测不到。解决方法是调大输入分辨率或者从摄像头安装角度上把视野调近一些。如果车辆普遍比较小建议换用YOLOv5m或者YOLOv5l而不是继续用yolov5s因为模型的网格数量和特征图通道数更多小目标检测能力更强。还有一类漏检来自车辆颜色与路面颜色过近。灰色车在灰色路面上检测器置信度会非常低。这种场景可以通过图像增强改善比如对帧做自适应直方图均衡化CLAHE提升对比度后再送进检测器。我在雨天场景实测过这一个预处理操作能把检出的车辆数提升10%以上。5.3 画面卡顿与延迟问题排查帧率上不去的时候建议先确认瓶颈在检测还是跟踪。可以在循环里分别计时。统计一下检测耗时和跟踪耗时就知道该优化哪一部分。有一个比较隐蔽的问题DeepSORT的特征提取模型在CPU上做前向推理特别慢因为很多实现里默认把特征提取放在CPU上运行即使检测在GPU上。这个特征提取是一个小CNN虽然模型小但每帧要对每个检测框都过一次。对于几十个跟踪目标CPU推理一样会卡。解决办法是查一下代码里特征提取模型有没有.to(device)的调用把它也强行放到GPU上。很多网上整合好的代码并没有处理这个问题这是我遇到的最常见的“为什么我显存占用不高但还是很慢”的原因。5.4 夜间与阴影环境下的准确率下降夜间车灯会造成严重的叠影和光晕YOLOv5的检测框经常会把两辆车框成一个或者把地面反光当作车。处理手段有几种我实际用过效果比较明显的是把夜间视频先做伽马校正提亮暗部让车身的轮廓更清晰检测置信度阈值降低到0.2~0.25防止夜间目标丢失有条件的话优先用红外摄像头或者频闪补光这是治本的办法。树荫、建筑阴影的干扰主要是让车辆底边与路面边界模糊导致检测框不稳。我的做法是画计数线的时候避开阴影覆盖区域把线放在曝光均匀的路段这样即使检测框有点抖动中心点也不会频繁跨线。5.5 实际项目部署的几个小建议最后说说真正落地到真实环境时容易被忽略的点。摄像头安装角度很关键不要用俯视视角过大或者过斜的机位视角越接近水平目标重叠和遮挡越严重跟踪的难度越大。最理想的是从正侧方或者正上方45度左右拍摄车辆之间不会互相遮挡。日志输出这步不要省。把每一帧的所有目标ID、坐标、置信度、是否计数等信息写入CSV或者JSON哪怕是只写一行摘要也会让事后排查错误变得轻松十倍。出了问题可以回放数据不用重新看视频逐帧找。如果要实时把统计数据推送到网页或者大屏最好不要在Python进程里直接做Web渲染而是把计数值从主循环里通过socket或者Redis推出去由单独的服务做展示。这样检测进程即使阻塞也不会影响统计面板。6. 复盘总结与扩展方向这套YOLOv5DeepSORT车流量统计系统整套流程跑下来最值钱的部分其实是“检测跟踪”这套组合带来的稳定性提升。DeepSORT不是万能的它的能力边界在于当车辆被完全遮挡超过一定帧数或者车辆外观发生剧变比如转弯导致角度从正后方变成侧面它就很难再维持同一个ID。理解了这个边界才能在工程上做好规避比如调整摄像头位置、调整计数线位置。后续如果想继续扩展可以直接在这个基础上加几类功能车道级统计把画面按车道划分统计每条车道的车流量这个从工程上就是把计数线改成多条线分别判断穿越哪条线车型分类用YOLOv5识别car、bus、truck按类别分开统计代码改动量也很小平均车速估计每辆车在画面里的位移除以时间间隔再乘上像素和实际距离的标定系数就能粗略估算车速不过这个需要先做相机标定。我个人的体会是这类计算机视觉实战项目看论文和跑通的难度完全是两码事。把YOLOv5和DeepSORT跑通一次比读十篇目标跟踪论文收获都要大。调试过程中积累的那些“怎么看ID切换是不是异常频繁”、“怎么判断计数误差是检测问题还是跟踪问题”这些经验才是这套项目真正的护城河。本文还有配套的精品资源点击获取
返回列表