ARTICLE DETAIL

资讯详情

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

YOLOv5+DeepSORT车辆行人追踪计数实战:从环境配置到防重复计数

YOLOv5+DeepSORT车辆行人追踪计数实战:从环境配置到防重复计数 简介这份资源面向计算机视觉初学者与进阶开发者聚焦车辆与行人追踪计数这一典型落地场景提供基于YOLOv5与DeepSORT的完整项目实践代码。YOLOv5负责实时目标检测输出带置信度与分类标签的边界框DeepSORT则借助卡尔曼滤波与匈牙利算法完成轨迹匹配即使目标短暂遮挡或视角变化也能保持连续追踪二者结合可构建智能监控与客流统计系统。压缩包共103个文件约79.93MB以42个py源码与51个pyc编译文件为主另含yaml配置、md说明、pt预训练权重及t7模型文件覆盖模型推理、跟踪逻辑、参数配置与辅助工具等模块。目前已有3289人学习下载。读者可据此理解检测与跟踪的衔接方式参考Detector类的封装思路并基于预训练权重快速跑通车辆行人计数流程为二次开发与场景调优提供可复用的工程模板。1. 从一段路口监控说起YOLOv5DeepSORT 到底在数什么路口摄像头架好画面里车流不断。你要的不是一张张标注框而是「今天下午三点到四点这个方向过了多少辆车、多少个人」。这件事拆开看是两件事先得在每一帧里把车和人找出来再得把跨帧的同一个目标串成一条轨迹最后在轨迹穿过一条虚拟线的瞬间加一。YOLOv5 负责前者DeepSORT 负责后者计数逻辑是你自己写的那几十行状态机。很多人第一次做这个项目卡住的地方不是模型跑不起来而是数出来的数字对不上一辆车被数了三次或者两辆车贴在一起只算了一辆。这不是模型精度问题是追踪 ID 跳变和计数线判定逻辑的问题。这篇笔记按「先跑通、再调准、最后防重复」的顺序把 YOLOv5DeepSORT 做车辆行人追踪和计数的完整路径讲清楚包括环境配置、参数怎么设、重复计数怎么修、以及树莓派这类边缘设备上部署时要注意什么。适合已经会跑 YOLOv5 推理、想把它变成可用计数系统的同学。2. 环境配置与最小可跑通链路conda 建环境到第一帧带 ID 的输出2.1 为什么选 YOLOv5DeepSORT 这套组合做车辆行人追踪计数检测器可以换追踪器也可以换但 YOLOv5DeepSORT 是目前资料最全、复现成本最低的组合。YOLOv5 的优势不在精度天花板而在于工程化程度高权重文件小、推理速度快、训练自己数据集的流程成熟yolov5s.pt在普通显卡上就能跑到实时。DeepSORT 的优势在于它把外观特征ReID 向量和运动信息卡尔曼滤波结合起来做匹配比纯 IOU 匹配的 SORT 更抗遮挡——车辆互相遮挡两三帧后重新出现DeepSORT 大概率还能把它认回原来的 ID。选型上有一个容易忽略的点DeepSORT 的 ReID 模型是独立于检测器的。也就是说你换 YOLOv5 的版本、换自己训练的权重DeepSORT 这边不用动。这个解耦对落地很友好检测器可以按场景迭代追踪器保持稳定。常见做法是直接用 ultralytics 的 YOLOv5 仓库加一个 DeepSORT 实现。我一般会先把检测和追踪分开验证先确认 YOLOv5 单帧检测没问题再接 DeepSORT 看 ID 是否稳定最后才加计数逻辑。一次性全接上出问题很难定位是检测漏了还是追踪跟丢了。2.2 conda 环境与依赖安装的具体命令环境配置这一步翻车最多主要是 torch 版本和 CUDA 对不上。下面是我常用的流程先建独立环境避免污染 base。# 创建 Python 3.9 环境YOLOv5 对 3.8-3.10 兼容较好 conda create -n yolo_track python3.9 -y conda activate yolo_track # 安装 PyTorch这里以 CUDA 11.8 为例具体按你的驱动版本改 # 没有 GPU 就装 CPU 版命令去 PyTorch 官网生成 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 克隆 YOLOv5 仓库并安装依赖 git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt # DeepSORT 依赖deep_sort_realtime 是封装较好的一个实现 pip install deep-sort-realtime opencv-python这里有几个参数要盯住。python3.9不是随便选的3.11 以上部分 DeepSORT 依赖会编译失败。torch 的 CUDA 版本必须和nvidia-smi右上角显示的 CUDA Version 兼容不是必须相等但不能超过。deep-sort-realtime相比原版 deep_sort 省去了单独下载 ReID 权重的步骤它会自动拉取适合快速验证。装完先跑一句验证python -c import torch; print(torch.cuda.is_available())输出True说明 GPU 可用。如果是False而你有显卡八成是 torch 装成了 CPU 版重装即可。2.3 检测加追踪的最小脚本先不写计数只把检测和追踪串起来确认每一帧的输出框上带稳定 ID。import cv2 import torch from deep_sort_realtime.deepsort_tracker import DeepSort # 加载 YOLOv5用本地权重避免每次联网 model torch.hub.load(ultralytics/yolov5, yolov5s, pretrainedTrue) model.conf 0.4 # 置信度阈值低于此值的框丢弃 model.iou 0.45 # NMS 的 IOU 阈值控制重叠框合并 # 初始化 DeepSORT tracker DeepSort( max_age30, # 目标丢失多少帧后删除轨迹 n_init3, # 连续命中多少帧才确认新轨迹 max_iou_distance0.7 # 级联匹配的 IOU 距离阈值 ) cap cv2.VideoCapture(test.mp4) while cap.isOpened(): ret, frame cap.read() if not ret: break # YOLOv5 推理只保留 car 和 person 两类 results model(frame, size640) dets results.xyxy[0].cpu().numpy() # 转成 DeepSORT 需要的格式[x, y, w, h], conf, class det_list [] for *xyxy, conf, cls in dets: if int(cls) in [0, 2]: # COCO 里 0 是 person2 是 car x1, y1, x2, y2 xyxy det_list.append(([x1, y1, x2 - x1, y2 - y1], conf, int(cls))) tracks tracker.update_tracks(det_list, frameframe) for t in tracks: if not t.is_confirmed(): continue l, tp, r, b t.to_ltrb() cv2.rectangle(frame, (int(l), int(tp)), (int(r), int(b)), (0, 255, 0), 2) cv2.putText(frame, fID{t.track_id}, (int(l), int(tp) - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imshow(track, frame) if cv2.waitKey(1) 0xFF 27: break cap.release() cv2.destroyAllWindows()逻辑上分三段YOLOv5 出检测框过滤出人和车两类转成 DeepSORT 的输入格式DeepSORT 用上一帧的轨迹和这一帧的检测做匹配输出带track_id的轨迹最后画框。max_age30表示目标消失 30 帧内还保留轨迹等它重新出现n_init3表示新目标要连续 3 帧被检测到才给正式 ID这个参数能过滤掉大量一闪而过的误检。max_iou_distance调大匹配更宽松遮挡场景可以适当加大但太大容易把两个目标混成一个。跑通这一步你会看到画面里每个车和人框上有一个相对稳定的 ID。ID 稳定了计数才有意义。3. 计数逻辑怎么写虚拟线、方向判定与去重状态机3.1 计数线的两种画法和方向判定计数本质是判断一个轨迹有没有「穿过」一条线。线有两种画法水平线和垂直线。水平线适合俯拍或侧拍的车道判断目标中心点的 y 坐标从线的一侧变到另一侧垂直线适合正对车流的画面判断 x 坐标。方向判定不能只看「穿过了」还要看「从哪边穿到哪边」否则来回走动的行人会被反复计数。做法是记录轨迹上一帧的中心点位置和当前帧比较# 计数线水平线 y400只统计从上往下穿过的目标 LINE_Y 400 counted_ids set() # 已计数的轨迹 ID防止重复 prev_center {} # 记录每个轨迹上一帧的中心点 def check_cross(track_id, cx, cy): if track_id in counted_ids: return False if track_id not in prev_center: prev_center[track_id] (cx, cy) return False _, prev_y prev_center[track_id] prev_center[track_id] (cx, cy) # 上一帧在线以上当前帧在线以下判定为向下穿过 if prev_y LINE_Y cy: counted_ids.add(track_id) return True return Falsecounted_ids这个集合是防重复计数的第一道防线。一个轨迹 ID 只要计过一次就不再计。prev_center记录上一帧位置用来判断穿越方向。这里有个细节如果目标移动快一帧跨过了整条线prev_y LINE_Y cy依然成立不会漏。但如果目标在线的附近抖动中心点反复跨越就可能误判——所以counted_ids必须配合使用。3.2 重复计数的三个来源和对应修法重复计数是这个项目最典型的坑热词里「重复计数」出现频率很高。来源主要有三个。第一个是 ID 跳变。目标被遮挡后 DeepSORT 给了新 ID同一个目标被当成两个计两次。修法是调大max_age让轨迹在遮挡期间保留更久同时确认 ReID 特征在正常工作如果deep-sort-realtime没加载到 ReID 权重匹配会退化成纯 IOU遮挡后基本必跳 ID。第二个是计数线附近的抖动。目标停在线上或缓慢移动中心点来回穿越。修法是加一个缓冲区只有目标中心点从线的一侧移动到另一侧超过一定像素比如 10 像素才判定穿越而不是一碰到线就算。BUFFER 10 # 缓冲区像素 def check_cross_stable(track_id, cx, cy): if track_id in counted_ids: return False if track_id not in prev_center: prev_center[track_id] (cx, cy) return False _, prev_y prev_center[track_id] prev_center[track_id] (cx, cy) # 必须完整跨过线加缓冲区 if prev_y LINE_Y - BUFFER and cy LINE_Y BUFFER: counted_ids.add(track_id) return True return False第三个是同一目标在画面里被检测成多个框。这属于检测层的问题靠调 NMS 的iou阈值解决把重叠框合并掉。如果两个框是同一辆车的前后部分说明模型对该场景训练不足需要用自己的数据集微调。3.3 把计数结果落到视频和日志计数不能只在画面上显示落地要能导出。常见做法是每帧把当前计数写到画面左上角同时在穿越发生时往 CSV 追加一条记录包含时间戳、轨迹 ID、类别、方向。import csv, time with open(count_log.csv, a, newline) as f: writer csv.writer(f) # 穿越发生时调用 writer.writerow([time.time(), track_id, class_name, down])这样即使视频处理中断日志也在。后续要做分时段统计直接读 CSV 按时间窗口聚合就行。注意 CSV 里的时间戳用浮点秒别用格式化字符串方便后续计算。如果导出到 Excel 发现数值变成科学计数法那是 Excel 的显示问题不是数据问题导入时把该列设为文本即可。4. 避坑与排查ID 跳变、漏检和性能这三类问题怎么定位4.1 现象同一辆车在画面里换了 ID原因DeepSORT 的外观特征匹配失效或者max_age太小导致轨迹被提前删除。遮挡、光照突变、目标尺寸剧烈变化都会触发。解决先确认 ReID 权重加载成功打印一下 tracker 的 embedder 是否非空。然后把max_age从 30 调到 50 试试代价是内存里保留的轨迹变多。如果还跳检查检测框是否稳定——检测框抖动大IOU 匹配也会失败可以适当降低 YOLOv5 的conf让检测更连续。4.2 现象计数比实际少有车没数到原因检测漏检或者目标从画面边缘进入时还没被确认n_init没满足就已经穿过了计数线。解决把计数线往画面中间挪给目标足够的帧数完成初始化。n_init从 3 降到 2 能加快确认但误检也会增加。如果是夜间或逆光漏检那是检测器的问题需要用对应场景的数据微调 YOLOv5调参救不了。4.3 现象帧率很低视频处理比实时慢很多原因YOLOv5 输入尺寸太大或者每帧都在做 ReID 特征提取。解决size从 640 降到 416 或 320速度提升明显代价是小目标检测变差。DeepSORT 的 ReID 是主要开销之一如果场景里目标外观差异大比如不同颜色的车可以降低 ReID 的调用频率每隔几帧提一次特征。树莓派上部署时这两条尤其重要树莓派 5 跑yolov5n加轻量追踪才勉强实时yolov5s基本跑不动。4.4 现象CSV 日志里计数对不上画面显示原因画面显示的是当前帧的累计值CSV 是穿越事件流两者统计口径不同。如果程序中途重启画面计数归零但 CSV 还在追加。解决统一以 CSV 为准画面计数从 CSV 读初始值。或者每次启动生成带时间戳的新日志文件避免混淆。4.5 现象换了自己训练的 YOLOv5 权重后追踪全乱原因自己训练的模型类别索引和 COCO 不一样过滤类别时用了错误的索引把该保留的类过滤掉了或者把背景当成了目标。解决打印模型的names属性确认类别顺序过滤逻辑按新索引改。另外自己训练的模型置信度分布可能不同conf阈值要重新调不能沿用 0.4。5. 进阶用自己数据集微调 YOLOv5 并验证计数精度5.1 训练自己数据集的流程要点通用 COCO 权重在特定场景比如某个路口、某种车型上漏检是常态。微调流程不复杂关键是数据标注质量。用 labelImg 或 Roboflow 标注导出 YOLO 格式每张图对应一个 txt每行是class x_center y_center width height坐标归一化到 0 到 1。目录结构按 YOLOv5 要求组织dataset/ images/ train/ val/ labels/ train/ val/然后写数据配置文件# data/mydata.yaml path: ./dataset train: images/train val: images/val nc: 2 names: [person, car]训练命令python train.py --img 640 --batch 16 --epochs 100 \ --data data/mydata.yaml --weights yolov5s.pt--img 640是训练分辨率要和推理时一致否则小目标表现会差。--batch 16按显存调显存不够就降。--weights yolov5s.pt表示从预训练权重开始比从头训练收敛快得多。训练完在runs/train/exp/weights/best.pt。5.2 超参数里最该动的三个YOLOv5 的超参数很多但做追踪计数真正影响结果的就三个。学习率lr0默认 0.01小数据集上容易过拟合可以降到 0.001。数据增强里的mosaic默认开启对小目标有帮助但如果你的场景目标都很大可以关掉减少训练噪声。anchor如果自己数据集的框尺寸和 COCO 差异大跑一次python train.py --noautoanchor对比或者用 k-means 重新聚类生成 anchor。5.3 计数精度的验证方法模型训完不能只看 mAP要看计数误差。做法是人工数一段视频的真实过车数然后跑系统对比。建议至少取三段不同时段的视频分别统计看误差是否稳定。如果误差集中在某个方向比如总是少数说明计数线位置或方向判定有系统偏差如果误差随机多半是 ID 跳变导致。一个实用的验证技巧把counted_ids和对应的轨迹 ID 一起导出人工抽查几个被计数的轨迹看是不是同一个目标被计了多次。这比只看总数更能定位问题。5.4 边缘部署的现实预期树莓派 5 上部署自己训练的 YOLOv5 模型现实是yolov5n加 ONNX Runtime 或 NCNN 推理配合轻量追踪能做到 5 到 10 FPS够用于低速场景。yolov5s基本在 2 FPS 以下不适合实时。如果一定要在边缘做考虑把检测放到服务端边缘只做推流或者用更轻的检测器替换 YOLOv5。这个取舍要在项目开始前想清楚别做到一半发现算力不够。我自己做这类项目养成的习惯是先把计数逻辑用一段固定视频跑通把counted_ids和轨迹日志打出来人工核对确认没有重复和漏计之后再换场景、换权重。跳过这一步直接上生产视频后面排查重复计数会非常痛苦。希望帮到你。本文还有配套的精品资源点击获取
返回列表