YOLOV8人流量统计Python源码实战:从检测追踪到计数训练全解析

发布时间:2026/9/15 1:56:38
YOLOV8人流量统计Python源码实战:从检测追踪到计数训练全解析
简介基于YOLOV8的进出口人流量统计识别Python源码与配套文档面向深度学习初学者、本科毕业生及课程设计开发者解决出入口双向人流量检测与计数问题。项目代码包含详细注释模型配置、推理预测、目标跟踪等模块一应俱全并附带界面截图、操作说明及使用手册简单部署即可运行系统功能完善、界面美观适合作为毕业设计、期末大作业或课程设计的高分参考。压缩包共72个文件总大小2.13MB以yaml配置、Python源码.py/.pyc、PNG/JPG预览图以及MD/DOCX文档为主目录结构清晰便于按需查阅与二次开发同时包含环境配置说明。已有128人浏览学习该资源经严格调试另含个人高分项目文档可帮助使用者快速理解YOLOV8的检测跟踪流程节省环境搭建与调参时间具有较高的实际应用价值。1. 为什么要把 YOLOV8 人流量统计做成一整套 Python 源码在商场出口、展会闸口这种固定区域人流统计的准确率从来不是单靠一个检测模型撑起来的。YOLOV8 负责把每个人从画面里找出来追踪模块还得给每个人分配一个临时 ID计数逻辑再把带 ID 的运动轨迹换算成“进”和“出”。任何一个环节掉链子最终累计人数都会偏得离谱。很多人在网上找一个 YOLOV8 行人检测脚本就跑结果画面里框画得很准计数却是一团乱麻同一人来回穿线被重复计数、两个人同行时 ID 互换导致方向判反。这类项目的难点往往不在“识别”而在如何把检测结果稳定地转成计数事件。本文按“检测 追踪 计数 训练 排错 验证”这条链路把进出口人流量统计识别 Python 源码里该有的模块、参数和落地坑位讲清楚。适合正要搭客流统计系统的工程师也适合拿 YOLOV8 练手、想搞懂检测之外那部分工程逻辑的 Python 开发者。2. 读懂 YOLOV8 人流量统计源码目录结构、依赖版本与最小运行2.1 源码包的典型模块拆解与数据流拿到类似基于YOLOV8的进出口人流量统计识别Python源码文档说明.zip的项目包第一步不是急着装环境而是先把目录结构过一遍。这类工程的核心通常由四个模块组成检测器、追踪器、计数器、出入口管理器。检测器只做单帧检测追踪器负责把帧间的框关联成轨迹计数器根据轨迹判断穿越事件出入口管理器负责维护区域状态和输出结果。模块常见文件输入输出检测器detector.py单帧图像人体框 xyxy、置信度追踪器tracker.py检测框序列轨迹 ID、中心点、生命周期计数器counter.py轨迹中心点进出事件、累计人数入口/出口管理gate.py 或 counter_cfg.py自定义线、区域配置方向标签、停留数据主流程run.py / main.py视频流或本地视频叠加结果、写入日志数据流是单向的视频帧先过 detector检测结果按帧喂给 trackertracker 输出带唯一 ID 的目标状态counter 再用这些状态去判断轨迹是否穿越了预先标定的线。很多人误以为计数在检测层就能做实际上没有追踪就没有方向只有框的分母没有方向的分子。2.2 用最小命令跑通一个 YOLOV8 计数流程环境装好后先跑一段本地视频确认链路通畅。命令行里最好把来源、权重、置信度、计数线、设备全部显式写出来避免默认值掩盖问题python run.py \ --source ./samples/gate.mp4 \ --weights ./models/yolov8n.pt \ --line 0.42 \ --conf 0.35 \ --iou 0.45 \ --device 0这段命令的意思是从本地视频读取帧用 COCO 预训练的 yolov8n 权重做检测把计数线放在画面高度 42% 的位置检测置信度低于 0.35 的框直接丢弃NMS 的 IoU 阈值取 0.45使用显卡推理。--line 0.42是按高度比例定义一条水平参考线入口和出口分别由轨迹从线的两侧穿越方向决定。注意计数线不要放在画面边缘。放太靠上人刚露头就被判成一次穿过转身时又穿一次噪声会被放大。实践中放在画面底部往上三分之一左右同时保证线的前后有足够的可视空间来积累轨迹。2.3 Python 源码环境的依赖组合与安装顺序YOLOV8 环境配置是新手最容易卡住的地方。以下是我试过比较省心的一套组合Python 3.9 到 3.11 任一版本PyTorch 2.xultralytics 8 系列配套 opencv-python、numpy、lap、cython。ByteTrack 类追踪器在部分平台需要编译 C 扩展lap库装不上时先检查是否有可用的 wheel不要急着用源码编译。pip install ultralytics pip install opencv-python onnxruntime pip install lap cython # ByteTrack 依赖逻辑说明先装 ultralytics 会把 torch、torchvision 一并带进来如果机器只有 CPU可以先去 PyTorch 官网装对应 CPU 版再装 ultralytics否则默认拉到的 CUDA 版在无独显机器上会出现torch.cuda.is_available()为 False又得重装。lap和cython服务于追踪器里的匈牙利匹配与线性分配缺了会在第一帧追踪时报ModuleNotFoundError。提示ultralytics 小版本间 API 有细微变化比如model.predict的classes参数和model.track的返回值结构。项目跑不起来时先对照实际安装版本的 release note再看代码。3. YOLOV8 检测与追踪结合的计数逻辑从 detect 到 track 再到 count3.1 YOLOV8 的 Anchor-Free 输出与人物类别过滤YOLOV8 的检测结果本质上是一批特征图上的解码框。它去掉了 Anchor 预设直接在特征图每格回归边距并输出一个解耦的分类分支。主干网络用了 C2f 结构也就是把输入分成两组一组直连一组经过若干 Bottleneck 后再拼接最终通过 split 和 concat 让浅层细节和深层语义同时参与预测。对人流量统计来说这意味着小目标——比如画面深处的行人——在同样 FLOPs 下会比早期 YOLO 保留更多信息。拿到检测结果后第一件事是过滤类别。COCO 80 类中person是 0 类项目里通常会写成classes[0]让模型只返回人。这样既减少后续追踪器的输入量也避免把包、行李箱误当成计数对象。from ultralytics import YOLO class PeopleDetector: def __init__(self, weightsyolov8n.pt, conf0.35, iou0.45, devicecpu): self.model YOLO(weights) self.conf conf self.iou iou self.device device def update(self, frame): results self.model.predict( sourceframe, confself.conf, iouself.iou, classes[0], # COCO class 0 对应 person verboseFalse, deviceself.device, ) boxes results[0].boxes.xyxy.cpu().numpy() scores results[0].boxes.conf.cpu().numpy() return boxes, scores逻辑说明results[0].boxes.xyxy是 N×4 的左上右下坐标conf是置信度数组。代码把检测封装成update方法方便在视频循环里逐帧调用。参数上conf0.35对进出口场景比较中庸调高会漏掉遮挡和远距行人调低会让追踪器收到大量误检框导致 ID 频繁跳动。3.2 为什么选 ByteTrack 而不是 DeepSORT进出口人流量统计里行人是高度相似的目标衣服颜色、体型差异都不足以作为可靠的外观特征。DeepSORT 依赖一个额外的 ReID 特征提取器来区分目标推理开销大而且入口通道里多人交叉时外观特征经常互相污染。ByteTrack 的思路更直接只看检测框的位置重叠度用卡尔曼滤波预测下一帧位置再做两轮匹配。ByteTrack 的特别之处在于第一轮用高置信度框做匈牙利匹配第二轮把低置信度框拿出来再做一次匹配。这个设计对密集人群很友好因为一个人被部分遮挡时检测框分数会下降传统方法会直接丢弃低分框造成 ID 断裂ByteTrack 则让这些低分框有机会保住轨迹。这就解释了为什么同样是 YOLOV8用 DeepSORT 的工程在拥挤出入口会出现“人走过去又闪回来”的乱跳换 ByteTrack 后轨迹平滑很多。以 ByteTrack 仓库的常见接口为例把它接进检测器后的主循环大致是这样tracker BYTETracker(args) for frame_id, frame in enumerate(frames): boxes, scores detector.update(frame) dets [[*box, score, 0] for box, score in zip(boxes, scores)] # x1,y1,x2,y2,score,class tracks tracker.update(dets, (H, W), frame_id) for t in tracks: x, y, w, h t.tlwh cx, cy x w / 2, y h / 2 print(t.track_id, cx, cy)这里的dets是给追踪器的统一格式前四位是坐标第五位是置信度第六位是类别。调用tracker.update时传入当前帧的检测结果和画面尺寸返回的tracks里每个目标带有track_id和tlwh。追踪的核心指标不是单帧检测多准而是 ID 能持续多久所以args里一般会配一个track_buffer控制轨迹丢失后保留多久进出口场景建议设在 30 到 60 帧之间。3.3 进出方向判定跨线检测与区域内计数两种实现有了稳定的轨迹计数就变成几何问题。跨线计数的做法是预先定义一条分割线用向量叉积判断目标中心点在线段的哪一侧。上一帧在一侧这一帧跑到另一侧就记录一次穿越穿越方向由侧的符号变化方向决定。下面是最核心的判断代码class LineCounter: def __init__(self, p1, p2): self.p1 np.array(p1, dtypenp.float32) self.p2 np.array(p2, dtypenp.float32) self.side {} # track_id - 上一次的侧向符号 self.in_count 0 self.out_count 0 def _side(self, cx, cy): return np.sign( (self.p2[0] - self.p1[0]) * (cy - self.p1[1]) - (self.p2[1] - self.p1[1]) * (cx - self.p1[0]) ) def update(self, tracks): for tid, cx, cy in tracks: s self._side(cx, cy) if tid in self.side and self.side[tid] ! 0 and self.side[tid] ! s: if self.side[tid] 1 and s -1: self.in_count 1 elif self.side[tid] -1 and s 1: self.out_count 1 self.side[tid] s return self.in_count, self.out_count逻辑说明_side用向量的叉积算目标中心在线的哪一侧返回1或-1。side字典以track_id为主键记录每个目标上一次的位置符号。只有符号发生翻转且旧符号非零时才计数避免目标一开始就压在线上造成误判。in_count和out_count的映射关系要看图像坐标系里 y 轴方向的定义实际项目通常用两个方向常量来配置。如果需求不是“进出通道”而是“某个开放区域内此刻有多少人”就换用区域计数判断目标中心点是否落在多边形内进入区域时加一离开时减一。停留时长检测则是记录每个 ID 首次进入区域的帧号与当前帧号做差。跨线适合闸机口、扶梯口区域适合大厅、展台一套源码里两条路径都应该保留用配置项切换。4. 用 YOLOV8 训练自己的行人检测模型再跑人流量统计4.1 数据准备进出口场景的采集与标注要点直接用 COCO 预训练权重做人流量统计其实可以跑但相机一旦装在俯视角模型会漏检头顶视角的人如果通道里有大量背对镜头、带大件行李的情况误检也会变多。要让项目真正可用一般会用自己的数据做增量训练俗称微调。数据量的经验值是单一进出口场景采集 3000 到 8000 张图足够。白天、夜间、逆光各留一部分负样本——没有人的空镜头——也要放进去减少误检。标注格式用 YOLO 的 txt 格式每行是class x_center y_center width height坐标都归一化到 0 到 1。这个项目只关注人所以类别定为person一类就够把“人”和“非人”分开比搞多个类别更容易收敛。path: ./dataset train: images/train val: images/val names: 0: personnames必须从 0 开始连续编号如果之前标了person和bag两类现在想只保留人需要把标注文件里bag类别全部剔除再训练否则模型会把行李误检成人计数翻倍。4.2 增量训练的命令与关键参数增量训练用官方预训练权重做起点比从零开始收敛快得多。命令里需要显式控制数据路径、批次和图像尺寸yolo detect train \ data./dataset/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ device0 \ optimizerSGD \ lr00.01 \ patience20 \ freeze10参数建议值作用与调参注意modelyolov8n.pt从预训练权重继续训练比随机初始化稳定得多epochs100进出口场景数据量小100 轮足够判断趋势imgsz640画面里行人较小时可提到 960但显存占用成倍上涨batch16按显存调整显存不足时优先减到 8不要动 imgszoptimizerSGD数据量小时 SGD 比 Adam 更不容易过拟合lr00.01微调阶段一般用 0.005-0.01太大直接崩损失patience2020 轮验证集指标不升就早停freeze10冻结主干前 10 层加快训练并保持底层特征不变逻辑说明freeze10是把主干网络前 10 层的权重锁住不更新。进出口这类单一场景只需要调整高层语义底层边缘纹理特征在 COCO 上已经学得很好冻结后训练速度快也不容易把预训练知识冲掉。训练完成后runs/detect/train/下会生成best.pt和last.pt推理时换用best.pt即可。4.3 用损失函数曲线图和验证指标判断模型是否可用训练完不要直接拿模型上线。runs/detect/train/下的results.png是最直观的体检表横轴是 epoch纵轴包含训练损失和验证指标。重点看train/box_loss、train/cls_loss、val/box_loss三条曲线三者都下降并趋于平稳模型收敛正常训练损失降、验证损失升说明过拟合验证损失震荡幅度大说明学习率偏高或数据分布不一致。results.csv是同一份数据的表格形式如果要把损失函数曲线图重新画到其他图里直接读它最省事import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/detect/train/results.csv) df.columns [c.strip() for c in df.columns] # 去掉列名两端的空格 plt.plot(df[epoch], df[train/box_loss], labeltrain box_loss) plt.plot(df[epoch], df[val/box_loss], labelval box_loss) plt.legend() plt.show()逻辑说明results.csv的列名是从训练日志直接生成的首尾可能带空格先 strip 再取列。只看 loss 不够还要看验证阶段的metrics/mAP50(B)进出口场景要求不高单类行人 mAP50 到 0.85 以上就能用于计数。如果 mAP 高但实际视频里还是漏检查是不是标注框对遮挡目标只标了可见部分——YOLO 格式标注的是整身体范围不是可见范围两者混着标会严重干扰回归。5. YOLOV8 人流量统计落地的工程排错遮挡、密集场景与推理速度5.1 置信度与 IOU 在入口出口场景下怎么调视频里人流密集时检测器的默认参数往往不是最优的。conf太高被遮挡一半的人直接丢掉conf太低远处误检框混进追踪器产生大量短命 ID计数器把这些短命轨迹也算一次穿越总数立刻失真。先看目标框在画面里的平均像素高度。目标高度小于 40 像素时可以试探性把imgsz提到 768 或 960同时把conf降到 0.25 观察漏检变化。iou在密集场景应保持 0.4 到 0.5太高会让重叠的人框合并成一个ID 少一半。判断参数合不合适不要只看画面上框多不多要看叠加轨迹后同一个人的track_id是否稳定延续。5.2 导出 ONNX 后计数链路还能不能跑YOLOV8 原生推理在 CPU 上大约只有几帧每秒实际落地时多数项目会把模型导出成 ONNX再用 ONNX Runtime 或 TensorRT 推理。导出命令yolo export modelbest.pt formatonnx opset12 dynamicTruedynamicTrue允许输入尺寸动态变化代价是部分 Runtime 会退化为 CPU 算子如果确定只跑 640×640建议去掉dynamic推理速度会更快。导出后跑计数流程只需替换 detector 的实现import onnxruntime as ort import numpy as np sess ort.InferenceSession( best.onnx, providers[CUDAExecutionProvider, CPUExecutionProvider], ) input_name sess.get_inputs()[0].name def infer(frame): blob frame[:, :, ::-1].transpose(2, 0, 1)[None] / 255.0 pred sess.run(None, {input_name: blob.astype(np.float32)})[0] return pred逻辑说明sess.run返回的原始输出还需要按 YOLOV8 方式解码也就是把 8400 个候选框的位置、置信度解出来再做 NMS。ONNX 只替换检测部分ByteTrack 和计数器完全不动整个计数链路依然是检测到追踪再到计数的结构。导出后如果框的位置偏了优先检查预处理是否做了 BGR 到 RGB 的转换和归一化这两步最容易和 PyTorch 版本不一致。5.3 多路视频流与密集场景的工程处理接了多路摄像头之后逐帧串行推理会有明显延迟。常见做法是生产者消费者模型读帧线程只管往队列里放帧推理线程从队列取帧做检测。队列长度要限制不然某一路画面卡住队列积压会让延迟越来越严重最终内存上涨。多路场景下Python 的 GIL 会让线程切换开销被放大4 路以内可以用线程加队列解决超过 4 路我一般会改成每路一个子进程进程间通过共享内存或消息队列传帧。密集区域还要给计数器加一个状态冷却机制同一个track_id在 5 帧内不允许触发第二次计数事件防止目标在计数线旁边来回移动时反复触发。注意相机安装角度直接影响整个系统的上限。侧上方 30 到 45 度角俯拍通道行人间遮挡最少正上方视角虽然美观但小目标多、检测框中心点抖动大跨线判断会变得不稳定。6. YOLOV8 人流量统计的精度验证技巧MOTA、IDSW 与置信度分段校准6.1 用事件序列验证方向准确率而不是只看总数只把累计进、出总数和人工计数对比掩盖的问题非常多。一个人从入口走进来又原路返回总人数可能没差但“进”“出”方向全错。正确做法是把项目输出的事件序列和人工标注的 Ground Truth 做对齐def f1_by_events(pred_events, gt_events): pred_events set(pred_events) # [(frame_id, direction, track_id)] gt_events set(gt_events) tp len(pred_events gt_events) precision tp / len(pred_events) if pred_events else 0 recall tp / len(gt_events) if gt_events else 0 f1 2 * precision * recall / (precision recall) if (precision recall) else 0 return precision, recall, f1逻辑说明pred_events是系统输出的计数事件gt_events是人工按帧标注的事件方向用in和out编码。直接按事件集合求交集的精度对时间戳偏差比较敏感所以每帧事件要允许前后 3 帧的容差窗口。通过这套指标能定位到具体是哪类错误track_id的大量切换会把一次真实穿线拆成两条轨迹表现为方向正确但事件数翻倍而track_id太少则表现为漏计需要回到追踪器的track_buffer上找原因。6.2 用置信度分段校准计数偏差统计计数时存在一种隐蔽偏差低置信度检测框在高人流时段激增系统把它们当成真实目标计数低峰时低置信度框占比下降同一套参数又没有问题。解决办法是把检测框按置信度分桶统计跑一段测试视频后和人工数对比找出在哪一段置信度区间产生了系统性多计。buckets [(0.25, 0.4), (0.4, 0.6), (0.6, 0.8), (0.8, 1.0)] stats {b: 0 for b in buckets} for score in all_conf_scores: for lo, hi in buckets: if lo score hi: stats[(lo, hi)] 1逻辑说明把全部检测框的置信度分桶统计能够观察不同人流密度下模型输出分布的变化。如果高人流时 0.25 到 0.4 区间的框数暴涨而这段区域的框大量是误检说明当前阈值定得太低把阈值提到 0.4 更合适。确认后把修正值写进配置文件下次接新场景时只需要改配置不改代码。本文还有配套的精品资源点击获取