YOLO+Sort车辆轨迹绘制代码出现列表索引越界错误求助
问题解决:车辆轨迹绘制的索引越界与全程轨迹保留
问题背景
使用YOLOv8+Sort算法追踪视频中的车辆,通过trajectories字典存储每个车辆ID对应的中心坐标(列表格式,每两个元素为一组(x,y)坐标),目标是全程保留车辆行驶轨迹。原代码尝试提取每个ID列表的最后四个坐标点绘制轨迹连线,但处理第二个车辆ID时触发IndexError: list index out of range,报错行:third_coordinate = trajectories[id][p + 2]。调整全局变量p、q的自增位置仍报错,移除自增则只能绘制前四个点,无法更新最新轨迹。
错误原因
- 全局变量冲突:
p、q是所有车辆ID共用的全局计数器,第一个ID处理后p、q会递增,后续ID的轨迹列表长度可能仅为4(如第二个ID的列表为[541,140,542,141]),此时p已变为2,p+2=4超出列表索引范围(列表最大索引为3)。 - 轨迹保留逻辑错误:原代码直接在每一帧的视频画面上绘制轨迹,但下一帧的画面是全新的视频帧,之前绘制的轨迹会被覆盖,无法实现“全程保留”。
- 取点逻辑偏差:需求是绘制全程轨迹,无需用全局计数器逐个取点,应基于每个ID的轨迹列表,依次连接所有连续坐标点。
修复方案
- 删除全局的
p、q变量,每个车辆ID的轨迹独立处理。 - 创建独立的轨迹画布,所有轨迹绘制在该画布上,避免被新帧覆盖。
- 遍历每个ID的轨迹列表,依次连接所有连续的坐标点,实现全程轨迹保留。
完整修复代码
import cv2 import numpy as np import math from ultralytics import YOLO from sort import Sort import cvzone from typing import Union, Any cap = cv2.VideoCapture("../Videos/3 way traffic.mp4") # 加载视频 model = YOLO("../Yolo-Weights/yolov8l.pt") classNames = ["person", "bicycle", "car", "motorbike", "aeroplane", "bus", "train", "truck"] mask = cv2.imread("mask.png") tracker = Sort(max_age=20, min_hits=3, iou_threshold=0.3) # 初始化Sort追踪器 trajectories = {} # 获取视频尺寸,创建空白轨迹画布(用于保留全程轨迹) frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) trajectory_img = np.zeros((frame_height, frame_width, 3), dtype=np.uint8) while True: success, img = cap.read() if not success: break imgRegion = cv2.bitwise_and(img, mask) results = model(imgRegion, stream=True) detections = np.empty((0, 5)) # 检测目标并筛选车辆类 for r in results: boxes = r.boxes for box in boxes: x1, y1, x2, y2 = box.xyxy[0] x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2) w, h = x2 - x1, y2 - y1 conf: float = math.ceil((box.conf[0] * 100)) / 100 cls = int(box.cls[0]) currentClass: Union[str, Any] = classNames[cls] # 筛选车辆类且置信度达标 if (currentClass in ["car", "truck", "bus", "motorbike"]) and conf > 0.3: currentArray = np.array([x1, y1, x2, y2, conf]) detections = np.vstack((detections, currentArray)) cvzone.putTextRect(img, f' {currentClass}', (x1 + 10, y1 + 20), colorT=(255, 255, 255), colorR=(60, 20, 220), font=cv2.FONT_HERSHEY_TRIPLEX, scale=0.75, thickness=1, offset=2) # 更新追踪结果 resultsTracker = tracker.update(detections) for result in resultsTracker: x1, y1, x2, y2, id = result x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2) w, h = x2 - x1, y2 - y1 # 绘制目标框和中心圆点 cvzone.cornerRect(img, (x1, y1, w, h), l=17, t=2, rt=1, colorR=(255, 228, 181), colorC=(60, 20, 220)) cx, cy = x1 + w // 2, y1 + h // 2 cv2.circle(img, (cx, cy), 5, (255, 0, 255), cv2.FILLED) # 更新轨迹字典 if id in trajectories: trajectories[id].extend([cx, cy]) else: trajectories[id] = [cx, cy] # 绘制全程轨迹:遍历当前ID的所有连续坐标点 traj = trajectories[id] if len(traj) >= 4: # 至少有两个坐标点才能连线 for i in range(0, len(traj)-2, 2): prev_x, prev_y = traj[i], traj[i+1] curr_x, curr_y = traj[i+2], traj[i+3] # 在轨迹画布上绘制连线(不会被新帧覆盖) cv2.line(trajectory_img, (prev_x, prev_y), (curr_x, curr_y), (0, 255, 0), 2) # 将轨迹画布与当前视频帧叠加显示 img_with_trajectory = cv2.addWeighted(img, 0.8, trajectory_img, 0.2, 0) cv2.imshow("Image", img_with_trajectory) # 按q退出,原waitKey(0)会逐帧暂停,改成waitKey(1)正常播放,可按需调整 if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()
关键优化点
- 新增
trajectory_img空白画布,所有轨迹绘制在该画布上,确保全程轨迹不会被新帧覆盖。 - 移除全局计数器,针对每个ID的轨迹列表,遍历所有连续坐标对绘制连线,实现完整轨迹展示。
- 调整
cv2.waitKey(0)为cv2.waitKey(1),让视频正常播放(若需逐帧查看可改回0)。
内容的提问来源于stack exchange,提问作者Shaair
相关产品推荐
相关产品推荐

