ARTICLE DETAIL

资讯详情

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

YOLOv5+DeepSORT车流人流统计实战:从环境配置到跨线计数避坑指南

YOLOv5+DeepSORT车流人流统计实战:从环境配置到跨线计数避坑指南 简介毕业设计项目基于YOLOv5与DeepSORT实现高速移动场景下的车流与人流量统计面向计算机相关专业正在准备毕业设计的学生及需要项目实战练习的开发者。项目经导师指导并在评审中获99分代码完整可运行也可作为课程设计或期末大作业参考。资源包共117个文件包含55个Python脚本、20个YAML配置、Markdown笔记、MP4演示视频、Dockerfile部署文件、操作手册等整体约80.21MB其中Python脚本负责检测跟踪与计数逻辑YAML配置便于调整模型和数据集参数笔记与手册提供使用说明视频与动图展示实际运行效果。附带Dockerfile支持快速复现环境结合演示视频和源码可深入理解DeepSORT关联逻辑与YOLOv5输出处理方式已有65人浏览学习适合据此快速搭建同类视觉计数项目整个项目结构清晰从模型推理到计数输出均有迹可循。1. 为什么把 YOLOv5DeepSORT 用在车流人流统计上是毕业设计的务实选择高速移动的车流是目标检测里最典型的难样本车速快、遮挡多、目标小单帧画面里人和车同时存在光靠 YOLOv5 做逐帧检测只能得到「这一帧有车」却回答不了「刚才那辆车和现在这辆是不是同一辆」。YOLOv5DeepSORT 的组合把问题拆成两半YOLOv5 负责每帧框出人和车DeepSORT 负责跨帧关联 ID之后自己写计数逻辑就能输出「某个方向过了多少辆车、多少个人」。对做毕业设计的学生来说这套方案有三个实在的优势视频数据好找、训练链路成熟、结果可展示。拿到一份优质项目源码跑通之后改数据集、调计数线、换场景每一步都有明确产出比较适合用来做课程设计或毕设主课题。这套系统的读者通常不是算法研究者而是想把一个完整项目落地复现的开发者。你不需要从头实现卡尔曼滤波也不需要自己写目标检测网络重点是理解四个模块怎么配合检测、追踪、计数、可视化。下面整篇按这个顺序走先跑通最小链路再训练自己的模型最后把计数逻辑和坑位讲透。2. 环境与最小可运行链路conda 环境、YOLOv5 推理和 DeepSORT 追踪2.1 conda yolov5 环境配置先锁版本再装依赖拿到一份带 YOLOv5 与 DeepSORT 的毕设源码第一步别急着双击入口脚本。很多项目源码的依赖版本交错直接 pip install -r requirements.txt 极易翻车。依赖装错会导致运行时才报底层错误半天定位不到问题。我习惯用 conda 把 Python 和 PyTorch 版本锁死再补装其他依赖成功率会高很多。conda create -n traffic python3.9 -y conda activate traffic conda install pytorch2.0.1 torchvision0.15.1 torchaudio2.0.1 pytorch-cuda11.8 -c pytorch -c nvidia pip install -r yolov5/requirements.txt这里 py 3.9 在 Windows 下和 opencv 配合比较稳py 3.10 之后个别轮子在离线安装时容易遇到版本匹配问题。pytorch 2.0.1 cuda 11.8 是覆盖面很广的组合30 系、40 系显卡都能直接跑CPU 机器也能退化成纯 CPU 推理只是慢一些。装完之后做一次快速验证比直接跑完整程序省时间python -c import torch; print(torch.__version__, torch.cuda.is_available()) python -c from yolov5.models.experimental import attempt_load; print(yolov5 import ok)第一行必须看到True否则后面推理全部走 CPU流量统计做成实时展示时会非常卡。第二行如果报错先看是不是缺少权重文件这类报错往往不代表环境坏了而是模型文件路径没对上。DeepSORT 的依赖里常出现 scipy 和 numba 版本冲突装完建议单独 import 一下deep_sort里的核心模块不通就重装 scipy 和 numba。2.2 用 YOLOv5 跑通第一个推理看懂检测输出再改代码环境就绪后先用官方权重跑一段视频验证检测链路。这个步骤会同时验证模型加载、视频读取和结果输出三条路径是后续改造成本项目的基础。python detect.py --source traffic_video.mp4 --weights yolov5s.pt --conf 0.4 --img-size 640 --save-txt参数说明--source可以是视频、图片或摄像头序号毕设测试阶段建议用 720p 短视频方便反复验证--weights先不换自己的权重用预训练yolov5s.pt它内置了 COCO 80 类其中就有 person、car、bus、truck 这些本次需要的类别--conf 0.4是置信度阈值阈值低则召回多、误检也多先按 0.4 起步跑完看结果再调--save-txt会把每个框的类别和坐标写到 txt 里。打开 detect 输出的 annotated 视频重点看三件事人有没有框住、车有没有框住、有没有把路牌误检成人。此时不需要 DeepSORT 参与只要确认「能检出」。如果跑下来发现漏检严重先别急着换模型把--img-size提到 1280 再试。高速场景下车辆目标小640 输入在远端容易把车身压成几个像素imgsize 提上来对小目标改善是立竿见影的代价是推理时间增加。这条经验在后续训练自己的模型时也是一样输入尺寸和最终精度直接相关。2.3 DeepSORT 首次接入从一帧的框到跨帧的 IDYOLOv5 输出的是每帧独立结果DeepSORT 要做的事是把这些框按位置和外观特征串成轨迹。市面上常见毕设源码里接入方式基本是同一套逻辑先解析 YOLOv5 的输出转成 DeepSORT 要求的[x, y, w, h]格式再送入 tracker。import cv2 import torch from deep_sort_realtime.deepsort_tracker import DeepSort # 初始化追踪器max_age 控制轨迹丢失后保留多少帧n_init 控制要连续几帧才确认 ID tracker DeepSort(max_age30, n_init3) # 假定是 YOLOv5 推理results.xyxy[0] 是 [x1, y1, x2, y2, conf, cls] results model(frame, size640) detections [] for *xyxy, conf, cls in results.xyxy[0].cpu().numpy(): if conf 0.4: continue x1, y1, x2, y2 [int(v) for v in xyxy] w, h x2 - x1, y2 - y1 # DeepSORT 传入的通常是中心点坐标加宽高 detections.append(([x1, y1, w, h], conf, int(cls))) tracks tracker.update_tracks(detections, frameframe) for track in tracks: if not track.is_confirmed(): continue ltrb track.to_ltrb() # 返回 [left, top, right, bottom] track_id track.track_id cv2.rectangle(frame, (int(ltrb[0]), int(ltrb[1])), (int(ltrb[2]), int(ltrb[3])), (0, 255, 0), 2) cv2.putText(frame, str(track_id), (int(ltrb[0]), int(ltrb[1]) - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)这段代码的核心在两个参数max_age30表示一个目标连续 30 帧没被检测到之后轨迹才被删除高速上车被短暂遮挡时靠这个参数保 IDn_init3表示一个候选轨迹至少要有 3 帧检测结果才会被确认并分配正式 ID。如果 ID 频繁切换先调大max_age如果出现大量幽灵轨迹再把n_init调大。第一次跑通 DeepSORT 时画面里每个目标应该带一个稳定数字。如果数字在不停跳变不要急着改代码先看是不是同一个人身上同时挂了 2 个框这是检测阶段重复框导致的DeepSORT 会把同一个目标的多个框当多个目标处理。3. 训练自己的检测模型数据集、标注格式和超参数的坑一起说3.1 数据集从哪来公开数据和自标注怎么平衡毕设版权问题不大但用 COCO 预训练权重直接统计车流有一个明显问题COCO 训练数据多以日常场景为主高速路侧视角的卡车、货车、行人和非机动车样本偏少。想提升场景精度最稳妥的做法是找一段角度类似的高速监控视频抽帧标注。常见做法是抽 1000 到 2000 帧按 7 比 2 比 1 分成训练、验证、测试类别只保留 person、car、bus、truck、motorcycle 这五类能覆盖绝大多数车流人流统计需求。自标注工具我建议直接用 LabelImg 或 AnyLabeling前者老但稳定后者方便转格式。标注的框不要像画壁纸一样精确贴着边界目标被树挡一半时框稍微外扩一点比框紧了效果好。高速视频里远端目标非常小标注这类小目标你的注意力会被拖垮但必须坚持标完否则你的模型在能见度好的近端很准远端全漏。3.2 把 VOC 转成 YOLO 格式转换脚本与四个边界坑LabelImg 默认导出 PASCAL VOC 的 xml 文件而 YOLOv5 训练需要的是 txt 标注每个 txt 对应一张图每行是一个目标的class cx cy w h坐标全部归一化到 0 到 1。转换脚本不难但四个边界坑很容易踩。import os import xml.etree.ElementTree as ET def convert_voc_annotation(xml_path, out_txt_path, classes): xml_path: 单张图对应的 VOC 标注文件 out_txt_path: YOLO 训练格式输出文件 classes: 类别名列表顺序必须与训练 yaml 里的 nc 一一对应 tree ET.parse(xml_path) root tree.getroot() width int(root.find(size/width).text) height int(root.find(size/height).text) with open(out_txt_path, w, encodingutf-8) as f: for obj in root.iter(object): name obj.find(name).text if name not in classes: continue xmin int(obj.find(bndbox/xmin).text) ymin int(obj.find(bndbox/ymin).text) xmax int(obj.find(bndbox/xmax).text) ymax int(obj.find(bndbox/ymax).text) # 边界坑一坐标必须归一化超界要裁剪 xmin max(0, xmin) ymin max(0, ymin) xmax min(width, xmax) ymax min(height, ymax) w xmax - xmin h ymax - ymin if w 0 or h 0: continue cx (xmin xmax) / 2.0 / width cy (ymin ymax) / 2.0 / height w_norm w / width h_norm h / height f.write(f{classes.index(name)} {cx:.6f} {cy:.6f} {w_norm:.6f} {h_norm:.6f}\n) classes [person, car, bus, truck, motorcycle] # 批量处理时注意xml 转 txt 的文件名必须与图片文件名完全一致仅后缀不同四个坑分别出现在坐标没有除以宽高导致的归一化错误标注框超出图像边界导致训练时 loss 异常类别索引没有和 yaml 文件对齐模型把人和车互换了空标注文件被覆盖成上一张图的内容。最后一种在批量脚本里特别隐蔽建议每写一行前先clear旧文件别用追加模式。3.3 训练配置文件与超参数设定yolov5 超参数不是越多越好训练前要写一个 dataset.yaml内容包含路径和类别数。路径用绝对路径在 Windows 上最容易出问题写成相对路径可能会更好建议把数据集放进项目根目录下。train: data/train/images val: data/valid/images nc: 5 names: [person, car, bus, truck, motorcycle]训练时最需要关心的超参数只有几个--img-size、--batch-size、--epochs、--hyp。高速小目标场景把输入尺寸固定到 640 起步显存够就上 1280小目标贡献明显。batch-size 在 8G 显卡上取 16 比较稳妥太大容易爆显存太小训练不稳定。epochs 对毕设来讲 100 轮足够配合--patience 20做早停20 轮没提升自动停省时间。--hyp是超参数文件不建议新手从零调直接用data/hyps/hyp.scratch-low.yaml起步。这个文件里最值得注意的两个参数是mosaic和mixupmosaic 把四张图拼成一张训练能显著提升小目标检测mixup 则会引入更多噪声车流场景建议保持默认。训练命令大致是python train.py --data dataset.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 100 --hyp data/hyps/hyp.scratch-low.yaml训练完成后看runs/train/exp/weights/best.pt这就是后续推理要用的权重。注意不要用last.pt后者是最后一轮权重训练后期可能过拟合精度不如 early stopping 选出来的 best.pt。训练完的部署路径就很直接把detect.py的--weights换成best.pt类别 yaml 换成同一份。此时跑一段视频你会发现预训练权重里那些把桥梁阴影误检成 bus 的问题显著减少这就是独有数据集带来的最大收益。4. 车流人流计数的实现虚拟计数线、方向判定与车人分流4.1 选计数方案区域计数还是虚拟线计数目标和 ID 都有了接下来要用什么规则统计「流量」。常见做法有两种区域计数和虚拟线计数。区域计数适合统计某个路口或广场内的人数比如画一个多边形只要目标中心点落在里面就计数虚拟线计数适合统计通行流量比如高速收费站、匝道进出口目标越过一条线就计一次。高速车流人流统计用虚拟线更合理。车的走向是明确的车不可能像人一样滞留或反复折返。区域计数的最大问题是目标在区域内来回走动会重复计数你得额外维护「同一 ID 不得重复进入区域」的集合而虚拟线天然只有「跨过」和「没跨过」两个状态逻辑简单很多。线的位置也很关键。我踩过的坑是把线画在画面中间结果一辆车在画面里走了很久才跨线中间一旦发生遮挡 ID 丢失计数就串了。正确做法是把线画在靠近画面下沿三分之一处目标刚出现在画面底部附近就先进入追踪状态等它从容跨线时 ID 通常已经稳定确认。4.2 跨线计数代码用相邻帧中心点做方向判定方向统计的核心不是判断「现在在线的哪一侧」而是判断「上一帧在线的哪一侧、这一帧又在线的哪一侧」。利用相邻两帧的中心点位置可以同时完成计数和方向识别。# 每条轨迹保留最近一次的中心点坐标 # prev_points[trace_id] (prev_cx, prev_cy) # line_y 是计数线在画面中的水平坐标 cross_down_ids set() # 已经向下穿过线的目标防止重复计数 cross_up_ids set() # 已经向上穿过线的目标 if prev_points.get(track_id) is not None and track_id not in cross_down_ids: prev_cx, prev_cy prev_points[track_id] # 目标从线下方移动到线上方记为向下通行 if prev_cy line_y and cy line_y: down_count 1 cross_down_ids.add(track_id) if prev_points.get(track_id) is not None and track_id not in cross_up_ids: prev_cx, prev_cy prev_points[track_id] # 目标从线上方移动到线下方记为向上通行 if prev_cy line_y and cy line_y: up_count 1 cross_up_ids.add(track_id) prev_points[track_id] (cx, cy)两个方向的集合在目标离开画面或 ID 消失时清理否则会一直膨胀。清理条件用 DeepSORT 的轨迹状态判断track.is_confirmed()为 False 且track.is_tentative()为 True 时从集合里移除。这段逻辑里最容易翻车的地方是目标跨线那一瞬中心点在线上抖动比如一辆车车头已经过线但因为检测框轻微缩放中心点又退回线另一侧就会造成重复计数。解决思路是给跨线动作加一个去重缓冲同一 track_id 一旦计数过就进入一个持续 30 帧的冷却期冷却期内不再判断这条轨迹的跨线动作。上面对应的是集合去重方式二者可以结合使用。4.3 车流与人流分离不同移动特性不同阈值车和人同时计数时一个常见误解是直接对检测类别做分流——检测结果是 car 就计入车流是 person 就计入人流。现实里误差来自汽车检测框中心点稳定程度。人的检测框中心点会因为走路姿态上下摆动而车框更稳定。这在跨线判定时会造成人的重复计数更严重。处理方式是对人和车使用不同的跨线判定策略。车辆位移量大只要中心点跨线就计行人位移速度慢需要用连续两帧都越线才计即确认目标真实移动而非姿态抖动导致的中心偏移。该方法在毕设源码里经常写成两条独立的计数线车道线画在司机视线附近人行走线画在靠近画面底部。这样人在人行道走、车在车道走互不干扰。另一个很容易被忽略的是车流方向统计的可视化。建议在原图上用箭头标注方向比如向下通行用绿色数字向上通行用黄色数字。高速监控视频本身信息杂箭头能显著提升答辩时的呈现效果。5. 跑通与改进中的常见问题避坑实录与排查思路以下四条来自同类项目最常出现的踩坑记录毕设阶段把它们跑通系统基本就稳了。5.1 现象一同一辆车 ID 频繁切换计数线旁边数字乱跳原因不在 DeepSORT而在检测端。置信度阈值太低时YOLOv5 会把车身拆成两个框输出或者带阴影的车型检测框一会大一会小导致追踪器关联失败。解决办法是先把检测置信度从 0.4 提到 0.5观察是否稳定如果目标确实小且模糊则把追踪器的max_age从 30 调到 60让轨迹有更长容错窗口。另一个隐蔽原因是视频抽帧间隔不均匀。有些源码用了跳帧加速2 帧抽 1 帧猛加速后车速快目标位置变化大IoU 匹配直接失效造成 ID 频繁切换。优化方式是不要跳帧改为把视频缩小到 640 宽后全帧处理。5.2 现象二同一辆车跨线后被计了两次中心点抖动的直接后果。车头跨线瞬间检测框可能因为纹理突变变大中心点跟着偏向线另一侧跨线判定立刻再次触发。我的习惯是「每个 track_id 计完数后加冷却期」上面代码里的cross_down_ids只去重了方向没带上时间窗口。完善方式是每计数一次记录last_count_frame当前帧超过 30 帧后才允许再次计数。同时把跨线条件从「中心点越线」改成「中心点越过线且距离线超过 5 像素」消除边界抖动。5.3 现象三Demo 里只有一辆车FPS 却降到了个位数问题出在 DeepSORT 的特征提取模型。很多 DeepSORT 实现会在每帧对每个检测框做一次外观特征提取目标一多耗时直接翻倍。可以确认的是毕设常见的 deep_sort_pytorch 源码默认加载了残差网络作为特征提取器在 GPU 上勉强跑CPU 上基本跑不动。解决思路有三个第一--device要传 0 而不是 0,1多 GPU 会导致额外同步开销第二把输入帧先等比缩到 640再用 1280 做检测的情况只在精度优先时开第三只对正式确认的轨迹做外观特征提取新出现的检测框先用位置信息和 IoU 关联等下一帧再提取特征。第三种是 DeepSORT 改进的常见思路改动不大但提速非常明显。5.4 现象四夜间或雨雪天漏检率飙升计数全线崩盘高速移动场景里远光、雨线、树影都会导致小目标检测失败。解决方向是强化数据而不是猛调置信度。我见过的有效做法是收集少量目标出现严重遮挡的夜间帧用几何变换增强再把整批数据喂进去重复训练 20 轮。注意增强图像后要同步做标注的仿射变换只翻转图片不翻转坐标会导致模型学到一个「镜像世界」反而把位置信息搞乱。此外如果条件允许可以尝试训练时开启--img 1280。代价是显存占用翻倍但对夜间远端小目标改善巨大。实机测试时用导航画面的预览如果打印板下面没人加这一段。6. 验证与收尾技巧把计数结果做成可复现的实验材料毕设评审时最怕的是「你说你数出了 300 辆车我怎么知道对不对」。验证方法要在项目里明确数字化。我的做法是准备一段 3 分钟的测试视频人工逐帧数出两个方向的车流总量再跑程序得到机器计数对比出偏差率。这个偏差率就是系统精度的最有力证明。代码层面建议把统计结果落成 CSV每 1 秒记录一次累计的 down_count 和 up_count同时把当前方向箭头画到输出视频里。这个 CSV 文件是答辩时的核心报表能直观展示系统在哪个时间段出现计数波动。另一个技巧是把计数结果和 ID 输出到 CSV 时额外记录 track_id 的存活帧数存活帧数过短的轨迹基本是误检出来的片段可以直接从统计里剔除降低偏差率。进阶改进方面DeepSORT 本身可以调的面不多常见做法是把追踪框与检测框的掩码重叠率从普通 IoU 换成 CIoU 或 DIoU高速场景下目标位置变化快中心点距离惩罚对快速移动的目标更友好。这个改进只需替换追踪器内部的匹配度计算函数改动量不大但能明显减少 ID Switch 数量。此外可以给计数线加一个「越线速度」判断当目标在相邻两帧间的位移超过车辆平均位移的 3 倍时认为是检测异常不参与计数。最后留一个我自己的习惯每次跑完一段视频会把带 ID 的中间帧单独截几张图存进debug/目录。截图里有检测框、ID 数字、跨线瞬间的箭头和计数累计值它既是排错材料也是答辩时最直观的展示素材。这个习惯帮我节省了大量回头定位问题的时间。希望这些经验能让你少绕几个弯一次把车流人流统计项目跑出可信的数据。本文还有配套的精品资源点击获取
返回列表