ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

YOLOv8人员轨迹跟踪实战:从检测到轨迹的完整方案

YOLOv8人员轨迹跟踪实战:从检测到轨迹的完整方案 简介这份资源围绕YOLOv8目标检测模型构建了一套完整的人员轨迹跟踪算法实现面向计算机视觉入门与进阶开发者、需要做行人追踪项目的学生及工程人员。它解决的是从检测到多目标跟踪的落地问题适合安防监控、客流统计、视频分析等场景。压缩包共8个文件约50.08MB包含4段mp4演示视频、1个pt权重文件、1个py脚本、1个txt依赖清单、1个md说明文档覆盖模型权重、运行脚本与效果验证素材。已有226人学习下载说明该方案具备一定参考价值。读者可获得可直接运行的YOLOv8人员跟踪代码、预训练权重、依赖配置说明以及多段输入输出对比视频便于快速复现检测与轨迹绘制效果并在此基础上调整参数、替换视频源或迁移到自有数据。整体结构紧凑适合作为课程设计、毕业设计或算法验证的起步模板。1. 从检测框到运动轨迹这套 YOLOv8 跟踪方案到底能跑出什么很多人第一次做人员轨迹跟踪都是拿 YOLOv8 跑通检测后直接接一个现成的跟踪器结果发现画面里人一多ID 就乱跳轨迹线像毛线团一样缠在一起。这套基于 YOLOv8 的人员轨迹跟踪算法核心思路是把检测、跟踪、轨迹管理拆成三层YOLOv8 负责逐帧出人框ByteTrack 或 BoT-SORT 负责把框和上一帧的轨迹做关联再用一个轻量的轨迹缓存模块把每个 ID 的历史坐标串成折线。它解决的不是“能不能检测到人”而是“同一个人在第 3 帧和第 300 帧能不能被认成同一个 ID”。适合做安防巡检、客流统计、工地安全帽轨迹回放这类需要连续身份保持的场景也适合拿来做毕业设计里“检测跟踪”的完整闭环。如果你只跑过 YOLOv8 的 detect 脚本没碰过跟踪器的关联逻辑这套东西正好补上中间缺的那一环。2. 环境搭建与权重准备从零把 YOLOv8 跟踪链路跑起来2.1 选 Ultralytics 还是自己搭推理后端常见做法是直接用 Ultralytics 官方包因为它把 YOLOv8 的推理、训练、导出都封装好了跟踪器也内置了 ByteTrack 和 BoT-SORT 的接口。如果你后面要部署到 RK3588 或 Orin 这类板端前期在 PC 上先用 Ultralytics 把跟踪逻辑调通再导出 ONNX 或 RKNN比一上来就啃底层推理省事得多。我一般会在 Ubuntu 20.04 上建一个干净的 conda 环境Python 锁 3.9 或 3.10PyTorch 按显卡选版本。GTX1660Ti 跑 YOLOv8n 跟踪640 输入尺寸下大概能到 40 到 60 FPS够实时看效果如果换成 YOLOv8m帧率会掉到 20 左右轨迹平滑度反而因为丢帧变差所以人员跟踪场景我更倾向用 n 或 s 模型。2.2 安装命令与权重下载# 创建环境Python 版本不要超过 3.10否则某些依赖轮子对不上 conda create -n yolov8_track python3.10 -y conda activate yolov8_track # 安装 PyTorchGTX1660Ti 属于图灵架构CUDA 11.8 版本比较稳 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装 Ultralytics它会自动拉取 opencv、numpy 等依赖 pip install ultralytics # 验证安装跑一行检测看看环境通不通 yolo predict modelyolov8n.pt sourcehttps://ultralytics.com/images/bus.jpg这段命令里conda create的python3.10是关键3.11 以上有些 opencv 轮子还没跟上。PyTorch 的 index-url 指向 cu118 源GTX1660Ti 用这个版本不会报 CUDA 版本不匹配。最后一行yolo predict会自动下载yolov8n.pt权重到当前目录如果卡在下载可以手动去 Ultralytics 的 GitHub release 页面找yolov8n.pt放进项目根目录。权重文件不大n 版本大概 6MB 左右s 版本 22MBm 版本 50MB 上下。2.3 跟踪推理的最小可运行脚本from ultralytics import YOLO import cv2 # 加载检测模型这里用 n 版本速度快适合实时跟踪 model YOLO(yolov8n.pt) # 打开视频文件也可以换成 0 调用摄像头 cap cv2.VideoCapture(test_people.mp4) # 跟踪器配置ByteTrack 对遮挡恢复更稳BoT-SORT 对相机运动更友好 tracker_config bytetrack.yaml while cap.isOpened(): ret, frame cap.read() if not ret: break # persistTrue 是跟踪的关键它让模型在帧间保持轨迹状态 results model.track( frame, persistTrue, trackertracker_config, classes[0], # 只跟踪 person 类COCO 里 person 的 id 是 0 conf0.4, # 检测置信度阈值太低会引入误检轨迹 iou0.5, # NMS 的 IoU 阈值人多时适当调低 verboseFalse ) # 把带轨迹 ID 的画面画出来 annotated results[0].plot() cv2.imshow(YOLOv8 Tracking, annotated) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()model.track和model.predict的区别就在persistTrue它让跟踪器在连续调用之间保留轨迹状态。classes[0]把检测范围锁死在 person 类避免其他类别干扰 ID 分配。conf0.4是我在人员场景常用的起点低于 0.3 时画面边缘的误检会生成短命轨迹高于 0.6 又会漏掉远处小目标。tracker参数可以换成botsort.yaml两者在遮挡和相机抖动下的表现差异后面会细说。3. 跟踪器选型与参数调优ByteTrack 和 BoT-SORT 到底怎么选3.1 两种跟踪器的关联逻辑差异ByteTrack 的核心是“两次关联”先用高分检测框和现有轨迹匹配再把低分检测框捡回来和没匹配上的轨迹做第二次关联。这个设计对人员被部分遮挡后重新出现的场景特别有效因为遮挡时检测分数会掉但低分框还能被利用。BoT-SORT 在 ByteTrack 基础上加了相机运动补偿和更精细的外观特征适合相机本身在动的情况比如云台摄像头或者车载场景。代价是 BoT-SORT 计算量更大GTX1660Ti 上跑 YOLOv8n BoT-SORT 会比 ByteTrack 低 5 到 8 FPS。如果你相机固定、人员走动为主ByteTrack 足够如果相机有轻微晃动或者要跟拍BoT-SORT 的轨迹连续性明显更好。3.2 关键参数含义与调整方向参数作用人员场景建议值调大后果调小后果track_high_thresh第一次关联的检测分数门槛0.5漏跟远处人员引入误检轨迹track_low_thresh第二次关联的低分门槛0.1低分框参与过多ID 跳变遮挡恢复变差new_track_thresh新建轨迹的分数门槛0.6新人员进入画面延迟建档短命轨迹增多track_buffer轨迹丢失后保留帧数30遮挡后 ID 切换减少但内存占用上升遮挡后容易分配新 IDmatch_thresh匹配时的 IoU 或特征距离阈值0.8匹配过松ID 混淆匹配过严轨迹断裂这些参数在bytetrack.yaml或botsort.yaml里改Ultralytics 会从包目录读取你也可以复制一份到项目里改然后在model.track里传自定义路径。我一般先把track_buffer从默认 30 调到 30 到 50 之间试人员被柱子挡两三秒再出来ID 能不能接上就看这个值。new_track_thresh别低于 0.5否则画面里飘过的误检会不断新建轨迹画面上全是短线段。3.3 轨迹绘制与 ID 颜色管理import numpy as np # 用 ID 做哈希映射颜色保证同一个 ID 在整段视频里颜色不变 def get_color(track_id): np.random.seed(track_id) return tuple(int(c) for c in np.random.randint(0, 255, 3)) # 在 results[0].boxes 里id 属性就是跟踪 ID boxes results[0].boxes if boxes.id is not None: for box, track_id in zip(boxes.xyxy, boxes.id): x1, y1, x2, y2 map(int, box) color get_color(int(track_id)) cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2) cv2.putText(frame, fID {int(track_id)}, (x1, y1 - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2)这段代码解决的是“同一个 ID 颜色跳来跳去”的问题。np.random.seed(track_id)让相同 ID 每次生成的随机颜色一致不会因为帧不同而变色。boxes.id在persistTrue时才有值如果为 None 说明当前帧没有活跃轨迹。画框时用xyxy坐标注意 OpenCV 的坐标是整数map(int, box)做转换。轨迹线可以再维护一个dict把每个 ID 的历史中心点存下来超过一定长度就丢弃最老的点这样画面不会越画越乱。4. 避坑与排查人员轨迹跟踪里最容易翻车的五个地方4.1 现象ID 频繁跳变同一个人几帧换一个号原因通常是检测框抖动导致 IoU 匹配失败或者track_buffer太小人一转身轨迹就断了。解决方法是先把conf提到 0.5 以上减少低质量框再把track_buffer加到 50给遮挡恢复留时间如果相机有运动换 BoT-SORT 并开启gmc_method做全局运动补偿。还有一个隐蔽原因是视频帧率不稳model.track按帧处理如果输入视频本身有丢帧轨迹预测会偏。4.2 现象画面里人一多轨迹线交叉后 ID 互换这是匹配阈值match_thresh太松的典型表现。ByteTrack 默认 0.8人员密集时两个框的 IoU 可能都超过阈值跟踪器就分不清谁是谁。把match_thresh降到 0.7 到 0.75 之间让匹配更严格。如果还不行就得引入外观特征BoT-SORT 的with_reid选项可以开但会额外加载一个 ReID 模型GTX1660Ti 上帧率会再掉一截需要权衡。4.3 现象跟踪脚本跑几分钟后内存暴涨常见原因是轨迹缓存没有清理。persistTrue会让跟踪器一直保留历史轨迹如果视频里不断有人进出轨迹字典会越来越大。解决方法是定期调用model.predictor.trackers[0].reset()或者手动限制轨迹数量超过一定长度就丢弃最老的。另一个原因是results[0].plot()每帧都生成新图像如果不用imshow及时释放OpenCV 的窗口缓冲也会堆积。4.4 现象RK3588 或 Orin 上部署后跟踪 ID 全乱板端部署时很多人只导出了检测模型跟踪逻辑还在 Python 里跑结果帧率对不上跟踪器的时间假设被破坏。正确做法是把跟踪器也移植到板端或者至少保证检测和跟踪在同一进程里按固定帧率处理。RK3588 上跑 YOLOv8n 的 RKNN 模型检测能到 30 FPS 以上但 ByteTrack 的关联计算在 CPU 上做如果 CPU 占用太高帧间间隔不稳ID 就会跳。建议把track_buffer适当调大抵消帧率波动。4.5 现象训练自己的数据集后跟踪效果反而比预训练模型差这通常不是跟踪器的问题而是检测模型过拟合了。自己标注的数据集如果场景单一模型在新视频里漏检率上升跟踪器拿不到稳定检测框轨迹自然断。解决方法是先用预训练权重跑一遍跟踪确认跟踪链路没问题再换自己训练的模型对比。如果确实要微调冻结 backbone 只训 head学习率设小一点避免把通用特征训丢。5. 轨迹后处理与效果验证把零散 ID 串成可回放的路径5.1 轨迹平滑与中心点提取检测框的中心点直接连起来画面上会有明显的锯齿因为框本身在抖。我一般用滑动平均或者 Savitzky-Golay 滤波对中心点序列做平滑。下面这段代码把每个 ID 的中心点存进字典再用简单移动平均输出平滑轨迹。from collections import defaultdict, deque # 每个 ID 维护一个固定长度的中心点队列 track_history defaultdict(lambda: deque(maxlen30)) def update_tracks(results): boxes results[0].boxes if boxes.id is None: return for box, track_id in zip(boxes.xyxy, boxes.id): x1, y1, x2, y2 map(float, box) cx, cy (x1 x2) / 2, (y1 y2) / 2 track_history[int(track_id)].append((cx, cy)) def draw_smooth_track(frame, track_id, window5): points list(track_history[track_id]) if len(points) window: return # 简单移动平均window 越大越平滑但延迟越高 smooth [] for i in range(len(points) - window 1): xs [p[0] for p in points[i:iwindow]] ys [p[1] for p in points[i:iwindow]] smooth.append((int(sum(xs)/window), int(sum(ys)/window))) for i in range(1, len(smooth)): cv2.line(frame, smooth[i-1], smooth[i], (0, 255, 0), 2)deque(maxlen30)限制每个 ID 最多存 30 个点避免内存无限增长。window5是平滑窗口太小平滑效果不明显太大轨迹会滞后于实际位置。人员正常行走速度下5 到 7 帧的窗口比较合适。这段逻辑放在model.track之后调用先更新历史再画线。5.2 验证跟踪稳定性的两个指标光看画面不够客观我习惯算两个数ID Switch 次数和 MOTA。ID Switch 就是同一个人被分配了新 ID 的次数手动数几段视频就能有个大概。MOTA 需要标注数据公式是 1 减去漏检误检ID Switch除以真实框总数。没有标注数据时可以看轨迹长度的分布如果大量轨迹只有几帧就消失说明跟踪器不稳定如果轨迹长度集中在几十帧以上说明 ID 保持得不错。另一个土办法是把视频调速到 0.5 倍慢放肉眼盯几个关键遮挡段看 ID 有没有断。5.3 导出跟踪结果做后续分析跟踪跑通后通常要把每个 ID 的轨迹导出成 CSV 或 JSON方便做热力图、停留时间统计。下面这段把track_history写进文件。import csv with open(tracks.csv, w, newline) as f: writer csv.writer(f) writer.writerow([track_id, frame_index, cx, cy]) for track_id, points in track_history.items(): for idx, (cx, cy) in enumerate(points): writer.writerow([track_id, idx, round(cx, 2), round(cy, 2)])注意这里frame_index用的是队列内的相对索引如果要绝对帧号需要在更新时额外存一个帧计数器。导出后可以用 pandas 做分组统计算每个 ID 的轨迹长度、平均速度、停留区域。这套数据拿去做客流热力图或者工地轨迹回放比单纯看视频直观得多。从那以后我每次调跟踪参数都强制先跑一段 30 秒的遮挡测试视频把 ID Switch 数出来再改track_buffer和match_thresh不再凭感觉调。希望帮到你。本文还有配套的精品资源点击获取
返回列表