尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于YOLOV8的进出口人流量统计:从训练到越线计数实战

基于YOLOV8的进出口人流量统计:从训练到越线计数实战 简介这份资源面向计算机视觉方向的毕业设计、期末大作业与课程设计需求者提供一套基于YOLOV8的进出口人流量统计识别完整Python项目。项目围绕视频流中人员进出方向的检测与计数展开可应用于商场、车站、校园等场景的客流分析代码注释详尽新手也能快速理解与部署。压缩包共72个文件约2.13MB其中21个py文件承载模型推理与统计逻辑26个yaml文件负责数据集与模型配置另有18个pyc编译文件、3张png与1张jpg用于界面与效果展示2个md及1份docx手册提供文档说明整体结构清晰、模块分明。目前已有129人学习关注。项目经过严格调试可直接作为毕设或大作业使用读者能获得完整可运行的源码、配置模板与说明文档便于二次开发与功能扩展具备较高的实际应用与参考价值。1. 进出口人流量统计为什么总在闸机口翻车做过商场、园区、地铁出入口客流统计的同行大概率都经历过这种场景模型在 COCO 上跑得挺欢一挪到自家闸机口人一多、一挤、一逆光计数就开始飘。基于 YOLOV8 的进出口人流量统计识别本质上是把「目标检测」和「越线计数」这两件事拼在一起YOLOV8 负责在每一帧里框出人计数逻辑负责判断这个人到底是进还是出。它解决的不是「有没有人」这种粗粒度问题而是「这一分钟进了几个、出了几个、当前场内还剩几个」这种带方向的统计需求。适合谁做智慧园区、连锁门店、景区出入口、工厂考勤通道的 Python 开发者尤其是手里已经有一份 YOLOV8 源码和文档说明、想把它真正跑在进出口场景里的人。热词里 yolov8 训练自己的数据集、yolov8 环境配置、python 安装这些诉求基本都会在这条链路上遇到。下面我按自己落地的顺序把选型、训练、计数、避坑一层层拆开讲。2. 进出口计数方案选型检测加越线为什么比端到端更稳2.1 三种主流路线的取舍进出口人流量统计市面上常见三条路线。第一条是纯检测加越线计数YOLOV8 出人框再用一条虚拟线判断轨迹方向第二条是检测加跟踪再加计数在检测基础上挂 ByteTrack 或 BoT-SORT靠 track id 去重第三条是端到端的人群计数网络直接回归人数密度图。我一般会选第一条或第二条原因很实在。端到端密度图回归出来的只是一个数字它不告诉你这个人是进还是出进出口场景最核心的「方向」信息直接丢了。而检测加越线方向是靠轨迹穿线的先后顺序算出来的逻辑透明出问题能查。检测加跟踪更稳但跟踪器在人群遮挡严重时会频繁换 id反而引入新的计数误差。所以我的默认方案是YOLOV8 检测 轻量跟踪 越线判定跟踪只用来做短时 id 关联不追求长时稳定。选 YOLOV8 而不是更早的版本主要是它在小目标和高密度场景下的召回比 v5 好一截而且 ultralytics 这套 Python 接口对新手友好python 安装完依赖就能跑推理不用折腾复杂的编译。2.2 环境配置与最小推理跑通先把环境跑通再谈训练。这一步对应热词里的 yolov8 环境配置和 python 安装。我习惯用 conda 建独立环境避免和系统里的 numpy、opencv 打架。# 创建独立环境python 版本建议 3.9 到 3.11 conda create -n yolov8_count python3.10 -y conda activate yolov8_count # 安装 ultralytics它会自动带上 torch 相关依赖 pip install ultralytics opencv-python numpy -i https://pypi.tuna.tsinghua.edu.cn/simple # 验证安装能打印版本号就说明环境通了 yolo version逻辑说明ultralytics 这个包把模型加载、推理、训练都封装成了统一接口装它等于把 YOLOV8 的主干能力一次性拉齐。参数说明-i指定国内镜像源装 numpy、opencv 这类包时能明显提速python 版本别选 3.12 以上部分 torch 轮子还没跟上容易在装依赖时卡住。环境通了之后先用官方权重跑一张图确认推理链路没问题from ultralytics import YOLO # 加载预训练权重第一次运行会自动下载 model YOLO(yolov8n.pt) # 只检测人这一类COCO 里人的类别 id 是 0 results model.predict(sourcetest.jpg, classes[0], conf0.4) # 打印每张图里检测到的人数 for r in results: print(检测到人数:, len(r.boxes))逻辑说明classes[0]是关键进出口场景我们只关心人把其他 79 类过滤掉能减少误检和后续计算量。参数说明conf0.4是置信度阈值进出口逆光、侧脸多阈值设太高会漏人设太低会把广告牌上的人像也框进来0.4 是我在多数闸机场景下的起步值后面按实际误检情况微调。2.3 自训练数据集怎么准备预训练权重能跑通不代表能用进出口的俯视角度、逆光、密集遮挡和 COCO 的日常视角差得远。热词里 yolov8 训练自己的数据集说的就是这一步。数据来源一般是现场摄像头截帧标注工具用 labelImg 或 X-AnyLabeling只标 person 一类。标注完转成 YOLO 格式目录结构按 ultralytics 的要求摆# 数据集目录结构 dataset/ ├── images/ │ ├── train/ # 训练集图片 │ └── val/ # 验证集图片 ├── labels/ │ ├── train/ # 对应 txt 标注 │ └── val/ └── data.yaml # 数据集描述文件data.yaml内容如下# 数据集路径用绝对路径最稳 path: /home/user/dataset train: images/train val: images/val # 类别数和类别名进出口场景只有人 nc: 1 names: [person]逻辑说明YOLO 格式的标注是「类别 中心x 中心y 宽 高」全部归一化到 0 到 1 之间和 VOC 的左上角加右下角坐标不一样转换时最容易在这里翻车。参数说明nc: 1表示单类names顺序必须和标注里的类别 id 对应标错顺序模型学出来的就是错的。3. 训练与调参让模型在闸机口认得出人3.1 训练命令与关键参数数据摆好就能开训。进出口场景我一般从 yolov8s 起步n 太小召回不够m 以上在边缘设备上又跑不动。# 从预训练权重开始微调比从头训收敛快很多 yolo detect train \ datadataset/data.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projectruns/train \ namegate_person逻辑说明modelyolov8s.pt表示在预训练权重基础上微调进出口数据量通常几千张从头训根本不够收敛。参数说明epochs100是上限配合patience20做早停验证指标 20 轮不涨就停省时间imgsz640是输入分辨率闸机口人离镜头近、目标大640 够用如果摄像头架得高、人很小要提到 960 或 1280batch16按显存调8G 显存跑 yolov8s 加 640 分辨率16 基本是上限。训练过程想看损失曲线ultralytics 会在runs/train/gate_person/下自动生成results.csv用几行 matplotlib 就能画出来这也是热词里 yolov8 画损失函数曲线图的常见做法import pandas as pd import matplotlib.pyplot as plt # 读取训练日志 df pd.read_csv(runs/train/gate_person/results.csv) # 去掉列名里的空格方便索引 df.columns df.columns.str.strip() plt.plot(df[epoch], df[train/box_loss], labelbox_loss) plt.plot(df[epoch], df[val/box_loss], labelval_box_loss) plt.xlabel(epoch) plt.ylabel(loss) plt.legend() plt.savefig(loss_curve.png)逻辑说明train 和 val 两条损失一起看才能判断是欠拟合还是过拟合。参数说明如果 val 损失早早开始往上走而 train 还在降说明过拟合要么加数据要么加增强如果两条都居高不下说明学习率或模型容量有问题。3.2 进出口场景的增强策略默认增强对进出口场景不够用逆光和遮挡是两大杀手。我一般会在训练配置里显式调几个增强参数# 在 data.yaml 同级或训练命令里覆盖增强参数 hsv_v: 0.5 # 亮度扰动抗逆光 hsv_s: 0.7 # 饱和度扰动 degrees: 5.0 # 小角度旋转模拟摄像头轻微倾斜 translate: 0.1 # 平移模拟人在画面里位置变化 scale: 0.5 # 缩放兼顾远近目标 mosaic: 1.0 # 马赛克增强提升密集场景鲁棒性逻辑说明hsv_v调高是为了让模型见过各种曝光条件闸机口早晚逆光差异极大不增强的话模型只在某一时段好用。参数说明degrees别开太大进出口摄像头基本是固定俯视角度旋转太多反而引入不真实的样本mosaic保持 1.0它对密集小目标提升明显但训练最后 10 轮建议关掉让模型适应真实单图分布。3.3 验证指标怎么看训练完别只看 mAP进出口计数更关心漏检和误检的平衡。跑验证yolo detect val modelruns/train/gate_person/weights/best.pt datadataset/data.yaml重点看三个数precision 高说明误检少recall 高说明漏检少mAP50 是综合。进出口场景我宁可 recall 高一点漏一个人计数就少一个误检还能靠后续的越线逻辑和面积过滤补救。如果 recall 明显偏低先把 conf 阈值降下来看是不是阈值卡太狠再考虑加数据。4. 越线计数把检测框变成进出数字4.1 虚拟线与方向判定原理检测只告诉你画面里有人计数要靠虚拟线。做法是在进出口画面里画一条线通常垂直于人流方向然后判断每个人的轨迹是从线的一侧穿到另一侧还是反向。核心是记录每个人上一帧的中心点和当前帧中心点连线看这条线段有没有和虚拟线相交相交的方向决定是进还是出。这里有个容易忽略的点单靠检测框中心点抖动会误判所以要么加跟踪拿稳定 id要么对中心点做几帧平滑。我一般用跟踪因为跟踪还能解决同一个人被重复计数的问题。4.2 用 ByteTrack 做 id 关联ultralytics 内置了跟踪接口一行就能挂上from ultralytics import YOLO import cv2 model YOLO(runs/train/gate_person/weights/best.pt) # 打开进出口摄像头或视频文件 cap cv2.VideoCapture(gate.mp4) # 虚拟线两端点坐标按实际画面调整 line [(100, 400), (900, 400)] while cap.isOpened(): ret, frame cap.read() if not ret: break # persistTrue 让跟踪器在帧间保持状态 results model.track(frame, classes[0], conf0.4, persistTrue, trackerbytetrack.yaml) # 后续越线判定逻辑在这里接入 annotated results[0].plot() cv2.imshow(gate, annotated) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()逻辑说明model.track在检测基础上挂了跟踪器每个框会多出一个id同一个人跨帧 id 不变这是去重的基础。参数说明persistTrue必须开否则每帧跟踪状态被重置id 会乱跳trackerbytetrack.yaml是 ultralytics 自带的配置文件人群密集时可以换成 BoT-SORT但速度会慢一些。4.3 越线判定的完整实现把跟踪结果接上越线逻辑核心是维护一个 id 到历史中心点的字典from collections import defaultdict # 记录每个 track id 的历史中心点 track_history defaultdict(list) # 进出计数 count_in, count_out 0, 0 def cross_line(p1, p2, p3, p4): 判断线段 p1p2 与 p3p4 是否相交 def ccw(a, b, c): return (c[1] - a[1]) * (b[0] - a[0]) (b[1] - a[1]) * (c[0] - a[0]) return ccw(p1, p3, p4) ! ccw(p2, p3, p4) and ccw(p1, p2, p3) ! ccw(p1, p2, p4) # 在每帧跟踪结果里遍历 boxes results[0].boxes if boxes.id is not None: for box, tid in zip(boxes.xyxy, boxes.id): tid int(tid) # 计算框中心点 cx float((box[0] box[2]) / 2) cy float((box[1] box[3]) / 2) history track_history[tid] history.append((cx, cy)) # 只保留最近 30 帧防止内存膨胀 if len(history) 30: history.pop(0) # 至少有两帧才判断越线 if len(history) 2: prev, curr history[-2], history[-1] if cross_line(prev, curr, line[0], line[1]): # 用 y 方向变化判断进出具体方向按摄像头朝向定 if prev[1] line[0][1] curr[1]: count_in 1 elif prev[1] line[0][1] curr[1]: count_out 1逻辑说明cross_line用叉积判断两条线段是否相交这是几何里的标准做法比单纯比较坐标稳。参数说明历史长度 30 帧是个经验值帧率 25 时约 1.2 秒足够覆盖一个人穿线的时间进出方向判断里的prev[1] line[0][1] curr[1]表示从线上方穿到下方算进实际部署时按摄像头朝向反过来即可这个方向一定要现场核对搞反了进出数字全错。5. 进出口计数避坑五条血泪经验5.1 同一个人被反复计数现象明明只过去一个人计数加了三四次。原因跟踪 id 在遮挡后断裂同一个人被分配了新 id历史轨迹清空后重新穿线。解决一是把track_history的清理逻辑改成按时间而不是按帧数id 消失超过一定时间才清二是加一个「已计数 id 集合」同一个 id 只允许计一次三是提高跟踪器的匹配阈值减少 id 切换。5.2 逆光时段漏检严重现象白天计数正常早晚逆光时人数明显偏少。原因训练数据里逆光样本太少模型对高曝光区域的人不敏感。解决现场补采逆光时段截帧重新标注训练时把hsv_v调到 0.5 以上推理时适当降低 conf 阈值宁可多检一点靠后续逻辑过滤。5.3 虚拟线位置选错导致来回抖动现象有人在门口徘徊计数反复加。原因虚拟线画在了人容易停留的区域比如闸机正前方。解决把线画在人流通过性最强、停留最少的位置通常离闸机一到两米同时加一个「穿线后冷却时间」同一个 id 在冷却期内不再计数。5.4 边缘设备上帧率掉到个位数现象本地电脑跑得好好的部署到 RK3588 这类边缘盒子后卡成幻灯片。原因没做模型转换和量化直接跑 PyTorch 权重。解决导出 ONNX 或 RKNN 格式用 INT8 量化输入分辨率按实际需要降进出口场景 640 通常够实在不行降到 416。热词里 yolov8 部署到 rk3588 说的就是这条链路转换时注意算子兼容性部分后处理要自己用 numpy 重写。5.5 类别过滤没做导致误检现象画面里的广告牌、海报人像被当成真人计数。原因推理时没限制类别或者训练数据里混入了非人样本。解决推理时classes[0]强制只输出人训练时把误检样本作为负样本加进去让模型学会区分真人和图像。6. 计数精度验证与长期稳定运行的小技巧模型上线只是开始进出口计数最怕的是「今天准明天飘」。我自己的习惯是每周抽一段录像做人工核对把系统计数和人工计数对比误差超过 5% 就回头查是漏检还是重复计数。验证时别只看总数要分时段看早高峰、午间、晚高峰各抽一段因为不同时段光照和人流密度差异很大总数对得上不代表每个时段都对。一个具体技巧是加「进出平衡校验」。正常情况下一段时间内的进场人数减去出场人数应该等于场内实际人数变化。如果这个等式长期对不上说明计数逻辑有系统性偏差比如方向判反了或者某类人被漏检。这个校验不需要额外硬件只要在数据库里存好每分钟的进出数写个定时任务算差值就行。# 简单的进出平衡校验按分钟聚合 def check_balance(records): records 是 [(minute, in_count, out_count), ...] total_in sum(r[1] for r in records) total_out sum(r[2] for r in records) # 场内净增人数 net total_in - total_out # 和实际场内人数变化对比偏差过大就告警 return net逻辑说明这个函数本身很简单价值在于把它接进监控告警让计数偏差能被及时发现而不是等运营来投诉。参数说明聚合粒度按场景定商场用分钟级景区用小时级就够。另一个习惯是给虚拟线和方向判定加「配置化」。进出口的摄像头角度、人流方向经常因为装修或调整而变化如果把线的坐标和方向写死在代码里每次调整都要改代码重新部署。我一般把它们放到配置文件里改完重启服务即可省下大量来回折腾的时间。这套方案值不值得做如果你手里已经有 YOLOV8 的源码和文档说明把检测跑通到计数上线通常一两周能出第一版剩下的时间基本都花在现场调参和避坑上而这些坑上面基本都列到了。希望帮到你。本文还有配套的精品资源点击获取
返回列表