☰
YOLOv5+DeepSORT多目标跟踪与车辆行人计数实战指南
2026/10/2 6:07:57 网站建设 项目流程

简介:面向毕业设计、课程设计及目标检测与多目标跟踪入门人群,这份资源基于YOLOv5与DeepSORT实现车辆、行人的实时检测、追踪与计数,部署门槛低,可直接用于项目演示。项目提供完整Python源码、预训练权重及检测、跟踪、工具三大模块,代码注释详细,新手也能看懂;整体功能完善,经调试可稳定运行,适合作为毕设或期末大作业的高分参考方案。压缩包共78个文件,以Python脚本为主,另有YAML模型配置、Shell运行脚本、依赖清单、Dockerfile、说明文档及测试视频等,覆盖模型推理、跟踪配置、环境还原与效果验证各环节,整体大小约82.69MB。资源结构清晰,核心逻辑包含检测器、跟踪器与主控脚本,并配有独立的目标关联子模块,便于按模块学习。目前已有246人学习下载,适合需要完整方案模板、快速搭建并展示追踪计数效果的高校学生。结合详细使用说明,可系统理解YOLOv5检测、DeepSORT关联及计数思路,是一份工程完整、上手友好的高分项目资料。

1. 为什么毕设题目里总是不约而同出现 YOLOv5 和 Deepsort

车辆和行人的追踪计数,几乎是计算机视觉方向毕业设计里最稳的题之一。这个项目标题的核心组合是 YOLOv5 负责检测,Deepsort 负责追踪帧间的身份,两者串起来后你就能在一段视频上看到每个行人和车辆被框出来、带着一个不变的编号从头走到尾。很多毕设选这个方向,是因为它有成熟的公开代码、清晰的两阶段结构、以及能拿数据说话的实验空间。不论是你打算直接跑通一份源码去复现,还是想把它改成自己的想法,以下内容会按环境搭建、代码拆解、训练替换、排错避坑这条路径讲下来,让你少走弯路、能落地。

2. 先把环境立住:conda 建环境、装依赖、跑通官方 demo

一份基于 YOLOv5 和 Deepsort 的毕设源码,通常包含两套依赖:YOLOv5 官方的 requirements.txt,以及 deep_sort_pytorch 的依赖文件。两套依赖直接合并没有想象中顺利,最常见的结果是 sklearn 版本冲突、numpy 版本冲突,在 import 阶段就报错。所以这一章从选型理由讲起,再给出一个实测比较稳的装法,最后告诉你怎么验证两套模型都加载正常。

2.1 为什么这对组合能在毕设里站稳脚

YOLOv5 是单阶段目标检测器,DeepSORT 是多目标跟踪算法。两者配合的逻辑很清楚:检测器回答“这一帧里有哪些目标、每个目标在哪”,追踪器回答“连续帧之间哪个框属于同一个身份”。对车辆行人追踪来说,这个 tracking-by-detection 的范式在实时性和精度之间能取到不错的平衡,这也是它在高校项目里被反复使用的原因。

有人会问:ByteTrack 不也做得很好吗,为什么毕设题目固定写 Deepsort?原因不是哪个更强,而是 Deepsort 的模块化更清晰,卡尔曼滤波、级联匹配、表观特征 embedding 三个组件都能单独展开写几千字,天然适合论文的第二章和第四章。对毕设而言,这是更稳妥的选择。

从工程复现的角度看,YOLOv5 的代码质量在开源检测器里算是很高的,文档全、模型文件好找。Deepsort 社区里做得比较完整的实现是 deep_sort_pytorch,它提供了一个在行人重识别数据上预训练好的 ReID 权重,拿来就能跑。车辆类的数据集要是有,也可以自己重新训练 ReID 模型来替换。

2.2 conda 环境创建与依赖安装:两个 requirements 怎么不打架

先说结论:不要想着把两份 requirements.txt 直接合并安装。一份是 PyTorch 生态,一份是 sklearn/scipy 生态,装完很容易把某个包升级到不兼容的版本。更稳的办法是分层装:

conda create -n traffic python=3.8 conda activate traffic # 按你机器 CUDA 版本选择 PyTorch,这里以 CUDA 11.3 为例 conda install -c pytorch pytorch=1.12.1 torchvision=0.13.1 cudatoolkit=11.3 # 装 YOLOv5 的核心依赖 pip install opencv-python numpy matplotlib pyyaml tqdm requests pillow # 装 deep_sort_pytorch 需要的追踪依赖 pip install scikit-learn==0.23.2 scipy==1.7.3 lap

参数说明:

  • python=3.8 兼容性最好。Python 3.10 之后部分旧版 PyTorch 安装失败,而 3.7 又太老,很多新库开始不支持。
  • 把 scikit-learn 固定到 0.23.2 是有意为之。deepsort_pytorch 在旧版本里使用sklearn.utils.linear_assignment_里的 linear_assignment 函数,这个函数在 sklearn 0.24 中被移除。如果不锁版本,装完以后 import 直接报 “cannot import name 'linear_assignment'”。
  • lap 是一个线性分配库,Deepsort 做帧间匹配时会用到匈牙利算法的加速实现,CPU 上跑也很快。

装完后做一次快速自检,确认关键 import 没问题:

python - <<'EOF' import torch, numpy, cv2, sklearn from sklearn.utils import linear_assignment_ print("torch:", torch.__version__) print("sklearn:", sklearn.__version__) print("linear_assignment available") EOF

说明:如果你的源码用的是较新版本的 deep_sort_pytorch,可能已经不再 import linear_assignment,而是使用 scipy.optimize.linear_sum_assignment。这种情况下不需要降 sklearn,自检命令可以改成只打印版本号。先 grep 一下源码里的 import 语句再决定装哪个版本。

2.3 项目目录结构与关键文件说明

拿到一份“项目源码+详细使用说明”之后,先不要急着运行,花五分钟把目录结构看清楚。典型布局长这样:

traffic_counter/ ├─ main.py # 程序入口,读视频并逐帧处理 ├─ yolov5/ │ ├─ detect.py │ ├─ models/ # YOLOv5 模型定义 │ ├─ data/ # 默认数据集配置 │ └─ weights/ │ └─ yolov5s.pt # 检测权重 ├─ deep_sort/ │ ├─ deep_sort/ │ │ ├─ deep_sort.py # 追踪器封装 │ │ ├─ track.py # Track 对象定义 │ │ ├─ nn_matching.py # 级联匹配逻辑 │ │ └─ deep/ │ │ └─ checkpoint/ │ │ └─ ckpt.t7 # ReID 特征提取权重 ├─ utils/ # draw、IO、计数的辅助函数 └─ requirements.txt

关键点如下:

  • yolov5s.pt 和 ckpt.t7 通常不会随源码包一起提供,需要手动下载。很多毕设代码里路径写的是相对路径,意味着文件必须放在上表指定位置,否则 FileNotFoundError 直接拦在开头。
  • main.py 是入口。你之后改输入视频、改检测置信度、改计数线时,主要就是改这一个文件。
  • utils 目录下的 draw.py / counter.py 每个项目都不太一样。有些作者会把计数逻辑直接塞进 main.py,但从工程上讲拆开更合理。

2.4 用官方 demo 验证两套模型都能加载

环境建好、文件放好后,先跑两个最小验证。第一个是 YOLOv5 自带的图片检测:

python detect.py --source data/images/bus.jpg --weights yolov5s.pt --conf 0.4 --device 0

正常现象是终端打印识别出的类别和置信度,并输出一张画了检测框的 bus.jpg。如果 CUDA 不可用,把 --device 0 改成 --device cpu,先确认逻辑没问题。

第二个是验证 Deepsort 能加载:

python - <<'EOF' from deep_sort import DeepSort tracker = DeepSort("deep_sort/ckpt.t7", max_dist=0.2, min_confidence=0.3, max_iou_distance=0.7, max_age=70, n_init=3) print("tracker ok") EOF

这段代码做了什么:创建了一个 DeepSort 实例,指定 ReID 权重路径和五个关键参数。只要最后打印 “tracker ok”,说明 Deepsort 模块和依赖都没有问题。

这两个验证都通过,后面调代码就不会再把时间浪费在环境排查上了。

3. 核心代码拆解:检测、追踪、计数怎么串成一条流水线

跑通 demo 之后,真正的目标是读懂主循环在干什么。大部分基于 YOLOv5+Deepsort 的毕设源码,主流程就是三件事:YOLOv5 做目标检测,Deepsort 做多目标追踪,再根据 track_id 做计数。这里按模块拆开讲,最后给出整合后的主循环。

3.1 把 YOLOv5 包装成检测函数:返回框、置信度和类别

项目里一般不会直接调 detect.py,而是把 YOLOv5 包装成一个类,在视频循环里逐帧调用。常见写法:

import torch import numpy as np class YOLODetector: def __init__(self, weights="weights/yolov5s.pt", device="cuda", conf=0.4, img_size=640): self.model = torch.hub.load("yolov5", "custom", path=weights, source="local") self.model.conf = conf self.model.classes = [0, 2, 5, 7] # person, car, bus, truck self.model.to(device) self.device = device self.img_size = img_size def detect(self, frame): # frame 是 OpenCV 读出的 BGR 图 results = self.model(frame, size=self.img_size) dets = results.xyxy[0].cpu().numpy() # 每行: [x1, y1, x2, y2, conf, cls] return dets

代码说明:

  • torch.hub.load 里 source="local" 是告诉 hub 直接加载本地仓库,不联网拉配置,速度快也更稳,在离线环境的毕设演示现场尤其重要。
  • self.model.classes = [0, 2, 5, 7] 表示只保留 COCO 类别里的 person、car、bus、truck。如果是纯行人数统计,改成 [0] 就行。
  • results.xyxy[0] 是一个 n×6 的张量,n 是这一帧的检测框数量,6 表示 x1、y1、x2、y2、confidence、class id。后面喂给 Deepsort 的正是这个矩阵。

参数说明:

  • conf=0.4 对车辆行人场景是一个比较折中的阈值。太高会把远处的小目标丢掉,太低又会引入大量误检,造成 ID 频繁切换。
  • img_size=640 是模型的输入边长。如果你处理的是 1080p 视频,帧不会直接送进去,内部会做等比缩放。往大了设会提高小目标召回,但推理时间同步上升。

3.2 Deepsort 追踪:update 流程与五个关键参数

Deepsort 的核心机制可以压缩成三句话:先用卡尔曼滤波预测每条轨迹在下一帧的位置,然后用 IoU 做一次粗匹配,最后用 ReID 特征做级联匹配,决定哪些检测框该继承旧 ID、哪些该开新 ID。理解这个流程以后,你会明白一个关键结论:检测器的质量决定 Deepsort 的上限,Deepsort 只是在下限上做补偿。

实际用起来并不复杂,每帧把检测结果交给 tracker.update:

from deep_sort import DeepSort class TrackProcessor: def __init__(self, model_path="deep_sort/ckpt.t7"): self.deepsort = DeepSort( model_path, max_dist=0.2, min_confidence=0.3, max_iou_distance=0.7, max_age=70, n_init=3, nn_budget=100, ) def update(self, detections): # detections: n×6 [x1, y1, x2, y2, conf, cls] tracks = self.deepsort.update(detections) return tracks # 每个 track 对象含 bbox 与 track_id

这五个参数是调追踪效果的入口,值得记一笔:

参数建议值作用与调整方向
max_dist0.2ReID 特征的最大余弦距离。人流密集场景适当降到 0.15,避免跨目标匹配;车流稀疏时升到 0.3 提高找回率
min_confidence0.3进入追踪器的检测置信度下限,低于这个值的框直接丢弃
max_iou_distance0.7两帧匹配时 IoU 的容差上限。运动速度特别快的场景建议调小到 0.5,防止错配
max_age70目标连续丢失 70 帧后轨迹才删除。行人遮挡频繁建议加到 100,但要警惕低质量轨迹占着 ID
n_init3新轨迹要连续匹配 3 帧才被确认输出。太大会漏掉短暂出现的目标,太小会让瞬时误检也变成一条轨迹

调参经验是:优先调 max_age 和 min_confidence,因为这两个对计数的影响最直接;max_dist 和 nn_budget 多数时候保持默认就行,改过头反而会丢掉原本正确的匹配。

3.3 计数逻辑:从轨迹 ID 到虚拟线过线判定

计数最常见的方式是虚拟线计数。在画面里定义一条直线,每次目标中心点跨过这条线,就认为完成了一次穿越。为了防止同一辆车被重复计数,核心技巧是按 track_id 做去重。

class LineCounter: def __init__(self, line_start=(0, 500), line_end=(1280, 500)): self.line_start = line_start self.line_end = line_end self.line_y = line_start[1] # 这里以水平线为例 self.previous_centers = {} # track_id -> (cx, cy) self.count_up = 0 # 从下往上 self.count_down = 0 # 从上往下 def update(self, tracks): current_centers = {} for trk in tracks: x1, y1, x2, y2 = trk.get_bbox() cx = (x1 + x2) / 2 cy = (y1 + y2) / 2 current_centers[trk.track_id] = (cx, cy) if trk.track_id in self.previous_centers: prev_cy = self.previous_centers[trk.track_id][1] if prev_cy < self.line_y and cy >= self.line_y: self.count_down += 1 elif prev_cy > self.line_y and cy <= self.line_y: self.count_up += 1 self.previous_centers = current_centers

代码说明:

  • 用字典记录每个 ID 上一帧的中心点坐标,而不是假设相邻两帧的目标一一对应。这个设计在面对漏检和 ID 丢失时依然能保持一定的稳定性。
  • 判断穿越用的是“上一帧在线上方、这一帧在下方”的条件,因此天然不会重复计数。如果一个目标停在线上抖动,除非中心真实跨过线,否则计数不会增加。
  • 双向计数拆成了 count_up 和 count_down,传入方向和传出方向分开统计。如果是商场客流,这两个值可以对应进店和出店。

3.4 主循环整合与可视化绘制

三个模块都准备好之后,把它们串进视频循环。主循环的骨架:

import cv2 def main(video_path): cap = cv2.VideoCapture(video_path) det = YOLODetector() tracker = TrackProcessor() counter = LineCounter() while True: ret, frame = cap.read() if not ret: break detections = det.detect(frame) tracks = tracker.update(detections) counter.update(tracks) frame = draw_results(frame, tracks, counter) cv2.imshow("traffic_counter", frame) if cv2.waitKey(1) & 0xFF == 27: # ESC 退出 break cap.release() cv2.destroyAllWindows()

画框和画计数的辅助函数建议单独放一个 draw.py 里:

def draw_results(frame, tracks, counter): for trk in tracks: x1, y1, x2, y2 = map(int, trk.get_bbox()) cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, f"ID:{trk.track_id}", (x1, y1 - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.line(frame, counter.line_start, counter.line_end, (0, 0, 255), 2) cv2.putText(frame, f"up: {counter.count_up} down: {counter.count_down}", (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 255), 2) return frame

这段绘制代码还解决了一个常见的显示问题:直接读视频流时图片很大,窗口放不下。建议在 imshow 前加一句frame = cv2.resize(frame, (960, 540)),这样演示时窗口更友好,也不会卡渲染。

主循环跑通后,你手上就有一个可以输出计数结果的系统了。接下来要做的,是把检测器换成自己训练的模型,让论文里的实验部分站得住脚。

4. 训练自己的车辆行人数据集:从标注、超参数到权重替换

如果这个毕设从头到尾都在用别人训练好的权重,答辩时很容易被问住。更稳的做法是自己准备一小批车辆行人数据,用 YOLOv5 训练一个自定义模型,再把权重替换回主流程。这一章从数据标注讲起,到训练命令、超参数调整,最后回到替代验证。

4.1 数据准备与 YOLO 格式标注转换

YOLOv5 训练数据的标准格式是:每张图片对应一个同名 txt,txt 的每一行是“类别 中心点x 中心点y 宽度 高度”,所有坐标归一化到 0~1。标注工作最常用的工具是 LabelImg 或 Labelme,导出 YOLO 格式。

点击图片手动框完后,LabelImg 可以直接保存到 YOLO 格式的 txt 文件。Labelme 保存的是 json,需要转换成 YOLO 格式。转换逻辑不复杂,核心就是把 json 里的多边形包络框转成归一化的 bbox:

import json import os def labelme_to_yolo(json_path, output_dir, image_width, image_height): with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) lines = [] for shape in data['shapes']: label = shape['label'] # 如 'person' / 'car' points = shape['points'] # [左上, 右上, 右下, 左下] xs = [p[0] for p in points] ys = [p[1] for p in points] x_min, x_max = min(xs), max(xs) y_min, y_max = min(ys), max(ys) cls_id = {'person': 0, 'car': 1, 'bus': 2, 'truck': 3}[label] x_center = ((x_min + x_max) / 2) / image_width y_center = ((y_min + y_max) / 2) / image_height w = (x_max - x_min) / image_width h = (y_max - y_min) / image_height lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") out_name = os.path.splitext(os.path.basename(json_path))[0] + '.txt' with open(os.path.join(output_dir, out_name), 'w') as f: f.write('\n'.join(lines))

参数说明:

  • 这个脚本假设你已经知道图片的原始宽高。如果 json 里存了 imageWidth 和 imageHeight 字段,直接读出来更稳妥。
  • 类别映射要和你之后数据集 yaml 里的 names 顺序保持一致,否则训练时会张冠李戴。
  • 转换后一定要抽查几张 txt 的内容,并写个回读画框的可视化脚本,看看标注框是否贴合目标。坐标归一化错误或者标注框超出图像边界,在训练时很难发现,但会直接影响 mAP。

4.2 数据集配置、训练超参数选择与训练命令

数据准备好后,先写一个数据集配置文件,告诉 YOLOv5 训练集、验证集放在哪,有多少类别:

# data/traffic.yaml train: data/traffic/train/images val: data/traffic/val/images nc: 4 names: ['person', 'car', 'bus', 'truck']

然后开始训练。毕设级别的数据量(几百到一千张图)不需要在模型结构上动刀,直接让训练脚本跑:

python train.py \ --data data/traffic.yaml \ --weights yolov5s.pt \ --epochs 200 \ --batch-size 16 \ --imgsz 640 \ --device 0 \ --patience 40 \ --hyp data/hyps/hyp.scratch-low.yaml

参数说明:

  • 使用 yolov5s.pt 做预训练权重开始,而不是用空权重从头训练。几百张图的数据量从头训练很难收敛,微调是唯一合理策略。
  • epochs=200 是折中值。官方默认 300,但数据量不大时 150~200 轮就够了,配合 patience=40 做早停,防止后期过拟合。
  • batch-size 16 大约需要 6G 以上显存。显存不够就减到 8,或者把 imgsz 降到 416。后一种做法对小目标检测精度影响很大,不建议盲目降低。
  • hyp.scratch-low.yaml 是稳定版本超参数,里面定了 lr0=0.01、momentum=0.937、weight_decay=0.0005 等基础值。数据量不大时不要随意调大 learning rate,否则前几个 epoch 损失就崩了。

训练日志和权重输出在 runs/train/exp 目录下。训练过程中可以用 TensorBoard 盯着看:

tensorboard --logdir runs/train

关注两个曲线:loss 曲线和验证集的 mAP 曲线。如果 mAP 在前 50 轮就基本平坦了,说明数据太简单或学习率衰减太快,可以考虑调低 lr0 或检查数据集的标注质量。

4.3 训练后验证与权重替换

训练结束后,runs/train/exp/weights/ 下会有 best.pt 和 last.pt。习惯是先跑一组之前的测试图,确认检测能力,再上视频。命令:

python detect.py \ --source data/traffic/val/images \ --weights runs/train/exp/weights/best.pt \ --conf 0.4 \ --device 0

确认输出图片里车、人、bus、truck 的框都正常后,把检测器的权重路径换成这个 best.pt:

det = YOLODetector(weights="runs/train/exp/weights/best.pt", conf=0.4)

这里有一个经常踩的坑:替换权重后,原来的类别 id 映射可能变了。举个例子,你用自定义数据集把 person 定义成了类别 0、car 定义成类别 1,那 Deepsort 一侧也要相应调整,之前代码里按 cls==2 判断车辆的逻辑就得同步改掉。检测端和追踪端的类别 id 必须始终对应同一套定义。

5. 避坑/常见问题排查:5 个让毕设翻车的典型问题

在多次接触这类毕设项目后,我整理出五个出现频率最高的坑,每个都按“现象 → 原因 → 解决”展开。对照你自己的运行结果,能省下大量排错时间。

5.1 检测框闪断、ID 跳变:先调 max_age 而不是先改模型

现象:视频里同一个行人,上一帧是 ID 12,下一帧变成 ID 37,画面上数字乱跳,人在遮阳伞下走一圈 ID 换了七八个。

原因:目标被短暂遮挡或检测器漏检的瞬间,Deepsort 的轨迹匹配失败,旧轨迹被判定丢失,新检测重新创建了一条轨迹,ID 就变了。

解决:先把 Deepsort 的 max_age 从默认值调到 70 以上。max_age 的含义是“目标连续多少帧没匹配上也可以保留轨迹”,取 70 就能覆盖大多数遮挡场景。同时把检测器 conf 提到 0.35~0.4,拦截那些置信度 0.2 左右的低级误检。最后看一下帧率是否满足 15FPS,如果太低,追踪的帧间位移会变得不稳定,ID 跳变会更频繁。

使用 n_init=3 也有帮助,它保证一条轨迹要连续确认 3 帧才输出,短暂出现的误检框不会被当成正经目标。要注意的是,n_init 调得太大,比如 5 或 10,快速穿过画面的目标反而不会被计数。

5.2 重复计数:Set 去重是唯一正确的解法吗

现象:一辆车穿过虚拟线,计数器加了三四次;一个行人站在线边缘来回移动,计数结果像“抽风一样跳”。

原因:重复计数的根因通常有两个。一是计数逻辑没有按 track_id 去重,同一个目标反复穿过判定;二是 Deepsort 本身给同一辆车分配了新 ID——目标被遮挡或检测中断后轨迹重建,ID 变了,新 ID 又一次穿越了同一根线。

解决:计数逻辑上,用集合保存“已经计过数的 ID”,一个 ID 只允许计数一次。用 set 收集即可:

if track_id not in self.counted_ids: self.counted_ids.add(track_id) if cls == 2: self.vehicle_count += 1

如果是 Deepsort 层面丢了 ID,上述办法解决不了。你需要先确认自己的 ID 变化到底发生在哪一步。简单做法:把每帧的 track_id 和中心点坐标输出到 CSV,对同一车辆观察 ID 是否从 5 变成 21。如果是,调大 max_age,并检查检测器是否在目标越过线时漏检——漏检是 ID 重建的最直接原因。理想情况下,ID 应在目标穿过线之前保持足够稳定,计数才会可靠。

5.3 deep_sort 的 ImportError:sklearn 版本兼容性

现象:安装所有依赖后,import deep_sort 报错,提示 cannot import name 'linear_assignment',或者在 from sklearn.utils import linear_assignment_ 这一行直接 ModuleNotFoundError。

原因:deepsort_pytorch 老版本依赖 sklearn.utils.linear_assignment_,该接口在 sklearn 0.24 被移除。很多毕设 requirements.txt 没有锁定 sklearn 版本,pip 默认装上最新版,导致运行失败。

解决:把环境里的 scikit-learn 降到 0.23.2。如果 numpy 版本太新导致冲突,把 numpy 固定到 1.21.6。装完后重新执行:

python - <<'EOF' from sklearn.utils.linear_assignment_ import linear_assignment print("sklearn ok") EOF

有些较新分支的 deep_sort_pytorch 已经改用 scipy.optimize.linear_sum_assignment,这种不需要降版本。先 grep 一下你的源码里实际用的是哪个 import,再决定怎么处理,不要盲目照抄命令。

5.4 权重文件缺失:yolov5s.pt 和 ckpt.t7 放哪里

现象:运行 main.py 时没有提示错误类别,直接卡在 torch.hub.load 或者 FileNotFoundError: No such file or directory。

原因:项目和源码包通常不包含权重文件。yolov5s.pt 是检测器权重,ckpt.t7 是 ReID 特征提取权重。作者会在使用说明里提示去下载,但很多人直接跳过说明跑代码。

解决:yolov5s.pt 放到 yolov5/weights/ 或项目根目录的 weights/ 下,取决于你的代码里 path 怎么写。ckpt.t7 放到 deep_sort/deep_sort/deep/checkpoint/ 下,这是 deep_sort_pytorch 仓库约定的默认位置。放完以后先检查路径大小写是否和源码一致;Windows 下路径大小写不敏感,Linux 下不一致就是 FileNotFoundError。

5.5 推理速度慢与显存不足:优化顺序

现象:视频播放明显卡顿,FPS 只有 8~12;跑一段时间后报 CUDA out of memory 并中断。

原因:最常见的情况是直接把 1080p 帧送进 YOLOv5,又没有对输入做缩放;同时 Deepsort 的 ReID 网络也在 GPU 上占显存。另一个隐藏问题是 nn_budget=1000 之类的设置会让匹配阶段计算量暴涨,帧率随即下降。

解决:按性价比从高到低依次做三件事。第一,把视频帧缩放后再送进检测器:

frame = cv2.resize(frame, (640, 640))

第二,把 Deepsort 的 nn_budget 降到 100,max_dist 设为 0.2,减少特征库规模和匹配时间。第三,如果还是不够快,用 TensorRT 对 YOLOv5 做加速,效果一般是 PyTorch 推理的 2~3 倍。

显存不足的应急手段是在主循环里主动清缓存:

torch.cuda.empty_cache()

但这只是缓解,别把它当常规手段。真正的解法是控制输入分辨率和 batch size。

6. 验证与进阶:让答辩老师看到你的实验闭环

毕设答辩被问“效果怎么样”时,不能只说“跑起来感觉还行”。至少要准备两个东西:定量的追踪计数精度,以及一组对比实验。

定量验证方面,自己找一个 3~5 分钟的交通路口视频,手动数一遍真实穿线人数和车辆数,作为 ground truth。然后跑你的系统,算出 precision 和 recall。precision 是系统统计的通过目标里有多少真实目标,recall 是真实目标里系统统计出了多少。注意把 Deepsort 的 ID 切换次数也统计出来,这是追踪模块最直观的指标。

对比实验方面,最省事的一组是把 Deepsort 换成纯 IoU 匹配(网上有现成实现),其他代码不动,对比两者的计数准确度。只要数据不是特别理想化,Deepsort 通常会有明显的精度优势,这张对比表直接能支撑论文里的一个实验小节。如果资源允许,再录一段系统运行录屏,卡在 ID 稳定穿过虚拟线的时刻,答辩时现场放给老师看。

进阶方向上,如果你还有时间,我推荐做以下三件事之一:第一,双向计数,把代码里 count_up 和 count_down 两个方向的统计输出到一个小面板;第二,热度图,把每个目标中心点累积到一副二维直方图,可以用 OpenCV 的 applyColorMap 上色,直观展示密集区域;第三,多线计数,在画面里画多条虚拟线,统计不同区域之间的穿行量,这个方向更贴近商业应用场景。

我个人的习惯是把训练时的那一套超参数变化曲线和追踪的 ID 切换曲线都截图保存下来,论文的实验章节基本就有着落了。最后再提醒一句:检测器的 conf 阈值尽量在整个演示过程中保持不变,不要为了追求效果在答辩现场临时改数字,否则结果和论文对不上,反而显得实验不可复现。希望这些方案和排错经验能帮到你,也祝你的毕设顺利通过。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询