资讯动态

YOLOv8+ByteTrack人员轨迹跟踪:从检测框到ID轨迹的CPU实战

发布时间:2026/10/1 2:29:53 来源:尧图企业网站定制
简介本资源为基于YOLOv8的人员轨迹跟踪算法实现包面向计算机视觉方向的学习者、算法工程师及需要快速搭建行人跟踪demo的开发者。资源围绕YOLOv8目标检测与多目标跟踪的融合应用展开可用于视频监控、客流统计、行为分析等场景帮助读者理解检测与跟踪的完整链路。压缩包共8个文件约50.08MB包含pt模型权重、py推理脚本、requirements依赖清单、md说明文档、txt配置文件以及多段mp4演示视频覆盖从环境配置到运行验证的主要环节。目前已有226人学习下载。通过该资源读者可获得可直接运行的跟踪代码与预训练权重借助演示视频直观对比输入输出效果并参考说明文档快速完成环境搭建与参数调整适合作为YOLOv8跟踪方向的入门实践与二次开发基础。1. 从检测框到身份链人员轨迹跟踪到底在解决什么厂区安全帽检测、门店客流统计、工地越界告警——这些场景里YOLOv8 给出的只是每一帧里散落的检测框而业务真正要的是「谁、从哪来、到哪去、停留多久」。把逐帧检测框串成带 ID 的连续轨迹就是人员轨迹跟踪算法要干的事。它本质上是「检测 关联」两段式流水线YOLOv8 负责每帧找出人跟踪器负责跨帧把同一个人的框连起来再叠加业务规则输出轨迹。适合有 Python 基础、想快速把检测升级成轨迹分析的工程师也适合拿它做毕业设计或落地原型。下面这套方案我按 CPU 也能跑通的最小闭环来写GPU 只是加速不是门槛。2. 检测与跟踪的分工为什么是 YOLOv8 加 ByteTrack2.1 两段式流水线的职责边界很多人一上来就想训一个端到端模型直接吐轨迹结果发现标注成本高得离谱而且换场景就得重标。常见做法是把问题拆开检测器只关心「这一帧有没有人、人在哪」跟踪器只关心「这一帧的框和上一帧的哪个框是同一个人」。这样检测器可以复用现成的 YOLOv8 预训练权重跟踪器用成熟的关联算法两者解耦后各自替换、各自调优。YOLOv8 在这里的角色是「高质量候选框生产者」。它输出的是[x1, y1, x2, y2, conf, cls]跟踪器拿到这些框后先按置信度过滤再用运动预测和外观特征做匹配。检测质量直接决定跟踪上限——漏检一帧轨迹就断误检一个框就可能凭空多出一个 ID。所以调跟踪之前先把检测的置信度和 NMS 调稳。跟踪器我一般选 ByteTrack原因是它不依赖 ReID 外观模型也能跑得不错对 CPU 友好且对低分框的处理很聪明高分框先匹配剩下的低分框再和没匹配上的轨迹做二次关联能救回一部分被遮挡的目标。相比 DeepSORT 需要额外跑一个 ReID 网络ByteTrack 在人员密集但外观差异不大的场景里性价比更高。2.2 环境搭建CPU 版本也能跑通的最小命令先解决「yolov8 环境配置」这个高频问题。Ubuntu 20.04 上 CPU 版本完全够做验证装包不要贪多。# 创建独立环境避免和系统 Python 冲突 conda create -n track python3.10 -y conda activate track # 安装 ultralytics自带 YOLOv8 和 ByteTrack 接口 pip install ultralytics # 跟踪场景常用的两个包opencv 读视频lap 做匈牙利匹配 pip install opencv-python lap # 验证安装能打印版本就说明环境通了 python -c import ultralytics; print(ultralytics.__version__)逻辑说明ultralytics这个包把模型加载、推理、跟踪封装成了一套 APImodel.track()内部就集成了 ByteTrack不需要你单独 clone 跟踪仓库。lap是线性分配库ByteTrack 做框与轨迹匹配时依赖它缺了会在运行时报lap not found。参数上Python 选 3.10 是因为 3.12 早期版本和部分 torch 轮子有兼容问题3.10 最稳。提示如果你只有 CPU第一次跑model.track()会明显慢属于正常现象先把流程跑通再考虑上 GPU 或换 RK3588、Orin 这类边缘板。2.3 最小可运行脚本把视频跑成带 ID 的轨迹下面这段是能直接抄的骨架输入一段监控视频输出带 ID 标注的结果视频和轨迹文本。from ultralytics import YOLO import cv2 # 加载预训练权重首次运行会自动下载 yolov8n.pt model YOLO(yolov8n.pt) # 打开视频源也可以换成 0 调用摄像头 cap cv2.VideoCapture(input.mp4) writer None # 用字典缓存每个 ID 的历史轨迹点 tracks {} while cap.isOpened(): ret, frame cap.read() if not ret: break # persistTrue 是关键它让跟踪器在帧之间保持状态 results model.track( frame, persistTrue, trackerbytetrack.yaml, classes[0], # 只跟踪 person 类COCO 里 person 的 id 是 0 conf0.3, # 检测置信度阈值太低会引入误检 iou0.5, # NMS 的 IoU 阈值 verboseFalse, ) if writer is None: h, w frame.shape[:2] writer cv2.VideoWriter( output.mp4, cv2.VideoWriter_fourcc(*mp4v), 25, (w, h) ) # 解析结果把框和 ID 画出来 if results[0].boxes.id is not None: boxes results[0].boxes.xyxy.cpu().numpy() ids results[0].boxes.id.cpu().numpy().astype(int) for box, tid in zip(boxes, ids): x1, y1, x2, y2 map(int, box) cx, cy (x1 x2) // 2, (y1 y2) // 2 tracks.setdefault(tid, []).append((cx, cy)) 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) writer.write(frame) cap.release() writer.release() # 轨迹落盘每行一个点方便后续做停留时长、越界判断 with open(tracks.txt, w) as f: for tid, pts in tracks.items(): for p in pts: f.write(f{tid},{p[0]},{p[1]}\n)逻辑说明persistTrue是整段代码的命门不加它每帧都会重新初始化跟踪器ID 会疯狂跳变。classes[0]把检测限制在人类减少无关目标干扰匹配。tracks字典用 ID 做 key 累积中心点这就是最朴素的轨迹表示后面做停留分析、热力图都从它出发。参数说明conf0.3是跟踪场景的经验值比纯检测常用的 0.25 略高因为低分框进入跟踪会制造假 IDiou0.5控制 NMS 合并程度人群密集时可以降到 0.45 减少框被吞。yolov8n.pt是最小的 nano 模型CPU 上帧率能接受精度要求高再换yolov8s.pt或yolov8m.pt。3. 让轨迹真正可用ID 稳定性和业务指标3.1 训练自己的数据集从 labelme 标注到 YOLO 格式预训练权重只认 COCO 的 80 类如果你的场景是特定工装、特定角度检测会飘跟踪自然跟着崩。这时要「yolov8训练自己的数据集」。标注工具用 labelme 或 labelImg 都行关键是转成 YOLO 需要的格式。import os import json import cv2 # 把 labelme 的 json 转成 YOLO 的 txt每行 class cx cy w h归一化 def labelme_to_yolo(json_dir, out_dir, class_map): os.makedirs(out_dir, exist_okTrue) for name in os.listdir(json_dir): if not name.endswith(.json): continue with open(os.path.join(json_dir, name)) as f: data json.load(f) img cv2.imread(os.path.join(json_dir, name.replace(.json, .jpg))) h, w img.shape[:2] lines [] for shape in data[shapes]: label shape[label] if label not in class_map: continue (x1, y1), (x2, y2) shape[points] cx (x1 x2) / 2 / w cy (y1 y2) / 2 / h bw abs(x2 - x1) / w bh abs(y2 - y1) / h lines.append(f{class_map[label]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(os.path.join(out_dir, name.replace(.json, .txt)), w) as f: f.write(\n.join(lines)) labelme_to_yolo(labels_json, labels_txt, {person: 0})逻辑说明YOLO 的标签是归一化后的中心点加宽高和 labelme 的绝对坐标两点表示不同必须转换。class_map把类别名映射成从 0 开始的整数person 固定为 0和跟踪时的classes[0]对齐。参数说明坐标保留 6 位小数足够多了没意义。转换后要检查有没有空 txt 文件空标签会被训练器当成负样本少量可以大量说明标注有问题。3.2 训练参数怎么设yolov8模型训练参数含义数据准备好后写data.yaml指定训练集、验证集路径和类别数然后开训。yolo detect train \ datadata.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projectruns/track逻辑说明modelyolov8n.pt表示在预训练权重上微调比从零训收敛快得多。patience20是早停20 轮验证指标不涨就停省时间。参数说明imgsz640是速度和精度的平衡点监控场景人比较小可以提到 960但显存和耗时翻倍batch16在 8G 显存上比较稳爆显存就降到 8lr00.01是初始学习率微调场景可以降到 0.001 更稳。训练完看runs/track/weights/best.pt把它替换到跟踪脚本的YOLO()里即可。3.3 从轨迹到业务指标停留、越界、计数有了 ID 和轨迹点业务指标就是几何计算。停留时长用同一 ID 首末点的时间差越界用线段相交判断计数用「首次出现」去重。# 判断点是否在多边形区域内用于区域停留统计 import numpy as np def in_polygon(point, polygon): # polygon 是 [(x1,y1), (x2,y2), ...] return cv2.pointPolygonTest( np.array(polygon, dtypenp.int32), point, False ) 0 # 统计每个 ID 在目标区域内的停留帧数 dwell {} for tid, pts in tracks.items(): count sum(1 for p in pts if in_polygon(p, region)) if count 0: dwell[tid] count / 25.0 # 假设 25fps换算成秒逻辑说明pointPolygonTest返回正数表示点在多边形内这是 OpenCV 自带的几何工具比自己写射线法省事。停留秒数用帧数除以帧率帧率要和视频实际帧率一致否则数值全错。参数说明region是业务画的区域多边形坐标要和视频分辨率同一坐标系。计数场景要注意 ID 切换问题一个人被遮挡后重新出现可能拿到新 ID导致重复计数后面避坑章节会讲怎么缓解。4. 避坑与排查ID 跳变、漏检、性能这三座大山4.1 现象同一个人 ID 频繁跳变原因检测框抖动大、遮挡导致轨迹中断、persist没开或跟踪器每帧重置。解决先确认persistTrue再把conf提到 0.4 以上过滤低质框遮挡严重的场景换botsort.yaml它带 ReID 外观特征代价是慢一些。如果还跳检查视频帧率是否被错误读取帧率不对会让运动预测失准。4.2 现象人一多就漏检、轨迹断原因NMS 的iou太高把重叠的人框合并了或者模型本身对小目标不敏感。解决把iou降到 0.45imgsz提到 960必要时用yolov8s.pt换掉 nano。密集场景还可以开agnostic_nmsFalse保留类别内独立框。4.3 现象CPU 上跑得比蜗牛还慢原因模型太大、分辨率太高、每帧都在做全图推理。解决换yolov8n.ptimgsz降到 480 先验证流程抽帧处理每 2 帧检测一次中间帧靠跟踪器预测。真要实时考虑 RK3588 或 Orin 这类带 NPU 的板子做部署CPU 只适合离线分析。4.4 现象轨迹文本里同一 ID 出现在两个相距很远的位置原因ID 复用旧轨迹没及时清理新目标被分配了已消失的 ID。解决在跟踪器配置里调track_buffer控制轨迹丢失后保留多少帧业务侧对轨迹做空间连续性校验相邻两点距离突变超过阈值就断开。4.5 现象换了场景精度暴跌原因预训练权重的域差异或者自己训的数据集类别不均衡。解决用目标场景数据微调标注时保证每个类别样本量接近训练时开mosaic增强提升小目标泛化但验证阶段关掉以免指标虚高。5. 进阶把跟踪结果做成可复现的评估闭环做到这一步脚本能跑、指标能出但你怎么证明「这次调参比上次好」靠肉眼看视频是不靠谱的得有量化评估。人员跟踪常用的指标是 MOTA、IDF1 和 ID Switch 次数MOT 格式的标注数据可以用motmetrics算。import motmetrics as mm import numpy as np # acc 累积器逐帧喂入真实框和预测框 acc mm.MOTAccumulator(auto_idTrue) # 每帧调用一次gt_ids 真实 IDpred_ids 预测 ID距离矩阵用 IoU 或中心点距离 acc.update( gt_ids, # 例如 [1, 2, 3] pred_ids, # 例如 [1, 5, 3] distance_matrix # shape (len(gt_ids), len(pred_ids)) ) mh mm.metrics.create() summary mh.compute(acc, metrics[mota, idf1, num_switches], namerun) print(summary)逻辑说明distance_matrix是真实框和预测框两两之间的距离通常用1 - IoU或中心点欧氏距离小于阈值才算匹配。num_switches就是 ID 跳变次数这个数字比 MOTA 更能反映跟踪稳定性调参时优先盯它。参数说明距离阈值一般取 0.5IoU 口径或按目标尺寸设中心点距离上限。评估要在同一段标注视频上跑换视频结果不可比。我自己的习惯是每次改完conf、iou、跟踪器配置都固定跑同一段 30 秒的标注片段记录num_switches和idf1只认数字不认感觉。血泪经验是很多看起来「顺眼」的调参其实让 ID Switch 涨了一倍只是肉眼没看出来。另外别迷信一步到位先把检测调稳再动跟踪参数两个一起改你根本不知道是谁的锅。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑