基于YOLOV8与ByteTrack的进出口人流量统计实战
简介这份资源面向计算机视觉方向的本科生与深度学习初学者提供一套基于YOLOv8的进出口人流量统计识别完整项目可直接用于毕业设计、期末大作业或课程设计。项目围绕视频流中行人检测与进出方向计数展开代码注释详尽新手也能读懂并快速部署运行。压缩包共72个文件约2.13MB其中21个Python脚本承担模型推理、视频处理与统计逻辑26个yaml文件用于数据集与模型参数配置另有18个pyc缓存、3张png与1张jpg示意图、2份md说明及1份docx手册结构清晰、模块分明。目前已有128人学习关注。项目经过严格调试功能完善、界面美观、操作简单配套文档对手册与运行流程均有说明读者可据此掌握YOLOv8训练、检测与进出口计数的完整实现思路并直接作为高分毕设或大作业提交。1. 从一段门口监控说起YOLOV8 进出口人流量统计到底在数什么商场、园区、地铁口、连锁门店几乎每个出入口上方都挂着摄像头但绝大多数只是录像没人真去数今天进了多少人、几点是高峰、哪个门承担了主要客流。基于 YOLOV8 的进出口人流量统计识别要解决的就是把这段只录不数的视频变成一条条带方向、带时间戳的进出记录。它的核心不是单纯检测到人而是判断这个人是在进还是在出再按时间窗口累加。整套方案用 Python 落地配合一份文档说明把环境配置、模型推理、越线判定、计数逻辑串成可复现的流程。适合做智慧门店、园区安防、客流分析的开发者也适合刚接触 YOLOV8、想找一个完整小项目练手的人。下面按先立住原理、再动手复现、最后讲坑的顺序拆开讲。2. 进出口计数的技术底座检测、跟踪、越线三件事怎么串2.1 为什么不能只靠 YOLOV8 检测框直接数人头很多人第一反应是YOLOV8 每帧都能框出人那我把每帧的人数加起来不就行了这个思路一跑就翻车。视频是 25 帧每秒同一个人在两秒内会被检测到 50 次直接累加得到的是人帧数不是人数。更麻烦的是检测框本身不带身份第 10 帧的框和第 11 帧的框是不是同一个人模型不会告诉你。所以进出口统计必须引入两个额外环节一是跟踪给每个检测到的人分配一个跨帧稳定的 ID二是越线判定只有当某个 ID 的轨迹从线的一侧移动到另一侧时才记一次进出。YOLOV8 负责看到人跟踪算法负责记住是谁越线逻辑负责判断方向三者缺一不可。这也是为什么单纯拿 YOLOV8 做计数会得到离谱数字的根本原因。2.2 检测与跟踪的选型YOLOV8 ByteTrack 的常见组合检测端用 YOLOV8权重一般选yolov8n.pt或yolov8s.ptn 版在 CPU 上也能跑到可用帧率s 版精度更好但更吃算力。跟踪端常见做法是 ByteTrack它不需要额外的外观特征模型靠检测框的 IoU 和运动预测做关联速度快、依赖少适合进出口这种目标密集但遮挡不极端的场景。ByteTrack 的核心思路是把检测框按置信度分成高、低两组先用高置信度框匹配已有轨迹再用低置信度框去救那些被短暂遮挡的目标这样能显著减少 ID 跳变。在代码里跟踪器通常和检测器封装在一起调用一次就返回带track_id的结果。选型上如果场景里人流量极大、遮挡严重可以考虑 BoT-SORT 或加 ReID 的方案但代价是算力上升CPU 环境基本跑不动。对大多数进出口场景YOLOV8 ByteTrack 是性价比最高的起点。2.3 越线计数的判定逻辑与方向定义越线计数的本质是几何判断。先在画面里画一条虚拟线通常取进出口中间的水平线或垂直线然后记录每个track_id的历史中心点。当某个 ID 的最新中心点与线的位置关系和它之前记录的位置关系发生翻转时就判定为一次穿越。方向由翻转的方向决定从上到下算进从下到上算出或者反过来取决于摄像头朝向和业务定义。这里有个关键细节不能只看相邻两帧因为目标在线上来回抖动会导致反复计数。常见做法是维护一个状态机给每个 ID 记录当前所在侧只有状态从 A 侧稳定切换到 B 侧才计数并且加一个最小位移阈值过滤掉原地晃动。下面这段代码展示了核心判定逻辑。# 越线计数核心逻辑简化版 # line_y: 虚拟线的纵坐标tracks: 当前帧的跟踪结果 # track_history: 记录每个 id 的历史中心点 # counted_ids: 已经计过数的 id 集合防止重复 def count_crossing(tracks, line_y, track_history, counted_ids, directiondown): in_count, out_count 0, 0 for track in tracks: tid track.track_id # 取检测框中心点 x1, y1, x2, y2 track.to_ltrb() cx, cy (x1 x2) / 2, (y1 y2) / 2 # 记录历史只保留最近若干帧 track_history.setdefault(tid, []).append(cy) if len(track_history[tid]) 30: track_history[tid].pop(0) # 至少要有两帧历史才能判断方向 if len(track_history[tid]) 2: continue prev_y track_history[tid][-2] # 从线上方移动到下方 if prev_y line_y cy and tid not in counted_ids: if direction down: in_count 1 else: out_count 1 counted_ids.add(tid) # 从线下方移动到上方 elif prev_y line_y cy and tid not in counted_ids: if direction down: out_count 1 else: in_count 1 counted_ids.add(tid) return in_count, out_count这段逻辑里line_y是虚拟线的位置需要根据实际画面分辨率调整比如 1080p 画面里进出口中线大概在 540 附近。track_history保留最近 30 帧是为了判断运动趋势帧数太少容易误判太多则反应迟钝。counted_ids是关键的去重机制一个人穿过一次后就不再重复计数避免在线上抖动导致数字虚高。direction参数决定哪边算进、哪边算出实际部署时要和摄像头安装方向对齐。注意这里用的是中心点纵坐标如果虚拟线是垂直的就要换成横坐标判断逻辑对称。3. 从零跑通环境配置、推理脚本与计数模块的落地步骤3.1 Ubuntu 20.04 下配置 YOLOV8 CPU 版环境先解决环境问题。Ubuntu 20.04 是很多人的开发环境CPU 版 YOLOV8 不需要显卡也能跑适合先验证逻辑再上算力设备。第一步确认 Python 版本建议 3.8 到 3.10太新的版本某些依赖还没跟上。用 conda 或 venv 建一个独立环境避免污染系统 Python。然后安装 ultralytics 包它会自动拉取 torch 等依赖。CPU 版要显式装 CPU 版 torch否则可能默认拉 GPU 版导致体积巨大且跑不起来。下面是一套可复制的命令。# 创建并激活虚拟环境 conda create -n yolo_count python3.9 -y conda activate yolo_count # 安装 CPU 版 PyTorch注意 index-url 指向 cpu 版本 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # 安装 ultralyticsYOLOV8 官方包 pip install ultralytics # 验证安装 python -c from ultralytics import YOLO; print(ok)这几条命令里--index-url指向 CPU 版 wheel 源是关键不加的话 pip 可能装到带 CUDA 的版本在无显卡机器上导入就报错。ultralytics装好后会自动带上 opencv、numpy 等依赖。验证那一步打印ok说明环境通了。如果卡在下载可以换国内镜像源但注意镜像源同步可能有延迟版本对不上时优先用官方源。环境配好后把yolov8n.pt权重放到项目目录第一次运行会自动下载也可以提前手动下载好放进去避免现场没网。3.2 用 YOLOV8 做检测加跟踪的最小推理脚本环境通了之后先写一个最小脚本把检测和跟踪跑起来确认能拿到带track_id的结果。ultralytics 的model.track()方法已经把 ByteTrack 封装好了直接调用即可。下面这段脚本读一段视频逐帧推理打印每帧的跟踪框数量先不计数只验证跟踪是否稳定。from ultralytics import YOLO import cv2 # 加载模型n 版适合 CPU model YOLO(yolov8n.pt) # 打开视频源0 表示摄像头也可传视频文件路径 cap cv2.VideoCapture(entrance.mp4) while cap.isOpened(): ret, frame cap.read() if not ret: break # persistTrue 让跟踪器在帧之间保持状态 results model.track(frame, persistTrue, classes[0], verboseFalse) # classes[0] 只检测人这一类COCO 里 person 的 id 是 0 boxes results[0].boxes if boxes.id is not None: ids boxes.id.int().cpu().tolist() print(f当前帧跟踪到 {len(ids)} 人ID: {ids}) cap.release()这里persistTrue是跟踪的关键参数不加的话每帧都会重新初始化跟踪器track_id就失去跨帧意义。classes[0]限定只检测人减少其他物体干扰也省算力。boxes.id就是跟踪分配的 ID如果为 None 说明这一帧没检测到人。跑通后你会看到 ID 在连续帧里保持稳定偶尔因为遮挡跳变这是正常的后面用越线逻辑和去重机制能兜住大部分问题。如果发现 ID 跳变特别频繁可以调低conf阈值或换更大的模型。3.3 把计数模块接进推理循环检测跟踪稳定后把第 2 章的越线逻辑接进来。需要先确定虚拟线的位置可以在画面上叠加一条线可视化方便调试。计数结果按帧累加同时把进出人数画到画面上方便肉眼核对。下面是把计数接进主循环的完整片段。from ultralytics import YOLO import cv2 model YOLO(yolov8n.pt) cap cv2.VideoCapture(entrance.mp4) # 读取一帧确定画面尺寸用于设定虚拟线 ret, first cap.read() h, w first.shape[:2] line_y int(h * 0.5) # 虚拟线放在画面中间 track_history {} counted_ids set() total_in, total_out 0, 0 while cap.isOpened(): ret, frame cap.read() if not ret: break results model.track(frame, persistTrue, classes[0], verboseFalse) boxes results[0].boxes if boxes.id is not None: for box, tid in zip(boxes.xyxy, boxes.id.int().cpu().tolist()): x1, y1, x2, y2 box.cpu().numpy() cx, cy (x1 x2) / 2, (y1 y2) / 2 track_history.setdefault(tid, []).append(cy) if len(track_history[tid]) 30: track_history[tid].pop(0) if len(track_history[tid]) 2: prev_y track_history[tid][-2] if prev_y line_y cy and tid not in counted_ids: total_in 1 counted_ids.add(tid) elif prev_y line_y cy and tid not in counted_ids: total_out 1 counted_ids.add(tid) # 画虚拟线和计数结果 cv2.line(frame, (0, line_y), (w, line_y), (0, 255, 0), 2) cv2.putText(frame, fIN: {total_in} OUT: {total_out}, (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) cv2.imshow(count, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这段代码把检测、跟踪、越线、可视化串成一条线。line_y取画面高度的一半实际部署时要根据摄像头俯仰角调整线要落在人实际经过的位置。track_history和counted_ids是全局状态跨帧保持。画面上叠加的 IN/OUT 数字方便实时核对如果发现数字跳得离谱多半是虚拟线位置不对或者 ID 跳变导致重复计数。调试阶段建议先用一段已知人数的视频跑比如让三五个人来回走看计数是否吻合再上真实场景。4. 进出口场景的避坑与排查那些让计数失准的细节4.1 现象同一个人被反复计数数字虚高原因通常有两个。一是虚拟线位置太靠近画面边缘或人停留区域目标在线上来回微动就触发多次穿越。二是counted_ids去重机制没生效比如每次循环重建了集合或者 ID 跳变后新 ID 被当成新人。解决方法是给越线判定加最小位移阈值要求中心点移动超过一定像素才算穿越同时确认counted_ids是全局变量。另外可以把虚拟线放在人不会停留的区域比如门口内侧而不是门槛正上方。4.2 现象ID 频繁跳变一个人变成好几个这是跟踪环节的典型问题。原因可能是检测框置信度波动大或者目标被遮挡后重新出现。解决方向一是调低conf阈值让检测更连续但别太低否则误检增多二是换用更大的模型提升检测稳定性三是缩短track_history的窗口让判定更灵敏。如果场景遮挡严重ByteTrack 扛不住就要考虑加 ReID 特征但 CPU 环境要权衡算力。实际项目里ID 跳变不可能完全消除靠去重和状态机兜底是更现实的做法。4.3 现象CPU 上帧率太低视频跑不动YOLOV8n 在普通 CPU 上单帧推理大概几十到上百毫秒25 帧视频跑不满实时。解决办法有几个一是跳帧处理每两帧或三帧推理一次中间帧复用上次结果对计数影响不大二是降低输入分辨率imgsz从 640 降到 416 或 320速度明显提升但小目标检测变差三是限定classes[0]只检测人省掉其他类别的计算。如果这些还不够就要考虑上 GPU 或专用加速设备比如 RK3588 这类带 NPU 的板子把模型转换后部署帧率能上一个大台阶。4.4 现象白天准晚上或逆光时漏检严重这是光照导致的检测退化不是计数逻辑的问题。YOLOV8 在训练数据里夜间样本少时对暗光目标置信度会明显下降。解决思路一是补夜间数据重新训练或微调模型这是最根本的二是加图像预处理比如直方图均衡化或 Gamma 校正提升暗部对比度三是调整摄像头曝光参数避免逆光时人变成剪影。如果短期内没法重训可以在推理前做一次亮度增强代价是增加一点预处理耗时。4.5 现象多人并排通过时只数到一个并排通过时检测框重叠跟踪器可能把两个人关联成一条轨迹导致漏计。原因是 ByteTrack 靠 IoU 匹配重叠严重时匹配歧义大。解决办法一是提高检测分辨率让并排的人框分得更开二是调低跟踪器的匹配阈值让关联更保守三是在业务上接受一定误差或者改用带 ReID 的方案区分个体。实际进出口场景里并排通过的比例不高多数情况影响可控但如果是宽通道就要认真对待这个问题。5. 让计数更稳的进阶技巧状态机、区域过滤与结果落库把基础流程跑通后真正决定这套方案能不能上生产的是稳定性和可追溯性。我一般会在越线逻辑外面再包一层状态机给每个track_id维护unknown / side_a / side_b三个状态只有状态发生确定迁移时才计数这样能过滤掉大量抖动。区域过滤也很实用在画面里画一个多边形只有中心点落在进出口区域内的目标才参与计数排除画面边缘路过的无关人员。结果落库方面把每次进出事件写成一条记录包含时间戳、方向、track_id存进 SQLite 或 CSV方便后续做时段客流分析。验证方法上我习惯用一段已知人数的视频做回归测试每次改参数都跑一遍看计数偏差是否在可接受范围。参数调优没有银弹虚拟线位置、历史窗口长度、置信度阈值这三个是最常动的建议做成配置文件别硬编码在脚本里。这套东西我踩过最深的坑就是一开始迷信模型精度后来发现计数准不准七成取决于虚拟线位置和去重逻辑模型只要稳定检测到人就够了。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →