尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

YOLOv5+DeepSORT车辆行人跨帧追踪与计数实战

YOLOv5+DeepSORT车辆行人跨帧追踪与计数实战 简介基于YOLOv5与DeepSORT的车辆行人追踪和计数系统面向计算机视觉、智能交通方向的学生及课程设计开发者也适合希望研究目标检测与多目标跟踪融合的进阶读者。项目已获导师指导并通过得到97分高分整体完整下载后无需修改即可运行可用于课程设计、期末大作业或图像识别项目参考。代码注释清晰模块划分合理能够单独替换检测器或跟踪参数。压缩包共101个文件以42个Python源码和51个pyc编译文件为主体另有YAML配置文件、YOLOv5m.pt模型权重、t7特征提取模型、README说明、示例图片等同时包含通用训练与检测工具脚本便于理解项目结构和二次修改整体约79.93MB。目前已有660人学习下载通过该资源可快速掌握YOLOv5检测与DeepSORT跟踪衔接、目标计数与轨迹绘制等关键技术项目中的计数结果可视化可直接用于效果演示。完整的代码、配置与说明文档能够支撑从环境搭建到结果输出的全流程对撰写课程设计报告和准备答辩演示也有较高参考价值。1. 车辆行人追踪计数难的不是检测而是跨帧关联一段 60 秒的路口视频里画面最多同时出现过 12 辆车但一个只做目标检测的计数程序却输出了 38 个计数因为每帧识别出车就加 1同一辆车从入画到出画累计被加了十几次。这不是检测精度不够而是缺少跨帧关联YOLOv5 只负责单帧里给出框、类别和置信度真正判断“这辆车还是上一帧那一辆”的活归 DeepSORT 干。车辆行人计数这类题目里计数结果的准确度七成由追踪决定两成由计数逻辑决定检测器反而是最不折腾的部分。这个选题最常出现在课程设计、交通流量统计和简单安防场景里。拿高分的关键不取决于把 YOLOv5 网络结构讲得多细而在于重复计数、ID 跳变这两个坑处理得干不干净。下文先讲清两个模型的边界与参数再给一套本地可跑的最小实现最后把数据组织、跟踪线计数、方向判定串成一条能直接演示的代码路径。2. YOLOv5DeepSORT 的分工与关键参数2.1 检测器数框追踪器数 IDYOLOv5 的输出为什么不够用YOLOv5 的主干是 CSPDarknet53配合 PANet 做多尺度特征融合输出层有三个尺度分别对应大小目标这也是它在车辆行人这种尺度差异明显的场景里比早期 YOLO 版本更顺手的原因。模型每帧输出格式是[x1, y1, x2, y2, conf, cls]意思很直接这个框在哪、置信度多少、属于哪一类。但注意它没有 ID也没有帧与帧之间的关联信息。摄像头 25 帧里同一辆车在 YOLOv5 眼里就是 25 个互不相干的检测框。DeepSORT 补的正是这一层。它内部的 SORT 基线用卡尔曼滤波预测目标在下一帧的位置再用匈牙利算法把检测框和已有轨迹做 IOU 匹配DeepSORT 在此基础上增加了一个外观特征分支用一个小型 ReID 网络为每个框提特征匹配时把“长得像不像”放进代价矩阵。代价就是车辆被遮挡后重现纯 SORT 会丢掉 IDDeepSORT 能靠外观特征续上。组件单帧输出跨帧能力主要开销YOLOv5框、类别、置信度无卷积计算GPU 友好SORT轨迹 ID、预测框运动模型 位置匹配卡尔曼滤波极低DeepSORT轨迹 ID、确认状态运动 外观双重匹配ReID 特征提取可接受所以车辆行人计数项目的正确姿势是YOLOv5 当眼睛DeepSORT 当记忆计数逻辑当大脑。眼睛负责看清记忆负责记住谁是同一个目标大脑再决定什么时候算“过了一辆车”。很多初版方案把计数写在检测循环里帧帧累加看到的数字当然会像股票行情一样跳。2.2 DeepSORT 状态管理与参数表先调谁后调谁车辆行人场景里追踪参数对结果的影响比检测参数更直接因为它们直接决定 ID 会不会断、会不会串。DeepSORT 把轨迹分为未确认和已确认两种新轨迹要连续n_init帧匹配成功才会转正转正后的轨迹才允许参与计数。这个设计很关键——如果你拿未确认轨迹去计数闪烁的误检框会直接污染统计结果。常用参数按优先级排我一般这样调参数默认值作用车辆/行人场景建议max_dist0.2外观特征余弦距离阈值行人 0.3车辆 0.15n_init3连续匹配帧数达到才确认轨迹路口场景调到 2降低延迟max_age30轨迹丢失后保留帧数遮挡多的场景调到 50max_iou_distance0.7位置匹配的 IOU 阈值车辆保持 0.7行人建议 0.5nn_budget100特征库容量同车型多的场景提到 200判断该调哪个参数有个土办法写一段临时代码打印每个 ID 连续两帧的质心位移匹配错乱时质心会瞬移。# 追踪结果 outputs 格式: [x1, y1, x2, y2, track_id, cls] prev_center {} for frame_id, outputs in enumerate(all_outputs): for box in outputs: tid int(box[4]) cx (box[0] box[2]) / 2 # 框中心 x cy (box[1] box[3]) / 2 # 框中心 y if tid in prev_center: dist abs(cx - prev_center[tid][0]) abs(cy - prev_center[tid][1]) if dist frame_w * 0.15: # 一帧移动超过画面宽度 15%基本可断定匹配错 print(fframe {frame_id}: ID {tid} 瞬时位移异常 dist{dist:.1f}) prev_center[tid] (cx, cy)这段代码的核心是“瞬时位移异常”判断。正常车辆在相邻帧的移动距离远小于画面宽度出现大幅瞬移要么是卡尔曼预测失败要么是把另一个目标的框匹配给了这个 ID。把异常帧数和对应时段记录下来再去调max_dist或max_age比盲试参数高效得多。提示调参顺序固定为max_dist→n_init→max_iou_distance不要上来就动特征相关超参。前三个参数影响的是“能不能匹配上”后两个影响的是“匹配上后能不能记得住”。3. 用 YOLOv5DeepSORT 跑通车辆行人追踪最小 demo3.1 环境准备与权重获取先把官方检测跑起来常见的做法是克隆 YOLOv5 仓库再单独挂一个 DeepSORT 实现。DeepSORT 的官方版本是独立的不少课程设计会直接引用现成的封装代码这里按最省事的路径来。权重文件需要注意YOLOv5 官方 release 里只有检测权重DeepSORT 的 ReID 权重需要单独下载通常是一个ckpt.t7文件放在deep_sort/deep/checkpoint/目录下。不下载这个文件追踪部分就没法运行。# 1. 创建环境并安装依赖 conda create -n trafcount python3.8 -y conda activate trafcount pip install -r requirements.txt # torch、opencv-python、numpy 等 # 2. 先验证检测链路处理一段路口视频 python detect.py --weights yolov5s.pt --source traffic.mp4 \ --conf-thres 0.3 --iou-thres 0.45 --save-txt # 3. 如果想直接调摄像头 python detect.py --weights yolov5s.pt --source 0 --conf-thres 0.3--conf-thres 0.3是置信度阈值比默认 0.25 更保守适合车辆行人这类误检代价高的场景--iou-thres 0.45控制 NMS 重叠框合并目标密集时调到 0.5 能减少漏检。这步跑通后再接 DeepSORT因为后续所有追踪问题都要先排除检测链路的因素。CPU 上 YOLOv5s 640 分辨率只有演示级帧率条件允许就用 GPU 跑追踪部分本身开销不大。3.2 把检测框转成 DeepSORT 输入代码骨架DeepSORT 的输入不是原始图像而是“检测结果 当前帧”。不同封装对检测结果的格式要求差异很大官方 deep_sort_pytorch 的update接口收的是[cx, cy, w, h]的归一化坐标加置信度数组而不少教程魔改版收的是[x1, y1, x2, y2, conf, cls]。写的时候要看清用的是哪个版本下面按常见封装写一版。import cv2 import torch import numpy as np from models.experimental import attempt_load from utils.general import non_max_suppression, scale_coords # 这行根据你引用的 deep_sort 封装而定 from deep_sort_pytorch.deep_sort import DeepSort # 车辆行人相关 COCO 类别: person0, car2, bus5, truck7 TARGET_CLS {0, 2, 5, 7} model attempt_load(yolov5s.pt, map_locationcuda) deepsort DeepSort(deep_sort_pytorch/deep_sort/deep/checkpoint/ckpt.t7, max_dist0.2, n_init3, max_age30, nn_budget100) cap cv2.VideoCapture(traffic.mp4) while True: ret, frame cap.read() if not ret: break h, w frame.shape[:2] # YOLOv5 推理 NMS输入分辨率 640×640 results model(frame[None, ...], size640)[0] dets non_max_suppression(results, conf_thres0.3, iou_thres0.45)[0] bbox_xywh [] confs [] if dets is not None and len(dets): # scale_coords 把 640 尺度坐标还原到原图 dets[:, :4] scale_coords((640, 640), dets[:, :4], (h, w)).round() for x1, y1, x2, y2, conf, cls in dets.tolist(): if int(cls) not in TARGET_CLS: continue # 转成 cx, cy, w, h 并归一化到 0~1 bbox_xywh.append([(x1 x2) / 2 / w, (y1 y2) / 2 / h, (x2 - x1) / w, (y2 - y1) / h]) confs.append(conf) if len(bbox_xywh) 0: # 输出: [x1, y1, x2, y2, track_id] outputs deepsort.update( np.array(bbox_xywh, dtypenp.float32), np.array(confs, dtypenp.float32), frame ) for x1, y1, x2, y2, track_id in outputs: cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2) cv2.putText(frame, fID {int(track_id)}, (int(x1), int(y1) - 6), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) cv2.imshow(track, frame) if cv2.waitKey(1) 0xFF ord(q): break这段代码有两个细节值得展开。scale_coords是 YOLOv5 工具函数因为模型输入是 640×640输出坐标基于缩放后的图必须映射回原图坐标才算对否则画框和计数都会偏。DeepSORT 的update里max_dist控制外观匹配的宽松度n_init3意味着一个目标要在连续 3 帧里被稳定匹配才赋予最终 ID视频前几帧出现的行人计数会偏慢这是正常现象不是 bug。COCO 类别 ID类别名是否纳入车辆行人计数备注0person是行人目标2car是最主要的车辆目标5bus是公交车、大巴7truck是货车和 bus 结构相近1bicycle视需求窄目标质心抖动大3motorcycle视需求建议单独建类再计数计数逻辑放在追踪输出之后右侧的track_id是唯一能区分“这辆车在前面是否已经计过”的依据。很多人问为什么计数程序要把类别过滤得这么细因为 COCO 80 类里超过一半和交通场景无关不过滤的话远处一个路牌、一只鸟都可能被当成计数对象。4. 车辆行人计数实现训练数据与跟踪线去重4.1 用跟踪线完成计数并解决重复计数视觉计数的原理说到底只有两种虚拟线圈和跟踪线。虚拟线圈是在画面里画一个固定区域统计进入区域的检测框数量跟踪线则是画一条虚拟的线目标质心从线的一侧穿到另一侧就算一次。我一般用跟踪线因为它天然区分方向还能靠track_id去重虚拟线圈对逗留目标的处理很麻烦。重复计数是车辆行人计数的头号问题。一个目标在画面里停留 20 秒每帧都检测到如果按帧数累加就会计出几百次。正确做法是只有“跨过线”的那一瞬计数而且每个 ID 只计一次。# 计数状态放在追踪循环外 line_y int(h * 0.6) # 跟踪线位置取画面 60% 高度处 counted_id set() # 已计数的 ID 集合防止重复计数 direction_count {up: 0, down: 0} # 按方向分开统计 def cross_count(track_id, prev_cy, cur_cy): 根据质心前后两帧位置判断是否过线并计数 if track_id in counted_id: return if prev_cy is None: return # 图像坐标系 y 轴向下prev 在线上方、cur 在线下方 车往下走 if prev_cy line_y cur_cy: direction_count[down] 1 counted_id.add(track_id) elif prev_cy line_y cur_cy: direction_count[up] 1 counted_id.add(track_id)这段逻辑有几个边界要处理。prev_cy记录的是上一帧该 ID 的质心而不是检测框的某个角因为框的宽高会随车辆远近变化角点抖动远大于质心。counted_id集合是去重的核心一个 ID 跨过线一次后就被记录之后无论它怎么在画面里掉头、再跨线都不会重复计数。如果你的场景允许车辆掉头后再合法地计一次那就按“方向 是否接近边界”两个条件同时判断而不是简单用 set 一票否决。目标在跟踪线附近丢失再重现时DeepSORT 会分配新 ID此时 set 里查不到就会造成漏计或重复计。这就是max_age参数的用武之地把max_age从 30 提到 50遮挡几帧后还能找回原轨迹从根源上减少这种“换胎”场景。所以计数逻辑调优的结论是计数代码本身要简单复杂度交给追踪参数去承担。4.2 训练自己的车辆行人数据集YAML 与 yolov5 超参数课程设计通常附带视频数据但预训练 COCO 权重在高速监控这类特殊视角下车牌弯折、车身比例怪异计数效果会打折。自己训一轮并不复杂关键是数据集格式要对。YOLOv5 的标注是 YOLO 格式的 txt每行class cx cy w h坐标归一化到 0~1和 labelImg 导出的格式一致。目录一般这样组织datasets/vehicle_person/ ├── images/ │ ├── train/ # 训练图片 │ └── val/ # 验证图片 └── labels/ ├── train/ # 对应的同名 txt └── val/数据集配置用一个 yaml 文件描述# data/vehicle_person.yaml train: ./datasets/vehicle_person/images/train val: ./datasets/vehicle_person/images/val nc: 2 names: [vehicle, person]这里有个坑如果你用 COCO 预训练权重做迁移学习初始权重能识别的car(2)、bus(5)、truck(7)三个类到你的一类vehicle里输出头维度变了预训练的特征层还在但分类头需要随机初始化重新学。常见做法是干脆保留三个 COCO 类最后计数时把 car、bus、truck 归成“车辆”一个计数项这样迁移学习的收益最大。强行裁成两类小样本下效果反而不稳。训练命令和关键超参数如下python train.py \ --data data/vehicle_person.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --hyp data/hyps/hyp.scratch-low.yaml \ --name veh_person超参数默认值含义车辆行人场景建议lr00.01初始学习率小数据集降到 0.005mosaic1.0是否启用马赛克增强保持 1.0能提升小目标fliplr0.5水平翻转概率车辆方向敏感时设为 0hsv_h0.015色相增强幅度夜间数据多时增大到 0.02cls0.5分类损失权重行人车辆不平衡时增到 1.0box0.05框回归损失权重默认即可不用动mosaic对车辆行人这种尺度变化大的场景很重要四张图拼一张小尺寸目标出现概率变高模型对小目标更友好。fliplr要谨慎交通场景里左右翻转会改变车辆的行驶方向语义训练集如果以“右侧行驶”为主翻转后车灯位置对不上反而干扰。数据不够时可以先用训练好的 COCO 模型对未标注视频做自动预标注生成一批带噪声的 txt再用标注界面人工修订这是扩大数据量代价最低的方式。5. YOLOv5DeepSORT 的进阶方向计数与 ID 稳定性回放5.1 方向计数用质心跨线的角度而不是框位置道路场景往往只需要统计某一方向的车流。跟踪线天然支持方向判别但要注意一点用“上一帧在线哪一侧”判断方向时目标贴着线走、反复横跳会来回计数。更稳的做法是记录目标首次被确认时的质心以及跨线瞬间的质心两者组成一个运动向量根据向量的 y 分量方向判方向。对于行驶速度较快的车辆这个向量和跟踪线法线方向的夹角不会太小可以顺便过滤掉斜穿的行人。5.2 用带 ID 的回放视频验证重复计数与漏检调完参数和计数逻辑最后一步是验证。不要只看最终数字把每帧的track_id画到视频里导出逐段回放观察两个点同一辆车在画面里是否一直保持同一个 ID以及跨线的那一瞬间数字是否正确加一。# 用 VideoWriter 保存验证视频 fourcc cv2.VideoWriter_fourcc(*mp4v) out cv2.VideoWriter(debug_track.mp4, fourcc, 25.0, (w, h)) # 在每帧绘制 draw_info: [x1, y1, x2, y2, track_id] for draw_info in track_debug: x1, y1, x2, y2, tid draw_info cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, fID:{int(tid)}, (x1, y1 - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) # 把跟踪线画出来计数瞬间把框标红 cv2.line(frame, (0, line_y), (w, line_y), (0, 0, 255), 2) out.write(frame)回放时重点看 ID 编号是否在某个区域频繁跳变。如果一辆车在画面中段从 ID 3 突然变成 ID 7说明旧轨迹丢失、新轨迹顶替这是漏计的直接原因。此时优先调高max_age、调低max_dist而不是去改检测置信度——conf_thres调高只能减少误检框救不了轨迹连续性。最终经验是在车辆多且外形相似的路口把max_dist从 0.2 调到 0.15、nn_budget提到 200带来的计数提升往往比更换任何检测模型都明显。本文还有配套的精品资源点击获取
返回列表