ARTICLE DETAIL

资讯详情

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

YOLOv5+DeepSORT车辆行人追踪计数实战:从检测到轨迹ID的完整方案

YOLOv5+DeepSORT车辆行人追踪计数实战:从检测到轨迹ID的完整方案 简介面向计算机视觉开发者和项目学习者这份实战资源围绕YOLOv5与DeepSORT的工程化整合解决视频场景下车辆行人的实时检测、连续跟踪与跨帧计数问题。压缩包共一百零三个文件大小约八十MB以Python源码、配置说明、模型文档和预训练权重为主代码结构覆盖目标检测、特征提取、轨迹关联、主流程调用与工具函数等模块便于直接运行和二次开发。目前已有三千二百八十七人浏览学习。资源一方面讲清两大核心算法的工作原理即YOLOv5通过卷积网络输出边界框与类别DeepSORT利用卡尔曼滤波预测状态再结合外观特征与匈牙利算法匹配轨迹另一方面提供封装好的Detector类与可运行工程骨架让读者快速理解从模型推理到追踪计数的完整链路适合用于智能监控、车流量统计、行人密度分析等真实应用场景。1. 从重复检测到持续追踪YOLOv5DeepSORT如何把检测结果变成持续轨迹在车流密集的十字路口单靠YOLOv5逐帧检测只能得到一堆孤立的边界框——同一辆白色SUV在第一帧是“新目标”第二帧又变成“另一个新目标”计数结果会膨胀到无法接受。这是所有视觉计数项目的第一道坎检测只回答“哪里有什么”而追踪才回答“这是谁从哪来到哪去”。YOLOv5负责高置信度的车辆行人检测DeepSORT用卡尔曼滤波预测下一帧位置再用深度学习外观特征解决遮挡后的身份恢复两者结合起来才构成一套能输出稳定轨迹ID的计数系统。这篇文章从工程落地角度拆解这套流程适合正在做智慧交通、大作业或实际监控系统预研的开发者你能从中拿到可直接改用的Detector封装方式和一组经过实践的参数建议。2. 环境搭建与模型选型为什么首选yolov5m.pt和现成DeepSORT权重2.1 依赖安装与版本坑跑通这套组合的关键不在代码本身而在依赖版本是否对齐。YOLOv5在PyTorch 1.8到2.x的范围内都能工作但DeepSORT的特征提取器通常依赖torchvision或第三方权重文件所以建议用虚拟环境管理。我常用的依赖版本如下表依赖推荐版本说明Python3.8 ~ 3.103.11以上部分tf工具不支持PyTorch1.12 ~ 2.1过低无法加载新权重过高可能遇到算子改动opencv-python4.8以上视频读写和画框numpy1.23 ~ 1.26过高会导致torch在部分CPU上报兼容问题seaborn0.11.0YOLOv5绘图时需要缺失会报错安装命令通常是pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install opencv-python numpy seaborn pandas tqdm这里有一个很隐蔽的坑如果你直接pip install torch默认安装CPU版本在N卡上推理会慢好几倍。因此要先确认CUDA版本。另外DeepSORT权重文件ckpt.t7是早期的PyTorch存储格式用torch.load加载时如果遇到zip is not a valid archive错误说明PyTorch版本太高需要用torch.load(f, map_locationcpu, weights_onlyFalse)显式声明。2.2 读懂项目里的YOLOv5工具脚本项目下载后你会看到general.py、yolo.py、torch_utils.py、json_logger.py这几个文件它们不是DeepSORT的一部分而是YOLOv5仓库中的基础模块。yolo.py定义了Detect头也就是YOLOv5最后输出边界框、置信度和类别的地方general.py包含很多通用函数比如非极大值抑制NMS、归一化坐标转换、color palette等torch_utils.py提供模型加载时的时间同步、参数量计算、EMA等工具json_logger.py则在训练阶段记录日志。在推理阶段我们完全不需要手动调用这些函数因为torch.hub.load会帮我们打包好。但如果你要基于这个项目做二次开发比如自定义检测后处理逻辑就需要从general.py里找到non_max_suppression和scale_coords直接使用。2.3 快速跑通一条视频拿到模型后不要急着封装先确认YOLOv5能跑通、DeepSORT能加载成功。最简单的验证方式是用一段路口视频逐帧调用YOLOv5检测打印出检测框数量。例如import torch model torch.hub.load(ultralytics/yolov5, custom, pathyolov5m.pt, force_reloadFalse) results model(street.jpg) results.show()能正常显示检测框后再试DeepSORT。常见做法是先构造一个空的检测列表让DeepSORT运行几帧确保卡尔曼滤波器初始化不报错。DeepSORT的构造函数需要传入特征提取权重路径以及几个关键阈值from deep_sort import DeepSort deepsort DeepSort( deepsort/ckpt.t7, max_dist0.2, # 外观特征余弦距离阈值 min_confidence0.3, # 检测置信度下限 nms_max_overlap1.0, # NMS阈值1表示不做额外NMS max_iou_distance0.7, # 级联匹配中的IoU阈值 max_age70, # 轨迹丢失后保留帧数 n_init3, # 连续匹配成功多少帧才创建轨迹 nn_budget100 # 特征池大小 )我一般会先用max_dist0.2、max_age70作为初始值跑一段视频观察ID切换频率再逐步调整。这个阶段的目的是暴露环境问题而不是追求精度。3. 封装Detector类让YOLOv5和DeepSORT形成稳定流水线3.1 设计思路单一入口管理检测与追踪在实际项目中我会把YOLOv5和DeepSORT封装成一个Detector类而不是在业务代码里零散调用。这样做的原因很直接追踪结果需要依赖上一帧的轨迹状态如果把deepsort.update()和yolo()分开写代码耦合会变得很难维护。Detector类对外只暴露两个方法detect(frame)获取当前帧的检测框track(frame, boxes, confs, clss)获取带追踪ID的检测结果。内部维护DeepSORT的轨迹状态调用方完全不感知。3.2 detect()从图像到边界框YOLOv5的推理结果是一个Detections对象我们需要从中提取坐标、置信度和类别。注意坐标需要从原始缩放后的尺寸映射回原图否则画框位置会偏移。下面是一个可直接复用的方法def detect(self, frame): # frame: BGR numpy array, shape (H, W, 3) results self.yolo(frame) pred results.pred[0] # tensor of shape (N, 6) boxes, confs, clss [], [], [] for det in pred.cpu().numpy(): x1, y1, x2, y2, conf, cls det if int(cls) not in self.target_classes: continue boxes.append([int(x1), int(y1), int(x2 - x1), int(y2 - y1)]) confs.append(float(conf)) clss.append(int(cls)) return boxes, confs, clss这段代码里self.target_classes在构造函数中配置比如[2, 0, 7]分别对应COCO数据集里的car、person和truck。之所以把坐标从(x1,y1,x2,y2)转成(x,y,w,h)是因为DeepSORT的update()接口期望输入格式为[x,y,w,h]。置信度阈值已经在YOLOv5初始化时通过self.yolo.conf0.4设置这里不需要再额外过滤。3.3 track()从边界框到轨迹IDtrack()内部调用DeepSORT的update核心参数是当前帧的检测框、置信度和类别。DeepSORT在内部会先做级联匹配外观特征和IoU匹配运动信息然后输出每条轨迹的(x1, y1, x2, y2, track_id, class_id)。注意输出坐标是整数像素且已经还原到原图坐标系。实现如下def track(self, frame, boxes, confs, clss): if len(boxes) 0: # 没有检测时仍然要调update否则轨迹不会过期清理 outputs self.deepsort.update([], [], [], frame) return [] outputs self.deepsort.update( np.array(boxes), np.array(confs), np.array(clss), frame ) tracked_boxes, ids, tracked_cls [], [], [] for output in outputs: x1, y1, x2, y2, track_id, cls output tracked_boxes.append([x1, y1, x2, y2]) ids.append(track_id) tracked_cls.append(cls) return tracked_boxes, ids, tracked_cls这里有一个容易踩坑的点不要把detect()里过滤掉的类别直接丢弃因为DeepSORT内部会维护不同类别的特征池。如果某帧只有行人没有车但之前有车的轨迹还在预测DeepSORT会使用上一帧的外观特征尝试匹配空检测但最终会因找不到匹配而让轨迹进入miss状态。所以即使检测列表为空也必须调用update()只是传入空数组即可。3.4 主循环加载视频逐帧处理封装好Detector后主程序就非常简洁了。我用cv2.VideoCapture读取视频逐帧调用detect和track然后画框并写入输出视频。为了测试跟踪连续性我还会在每帧打印当前所有轨迹IDcap cv2.VideoCapture(video_path) detector Detector() while cap.isOpened(): ret, frame cap.read() if not ret: break boxes, confs, clss detector.detect(frame) tracked_boxes, ids, tracked_cls detector.track(frame, boxes, confs, clss) for (x1, y1, x2, y2), track_id, cls in zip(tracked_boxes, ids, tracked_cls): cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, fID:{track_id}, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 0, 0), 2) cv2.imshow(result, frame) if cv2.waitKey(1) 0xFF ord(q): break这段代码可以直接跑通。如果看到ID在连续帧中保持不变说明YOLOv5和DeepSORT已经完成串联。如果ID频繁跳变问题大概率出在检测质量或外观特征权重上而不是代码本身。4. 追踪与计数如何用DeepSORT参数和轨迹ID做双向计数4.1 DeepSORT关键参数外观距离、IoU、max_age调优DeepSORT的调参直接影响计数稳定性。最常动的是max_dist、max_iou_distance和max_age。max_dist控制外观特征之间允许的最大余弦距离值越小匹配越严格但太小的结果会导致遮挡后无法重识别max_iou_distance控制IoU匹配阈值主要抑制相邻目标的错误关联max_age决定轨迹丢失后保留多少帧。下表是我针对车辆行人场景的推荐范围参数推荐值范围场景说明max_dist0.15 ~ 0.25行人0.2、车辆0.15更严格max_iou_distance0.5 ~ 0.8拥挤路口用0.5稀疏公路用0.8max_age30 ~ 100路口常遮挡用70以上n_init2 ~ 4值越小轨迹产生越快但噪声也越多nn_budget50 ~ 200内存充足时调大改善外观特征漂移我通常在车辆计数场景把max_dist降到0.15因为车辆外观差异一般较大太宽松会让相邻车道的车ID互换。行人场景则放宽到0.25因为行人外观特征更容易受穿着变化影响。4.2 虚拟线计数判断轨迹跨越方向最常用的计数方式是虚拟线计数在视频画面中画一条线记录每个目标前后两帧中心点坐标如果中心点跨过这条线就判断为一次进入或离开。实现时需要维护一个字典存储上一帧每个ID的中心点prev_centers {} count_in 0 count_out 0 line_x 640 # 垂直线的x坐标 def count_crossing(id, current_center, line_x): global count_in, count_out if id in prev_centers: prev_x prev_centers[id][0] cur_x current_center[0] if (prev_x - line_x) * (cur_x - line_x) 0: # 跨线 if cur_x prev_x: count_in 1 else: count_out 1 prev_centers[id] current_center这段逻辑的核心是判断两帧中心点与线的相对位置是否发生符号变化。对于垂直方向的车流把坐标换成y方向即可。注意要定期清理prev_centers中已经消失的ID否则字典会无限增长。4.3 区域计数统计指定范围内的目标相比虚拟线区域计数更适用于停留统计比如监控某个停车位是否有车、某个十字路口进口方向的车流量。做法是定义一个人工多边形区域使用点是否在多边形内来判断目标是否进入。可以用OpenCV的cv2.pointPolygonTestpolygon np.array([[100, 200], [300, 150], [400, 300], [200, 350]]) def in_region(center): return cv2.pointPolygonTest(polygon, tuple(center), False) 0 # 在track循环中 if track_id not in state: state[track_id] {in_region: False} is_inside in_region((cx, cy)) if is_inside and not state[track_id][in_region]: count_region 1 state[track_id][in_region] is_inside这段代码维护每个ID的“是否在区域内”状态只有从区域外进入区域内才计数。需要特别注意的是DeepSORT的轨迹ID可能复用比如旧ID消失后新ID可能使用相同的数字所以仅靠track_id判断状态可能导致历史状态被错误继承。我一般会同时记录max_age或最后更新时间超过一定帧数就重置该ID的状态。4.4 防止同一个ID被重复计数实际场景中一个目标可能因为检测抖动导致轨迹分裂成两个ID或者同一帧两个ID被错误合并。最简单的防护是增加“冷却期”机制目标一旦被计数在后续10帧内即使跨线或进区域也不再计数。代码实现counted {} COOLDOWN 10 def should_count(track_id, current_center): now time.time() if track_id in counted: if time.time() - counted[track_id] COOLDOWN: return False return True # 计数成功后 counted[track_id] time.time()这个技巧虽然简单但能有效过滤掉由于单帧抖动引起的重复计数。更严格的做法是结合轨迹置信度只有当轨迹存活超过n_init帧且连续匹配成功多次才参与计数。5. 实战优化遮挡、漏检和加速的常用补刀手段5.1 提高外观特征的重识别鲁棒性DeepSORT自带的ckpt.t7是基于Market1501数据集训练的行人重识别模型对车辆效果一般。如果你只追踪车辆建议换成在VeRI或VehicleID上训练过的特征模型。做法是把DeepSORT中的特征提取网络替换为open-reid或fast-reid导出的模型。我经常用torchreid库的OSNet模型输出512维特征替换后车辆ID切换率能下降30%以上。如果不想训练也可以降低max_dist到0.1强制外观匹配更严格。5.2 处理轨迹中断与身份切换当目标被完全遮挡超过max_age帧后DeepSORT会删除该轨迹目标重现时会被分配新ID这会导致计数偏大。一个常用技巧是同时维护一个“待定轨迹池”用来存放删除前的外观特征和最后位置。当新轨迹出现且与待定池中某特征的距离小于阈值时手动合并ID。实现上需要额外代码但能明显减少重复计数。5.3 用ONNX/TensorRT压缩推理延迟YOLOv5的推理瓶颈在模型本身DeepSORT的特征提取也是消耗。常见做法是把YOLOv5导出为ONNX再转成TensorRT的engine文件在Jetson或N卡上能获得2~3倍加速。导出命令python export.py --weights yolov5m.pt --include onnx --dynamic导出后用onnxruntime进行推理如果模型本身不大CPU也能跑到25 FPS左右。DeepSORT的特征提取网络较小通常不需要加速但要注意它用的是PyTorch的extract函数用torch.no_grad()包裹能省些显存。5.4 用人工标注片段验证计数准确率调参到最后一定要做数据验证。我通常的做法是截取一段3分钟的路口视频人工记录真实的进出数量然后跑自己的计数逻辑统计“正确计数、漏计、重复计”三类比例。下面是一个简易验证脚本def evaluate(real_in, real_out, pred_in, pred_out): in_error abs(real_in - pred_in) / real_in out_error abs(real_out - pred_out) / real_out return 1 - (in_error out_error) / 2 print(fCounting accuracy: {evaluate(58, 47, 55, 50) * 100:.1f}%)这个脚本的意义在于让你知道当前参数适不适合现场场景。如果准确率低于85%先检查YOLOv5的漏检率再检查DeepSORT的ID切换率而不是盲目调阈值。把每次实验的max_dist、max_age、conf数值与验证结果记录下来三四轮后就能找到当前视频的最佳组合。最后一个小技巧在业务部署中不要直接修改yolov5.py或deepsort.py把你要调的参数都集中到一个config.py里每个参数写上注释和默认值。这样当老板让你“试一下更严格的阈值”时你只需要改一行而不是翻一遍代码。以上就是这套车辆行人追踪计数项目从装机到调优的完整实践路径。如果你照着做应该能在一晚上跑通基础流程再用一天时间把计数准确率调到你满意的水平。本文还有配套的精品资源点击获取
返回列表