ARTICLE DETAIL

资讯详情

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

YOLOv5+DeepSORT人流量统计与轨迹追踪原理及实战解析

YOLOv5+DeepSORT人流量统计与轨迹追踪原理及实战解析 简介这是一份基于Python的YOLOv5-DeepSORT人群分析实战源码面向计算机视觉学习者、安防监控与零售运营等相关开发者解决实时视频流中的人流量统计、浏览轨迹跟踪与显示问题。项目结合YOLOv5目标检测和DeepSORT多目标跟踪清晰展示了视频预处理、目标检测、特征提取、数据关联、轨迹绘制等完整流程可用于商超客流分析、安防异常行为检测、交通行人管理与体育赛事分析等场景。压缩包共169个文件包含55个py核心源码、21个yaml配置、2个pt模型权重、3个mp4演示视频及其他说明文档整体约133.44MB覆盖依赖声明、模型配置和运行脚本便于直接复现和二次开发。已有1030人学习下载适合希望系统掌握深度学习目标检测与多目标跟踪落地流程的读者借助源码可快速梳理主线并扩展自定义功能。1. 为什么人流量统计要同时跑YOLOv5和DeepSORT而不是只数人头我在调试一个商场展厅的客流分析功能时发现只靠 YOLO 检测每一帧的人框数量来统计人流量数据完全没法用一个人在摄像头前多停几秒、绕一圈再走就会被重复计数好几次两个人前后交错时计数更是乱成一团。真正能落地的方案是让模型把“人”检测出来之后再交给一个跟踪模块分配全局唯一的 ID把同一时刻出现过的目标从第一帧到最后一帧串成一条轨迹。这就是这个项目把 YOLOv5 和 DeepSORT 放在一起的原因。YOLOv5 负责看清“这一帧里有哪些人”DeepSORT 负责追着这些人走输出连续 ID 和轨迹点最终既能得到总人次也能还原每个人在画面里的浏览路径。适合要做毕业设计、门店客流分析或安防行为识别的开发者也适合想学习检测与跟踪如何无缝配合的入门者。2. YOLOv5 与 DeepSORT 的协作逻辑检测给位置跟踪给 ID2.1 YOLOv5 检测头输出从特征图到人框YOLOv5 使用 CSPDarknet 作为骨干网络配合 PANet 做多尺度特征融合最后通过三个不同尺度的检测头输出。在 person 这个类别上它不只输出一个框而是输出 x1、y1、x2、y2、confidence、class 六个维度的张量。多尺度融合在这里很关键人流密集时很多人身体互相遮挡只露出头部或半身只靠高分辨率那一层很难稳定召回远处的小目标则需要浅层特征。YOLOv5 通过 80x80、40x40、20x20 三种特征图把不同尺寸的人体都覆盖到。在调用这个项目时你拿到的最终检测结果不是原图的绝对像素而是经过缩放和归一化后的坐标。以本项目常见的调用方式为例# 从 YOLOv5 的推理结果中取出人框并转成 DeepSORT 需要的格式 import numpy as np def filter_person_detections(results, conf_thres0.4): # results.xyxy[0] 每一行是 [x1, y1, x2, y2, conf, cls] boxes results.xyxy[0].cpu().numpy() # COCO 数据集中 person 类别的索引是 0 person_boxes boxes[boxes[:, 5] 0] person_boxes person_boxes[person_boxes[:, 4] conf_thres] # 前 4 列是坐标第 5 列是置信度类别列不再需要 return person_boxes[:, :5]这里类别 0 是 COCO 数据集定义的 person 类别。如果你训练过自己的数据集需要把这一行改成你自己的类别索引否则 DeepSORT 会拿其他类别的检测框去跟踪轨迹里就会混进背包、车等目标。传回的person_boxes[:, :5]只保留坐标和置信度因为 DeepSORT 的输入只需要位置信息类别信息对它没有意义。2.2 DeepSORT 的卡尔曼滤波与级联匹配DeepSORT 并不是简单地把相邻两帧的框做 IoU 匹配而是分成两条线索。一条用卡尔曼滤波器预测目标在当前帧的中心位置和宽高比另一条用 ReID 特征提取器计算当前帧检测框的外观特征。级联匹配会优先匹配距离更短、特征更相似的轨迹避免因为目标短暂遮挡导致 ID 跳变。# DeepSORT 更新一次的典型接口 from deep_sort_pytorch.deep_sort import DeepSort deepsort DeepSort( model_pathdeep_sort/deep/checkpoint/ckpt.t7, max_dist0.2, # 特征距离阈值越小越严格 max_iou_distance0.7, # 级联匹配中 IoU 距离上限 max_age70, # 目标消失后保留 track 的帧数 n_init3 # 必须连续匹配多少帧才确认新目标 ) # 输入需要 [cx, cy, w, h] 格式而非 xyxy outputs, _ deepsort.update(bbox_xywh, confs, ori_img) # outputs 是数组每行是 [x1, y1, x2, y2, track_id]上面的max_dist控制外观特征匹配的严格程度值越小越不允许同一个人的特征漂移适合人比较多的场景max_age控制在目标离开画面或被完全遮挡后track 还能活多久。如果设得过大一个人走出去很久后又走回来可能会拿到同一个 ID造成重复计数的假象设得太小遮挡几帧就会断掉。常见做法是先保持默认再根据现场视频里遮挡的严重程度调max_age。2.3 轨迹 ID 是人流量统计的最小单元如果只做检测每帧的框之间没有任何关联DeepSORT 输出的track_id是统计、画轨迹的唯一依据。一个人从画面右下角进入走到展台前停留五秒再左上角绕出去这一整个过程在代码里表现为同一个track_id对应一串中心点坐标。模块输出内容后续用途YOLOv5x1, y1, x2, y2, conf, cls过滤人框、裁剪、传给跟踪器DeepSORTx1, y1, x2, y2, track_id人流量计数、轨迹绘制、行为分析人流量统计的本质就是把这一串坐标映射到“进入”和“离开”两个事件上。所以实现时不要只看当前帧有多少个框而是去维护一个字典键是track_id值是该 ID 的历史中心点列表。每来一帧就追加一个点这个列表就是最后画轨迹的原料。3. 从源码包到可运行环境目录、依赖与权重文件3.1 解压后的目录结构项目解压后是典型的 YOLOv5 DeepSORT 工程布局根目录下的文件不算多但需要看清哪些是源码、哪些是打包残留。常见结构如下yolov5-deepsort/ ├── yolov5/ # YOLOv5 模型源码与权重目录 ├── deep_sort/ # DeepSORT 跟踪实现 ├── deep_sort_pytorch/ # PyTorch 版 ReID 特征提取 ├── track.py # 检测跟踪主入口 ├── train.jpg # 测试图用于快速验证 ├── README.md # 运行说明 └── easydict-1.10.dev0-py3.8.egg # easydict 安装包文件/目录作用track.py主程序读取视频逐帧检测加跟踪train.jpg快速验证模型是否加载成功的测试图deep_sort/deep/checkpoint/ckpt.t7ReID 模型权重用于行人重识别特征提取yolov5/weights/yolov5s.ptYOLOv5 检测权重默认使用 s 版本根目录里的easydict-1.10.dev0-py3.8.egg是 DeepSORT 依赖的 easydict 包的一个安装文件如果启动时报ModuleNotFoundError: No module named easydict直接指定该文件安装即可不需要去网络重新下载。MANIFEST.in和Dockerfile分别是打包与容器化用的本地调试一般用不到。3.2 创建虚拟环境并安装依赖我一般会先用虚拟环境把这套代码和系统 Python 隔离避免和已有的 PyTorch 版本冲突。注意 YOLOv5 的依赖和 DeepSORT 的依赖要一起装不要只装torch和opencv-python。cd yolov5-deepsort python -m venv venv source venv/bin/activate pip install -r yolov5/requirements.txt pip install easydict # 如果上面的 egg 文件存在也可以这样装 pip install ./easydict-1.10.dev0-py3.8.egg第一条命令创建虚拟环境第二条激活它。yolov5/requirements.txt里包含了 torch、opencv、seaborn 等基础依赖DeepSORT 部分的额外依赖通常是easydict和scipy所以再用pip install easydict补上。安装完成后不要在项目根目录下再创建一个同名deep_sort文件夹存放自己的代码容易覆盖依赖里的同名包。3.3 用 train.jpg 先跑通闭环环境配好后不要直接拿大视频测试先用项目自带的train.jpg验证检测到跟踪的整个链路是否通。命令行入口通常长这样python track.py \ --source train.jpg \ --yolo_weights yolov5s.pt \ --deep_sort_weights deep_sort/deep/checkpoint/ckpt.t7如果正常runs/track/exp下会生成一张画有检测框和轨迹点的图。这一步能在几秒内判断环境是否通避免直接拿大视频排错。注意--yolo_weights和--deep_sort_weights是两个独立的权重路径很多新手只改检测权重忘掉 DeepSORT 侧还需要单独的 ReID 权重然后发现跟踪 ID 总是跳变就是这一步没有配对。4. 人流统计与轨迹绘制的实战参数从一帧到一条完整轨迹4.1 检测后处理参数conf-thres 与 iou-thres 怎么设YOLOv5 的检测结果需要经过 NMS 后处理项目中对应参数是conf_thres和iou_thres。conf_thres是置信度阈值低于它的框会被丢掉iou_thres是 NMS 阶段用来消除重叠框的 IoU 阈值。人流密集时conf_thres不要设得过高否则远处的人和被遮挡一半的人会被丢掉导致跟踪轨迹中途断裂。参数取值范围场景建议conf_thres0.25 ~ 0.5人群密集场景建议 0.3稀疏场景可 0.4iou_thres0.3 ~ 0.7NMS 时建议 0.45 ~ 0.5过高会保留大量重叠框img_size640 / 320视频画面大但目标小保持 640追求速度可降到 320在代码里这两个值会直接影响 DeepSORT 拿到的框数量。置信度阈值过高会导致一个人只检测到半个身体跟踪器因为特征不足而迟迟无法确认新轨迹过低则会把背景噪声当成目标出现大量短暂 ID。建议先用默认值看效果再通过视频里人的远近缩放img_size而不是一味调置信度。4.2 DeepSORT 参数对轨迹连续性的影响DeepSORT 的核心参数集中在DeepSort构造函数里。下面这组参数是我在商场客流场景调过的版本deepsort DeepSort( model_pathdeep_sort/deep/checkpoint/ckpt.t7, max_dist0.2, # 外观特征余弦距离阈值 min_confidence0.3, # 检测置信度低于该值的框不参与跟踪 max_iou_distance0.7, # 卡尔曼预测框与检测框的 IoU 距离上限 max_age50, # track 在丢失目标后存活的最大帧数 n_init3, # 目标显示 3 帧且特征匹配成功才确认 ID nn_budget100 # 特征库大小控制内存占用 )max_dist越小越要求同一个人前后外观一致适合人穿相同颜色工作服的场景如果现场光照变化大人的外观特征会漂移应该适当调大到 0.3。max_age控制目标离开画面或被完全遮挡后track 还能活多久。设置过大一个人走出去很久又走回来会被当作同一个人造成重复计数假象设置过小遮挡几帧就会断掉。项目默认值通常是 50 到 70如果现场没有严重遮挡可以降到 30。4.3 人浏览统计的两种计数方案得到稳定的track_id后统计人流量直接从“第一帧到最后一帧”这个维度做而不是每帧人数求和。最常见的是中线交叉法在画面中画一条虚拟线当一个 track 的中心点从线的一侧移动到另一侧就记一次进入或离开。# 中线计数track_id - 最近一次中心点的 Y 坐标 line_y 400 in_count, out_count 0, 0 last_pos {} for track in outputs: x1, y1, x2, y2, track_id track cx, cy (x1 x2) / 2.0, (y1 y2) / 2.0 if track_id in last_pos: prev_y last_pos[track_id] if prev_y line_y and cy line_y: in_count 1 elif prev_y line_y and cy line_y: out_count 1 last_pos[track_id] cy这段代码用last_pos字典保存每个 ID 上一次的中心点 Y 坐标。只有当目标跨过line_y且是从上到下或从下到上才累加计数避免目标在线的附近来回小幅摆动造成重复计数。实际项目中我会把line_y换成一条线段去做向量叉积判断以支持倾斜的虚拟线还要给每个 track 加一个“已计数”标志防止同一个 ID 来回跨线被多次累加。另一种是 ROI 区域法当目标中心点进入指定多边形区域时记录进入时间离开时记录离开时间。这种方法更适合统计某个展台前停留了多久实现时用cv2.pointPolygonTest判断点是否在多边形内逻辑比中线法更贴近“浏览”这个概念。4.4 轨迹绘制坐标点管理与降噪轨迹显示的原始数据是所有track_id的历史中心点。绘制时不能把每一帧的点都直接连起来否则人在原地静止时轨迹线会堆成一团。我一般会先做降采样每 N 帧取一个点再用最近 M 个点连成短线防止旧轨迹干扰当前目标位置。import cv2 trajectories {} line_len 30 # 最多画多少个历史点 skip_frames 2 # 每隔几帧取一个点 for track in outputs: x1, y1, x2, y2, track_id track cx, cy int((x1 x2) / 2), int((y1 y2) / 2) if track_id not in trajectories: trajectories[track_id] [] trajectories[track_id].append((cx, cy)) trajectories[track_id] trajectories[track_id][-line_len:] # 每隔 skip_frames 取一个点避免线下密集重影 pts trajectories[track_id][::skip_frames] if len(pts) 2: for i in range(1, len(pts)): cv2.line(frame, pts[i - 1], pts[i], (0, 255, 0), 2)line_len控制轨迹的显示长度25 到 40 比较合适太长的话目标早已离开的区域还会留着旧线。skip_frames的作用是把密集的点抽稀让画出来的轨迹更接近真实移动方向。这段代码直接放在track.py的推理循环里每帧都会更新trajectories字典并把轨迹线画到当前帧上。注意这里用的是中心点而不是头部点因为检测框的底部往往被遮挡中心点稳定得多。5. 验证与提速技巧把自己的视频跑出稳定帧率5.1 先图片后视频再固定首帧基准视频调试前一定要先用静态图片确认检测和跟踪能同时输出。图片没有时间维度DeepSORT 只能输出一个未确认的 track但至少能看出检测框是否正常。确认后再用一段 10 秒左右的短视频跑完整流程把首帧的检测结果与手动标记的人数对比作为每次改参数后的基准。python track.py \ --source shop.mp4 \ --yolo_weights yolov5s.pt \ --deep_sort_weights deep_sort/deep/checkpoint/ckpt.t7 \ --conf_thres 0.35 \ --iou_thres 0.45 \ --save-txt--save-txt会输出每帧目标的位置和 track_id方便你统计共有多少个 ID 出现过。用这个数据和实际视频里的人对比能快速判断计数是否偏多或偏少。5.2 CPU 上提速的三招这套代码在 CPU 上跑 1080p 视频通常只有个位数帧率。常见处理是三个组合拳缩小推理尺寸、抽帧处理、把结果缓存起来。# 推理尺寸降到 320x320帧率通常能提升一倍以上 results model(frame, size320) # 每隔 2 帧做一次检测跟踪中间帧用上一帧的坐标 frame_idx 0 for frame in video_stream: if frame_idx % 3 0: outputs deepsort_update(frame) last_outputs outputs else: outputs last_outputs frame_idx 1降尺寸到 320 对密集型人群检测精度有一定损失但在 CPU 场景下比调conf_thres更有效。抽帧则会让轨迹看起来略有跳变这时要把轨迹绘制也放在跳变帧上不要用旧坐标去猜新坐标。5.3 把人流量统计结果导出 CSV最后的统计结果不要只停留在画面上的数字建议每隔一段时间把每个 track 的进入时间、离开时间、中心点采样写入 CSV方便后续用 pandas 做小时级客流分析。导出时注意把帧号换算成实际时间否则后续无法对齐监控录像。import csv with open(flow.csv, w, newline) as f: writer csv.writer(f) writer.writerow([track_id, frame, x, y]) for track_id, pts in trajectories.items(): for frame_idx, (x, y) in enumerate(pts): writer.writerow([track_id, frame_idx, x, y])这里的frame_idx是当前轨迹点所在的帧索引你可以在track.py里用一个全局变量把帧号传进来。CSV 里同时保存x, y而不是只存轨迹线是为了后续能重新绘制或计算每个目标的移动距离这也是把检测、跟踪和统计分析分离之后最直接的好处。本文还有配套的精品资源点击获取
返回列表