ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于YOLOv8的进出口人流量统计:检测跟踪越线计数全流程

2026/10/5 19:11:00 拓冰建站 浏览量
基于YOLOv8的进出口人流量统计:检测跟踪越线计数全流程 简介这份资源面向计算机视觉方向的毕业设计、期末大作业与课程设计需求者提供一套基于YOLOV8的进出口人流量统计识别完整Python项目。项目围绕视频流中人员进出方向的检测与计数展开代码注释详尽新手也能读懂部署后即可运行可直接作为毕设或课程设计提交。压缩包共72个文件约2.13MB其中21个py文件承载模型推理、视频检测与统计逻辑26个yaml文件用于数据集与模型配置另有18个pyc编译文件、3张png与1张jpg示意图、2个md说明及1份docx手册结构清晰便于按模块查阅。目前已有129人学习关注。项目经过严格调试功能完善、界面美观、操作简单配套文档对手册与运行流程均有说明读者可据此快速复现进出口人流统计效果并在此基础上替换数据集或调整跟踪参数完成二次开发与答辩展示。1. 进出口人流量统计为什么YOLOv8成了当前最稳的落地方案做进出口人流量统计最怕的不是模型精度不够而是场景太碎。商场大门、景区闸机、园区通道、地铁出入口每个口子的光照、角度、遮挡情况都不一样传统背景建模或者HOGSVM那套方案换个摄像头就得重新调参维护成本高得离谱。YOLOv8在这类场景里能快速上位核心原因是它把检测和跟踪的工程链路压缩到了可维护的范围内单阶段检测器推理快Python生态成熟训练和部署的代码量比两阶段方案少一大截。这套「基于YOLOV8的进出口人流量统计识别Python源码文档说明」本质上解决的就是一件事——给定一段进出口监控视频或RTSP流自动统计进出方向的人数输出结构化数据。适合谁用做智慧园区、连锁门店客流分析、景区人流管控的Python开发者有基本深度学习环境配置能力想找一个能跑通、能改、能落地的基线方案。热搜里yolov8训练自己的数据集、yolov8环境配置这些词频繁出现说明大量人卡在从零搭环境这一步这篇就按能复现的路径拆开讲。2. 进出口人流量统计的技术链路从检测到计数的完整拆解2.1 为什么选YOLOv8而不是YOLOv5或RT-DETRYOLOv8相比YOLOv5最大的工程优势在Anchor-Free解耦头。进出口场景里人形目标尺度变化大近处的人占画面三分之一远处的人只有几十像素Anchor-Based方案需要针对数据集重新聚类Anchor尺寸调参成本高。YOLOv8的Anchor-Free设计直接预测中心点和宽高省掉了这一步。RT-DETR虽然精度有优势但推理速度在边缘设备上不如YOLOv8稳定而且Python部署链路没有Ultralytics这套库成熟。实际选型时我一般看三个指标mAP0.5能到0.85以上、单帧推理在目标设备上低于50ms、模型导出ONNX后不出现算子不支持。YOLOv8n在COCO上mAP0.5约52.9换到人形检测微调后通常能到0.9以上这个基线足够支撑计数任务。2.2 检测跟踪越线计数的三段式架构人流量统计不是单纯的目标检测完整链路是逐帧检测人形框 → 多目标跟踪分配ID → 判断ID轨迹是否跨越预设的进出口界线。检测用YOLOv8跟踪用ByteTrack或BoT-SORT越线判断用射线法或向量叉积。为什么不用检测框中心点直接计数因为同一个人在两帧之间可能被漏检中心点跳变会导致重复计数。跟踪器的作用是把帧间的检测结果关联成轨迹只有轨迹稳定跨越界线才计数一次。ByteTrack的优势是低分检测框也参与关联遮挡场景下ID切换少进出口这种人群密集场景比SORT系列更稳。2.3 最小可运行环境搭建与依赖安装环境配置是热搜里出现频率最高的卡点。我一般用conda建独立环境Python版本锁3.9或3.103.11以上有些依赖轮子还没跟上。CUDA版本看显卡GTX1660Ti用CUDA 11.8就够RTX40系建议12.1以上。安装命令如下conda create -n yolov8_count python3.10 -y conda activate yolov8_count pip install ultralytics8.1.0 pip install opencv-python4.9.0.80 pip install lap0.4.0 pip install filterpy1.4.5ultralytics是YOLOv8的官方库封装了训练、推理、导出全流程。lap是线性分配库ByteTrack做匈牙利匹配时依赖它。filterpy用于卡尔曼滤波跟踪器预测目标下一帧位置时用。版本号不要随意升ultralytics 8.1.x和8.2.x的API有细微差异源码里如果写死了某个版本的方法名升级后会报AttributeError。装完后用yolo checks验证环境输出里CUDA状态显示可用才算配好。2.4 用YOLOv8跑通单帧检测的最小代码先不急着上跟踪把检测跑通确认模型和环境没问题from ultralytics import YOLO import cv2 # 加载预训练模型首次运行会自动下载yolov8n.pt model YOLO(yolov8n.pt) # 只检测人这一类COCO数据集中人的类别ID是0 results model.predict( sourcetest_gate.jpg, classes[0], # 过滤只保留人 conf0.4, # 置信度阈值进出口场景建议0.35-0.5 iou0.5, # NMS的IoU阈值 imgsz640, # 推理尺寸和训练尺寸保持一致 device0 # 0表示第一块GPUCPU填cpu ) for r in results: boxes r.boxes for box in boxes: x1, y1, x2, y2 box.xyxy[0].tolist() conf box.conf[0].item() print(fperson at ({x1:.0f},{y1:.0f})-({x2:.0f},{y2:.0f}) conf{conf:.2f})classes[0]是关键参数不加的话模型会把背包、手提袋也检出来后续跟踪会引入大量噪声轨迹。conf设0.4是平衡漏检和误检的经验值进出口逆光场景可以降到0.35但低于0.3后误检率明显上升。imgsz必须和训练时一致用640训练的模型推理时改成1280小目标召回会提升但速度减半而且框的坐标映射容易出偏差。2.5 接入ByteTrack实现轨迹ID分配检测框有了下一步给每个人分配稳定IDfrom ultralytics import YOLO import cv2 import numpy as np model YOLO(yolov8n.pt) cap cv2.VideoCapture(gate_video.mp4) # 用ultralytics内置的跟踪模式tracker配置选bytetrack.yaml while cap.isOpened(): ret, frame cap.read() if not ret: break results model.track( sourceframe, classes[0], conf0.4, iou0.5, trackerbytetrack.yaml, # 内置跟踪配置 persistTrue, # 保持帧间跟踪状态 verboseFalse ) if results[0].boxes.id is not None: ids results[0].boxes.id.int().cpu().tolist() boxes results[0].boxes.xyxy.cpu().numpy() for tid, box in zip(ids, boxes): x1, y1, x2, y2 box.astype(int) cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, fID:{tid}, (x1, y1-8), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imshow(count, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()persistTrue是跟踪模式的核心参数它让跟踪器在帧之间保持状态不加的话每帧都重新初始化ID会乱跳。bytetrack.yaml里有两个关键阈值track_high_thresh默认0.5track_low_thresh默认0.1前者决定哪些检测框进入主关联后者决定低分框是否用于二次匹配。人群密集时把track_low_thresh调到0.05能减少ID丢失但计算量会增加约15%。3. 越线计数逻辑怎么判断一个人是进还是出3.1 射线法与向量叉积的选型对比判断轨迹跨越界线常见做法有两种。射线法适合任意多边形区域判断点是否在区域内但需要记录目标上一帧和当前帧的区域状态状态翻转时计数。向量叉积法适合直线界线计算轨迹线段与界线的交点根据叉积符号判断方向。进出口场景界线通常是一条直线或者窄矩形带向量叉积法更直接计算量也小。我一般用叉积法代码逻辑是取目标轨迹的前一帧中心点P1和当前帧中心点P2界线两端点A和B计算cross(B-A, P1-A)和cross(B-A, P2-A)符号相反说明跨越了界线再根据cross(B-A, P2-P1)的符号判断进出方向。3.2 越线计数的完整实现代码import numpy as np from collections import defaultdict # 界线定义进出口通道中间画一条线A和B是线的两个端点 LINE_A np.array([200, 400]) LINE_B np.array([800, 400]) # 存储每个轨迹ID的历史中心点 track_history defaultdict(list) # 计数结果 count_in 0 count_out 0 def cross(o, a, b): 向量oa和ob的叉积 return (a[0]-o[0])*(b[1]-o[1]) - (a[1]-o[1])*(b[0]-o[0]) def check_cross_line(prev_pt, curr_pt): 判断轨迹是否跨越界线返回1表示进-1表示出0表示未跨越 d1 cross(LINE_A, LINE_B, prev_pt) d2 cross(LINE_A, LINE_B, curr_pt) if d1 * d2 0: return 0 # 同侧未跨越 # 计算轨迹方向与界线法向的关系 d3 cross(prev_pt, curr_pt, LINE_A) d4 cross(prev_pt, curr_pt, LINE_B) if d3 * d4 0: # 根据叉积符号判断方向具体正负取决于界线定义方向 return 1 if d1 d2 else -1 return 0 # 在跟踪循环中调用 for tid, box in zip(ids, boxes): cx (box[0] box[2]) / 2 cy (box[1] box[3]) / 2 curr_pt np.array([cx, cy]) if tid in track_history and len(track_history[tid]) 0: prev_pt track_history[tid][-1] direction check_cross_line(prev_pt, curr_pt) if direction 1: count_in 1 elif direction -1: count_out 1 track_history[tid].append(curr_pt) # 限制历史长度防止内存泄漏 if len(track_history[tid]) 30: track_history[tid].pop(0)track_history用defaultdict存储每个ID的中心点序列只保留最近30帧避免长视频内存持续增长。check_cross_line里d1d20表示两点在界线同侧直接返回0。d3d40确保轨迹线段和界线线段真正相交而不是在延长线上。方向判断的1和-1哪个是进哪个是出取决于LINE_A和LINE_B的坐标顺序实际部署时先用已知方向的视频校准一次。3.3 去重与防抖同一个人不能被计两次越线计数最常见的翻车是同一人来回走被重复计数。解决思路有三层第一层是轨迹长度过滤ID出现少于5帧的不参与计数排除误检产生的短轨迹第二层是越线冷却时间同一个ID在2秒内不重复计数第三层是轨迹合并如果两个ID的轨迹在时空上高度重叠判定为ID切换合并计数。冷却时间的实现用一个字典记录每个ID上次计数的时间戳import time last_count_time {} def should_count(tid, cooldown2.0): now time.time() if tid in last_count_time: if now - last_count_time[tid] cooldown: return False last_count_time[tid] now return Truecooldown设2秒是经验值人正常步行速度1.2m/s进出口通道宽度一般2-3米穿越时间约2秒。设太短防不住抖动设太长会漏计快速往返的人。4. 避坑与排查进出口人流量统计的5个血泪教训4.1 现象计数结果比实际人数多出30%以上原因YOLOv8在进出口场景把玻璃门反光、广告牌人像、电视屏幕里的人形也检出来了这些误检产生短轨迹每条轨迹跨越界线就计一次。解决提高conf到0.5以上同时在检测后加一个宽高比过滤正常人形框的宽高比在0.2到0.6之间超出范围的框直接丢弃。另外可以在界线附近设一个ROI区域只有中心点进入ROI的轨迹才参与计数。4.2 现象跟踪ID频繁切换同一个人被分配了多个ID原因ByteTrack的track_high_thresh设太高遮挡时检测框分数掉到阈值以下跟踪器丢失目标后重新分配新ID。解决把track_high_thresh从0.5降到0.4track_low_thresh从0.1降到0.05让低分框参与二次关联。同时把跟踪器的max_age参数调大默认30帧人群密集场景可以调到50帧给跟踪器更多时间找回目标。4.3 现象模型在GPU上推理正常导出ONNX后精度暴跌原因YOLOv8导出ONNX时默认用opset 17某些显卡的TensorRT版本不兼容导致算子回退到CPU或者精度损失。解决导出时指定opset12加simplifyTrue命令是yolo export modelbest.pt formatonnx opset12 simplifyTrue。导出后用onnxruntime加载验证对比PyTorch和ONNX的输出差异mAP掉超过2个点就要检查预处理是否一致。4.4 现象RTSP流读取延迟越来越高画面卡顿原因OpenCV的VideoCapture默认缓冲区会累积帧如果推理速度跟不上视频帧率缓冲区越积越多。解决设置cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)只保留最新一帧。另外把推理和读取放在不同线程读取线程只负责取帧推理线程从队列取最新帧处理丢掉的旧帧不影响计数结果。4.5 现象夜间红外模式下模型几乎检不出人原因COCO预训练模型没有红外图像YOLOv8的浅层特征对红外灰度图不敏感。解决用红外场景的数据微调模型至少准备500张标注图训练时冻结backbone前10层只训练neck和head学习率设0.00120个epoch就能看到明显提升。没有标注数据的话先把输入图像做直方图均衡化再送进模型能恢复一部分召回。5. 从能跑到好用进出口人流量统计的进阶调优技巧5.1 用区域掩码替代单条界线单条界线只能统计进出没法区分多个通道。实际进出口往往有2-3条并行通道每条通道单独计数才有意义。做法是给每条通道定义一个多边形ROI用射线法判断目标中心点落在哪个ROI内再在ROI内做越线判断。ROI的坐标用标注工具在视频首帧上画存成JSON配置文件部署时加载。这样一套模型可以同时统计多个通道互不干扰。5.2 模型量化与TensorRT加速YOLOv8n在RTX3060上FP32推理约8ms导出TensorRT FP16后降到3msINT8量化后能到2ms以下。INT8量化的关键是校准集要覆盖实际场景的光照和角度我一般从部署现场录10段各1分钟的视频抽300帧做校准。TensorRT引擎和显卡型号绑定换设备要重新生成所以部署包里要保留ONNX和校准集不能只发engine文件。精度推理耗时(RTX3060)mAP0.5变化适用场景FP328ms基线调试阶段FP163ms-0.2%生产部署INT81.8ms-1.5%边缘设备5.3 计数结果的持久化与可视化计数结果不能只打印在终端要落库。我一般用SQLite存每条越线记录字段包括时间戳、轨迹ID、方向、通道编号、置信度。SQLite零配置单文件适合边缘设备。可视化方面除了画面上的计数叠加再生成一张按小时聚合的折线图用matplotlib画存成PNG供后台展示。折线图的横坐标时间密度问题用plt.gca().xaxis.set_major_locator(mdates.HourLocator(interval1))控制避免标签重叠。import sqlite3 from datetime import datetime conn sqlite3.connect(flow_count.db) conn.execute( CREATE TABLE IF NOT EXISTS cross_events ( id INTEGER PRIMARY KEY AUTOINCREMENT, ts TEXT NOT NULL, track_id INTEGER, direction TEXT, channel INTEGER, confidence REAL ) ) def log_event(tid, direction, channel, conf): conn.execute( INSERT INTO cross_events (ts, track_id, direction, channel, confidence) VALUES (?,?,?,?,?), (datetime.now().isoformat(), tid, direction, channel, conf) ) conn.commit()这套方案我从去年跑到现在最大的体会是模型精度只是及格线计数逻辑的去重和防抖才是决定能不能上生产的关键。调参时别盯着mAP看拿一段已知人数的视频跑完整链路对比计数结果和实际人数的偏差偏差控制在3%以内才算过关。希望帮到你。本文还有配套的精品资源点击获取