
简介基于YOLOv5与DeepSort的车辆行人追踪计数项目适用于计算机视觉相关专业的毕业设计、期末大作业与课程设计场景也可作为目标检测与多目标跟踪的入门练手项目。代码按模块化组织主程序、检测器、跟踪器与工具模块划分清晰关键位置附有注释配合说明文档新手也能较快上手项目经过严格调试可直接作为完整毕设或课设方案使用。压缩包共78个文件整体82.69MB以50个Python脚本为核心同时包含YOLO模型配置、DeepSort权重、预训练模型、测试视频、依赖清单与README等内容目录结构清晰便于按需调用。目前已有173人学习/下载下载后完成环境配置即可直接使用能帮助读者快速完成一个功能完整、界面直观且具备实际应用价值的跟踪计数系统。1. 基于YOLOv5DeepSORT的车辆行人追踪计数毕设到底在做什么把一段路口监控视频丢进程序出来的是每辆车、每个人被框住并带着独立编号画面边上实时跳着车辆 12 辆、行人 35 人的统计数字。这就是这个标题描述的事情用 YOLOv5 做目标检测把每一帧里的车辆和行人找出来用 DeepSORT 做多目标跟踪给同一个目标分配稳定 ID最后基于 ID 和轨迹做计数。毕设里常见的完整源码说明文档指的就是这条完整链路。这套方案适合两类人一类是做车辆检测、交通流量统计方向毕业设计的学生另一类是刚接触目标跟踪、想快速把检测和跟踪串起来的工程师。需要先说明的是检测和跟踪是两条独立技术线但最终效果的好坏七成取决于怎么把两条线粘在一起剩下的三成才取决于模型精度。下面按这条主线的关键节点展开。2. YOLOv5 车辆行人检测从数据集到模型的落地细节2.1 用 YOLOv5 训练自己的车辆行人数据集标题明确要求车辆行人直接使用 COCO 预训练权重虽然能识别 person、car、truck、bus但如果视频场景是固定视角的校园路口或园区门口用预训练模型往往在远处小目标和遮挡情况下频繁漏检。常见做法是拿 COCO 预训练权重做迁移学习用自己的视频抽帧标注训练 100 轮左右就能得到明显优于原版的效果。数据准备阶段需要三类东西图片、标注文件、数据集描述 yaml。用 LabelImg 或 Labelme 标注时只需要四个类person、car、truck、bus也可以把 motorcycle 和 bicycle 加上。标注完的目录结构一般是dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── vehicle.yamlvehicle.yaml内容如下train: dataset/images/train val: dataset/images/val nc: 4 names: [person, car, truck, bus]这里nc是类别数names与标注文件的类别 id 一一对应。注意标注文件的第一个数字就是类别 id顺序必须和names一致否则训练完检测结果会张冠李戴。然后启动训练python train.py --data vehicle.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 100 --device 0--img 640是训练分辨率固定视角的交通场景用 640 就够了没必要上 1280--batch 16在 8GB 显存下比较稳显存小就降到 8--weights yolov5s.pt会根据 COCO 的 80 类权重自动调整输出层以适应 4 类数据。训练完成后最佳权重保存在runs/train/exp/weights/best.pt。2.2 YOLOv5 超参数怎么调先观察 loss再动锚框训练自己的数据集时很多人一上来就改学习率、改 batch但这套默认超参数在大多数交通场景下已经够用。真正需要干预的是两类情况漏检多和误检多。漏检多先加 epochs比如从 100 加到 150观察val/obj_loss是否还在下降误检多优先检查标注质量看是不是把远处的人没标或者把车尾阴影标进去了。如果数据里车辆占比大、行人占比小需要调--hyp指向的 yaml 里的类别损失权重。YOLOv5 的默认超参文件是data/hyps/hyp.scratch-low.yaml可以复制一份修改cls: 0.5 box: 0.05 obj: 1.0当行人容易被漏检时把cls从 0.5 提到 0.7obj从 1.0 提到 1.2代价是训练时间略微变长但对小目标的 recall 会明显改善。调完之后在启动命令中指定你自己的 hyp 文件。关于 YOLOv5 网络结构yolov5s.yaml里的 C3 模块和 SPPF 模块决定了特征提取深度s 版本在车辆行人这类大目标场景下性价比最高m 和 l 版本收益很小但推理速度下降明显。调试阶段必看的是runs/train/exp/results.png里的mAP0.5曲线。训练正常的情况下 mAP0.5 会涨到 0.9 以上如果卡在 0.7 左右首先排查标注框有没有明显错位其次是数据里有没有大量极端天气或夜间样本导致模型学到的是亮度特征而非形状特征。2.3 推理阶段的置信度阈值是连接 DeepSORT 的关键检测模型训练好后下一阶段是把它接到 DeepSORT 上。这里有一个关键参数conf_thres。做检测演示时大家习惯把置信度调到 0.25这样框多显得检测能力强但跟踪任务里低置信度会引入大量假阳性框导致 DeepSORT 创建大量幽灵轨迹ID Switch 会变得非常严重。我一般会把 YOLOv5 的推理置信度设为 0.4 到 0.5iou_thres保持默认 0.45。行人目标小可以用 0.35车辆目标大且特征明显0.5 更干净。调整方式是在 YOLOv5 的detect.py里改参数或者在自定义推理脚本里显式传入results model(frame, size640, conf_thres0.45, iou_thres0.45)得到的results.xyxy[0]是[x1, y1, x2, y2, confidence, class]格式的张量这个输出会被直接喂给 DeepSORT所以这一层的阈值选择决定了跟踪器输入质量。3. DeepSORT 跟踪卡尔曼滤波和级联匹配的工程理解3.1 卡尔曼滤波、匈牙利匹配和级联匹配分别解决什么问题DeepSORT 拆开看只有三件事预测、关联、更新。卡尔曼滤波负责预测每个目标在下一帧的位置哪怕这一帧检测器没输出这个目标它也能根据历史速度外推一个位置匈牙利匹配负责把检测框和已有轨迹做最优配对级联匹配是 DeepSORT 相对于 SORT 的核心改进它优先匹配那些最近刚出现过的轨迹而不是让很久没更新过的老轨迹抢占新检测框。理解级联匹配的工程意义很重要当一辆车被遮挡三秒后重新出现它的轨迹已经连续多帧没有匹配上检测框如果按照普通匹配策略新出现的检测框很容易被这条老轨迹占用而真正的新目标反而要重新分配 ID。级联匹配按轨迹最近一次匹配成功的帧号排序最近匹配过的先匹配老轨迹往后排这样就避免了 ID 被频繁抢占。3.2 DeepSORT 的配置文件与 tracker 实例化DeepSORT 在开源社区里最常用的实现是deep_sort_pytorch它把配置集中在deep_sort/deep_sort/configs/deep_sort.yaml中。改动最多的参数是下面几个DEEPSORT: MAX_DIST: 0.2 # 余弦距离阈值 MIN_CONFIDENCE: 0.5 # 检测置信度下限 NMS_MAX_OVERLAP: 0.7 # 检测框 NMS MAX_IOU_DISTANCE: 0.7 # 级联匹配中 IOU 距离阈值 MAX_AGE: 70 # 轨迹丢失后存活帧数 N_INIT: 3 # 连续多少帧匹配成功才确认轨迹 NN_BUDGET: 100 # 外观特征缓冲池大小MAX_DIST控制外观特征匹配的严格程度值越小要求越严能减少跨目标串 ID但被遮挡后重新出现的目标也更难找回MAX_AGE控制轨迹丢失后被保留的时间交通场景车流方向固定车辆重新出现位置可预测设 70 帧够用NN_BUDGET是留给每个轨迹的历史外观特征数量行人这类外观变化快的目标可以适当降到 50减少内存占用。在推理脚本中实例化跟踪器的常见写法是from deep_sort.deep_sort import DeepSort deepsort DeepSort( model_pathdeep_sort/deep/checkpoint/ckpt.t7, config_pathdeep_sort/deep_sort/configs/deep_sort.yaml )model_path指向的是 DeepSORT 用的 ReID 特征提取模型权重它把每个检测框截出来提取一个 512 维外观特征向量供后续余弦距离计算使用。这里有个容易踩的坑ReID 模型的输入尺寸和 YOLOv5 的检测框尺寸没有关系检测框会被自动裁剪缩放但太小的框比如远距离行人只有 20×40 像素提取出来的特征质量很差这也是为什么检测阶段的置信度阈值不能太低。3.3 把检测结果送入跟踪器的完整循环检测和跟踪在每一帧里的串联逻辑不复杂核心代码如下bbox_xywh [] confss [] for x1, y1, x2, y2, conf, cls in detections: obj [int((x1x2)/2), int((y1y2)/2), int(x2-x1), int(y2-y1)] bbox_xywh.append(obj) confss.append(conf) xywhs torch.Tensor(bbox_xywh) confss torch.Tensor(confss) outputs deepsort.update(xywhs, confss, frame)deepsort.update()接收中心点坐标格式的检测框和对应置信度返回的是[x1, y1, x2, y2, track_id, cls]格式的确认轨迹。注意检测框的坐标形式要转成中心点加宽高这是 DeepSORT 内部卡尔曼滤波器状态变量的格式要求。跟踪器内部会先过滤掉置信度低于MIN_CONFIDENCE的检测框再做 NMS然后分两步匹配第一步用外观特征算余弦距离矩阵做级联匹配第二步对未匹配上的检测框用 IOU 距离做匹配兜底。只有连续N_INIT帧都匹配上的轨迹才会被标记为 confirmed 并输出所以视频前几帧看不到 ID 是正常的。实践中有个细节每帧送入跟踪器之前最好把检测框坐标裁剪到画面边界内。DeepSORT 的卡尔曼滤波不会限制状态范围一旦检测框部分出画面预测的位置可能跑到负坐标IOU 计算会出问题表现为目标 ID 在画面边缘频繁跳变。4. 车辆行人计数从跟踪结果到统计输出的工程实现4.1 基于轨迹的计数逻辑先想清楚定义跟踪做完之后计数这个词要定义清楚。毕业设计里最常见的两个需求是区域计数和断面计数。区域计数是统计画面中某个区域如停车位、广场内当前有多少目标断面计数是统计目标穿过某条虚拟线如车道停止线的累计数量。两种计数逻辑差别很大混在一起会导致数据对不上。断面计数的核心是判断目标的运动方向。常见做法是定义一条有向线段用前后两帧目标中心点坐标计算其相对于线段的位置关系当位置关系发生变化时计一次数。这里的关键是变化判断而不是距离判断否则目标在线附近来回晃动会被重复计数。4.2 车辆行人计数代码虚拟线法和去重技巧下面这段代码实现了简单的虚拟线计数line_start (0, 400) line_end (1280, 400) direction 1 # 1 表示从上往下穿线计数 for track in outputs: x1, y1, x2, y2, track_id, cls track cx, cy (x1x2)/2, (y1y2)/2 # 计算点与线的叉积判断点在线的哪一侧 cross (line_end[0]-line_start[0])*(cy-line_start[1]) - \ (line_end[1]-line_start[1])*(cx-line_start[0]) if track_id not in last_pos: last_pos[track_id] (cx, cy, cross) continue _, _, prev_cross last_pos[track_id] if prev_cross * cross 0: if direction 1 and cross 0: vehicle_count 1 elif direction -1 and cross 0: vehicle_count 1 last_pos[track_id] (cx, cy, cross) else: last_pos[track_id] (cx, cy, cross)叉积正负号表示目标中心在线段的哪一侧从正变负或从负变正说明目标穿过了线段。prev_cross * cross 0这个条件可以过滤掉目标在线上反复横跳的情况。每个track_id只保留一个历史位置穿线后更新位置不删除 ID这样目标穿过线后停在现场不会重复计数。区域计数的思路类似只是把穿线判断换成目标中心点是否落在多边形区域内再按track_id去重。这段代码的缺陷是不处理目标从画面边缘进入、或者在穿线之前就丢失轨迹的极端情况但毕业设计场景下这个精度已经足够。如果视频分辨率高、车辆密集需要把计数逻辑从逐帧判断改成短轨迹拟合即对连续出现的中心点做线性拟合再判断拟合线与虚拟线的交点。4.3 把计数结果叠加到帧上并输出统计视频跟踪结果和计数数据要可视化输出到视频上通常的做法是用 OpenCV 叠加绘制每 30 帧或每 1 秒刷新一次统计面板cv2.putText(frame, fVehicles: {vehicle_count}, (20, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.line(frame, line_start, line_end, (0, 255, 255), 2) writer.write(frame)叠加绘制时要区分路面上原本的线条和虚拟线建议用亮黄色或品红色粗一点方便在论文截图里看清楚。输出视频用 cv2.VideoWriter编码格式选 mp4v帧率与原视频保持一致。注意写入的帧尺寸必须与 VideoWriter 初始化时设置的尺寸一致否则输出的视频会损坏。这里建议把计数统计放到一个独立线程里去写文件避免视频写入阻塞主循环导致跟踪变慢。4.4 导出 ONNX 或 TensorRT 模型提升推理速度如果毕设要求实时性或者视频分辨率是 1080p 甚至更高纯 PyTorch 推理在 CPU 上通常只有 5 到 10 FPS。合理做法是把 YOLOv5 导出成 ONNX再用 ONNX Runtime 推理速度能提升一倍左右。在 YOLOv5 目录下执行python export.py --weights runs/train/exp/weights/best.pt --include onnx导出后用 ONNX Runtime 替换原推理逻辑检测结果格式不变。如果想进一步提速可以导出 TensorRT 引擎依赖本机显卡型号生成 .engine 文件后再推理。注意导出 ONNX 时--dynamic参数不要随便开动态 batch 会拖慢推理性能固定 batch 1 就够用。整个检测加跟踪的 pipeline推理耗时主要在 YOLOv5 上DeepSORT 的 ReID 模型很小对 FPS 影响有限所以优化重心放在检测侧。python 环境这块建议训练和导出在 Linux 上进行推理端在 Windows 上用 ONNX Runtime 即可两个平台分别激活对应的 python 虚拟环境避免依赖冲突。5. 三个排错技巧ID Switch 看不清就加轨迹可视化第一个技巧是逐帧绘制检测框和轨迹 ID 时把所有未确认的轨迹也用不同颜色画出来。DeepSORT 内部有track.confirmed标志未确认的轨迹通常是被遮挡后刚恢复的目标。把这些轨迹画出来能立刻看出 ID Switch 发生在哪一帧。修改deep_sort源码中的draw_tracks函数用 CtrlF 搜confirmed把确认和未确认轨迹分色绘制这个调试收益非常高。第二个技巧是周期性输出轨迹的连续性统计。每处理完一段视频统计每个track_id首次出现和最后出现的帧号如果出现轨迹 A 消失的帧号 1 就是轨迹 B 出现的帧号这种模式说明极大概率发生了 ID Switch。定位到具体帧后导出该帧的检测框和轨迹状态看是检测漏检导致轨迹丢失还是 ReID 特征太相近导致两条轨迹匹配错误。第三个技巧是设置一个检测置信度与计数结果联动的开关。当某类目标的计数值出现跳变时自动把该目标重新分配到置信度高于 0.3 的检测框序列里再算一次轨迹。这个方式不改变 DeepSORT 内部逻辑只是在外层做后处理过滤对论文里的定量评估很有用。参数基线上白天正常光照、固定视角的交通场景推荐conf_thres0.45、MAX_DIST0.2、MAX_AGE50阴天或逆光场景把conf_thres降到 0.35MAX_DIST调到 0.3能让行人的漏检率明显下降夜间场景需要额外在检测侧做数据增强跟踪侧调参收益有限建议直接换带红外或补光的视频源。最后检查deepsort.yaml里NN_BUDGET是否为 100数值太大在长时间视频里会让轨迹特征库积累过多干扰特征导致匹配越来越乱。本文还有配套的精品资源点击获取