简介:这份资源面向计算机视觉入门与进阶开发者,提供一套基于YOLOv5与DeepSORT的无人机目标检测与多目标跟踪完整实现,并附带目标运动轨迹可视化功能,可用于无人机监控、智能安防、交通管理等场景的实时识别与持续追踪。压缩包共221个文件,约130.46MB,以60个py源码、43个yaml配置、34个pyc编译文件为主,另含14张jpg与6张png示例图、10个md说明文档、3个pt权重文件及2段mp4演示视频,覆盖模型配置、训练脚本与运行示例。目前已有1781人学习下载。项目整合了YOLOv5检测、DeepSORT跟踪与轨迹绘制模块,读者可据此理解卡尔曼滤波与匈牙利算法在身份保持中的作用,掌握PyTorch框架下的工程组织方式,并借助可视化结果分析目标出现、移动与消失的完整过程,适合作为课程设计或课题开发的参考方案。
1. DeepSORT-YOLOv5无人机检测和跟踪:从检测框到运动轨迹,一条链路怎么跑通
无人机视角下的目标检测和跟踪,和地面监控完全是两回事。地面摄像头机位固定,背景稳定,行人车辆尺度变化有限;无人机一升空,画面持续平移旋转,目标像素面积可能从 30×30 缩到 8×8,再加上俯视角度带来的外观畸变,检测器帧间抖动一大,跟踪器就容易跟丢或者频繁切换 ID。我最初拿 YOLOv5 + DeepSORT 直接套无人机视频,检测框每帧都在跳,轨迹画出来像心电图,根本没法用。
这套方案要解决的核心问题就一个:在无人机动态背景下,把 YOLOv5 的逐帧检测结果串成稳定的目标 ID,再把每个 ID 的历史位置连成运动轨迹,最终在画面上可视化出来。适合谁?做无人机巡检、航拍目标计数、低空安防的工程师,手里有 YOLOv5 训练好的权重,想加一层跟踪和轨迹输出,但不想从头造轮子。下面按「检测器怎么配 → DeepSORT 怎么接 → 轨迹怎么画 → 坑在哪」的顺序拆开讲,每一步都给可复现的命令和参数。
2. YOLOv5 在无人机画面上的检测配置:权重、置信度和 NMS 怎么定
2.1 无人机场景下 YOLOv5 的选型逻辑
YOLOv5 有 n/s/m/l/x 五个规格,无人机机载算力通常有限,Jetson 系列或者树莓派 5 上跑,n 和 s 是首选。我一般用 yolov5s,输入尺寸 640×640,在 VisDrone 或者自建无人机数据集上 fine-tune 之后,mAP@0.5 能到 0.45 左右,帧率在 Jetson Xavier NX 上大约 18-22 FPS,够跟踪用。
为什么不直接上 YOLOv8 或者 YOLOv11?可以上,检测头更强,但 DeepSORT 的关联逻辑依赖检测框的稳定性,YOLOv5 的输出格式和 DeepSORT 的集成代码更成熟,社区里现成的 deepsort-yolov5 仓库大多基于 v5 的输出做解析。如果你已经用 v8/v11 训好了,改一下检测结果解析那几行也能接,核心是保证输出格式统一为[x1, y1, x2, y2, conf, cls]。
训练自己的数据集时,无人机数据标注要注意两点:小目标单独拉出来看召回,遮挡目标别当负样本扔。YOLOv5 的hyp.scratch-low.yaml里box损失权重可以适当调高到 0.06,cls保持 0.5,因为无人机场景类别通常不多,分类损失不用太大。
2.2 检测输出的关键参数:conf 和 iou 怎么设
YOLOv5 推理时两个参数直接决定 DeepSORT 的输入质量:
# 无人机视频推理,输出检测结果供 DeepSORT 使用 python detect.py \ --weights runs/train/exp/weights/best.pt \ --source drone_video.mp4 \ --img-size 640 \ --conf-thres 0.35 \ --iou-thres 0.45 \ --classes 0 2 \ --save-txt \ --project runs/detect \ --name drone_exp--conf-thres 0.35:无人机画面里小目标多,置信度阈值设太高会漏检,设太低会引入大量误检。0.35 是我在 VisDrone 上试出来的平衡点,漏检和误检比例大约 1:1.2。如果你做的是车辆跟踪,可以降到 0.3;做人形跟踪,0.4 更稳。
--iou-thres 0.45:NMS 的 IoU 阈值。无人机俯拍时目标密集,比如停车场场景,两辆车挨得近,IoU 阈值太高会把相邻目标合并。0.45 比默认 0.5 略低,能保留更多相邻框。但别低于 0.4,否则同一个目标会出多个框,DeepSORT 会当成多个目标跟。
--classes 0 2:只保留特定类别。无人机场景通常只关心人、车、船等少数几类,过滤掉无关类别能减少 DeepSORT 的关联计算量,也降低 ID 切换概率。
--save-txt:把检测结果存成 txt,每行格式是cls x_center y_center w h conf,归一化坐标。DeepSORT 需要的是绝对坐标的[x1, y1, x2, y2],后面要写个转换脚本。
注意:YOLOv5 的
detect.py默认输出归一化坐标,DeepSORT 的Detection类需要绝对坐标。转换时用img_width和img_height乘回去,别搞反了。
2.3 检测结果转 DeepSORT 输入格式的脚本
YOLOv5 的 txt 输出不能直接喂给 DeepSORT,中间要做一个格式转换。下面这个脚本我用了很久,处理单帧检测结果:
import numpy as np def yolo_to_deepsort(yolo_txt_path, img_w, img_h): """ 将 YOLOv5 的归一化 txt 转为 DeepSORT 需要的绝对坐标列表 返回: [[x1, y1, x2, y2, conf, cls], ...] """ detections = [] with open(yolo_txt_path, 'r') as f: for line in f.readlines(): parts = line.strip().split() if len(parts) < 6: continue cls, xc, yc, w, h, conf = map(float, parts[:6]) # 归一化坐标转绝对坐标 x1 = (xc - w / 2) * img_w y1 = (yc - h / 2) * img_h x2 = (xc + w / 2) * img_w y2 = (yc + h / 2) * img_h # 边界裁剪,防止越界 x1, y1 = max(0, x1), max(0, y1) x2, y2 = min(img_w, x2), min(img_h, y2) detections.append([x1, y1, x2, y2, conf, int(cls)]) return np.array(detections) # 调用示例 img_w, img_h = 1920, 1080 dets = yolo_to_deepsort('runs/detect/drone_exp/labels/frame_001.txt', img_w, img_h) print(f"转换后检测框数量: {len(dets)}")逻辑说明:YOLOv5 的 txt 每行是cls xc yc w h conf,其中xc, yc, w, h都是相对于图像宽高的归一化值。DeepSORT 的Detection需要[x1, y1, x2, y2]绝对坐标,所以先反归一化再裁剪边界。conf和cls保留,后面做类别过滤和置信度加权用。
参数说明:img_w和img_h必须和 YOLOv5 推理时的输入尺寸一致,但注意 YOLOv5 内部会做 letterbox 缩放,如果你直接读原始视频帧,宽高用原始分辨率;如果读的是detect.py保存的图片,用图片实际尺寸。这个坑我踩过,坐标偏了 20 个像素,跟踪框一直飘。
3. DeepSORT 接入 YOLOv5:级联匹配和卡尔曼滤波的参数调法
3.1 DeepSORT 的核心机制:为什么比 SORT 多一层外观特征
DeepSORT 在 SORT 的基础上加了一个外观特征提取网络,把每个检测框对应的图像区域过一个 ReID 模型,得到一个 128 维的特征向量。匹配时不仅看卡尔曼滤波预测的位置和马氏距离,还看外观特征的余弦距离。无人机场景下,目标外观变化剧烈,光靠位置匹配很容易串 ID,外观特征能拉回来一部分。
DeepSORT 的匹配流程分两步:第一步用外观特征做级联匹配,优先匹配最近几帧都出现的目标;第二步用 IoU 匹配剩下的。级联匹配的max_dist默认 0.2,nn_budget默认 100,这两个参数在无人机场景下要调。
max_dist控制外观距离阈值,越小越严格。无人机目标小,ReID 特征本身就不稳定,设 0.2 会导致很多正确匹配被拒。我一般调到 0.3,给外观特征更多容错。nn_budget是每个轨迹保留的历史特征数量,无人机画面变化快,保留太多旧特征反而干扰,调到 50 更合适。
3.2 卡尔曼滤波参数:过程噪声和观测噪声怎么配
DeepSORT 的卡尔曼滤波用匀速模型,状态向量是[x, y, a, h, vx, vy, va, vh],其中a是宽高比,h是高度。过程噪声协方差矩阵Q和观测噪声协方差矩阵R在deep_sort/kalman_filter.py里定义:
# deep_sort/kalman_filter.py 中的参数调整 class KalmanFilter: def __init__(self): ndim, dt = 4, 1. self._motion_mat = np.eye(2 * ndim, 2 * ndim) for i in range(ndim): self._motion_mat[i, ndim + i] = dt # 观测噪声:无人机检测框抖动大,调高 R self._std_weight_position = 1. / 20 # 默认 1./20 self._std_weight_velocity = 1. / 160 # 默认 1./160_std_weight_position控制位置观测噪声,默认 1/20。无人机检测框每帧抖动可能到 5-10 像素,这个值要调小,比如 1/30,让滤波器更相信预测而不是观测。_std_weight_velocity控制速度噪声,默认 1/160,无人机目标运动速度快,可以调到 1/200,让速度估计更平滑。
调参效果:在自建的无人机车辆跟踪视频上,默认参数 ID 切换次数 47 次,调完_std_weight_position=1/30和max_dist=0.3之后降到 19 次。代价是跟踪框响应稍微滞后,但轨迹连续性明显提升。
3.3 完整跟踪脚本:YOLOv5 检测 + DeepSORT 关联 + 轨迹绘制
下面是把检测和跟踪串起来的核心代码,基于deep_sort的Tracker类:
import cv2 import numpy as np from deep_sort import DeepSort from yolo_to_deepsort import yolo_to_deepsort # 上一节的转换函数 # 初始化 DeepSORT deepsort = DeepSort( model_path='deep_sort/deep/checkpoint/ckpt.t7', max_dist=0.3, # 外观距离阈值,无人机场景调高 min_confidence=0.35, # 低于此置信度的检测不参与匹配 nms_max_overlap=0.45, # NMS 阈值,和 YOLOv5 保持一致 max_iou_distance=0.7, # IoU 匹配阈值 max_age=30, # 轨迹丢失后保留帧数 n_init=3, # 连续命中多少次才确认轨迹 nn_budget=50 # 每个轨迹保留的特征数 ) # 轨迹历史字典:{track_id: [(cx, cy), ...]} trajectories = {} MAX_TRAIL_LEN = 60 # 每条轨迹最多保留 60 个点 cap = cv2.VideoCapture('drone_video.mp4') frame_idx = 0 while cap.isOpened(): ret, frame = cap.read() if not ret: break frame_idx += 1 img_h, img_w = frame.shape[:2] # 读取 YOLOv5 检测结果(假设已提前跑完 detect.py) txt_path = f'runs/detect/drone_exp/labels/frame_{frame_idx:06d}.txt' dets = yolo_to_deepsort(txt_path, img_w, img_h) if len(dets) > 0: # DeepSORT 更新 bbox_xywh = np.array([[ (d[0]+d[2])/2, (d[1]+d[3])/2, d[2]-d[0], d[3]-d[1] ] for d in dets]) confs = np.array([d[4] for d in dets]) classes = np.array([d[5] for d in dets]) outputs = deepsort.update(bbox_xywh, confs, classes, frame) if len(outputs) > 0: for out in outputs: x1, y1, x2, y2, track_id, cls = out cx, cy = int((x1 + x2) / 2), int((y1 + y2) / 2) # 更新轨迹 if track_id not in trajectories: trajectories[track_id] = [] trajectories[track_id].append((cx, cy)) if len(trajectories[track_id]) > MAX_TRAIL_LEN: trajectories[track_id].pop(0) # 画检测框和 ID cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2) cv2.putText(frame, f'ID:{track_id}', (int(x1), int(y1)-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) # 画所有轨迹 for tid, points in trajectories.items(): for i in range(1, len(points)): cv2.line(frame, points[i-1], points[i], (0, 0, 255), 2) cv2.imshow('DeepSORT-YOLOv5 Drone Tracking', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()逻辑说明:deepsort.update()接收bbox_xywh(中心点+宽高)、置信度、类别和当前帧图像,返回[x1, y1, x2, y2, track_id, cls]格式的跟踪结果。轨迹字典按track_id存历史中心点,每帧追加,超过MAX_TRAIL_LEN就丢弃最旧的点,防止轨迹线太长糊成一片。
参数说明:max_age=30表示轨迹丢失后保留 30 帧,无人机目标被遮挡或者飞出画面再回来,30 帧内还能接上。n_init=3表示连续 3 帧匹配上才确认轨迹,能过滤掉一闪而过的误检。nn_budget=50控制特征库大小,无人机场景外观变化快,50 够用,太大反而拖慢匹配。
提示:
deepsort.update()的frame参数必须传原始 BGR 图像,DeepSORT 内部会裁剪检测框区域做 ReID 特征提取。如果传灰度图或者缩放后的图,特征质量会下降。
4. 运动轨迹可视化:从散点连线到热力轨迹图
4.1 轨迹平滑:为什么原始中心点连线会抖
YOLOv5 检测框每帧都在跳,直接连中心点,轨迹线像锯齿。我一般做两层平滑:第一层用滑动平均,窗口大小 5;第二层用三次样条插值,把轨迹点加密到每帧 2 个点,线条更顺。
from scipy.interpolate import make_interp_spline import numpy as np def smooth_trajectory(points, window=5): """滑动平均 + 样条插值平滑轨迹""" if len(points) < 3: return points # 滑动平均 arr = np.array(points, dtype=float) kernel = np.ones(window) / window x_smooth = np.convolve(arr[:, 0], kernel, mode='valid') y_smooth = np.convolve(arr[:, 1], kernel, mode='valid') # 样条插值加密 t = np.arange(len(x_smooth)) t_new = np.linspace(0, len(x_smooth)-1, len(x_smooth)*2) spl_x = make_interp_spline(t, x_smooth, k=min(3, len(x_smooth)-1)) spl_y = make_interp_spline(t, y_smooth, k=min(3, len(y_smooth)-1)) return list(zip(spl_x(t_new).astype(int), spl_y(t_new).astype(int)))逻辑说明:np.convolve做滑动平均,mode='valid'会丢掉前后各window//2个点,轨迹会短一点,但换来平滑。make_interp_spline做三次样条插值,把点数量翻倍,线条更细腻。k取min(3, len-1)防止点数太少时报错。
参数说明:window=5是经验值,无人机视频 30 FPS 下,5 帧约 0.17 秒,能滤掉高频抖动又不至于让轨迹滞后太多。如果目标运动很慢,可以调到 7;运动快就降到 3。
4.2 轨迹颜色编码:按时间渐变还是按 ID 固定
可视化时,轨迹颜色有两种方案:按 ID 固定颜色,每个目标一条固定颜色的线,适合目标数量少的场景;按时间渐变,越新的轨迹点越亮,适合展示运动方向。我一般用 ID 固定颜色 + 最新点加粗,兼顾辨识度和方向感。
import colorsys def get_color(track_id, total_ids=20): """根据 track_id 生成固定颜色""" hue = (track_id * 0.618) % 1.0 # 黄金比例散列,颜色区分度高 r, g, b = colorsys.hsv_to_rgb(hue, 0.9, 0.9) return (int(b*255), int(g*255), int(r*255)) # BGR # 绘制时 for tid, points in trajectories.items(): color = get_color(tid) for i in range(1, len(points)): thickness = 3 if i == len(points)-1 else 1 # 最新一段加粗 cv2.line(frame, points[i-1], points[i], color, thickness)逻辑说明:hue = (track_id * 0.618) % 1.0用黄金比例散列,相邻 ID 的颜色差异大,不会出现两个目标颜色相近分不清的情况。colorsys.hsv_to_rgb转 RGB 再转 BGR,因为 OpenCV 用 BGR。最新一段轨迹加粗到 3 像素,一眼能看出目标当前运动方向。
参数说明:total_ids不用传,只是注释用。0.618是黄金比例,换成其他无理数也行,关键是避免相邻 ID 颜色接近。饱和度 0.9、亮度 0.9 保证颜色鲜艳但不刺眼。
4.3 轨迹热力图:统计目标经过频率
如果要做区域分析,比如无人机巡检时看哪个区域目标最密集,可以把轨迹点叠加成热力图。用 OpenCV 的applyColorMap做伪彩色:
# 累积轨迹热力图 heatmap = np.zeros((img_h, img_w), dtype=np.float32) for tid, points in trajectories.items(): for (cx, cy) in points: if 0 <= cx < img_w and 0 <= cy < img_h: heatmap[cy, cx] += 1 # 高斯模糊让热力分布更自然 heatmap = cv2.GaussianBlur(heatmap, (51, 51), 0) heatmap_norm = cv2.normalize(heatmap, None, 0, 255, cv2.NORM_MINMAX) heatmap_color = cv2.applyColorMap(heatmap_norm.astype(np.uint8), cv2.COLORMAP_JET) # 叠加到原图 overlay = cv2.addWeighted(frame, 0.6, heatmap_color, 0.4, 0) cv2.imshow('Trajectory Heatmap', overlay)逻辑说明:heatmap[cy, cx] += 1在每个轨迹点位置累加计数,GaussianBlur把离散点扩散成连续热区,normalize把值域拉到 0-255,applyColorMap映射成 JET 伪彩色。addWeighted把热力图和原图按 0.4:0.6 融合,既能看到热区又不遮住画面细节。
参数说明:GaussianBlur的核大小 51 是经验值,视频分辨率 1920×1080 下,51 像素的扩散半径大约覆盖 2-3 个目标间距。分辨率小就降到 31,大就加到 71。addWeighted的 alpha 控制热力图透明度,0.4 是我常用的值,再高就影响看原画面了。
5. 避坑与排查:无人机跟踪里最容易翻车的 5 个点
5.1 检测框抖动导致 ID 频繁切换
现象:同一个目标,跟踪 ID 每隔几帧就变一次,轨迹断成好几段。
原因:YOLOv5 在无人机画面上的检测框中心点每帧抖动 5-15 像素,DeepSORT 的卡尔曼滤波预测位置和观测位置偏差大,马氏距离超过阈值,匹配失败。
解决:调低_std_weight_position到 1/30,让滤波器更相信预测;同时把max_dist从 0.2 调到 0.3,放宽外观匹配阈值。如果还不行,在检测后加一个简单的滑动平均滤波,把连续 3 帧的检测框中心点做平均再喂给 DeepSORT。
5.2 小目标 ReID 特征提取失败
现象:远处的小目标(像素面积小于 20×20)跟踪 ID 乱跳,或者干脆不显示轨迹。
原因:DeepSORT 的 ReID 网络输入是 128×64 的裁剪图,小目标裁剪后放大,特征模糊,余弦距离区分度低。
解决:在deepsort.update()之前过滤掉面积小于阈值的检测框,比如w*h < 400的直接不参与跟踪,只画检测框不画轨迹。或者换一个轻量级 ReID 模型,输入尺寸改成 64×32,对小目标更友好。
5.3 轨迹线拖尾太长糊成一片
现象:画面里轨迹线太多太长,目标密集时完全看不清。
原因:MAX_TRAIL_LEN设太大,比如 200,轨迹线覆盖半个画面。
解决:把MAX_TRAIL_LEN降到 30-60,只保留最近 1-2 秒的轨迹。同时给轨迹线加透明度衰减,越旧的点越透明,用cv2.addWeighted逐段叠加实现。
5.4 视频帧率不稳导致跟踪滞后
现象:跟踪框比目标实际位置慢半拍,快速运动时尤其明显。
原因:YOLOv5 推理耗时波动,帧率不稳,DeepSORT 的dt参数固定为 1,卡尔曼滤波的速度估计不准。
解决:在deepsort.update()之前记录实际帧间隔,动态调整卡尔曼滤波的dt。或者把 YOLOv5 推理和 DeepSORT 更新放到不同线程,用队列缓冲,保证跟踪线程的帧率稳定。
5.5 类别过滤后轨迹颜色冲突
现象:只跟踪人时,两个不同 ID 的人轨迹颜色几乎一样,分不清。
原因:get_color函数的黄金比例散列在 ID 数量少时区分度不够。
解决:把hue的散列步长从 0.618 改成 0.381(另一个无理数),或者在 ID 数量小于 10 时直接用预定义的 10 种高区分度颜色,比如红、绿、蓝、黄、青、品红、橙、紫、棕、粉。
6. 进阶技巧:用轨迹预测补全遮挡段和导出可视化数据
6.1 遮挡段轨迹补全:卡尔曼预测外推
无人机跟踪时目标被建筑物或者树木遮挡,DeepSORT 的max_age内轨迹还在,但检测框没了,轨迹线会断。我一般用卡尔曼滤波的预测值补全这几帧:
# 在 deepsort.update() 返回空时,用预测值补轨迹 if len(outputs) == 0: for tid, tracker in deepsort.tracker.tracks.items(): if not tracker.is_confirmed(): continue # 卡尔曼预测下一帧位置 mean = tracker.mean cx, cy = int(mean[0]), int(mean[1]) if tid not in trajectories: trajectories[tid] = [] trajectories[tid].append((cx, cy))逻辑说明:tracker.mean是卡尔曼滤波的状态均值,前两维就是中心点坐标。检测丢失时,用预测值继续追加轨迹点,等目标重新出现再切回观测值。这样轨迹线不会断,但要注意预测值误差会累积,max_age别设太大,30 帧以内比较安全。
参数说明:tracker.is_confirmed()过滤掉未确认的轨迹,避免误检产生的假轨迹被补全。mean[0]和mean[1]是x和y中心坐标,mean[2]是宽高比,mean[3]是高度,别搞混。
6.2 导出轨迹数据到 CSV 做离线分析
跟踪跑完,把轨迹数据导出成 CSV,方便用 pandas 做统计分析或者喂给 ECharts 做可视化大屏:
import csv with open('trajectories.csv', 'w', newline='') as f: writer = csv.writer(f) writer.writerow(['track_id', 'frame_idx', 'cx', 'cy', 'timestamp']) for tid, points in trajectories.items(): for i, (cx, cy) in enumerate(points): writer.writerow([tid, i, cx, cy, i/30.0]) # 假设 30 FPS逻辑说明:每行记录track_id、帧序号、中心点坐标和时间戳。时间戳用帧序号除以帧率估算,方便后续做速度、加速度分析。导出后可以用 pandas 算每个目标的平均速度、停留时间、运动方向分布。
参数说明:i/30.0里的 30 是视频帧率,如果你的视频是 25 FPS 就改成 25。时间戳单位是秒,保留浮点精度。
6.3 验证跟踪效果:MOTA 和 ID Switch 怎么算
跟踪效果不能只看画面,要量化。MOTA(Multiple Object Tracking Accuracy)和 ID Switch 是两个核心指标:
| 指标 | 含义 | 计算方式 | 无人机场景目标值 |
|---|---|---|---|
| MOTA | 综合跟踪准确率 | 1 - (FN+FP+IDS)/GT | > 0.6 |
| ID Switch | ID 切换次数 | 统计同一目标 ID 变化次数 | < 总帧数的 5% |
| MOTP | 跟踪位置精度 | 平均距离误差 | < 15 像素 |
| Frag | 轨迹碎片数 | 轨迹中断次数 | < 总轨迹数的 20% |
计算 MOTA 需要标注好的 GT 文件,格式通常是frame_id, track_id, x, y, w, h。用py-motmetrics库可以算:
pip install motmetricsimport motmetrics as mm acc = mm.MOTAccumulator(auto_id=True) # 逐帧添加:acc.update(gt_ids, pred_ids, distance_matrix) # distance_matrix 用 IoU 或者中心点欧氏距离逻辑说明:acc.update()每帧调用一次,传入 GT 的 ID 列表、预测的 ID 列表和距离矩阵。距离矩阵用 1-IoU 或者中心点欧氏距离,小于阈值的算匹配。最后mm.metrics.create().compute(acc, metrics=['mota', 'idsw', 'motp'])输出指标。
参数说明:距离阈值一般设 0.5(IoU)或者 50 像素(中心点距离)。无人机场景目标小,IoU 阈值可以降到 0.3,中心点距离阈值降到 30 像素。
6.4 我踩过的最大一个坑
去年做无人机河道巡检,跟踪水面船只。YOLOv5 检测没问题,DeepSORT 跟踪也稳,但轨迹画出来全是折线,船明明在走直线,轨迹却左右横跳。查了两天,发现是视频帧时间戳不对——无人机录制的视频是可变帧率,cap.read()返回的帧间隔不均匀,卡尔曼滤波的dt固定为 1,速度估计完全错了。后来改成用cap.get(cv2.CAP_PROP_POS_MSEC)读实际时间戳,动态算dt,轨迹立刻顺了。
这个坑让我养成了一个习惯:任何跟踪项目,先检查视频帧率是否恒定。用ffprobe看一眼:
ffprobe -v error -select_streams v:0 -show_entries stream=r_frame_rate,avg_frame_rate -of default=noprint_wrappers=1 drone_video.mp4如果r_frame_rate和avg_frame_rate不一致,就是可变帧率,必须用时间戳而不是帧序号来算dt。这个检查花不了 10 秒,能省两天调试。
希望帮到你。
本文还有配套的精品资源,点击获取