简介本资源是一套基于Python实现的YOLOv5与DeepSORT融合的车流量统计算法源码面向智能交通、计算机视觉方向的开发者及高校科研学习者解决视频流中车辆检测、ID持续追踪、跨线计数与运动轨迹可视化等核心问题。压缩包共169个文件含55个核心Python脚本涵盖模型加载、推理、跟踪器初始化、轨迹绘制等模块、21个YAML配置文件定义模型参数、追踪阈值与ROI区域、61个pyc缓存文件、3个MP4演示视频及2个预训练.pt模型整体大小为133.44MB。已有2841人学习下载资源结构完整包含CHANGES更新日志、Dockerfile容器化支持、LICENSE授权说明及README.md使用指南便于快速部署与二次开发。读者可直接运行获得带ID标注、实时计数与彩色轨迹线的可视化结果并深入理解YOLOv5检测输出与DeepSORT卡尔曼滤波外观特征匹配的协同机制。 我大概花了两周时间把“基于Python的YOLOv5DeepSORT车流量统计系统”从零到一完整跑通现在把整个实现过程、踩过的坑和最终的源码思路一并整理出来。这个项目解决的核心问题是对一段监控视频或实时视频流自动识别其中的车辆轿车、卡车、公交车等持续跟踪每一辆车的运动轨迹并统计通过指定区域或虚拟检测线的车流量。项目适合计算机视觉方向的初学者、做智慧交通相关课题的学生以及打算在边缘设备或服务器上部署视频分析服务的开发者参考。你如果手头已经装好了Python环境和CUDA按照文中的步骤大概率一个下午就能把Demo跑起来。先说结论这套方案的实用性在于“检测跟踪”的组合思路YOLOv5负责把每一帧画面里的车辆“找出来”DeepSORT负责把不同帧里的同一辆车“连起来”。单独做检测做不到车流量统计因为你需要知道这一秒出现的车和上一秒是不是同一辆单独做跟踪也不行因为你需要先知道目标是什么、在哪。两者配合之后车流量统计、平均车速估算、轨迹回放这些功能才有实现的基础。1. 项目整体设计与方案选型1.1 为什么选YOLOv5而不是YOLOv8或其他检测模型很多人在选型时会纠结既然已经有YOLOv8甚至YOLOv9了为什么还要用YOLOv5我个人的判断标准是“稳定、生态成熟、资料多”。YOLOv5经过两年多的社区迭代教程覆盖最全遇到问题基本都能搜到解决方案。而且在车辆检测这个场景里YOLOv5的精度和速度平衡做得非常好s模型在1080Ti上跑实时视频流完全没问题m模型可以保证更高精度x模型适用于离线分析。另外一个重要原因是DeepSORT官方和社区实现大多基于YOLOv5的检测结果接口很多现成案例直接就是“yolov5deepsort”组合。虽然YOLOv8也有对应的deepsort实现但相对较少出了问题不好查。作为一个人项目或者课程设计YOLOv5是性价比最高的选择。1.2 DeepSORT在车流统计里到底扮演什么角色DeepSORT的全称是Deep Cosine Metric Learning SORT本质上是一个多目标跟踪算法。它接收YOLOv5输出的每一帧检测框bbox、置信度和类别然后通过卡尔曼滤波预测每个目标在下一帧的位置再通过外观特征匹配和IOU匹配把检测框和已有的跟踪轨迹关联起来。在车流统计场景中DeepSORT会为每一辆车分配一个全局唯一的ID。这个ID非常关键是车流量统计的基础。比如我设定一条虚拟检测线当一辆车的中心点坐标跨过这条线时如果这个ID之前没有计数过就计数加一。如果没有跟踪ID只靠单帧检测同一辆车在视频里出现100帧就会被当成100辆车完全没法用。1.3 车流量统计的两种主流实现方式我试验过两种计数思路。第一种是虚拟线圈/检测线在画面中画一条线或者一个矩形区域当跟踪目标首次进入或穿过该区域时计数。这种方式逻辑简单、实时性好适合在固定摄像头视角下统计断面车流量。第二种是区域进出统计比如统计某个路口四个方向进入和离开的车辆数需要定义“进入区域”和“离开区域”并对每个跟踪ID维护状态机。这种方式能分析转向流量但代码复杂度高很多需要小心处理目标在区域边缘反复横跳导致重复计数的问题。这个项目默认采用的是第一种方式即虚拟检测线ID去重计数。后续如果你想扩展成路口流量分析在现有框架上加状态机逻辑即可不用推翻重来。1.4 整体架构一个视频帧的完整旅程为了让你对系统运行流程有个直观认识我画一个文字流程描述视频帧被读取后先送入YOLOv5模型前向推理得到一批检测框然后这些检测框连同原始帧一起送入DeepSORT的更新接口DeepSORT在内部做卡尔曼预测、外观特征提取、级联匹配、IOU匹配最终输出每个目标的跟踪框和ID接下来程序根据跟踪框的中心点坐标判断是否越过虚拟检测线如果越过且未计数则累加最后把检测框、ID、轨迹点画到帧上用OpenCV显示或写入输出视频。这个流程看起来简单但每个环节都有细节。比如YOLOv5前向推理时要注意输入尺寸和letterbox处理DeepSORT的特征提取器需要单独加载一个ReID模型权重计数逻辑需要考虑目标在检测线附近被短暂遮挡的情况。后面我会逐一展开讲。2. 环境搭建与依赖配置2.1 Python和CUDA环境准备建议使用Python 3.8到3.10之间的版本实测3.10稳定3.11部分依赖编译会出问题。CUDA建议11.3以上PyTorch用1.10到2.0之间的版本都可以。显卡显存最好在4GB以上没有独显的话用CPU跑也能出结果但速度大概只有0.5到2 FPS调试时勉强够用。装PyTorch时有一个很关键的细节不要直接用pip install torch因为这样装的是CPU版本。应该到PyTorch官网选择对应的CUDA版本命令来安装比如pip install torch1.12.1cu113 torchvision0.13.1cu113 --extra-index-url https://download.pytorch.org/whl/cu113安装完成后可以用下面这段代码验证CUDA是否可用import torch print(torch.__version__) print(torch.cuda.is_available())如果输出True说明GPU环境正常。如果输出False请先检查驱动和CUDA版本是否匹配不要急着往后走。2.2 依赖库清单哪些必须装、哪些可选项目核心依赖如下torch和torchvisionYOLOv5的推理后端opencv-python视频读取、图像处理、结果展示numpy数组和矩阵运算scipyDeepSORT中卡尔曼滤波和匈牙利匹配的依赖matplotlib可选画轨迹图或展示统计曲线时用pillow图像预处理时用tqdm可选显示进度条用requirements.txt批量安装时建议把版本号放宽比如numpy1.21避免版本冲突。不要一次装太多不相关的库比如pandas、flask等真正需要时再装。我刚开始图省事装了一堆后来排查问题时反而被版本冲突干扰。2.3 拉取YOLOv5和DeepSORT项目代码YOLOv5直接使用官方仓库即可git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txtDeepSORT部分我建议不要用官方那个纯学术代码而是找合适的第三方封装。GitHub上有不少把YOLOv5和DeepSORT集成好的仓库比如mikel-brostrom/yolov5_deepsort它的接口设计比较清晰。下载后注意看它的README里面有权重文件下载链接DeepSORT需要两个关键权重一个是YOLOv5的yolov5s.pt另一个是ReID模型权重osnet_x0_25_msmt17.pt不同仓库可能不同也可能是deepsort.pth。权重下载是一个常见卡点因为托管在GitHub Release或Google Drive上国内下载很慢。我的做法是用镜像站或者让朋友帮忙下载后网盘转存yolov5s.pt大约14MBReID权重大约20MB左右不算大但下载通道经常超时。2.4 验证环境先让YOLOv5单独跑通在集成DeepSORT之前先单独验证YOLOv5能不能正常检测python detect.py --source data/images/bus.jpg --weights yolov5s.pt --conf-thres 0.4如果你能看到输出图片中有检测框说明YOLOv5环境没问题。这一步没有做好的话不要进入下一步否则后面出了问题很难定位是哪个环节的锅。3. 核心模块拆解与代码实现3.1 检测模块YOLOv5推理封装为了让YOLOv5检测结果能方便地传给DeepSORT需要把检测逻辑封装成一个类。核心思路加载模型后对输入帧做letterbox预处理保持宽高比缩放并填充推理得到原始坐标的检测框再映射回原图坐标。下面是一个参考实现import cv2 import torch import numpy as np class YOLOv5Detector: def __init__(self, weightsyolov5s.pt, device0, conf_thres0.4, iou_thres0.5): self.model torch.hub.load(ultralytics/yolov5, custom, pathweights, force_reloadFalse) self.model.conf conf_thres self.model.iou iou_thres self.device device if device ! cpu: self.model.cuda() self.class_names self.model.names def detect(self, frame): results self.model(frame) bboxes results.xyxy[0].cpu().numpy() boxes [] scores [] class_ids [] for det in bboxes: x1, y1, x2, y2, conf, cls_id det if int(cls_id) in [2, 3, 5, 7]: # car, motorcycle, bus, truck boxes.append([x1, y1, x2, y2]) scores.append(conf) class_ids.append(int(cls_id)) return np.array(boxes), np.array(scores), np.array(class_ids)注意一个细节这里过滤了类别只保留car(2)、motorcycle(3)、bus(5)、truck(7)。如果不过滤行人、自行车也会进入跟踪器对车流量统计造成干扰。同时保留motercycle是因为在一些城市场景里摩托车也是重要的交通流量组成部分你可以根据需求调整。3.2 跟踪模块DeepSORT初始化与更新DeepSORT的封装相对固定关键是理解它的输入输出格式。输入是(x1, y1, x2, y2, score, class_id)格式的数组输出是(x1, y1, x2, y2, track_id, class_id)格式的数组。这里class_id其实是从检测结果透传过来的DeepSORT本身不关心物体类别。class DeepSORTTracker: def __init__(self, model_pathosnet_x0_25_msmt17.pt, devicecuda): self.tracker None from deep_sort import DeepSort self.deepsort DeepSort(model_path, devicedevice) def update(self, bboxes, scores, class_ids, frame): if len(bboxes) 0: return np.empty((0, 6)) track_bbs np.concatenate([bboxes, scores.reshape(-1, 1)], axis1) outputs self.deepsort.update(track_bbs, frame, class_ids) return outputs这里要特别提醒DeepSort.update()的第二个参数frame必须是原始的BGR图像因为DeepSORT内部会用ReID模型提取目标的外观特征。如果你传的是缩放后的图会导致特征提取不准跟踪ID容易切换。3.3 计数模块虚拟检测线与过线判断计数是整个项目里逻辑上最容易出Bug的部分。我的实现思路是在视频画面中定义一条水平线line_y int(height * 0.6)即画面高度60%的位置。每一帧拿到跟踪结果后计算每个目标的中心点坐标。如果目标上一帧的中心点在线上方当前帧中心点在线下方或反过来判定为“过线”。过线后检查该目标ID是否已在计数集合中若不在则计数加一并标记。def check_cross_count(self, track_id, center_y, line_y): if track_id not in self.track_history: self.track_history[track_id] center_y return False prev_y self.track_history[track_id] if prev_y line_y and center_y line_y: if track_id not in self.counted_ids: self.counted_ids.add(track_id) self.total_count 1 return True self.track_history[track_id] center_y return False这个逻辑看似简单但有一个细节要注意track_history只保存最近一帧的中心点Y坐标不能保存多帧历史否则会导致判线滞后。而且如果目标在检测线附近被跟丢了历史坐标会被清掉可以通过设置超时时间自动清理重新出现时它会被分配一个新的ID这就会多计一次。为了减少这种“跟丢后重复计数”我可以把track_history里超过30帧没有更新的目标清理掉import time self.last_seen[track_id] time.time() # 在每帧开始前清理 for tid in list(self.last_seen.keys()): if time.time() - self.last_seen[tid] 1.0: del self.track_history[tid] del self.counted_ids[tid] del self.last_seen[tid]时间阈值1秒是比较合理的。如果目标真的已经离开画面这个ID就不该继续参与计数了。3.4 轨迹显示模块画线、画框、写ID轨迹显示本身不复杂就是用OpenCV的画图函数。我维护一个字典trajectory_points[track_id] [(x1, y1), (x2, y2), ...]每帧往对应ID的列表中追加当前中心点并且只保留最近50个点。def draw_tracks(self, frame, outputs, trajectory_points): for output in outputs: x1, y1, x2, y2, track_id, class_id output.astype(int) cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, fID: {track_id}, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) center ((x1 x2) // 2, (y1 y2) // 2) trajectory_points.setdefault(track_id, []).append(center) pts trajectory_points[track_id][-50:] for i in range(1, len(pts)): cv2.line(frame, pts[i-1], pts[i], (255, 0, 0), 2) return frame这里有一个性能优化细节不要每一帧都绘制全部轨迹点只绘制最新50个点就足够了。旧轨迹点会干扰画面而且没必要。如果你希望轨迹更流畅可以使用cv2.polylines一次性绘制但要处理点不够的情况。3.5 主循环把各模块串起来主循环的伪代码如下cap cv2.VideoCapture(video_path) line_y int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT) * 0.6) fps cap.get(cv2.CAP_PROP_FPS) while cap.isOpened(): ret, frame cap.read() if not ret: break bboxes, scores, class_ids detector.detect(frame) outputs tracker.update(bboxes, scores, class_ids, frame) for output in outputs: x1, y1, x2, y2, track_id, class_id output.astype(int) center_y (y1 y2) // 2 if check_cross_count(track_id, center_y, line_y): cv2.line(frame, (0, line_y), (frame.shape[1], line_y), (0, 0, 255), 2) frame draw_tracks(frame, outputs, trajectory_points) cv2.putText(frame, fCount: {total_count}, (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) cv2.imshow(Vehicle Counting, frame) if cv2.waitKey(1) 0xFF ord(q): break实际开发中你需要把计数状态封装到类里避免主循环里堆大量局部变量。我自己的项目里是用一个CounterState类来管理track_history、counted_ids和total_count这样代码更干净也方便做多线计数。4. 完整实操过程从准备到跑通4.1 数据集准备先用本地视频测试模型权重是官方预训练好的不需要自己训练就能做推理。但视频素材要好好选。建议用固定机位、光线较好、视野较开阔的监控视频分辨率不用太高720P或1080P均可。我用过一些公开数据集里的交通视频比如UA-DETRAC的片段还有从B站找的延时摄影素材。有一点要提醒你如果你用手机拍的视频来测试画面抖动会导致跟踪不稳定ID切换频繁。最好用三脚架固定机位或者选择本身就很稳定的素材。4.2 命令行启动参数解析与配置文件为了让程序便于复用我在项目根目录加了一个config.yaml把视频路径、检测置信度、检测线位置等参数集中管理video_path: data/test_video.mp4 output_path: output/result.mp4 weights: weights/yolov5s.pt reid_model: weights/osnet_x0_25_msmt17.pt conf_thres: 0.4 iou_thres: 0.5 line_position: 0.6 max_trajectory_len: 50 skip_frames: 0skip_frames参数表示每隔几帧处理一帧0表示逐帧处理。如果性能不够可以设为1或2但如果帧率太低跟踪效果会明显下降DeepSORT对帧间一致性有一定要求不建议跳过太多帧。4.3 核心代码结构参考最终项目代码建议这样组织vehicle_counting/ ├── config.yaml ├── main.py ├── detector.py ├── tracker.py ├── counter.py ├── visualizer.py └── weights/ ├── yolov5s.pt └── osnet_x0_25_msmt17.ptmain.py只负责任务编排业务逻辑分散到各模块。这样后续如果要加“按车道统计”或者“导出统计报表”只需要在对应模块上做增量开发。4.4 效果评估如何知道统计准不准跑通Demo之后我建议做一次定量评估。方法是找一段包含100辆车左右的标准视频人工数出真实车辆数再和程序统计结果做对比。一般来说检测漏检会导致少计跟踪ID切换会导致多计两方面因素会部分抵消。我的测试结果是在光线正常的日间视频上准确率大约在85%到90%左右。错计的主要原因有两个一是车辆被大面积遮挡时检测框丢失重新出现时被分配了新ID导致重复计数二是有些目标在检测线附近静止不动比如等红灯偶尔从检测框变为消失再出现会触发一次新的过线判断。针对这两类问题我的优化经验是把检测置信度从0.4提高到0.5减少低置信度框带来的抖动在计数逻辑中增加“连续两帧过线”的条件避免单帧误判。你可以根据自己视频的实际效果做参数调整。5. 常见问题与排查技巧实录5.1 问题速查表问题现象可能原因解决方案程序启动报ModuleNotFoundError缺少依赖库按requirements.txt重新安装逐个验证import检测框出现但跟踪ID一直为-1DeepSORT没有正确接收检测结果检查输入数组格式是否包含score列检测速度很慢只有1FPS使用了CPU推理确认GPU可用或降低输入分辨率同一辆车被分配多个IDReID特征提取不稳定更换更强的ReID权重或降低检测置信度阈值计数总是很大或很小检测线位置不合适在画面上调试输出检测线调整到合适位置画面中车辆检测不到视频分辨率太低或光照过暗调低检测置信度或使用更大模型如yolov5m5.2 为什么我的跟踪ID总是跳变这是最让人头疼的问题。DeepSORT的ID跳变主要受两个因素影响检测框的质量和ReID特征的区分度。如果YOLOv5输出的检测框抖动很厉害即同一辆车相邻两帧的框位置和大小变化很大那么IOU匹配和特征匹配的置信度都会下降容易导致匹配失败并创建新轨迹。我的排查方法是先把DeepSORT换成最简单的SORT算法做对比如果SORT的ID跳变比DeepSORT更严重说明是检测框抖动的问题如果两者表现差不多说明检测系统本身框不稳定。这时候可以通过调高iou_thres来减少重叠框或者把检测置信度阈值适当提高把低质量框过滤掉。另一个容易忽略的因素是视频帧率。如果视频帧率很低比如15FPS以下同一辆车在相邻帧之间的位移很大卡尔曼滤波的预测误差会变大匹配难度增加。这种情况下可以把检测线画得离摄像机远一些因为远处车辆像素位移相对较小。5.3 显存不足怎么优化如果运行时报CUDA out of memory优先确认是否同时加载了多个模型。很多人会把YOLOv5和DeepSORT的ReID模型都加载到GPU上显存占用会叠加。解决方案是让YOLOv5跑GPU、DeepSORT跑CPU或者反之。实测中YOLOv5跑GPU、ReID跑CPU的方式对整体速度影响不大因为ReID提取特征的计算量相对较小。from deep_sort import DeepSort self.deepsort DeepSort(model_path, devicecpu)如果还是有压力可以降低输入分辨率。在detector.detect()里对原始帧做一次缩放比如frame_resized cv2.resize(frame, (640, 640))但注意检测框坐标要映射回原始帧尺寸再进行跟踪计算。5.4 车辆检测漏检严重怎么办漏检通常要用更大的模型或更低的置信度阈值来解决。模型从yolov5s换成yolov5mmAP大约能提升3到5个百分点但推理时间会增加30%左右。如果视频分辨率为1080P我建议先把输入分辨率降到640x640因为YOLOv5的COCO预训练模型主要适应640附近的分辨率直接推理大分辨率并不一定提升小目标检测效果。还有一种情况是目标太小。当车辆距离摄像头很远时在画面中可能只有几十个像素。这种情况下的确很难检测不太建议通过换超大模型来解决效率太低。换机位或者换预训练模型不太现实的话可以在预处理环节做一个ROI放大只对车辆可能出现的区域做高分辨率推理但实现复杂度会明显上升前期不必追求。5.5 输出视频没有画面或文件损坏用OpenCV的VideoWriter写视频时常见问题是编码器不支持或宽高设置不对。必须确保VideoWriter的尺寸和写入帧的尺寸完全一致不能一个是640x360、另一个是1920x1080。还有一个坑cv2.VideoWriter要求传入的帧是连续内存某些经过转换的图像可能会出现写入异常可以调用np.ascontiguousarray(frame)强制预处理。out cv2.VideoWriter(output_path, cv2.VideoWriter_fourcc(*mp4v), fps, (width, height)) out.write(np.ascontiguousarray(frame))5.6 如何调试检测线位置我习惯先在命令行模式打印鼠标点击的坐标或者写一个临时脚本把视频的某一帧定格显示然后用鼠标点击的方式确定检测线应该放在哪。这样比盲猜line_position0.6然后反复跑全视频要高效得多。你还可以在画面里画一条可拖动滑块的窗口用cv2.createTrackbar实现实时调整检测线位置并观察计数变化这个方法在调参阶段特别好用。6. 功能扩展与实际部署建议6.1 从单线统计升级到双向车道统计如果想把单向车流量改成双向需要定义两条虚拟检测线一条用于向上方向一条用于向下方向。实现上可以给每条检测线一个方向开关即目标中心点从上方到下方记为down从下方到上方记为up然后分别维护两个计数集合。if prev_y self.down_line_y and center_y self.down_line_y: self.down_count 1 if prev_y self.up_line_y and center_y self.up_line_y: self.up_count 1注意两条线之间的区域不能重叠否则目标可能同时触发两个方向的判断导致计数逻辑混乱。6.2 保存每辆车的轨迹记录并生成热力图有些场景下你不仅想知道车流量还想分析车辆的行驶轨迹热点。做法是在每帧跟踪结果里把目标ID、中心点坐标、时间戳追加到一个CSV文件。视频处理完后用matplotlib把所有轨迹点画在同一张图上颜色按ID区分就可以看到主要车流路径。还可以把所有轨迹点按二维直方图统计生成一个热力图。这个功能对交通规划研究很有价值。我实现后觉得效果不错代码量很小大概30行import numpy as np import matplotlib.pyplot as plt traj np.loadtxt(trajectories.csv, delimiter,) heatmap, xedges, yedges np.histogram2d(traj[:, 0], traj[:, 1], bins50) plt.imshow(heatmap, cmaphot) plt.colorbar() plt.show()6.3 接入RTSP实时视频流要处理实时视频流只需要把cv2.VideoCapture的参数从本地文件路径换成RTSP地址即可。但实时流有三个额外问题需要注意一是网络延迟导致CPU占用升高二是帧率不稳定会导致跟踪质量下降三是程序需要做断线重连。我建议的处理方式是启动一个独立的抓帧线程不断从RTSP地址拉取最新帧放入队列主线程从队列中取帧处理。这样即使某帧处理时间较长也不会积压太多旧帧保证实时性。断线重连可以用一个while True循环如果cap.read()返回False就释放并重新打开。def capture_stream(rtsp_url, frame_queue): cap cv2.VideoCapture(rtsp_url) while True: ret, frame cap.read() if not ret: cap.release() time.sleep(2) cap cv2.VideoCapture(rtsp_url) continue if frame_queue.qsize() 5: frame_queue.put(frame) else: # 丢弃旧帧保持实时性 try: frame_queue.get_nowait() frame_queue.put(frame) except: pass6.4 模型轻量化与边缘设备部署如果你打算在Jetson Nano、RK3568等边缘设备上部署YOLOv5s整体还是偏重。建议先用yolov5s.pt导出为TensorRT引擎python export.py --weights yolov5s.pt --include engine --device 0DeepSORT的ReID模型也可以换成更轻量的结构。实测在Jetson Nano上YOLOv5sTensorRT大约能跑到20到30FPSReID用CPU跑也没问题。整体管线要在边缘设备上稳定运行核心瓶颈在检测环节优化好检测基本就能实时。6.5 与Web前端结合做可视化大屏这属于锦上添花。项目跑通后你可以把计数结果定时写入数据库然后用Flask或FastAPI提供查询接口前端用ECharts展示车流量曲线。这个扩展对毕设或者完整项目交付特别加分。我自己的做法是每处理完一帧把total_count和统计时间写入一个内存队列再由独立线程定时刷入SQLite数据库Web端每5秒刷新一次。7. 关键参数调优与实验记录7.1 置信度、IOU阈值和最大检测框数这三个参数是检测效果的“三驾马车”。conf-thres控制检测框保留的置信度门槛太高会漏检尤其远处车辆太低会产生大量误检框干扰跟踪。iou-thres是NMS去重阈值太高会让重叠框同时保留太低会误删真正相邻的车辆。max-det限制每帧最多输出的检测框数量对性能优化有意义但对车流场景通常不用设太紧。我做了一组实验用同一段2分钟视频在不同conf-thres下的统计结果conf-thres检测车辆数实际车辆数误差0.2147126多计210.3133126多计70.4128126多计20.5119126少计7可以看到阈值在0.4附近效果最好。这个值不是固定的如果视频画质更清晰、机位更高可以适当提高如果画面中车辆大多比较小建议下调到0.35左右。7.2 检测线位置对计数的巨大影响检测线太靠近画面上方目标较远时很多车辆还没被稳定跟踪就进入计数区容易重复计数检测线太靠近画面底部目标较近时车辆刚出现就被计数但跟踪ID可能还没稳定。我最终选在画面高度60%左右的位置既保证目标在进入检测线前已经被跟踪了至少十几帧又避免目标过于靠近边缘导致检测框不全。如果你想做精确的多车道分析建议把检测线往下放一点并配合ROI区域限制检测范围避免对隔壁车道车辆误计。7.3 不同规模YOLOv5模型的性能对比模型推理耗时(ms/帧)显存占用(GB)统计准确率yolov5s121.888%yolov5m222.691%yolov5l404.293%如果你的显存足够、对准确率要求高可以选m或l模型。s模型已经能满足大多数场景。在没有GPU的机器上s模型CPU推理大约800ms一帧基本只能做离线分析。7.4 实战调优心得从“能用”到“好用”第一次跑通时输出视频里ID切换频繁、计数偏差严重我一度以为是代码写错了。后来发现是检测置信度设得太低0.1导致同一辆车周围出现大量抖动框DeepSORT的匹配逻辑被低质量检测干扰。把置信度提高到0.4后效果立刻改善。所以在调参时优先调整检测端参数不要一上来就怀疑跟踪端和计数逻辑有问题。另一个心得是不要过分追求“零漏检”。交通场景下的车流量统计完全准确的指标是不存在的。你要做的是在“多计”和“少计”之间找到平衡点并尽可能用跟踪ID去重来抑制多计。对误差做合理容忍对系统落地的帮助更大。8. 常见报错的解决方案与代码改进8.1 torch.hub.load下载超时或失败在代码里用torch.hub.load(ultralytics/yolov5, custom, pathyolov5s.pt)时PyTorch Hub默认会尝试从GitHub拉取ultralytics/yolov5仓库信息如果网络环境不稳定就会卡住。有两个解决办法一是提前把仓库clone到本地然后设置sourcelocal二是直接绕过Hub用YOLOv5官方detect.py里的attempt_load函数加载权重。try: model torch.hub.load(ultralytics/yolov5, custom, pathweights/yolov5s.pt, force_reloadTrue) except Exception: import sys sys.path.insert(0, yolov5) from models.experimental import attempt_load model attempt_load(weights/yolov5s.pt, map_locationdevice)8.2 DeepSORT报错AssertionError: size of input does not match这个报错通常发生在DeepSORT的ReID模型加载时PyTorch版本不一致导致权重文件读取出错。特别是PyTorch 2.0以后对旧版本权重文件的兼容性有变化。解决办法是尽量保持PyTorch版本在1.8到1.13之间或者重新下载对应版本重新编译的ReID权重。如果换不了版本可以尝试用torch.load(weights, map_locationcpu)加载后重新保存一遍往往能解决问题。8.3 非极大值抑制导致两个相邻车辆只检测出一个框当两辆车并排或前后紧贴时YOLOv5的NMS有时会把两个框合并成一个。这会导致跟踪端少跟踪一个目标计数偏少。遇到这种情况可以直接调低iou-thres比如从0.5降到0.3让NMS更保守地保留重叠框。但阈值太低会带来另一个问题——同一个车辆出现多个重复框进而导致同一辆车被多次计数。所以需要反复测试找到适合你场景的值。8.4 输出视频的帧率与原始视频不一致VideoWriter写入帧率需要和实际处理帧率区分开。fps参数决定播放速度不能直接设成25或30就完事应该与原始视频保持一致。如果你跳帧处理了输出fps应该设置为原始fps / (skip_frames 1)。否则视频回放时会出现车速变化的假象干扰你判断算法效果。8.5 代码健壮性改进空检测、空跟踪结果的处理一个容易被忽略的场景是连续多帧没有检测到任何车辆。如果不做判断直接对空数组做后续操作会报错。我习惯在每次检测和跟踪后都加一个长度判断if len(bboxes) 0: cv2.putText(frame, No vehicles detected, (20, 80), ...) continue8.6 性能瓶颈分析与加速建议我用cProfile做了性能分析发现检测阶段占用了大约75%的时间DeepSORT的ReID特征提取占20%其余画图、IO等占5%。所以想让整个程序跑得更快优先要在检测阶段做优化。除了换成TensorRT还可以把输入分辨率从1280降到640检测速度几乎能提升3到4倍精度损失在可接受范围内。另外OpenCV的imshow在实时显示时会阻塞主循环影响速度。如果你不需要看实时画面只关心最终输出的视频文件可以在主循环里去掉imshow这能提升不少吞吐量。老实说这套系统离商用还有一段距离但作为个人项目、毕业设计或者技术验证已经足够扎实。我最后想分享的一个技巧是任何调参都基于数据而不是感觉。我建议你在项目里加一个日志功能把每帧的检测框数、跟踪ID数、当前计数、处理耗时都写入CSV这样跑完一段视频就能拿到一份定量分析报告排查问题时一眼就能看出是检测端还是跟踪端的异常。用数据说话是让整个系统从“能跑”走向“靠谱”的关键。本文还有配套的精品资源点击获取