车辆预测跟踪与卡尔曼滤波:从状态估计到多目标关联的Matlab实践
2026/9/15 18:39:15 网站建设 项目流程

简介:面向自动驾驶与智能交通领域的Matlab车辆预测跟踪项目,提供一套可运行的完整源码,适合初学者及有一定经验的算法开发人员快速上手目标检测与轨迹预测。包内共有626个文件,以bmp图像帧为主(616个),便于可视化验证跟踪效果;另含4个m主程序与4个asv备份文件,配套docx说明文档和db数据库文件,整体压缩包仅15.9MB,轻量易部署。资源中图像序列覆盖多组道路监控场景,可用于测试车辆检测、特征匹配与预测跟踪算法;m文件经过校正调通,下载后可直接运行。目前已有672人学习下载,适合用于课程设计、毕业设计或算法预研。通过分析源码与图像序列,读者可掌握基于Matlab的车辆跟踪实现思路,并在此基础上扩展多目标跟踪或深度学习检测模块。

1. 车辆预测跟踪的工程坐标:检测与跟踪之间差了一个状态估计

在自动驾驶感知链路上,“车辆跟踪”往往被误读成“连续视频帧里给同一辆车画同一个框”。实际做过量产项目的工程师都清楚,检测器输出的是一串离散、带噪声、偶尔漏检的矩形框,而下游融合、规划模块需要的是每帧平滑、连续、能回答“车在哪儿、往哪儿去”的运动状态。车辆预测跟踪解决的核心问题,是用状态估计模型把检测框序列转化为对目标位置和速度的递推预测,并在检测丢失时用模型外推撑住若干帧。Matlab 生态里,这一套东西既可以用 Sensor Fusion and Tracking Toolbox 的trackerGNN一行接入,也可以从filter类手写卡尔曼滤波逐步落地,后者更适合理解参数如何影响跟踪质量。本文按“建模—单目标实现—多目标关联—参数调优”的顺序展开,所有代码可以直接拷进 Matlab R2021b 及以上版本运行,新手能跟完,老手可以看到 Q 矩阵和门控阈值这些容易被忽视的坑。

2. 车辆运动建模与状态方程:预测跟踪的数学地基

2.1 先把“预测”翻译成状态转移

预测跟踪的第一步不是写滤波代码,而是决定状态向量和运动模型。对地面车辆而言,最常见的是恒定速度(CV)模型和恒定转率和速度(CTRV)模型。CV 模型假设车辆在采样间隔内速度不变,状态向量取[px; py; vx; vy],离散化后的状态转移矩阵是:

F = [1 0 dt 0; 0 1 0 dt; 0 0 1 0; 0 0 0 1]

其中dt是传感器帧间隔。量测向量一般取[px; py],即检测框中心在自车坐标系下的位置,对应观测矩阵H = [1 0 0 0; 0 1 0 0]。CV 模型在高速公路直行场景下已经够用,但如果目标在弯道或十字路口转弯,CV 模型的预测会系统性滞后,这时候需要换成 CTRV,状态向量为[px; py; v; psi; psi_dot]psi是航向角,psi_dot是偏航率。CTRV 的状态转移方程是非线性的,因此滤波要用扩展卡尔曼滤波(EKF)或无迹卡尔曼滤波(UKF)。

2.1.1 为什么状态向量里不加速度项

在车辆跟踪中,我一般不在初始状态向量里同时放加速度,原因有两个。第一,加速度在量产传感器噪声量级下很难被准确观测,强行估计会引入额外方差;第二,匀速模型配合适当的过程噪声 Q,其实可以吸收小幅加速度带来的模型失配。换句话说,把加速度放进“噪声”里,比把它放进“状态”里更稳健。只有当目标存在频繁启停(如城市场景)时才需要切换到恒定加速度(CA)模型,或者用交互多模型(IMM)做模型自适应。

2.2 过程噪声 Q 和量测噪声 R:两个决定命运的矩阵

卡尔曼滤波的预测误差协方差递推公式是P_pred = F * P * F' + Q,这里 Q 表示模型预测和真实运动之间的偏差协方差。Q 的数值大小决定了滤波器对检测新息的信任程度。Q 设得过大,估计结果会剧烈跳变,跟踪轨迹看起来像是在“追”检测框;Q 设得过小,滤波器反应迟钝,车辆急刹或变道时跟踪框会严重滞后。实用的做法是把 Q 拆成加速度噪声强度sigma_a^2和位置噪声强度sigma_p^2,然后按如下方式构造:

% 构造 CV 模型的过程噪声矩阵 Q sigma_a = 2.0; % 加速度噪声标准差,单位 m/s^2,城市场景常用 1.5~3 sigma_p = 0.5; % 位置噪声标准差,单位 m,用于吸收未建模位置扰动 dt = 0.1; % 帧间隔,单位 s,对应 10Hz 传感器 % 分块矩阵构造方式:对位置噪声和速度噪声分别处理 Q11 = sigma_p^2 * eye(2); Q12 = zeros(2); Q21 = zeros(2); Q22 = sigma_a^2 * [dt^4/4 0; 0 dt^4/4]; % 合并为 4x4 矩阵 Q = [Q11 Q12; Q21 Q22];

量测噪声 R 在单目标跟踪时通常直接取自检测器的位置方差。如果检测框中心坐标的抖动标准差是 0.8 米,那么R = 0.8^2 * eye(2)。这里有一个常见的误用手法:某些工程师为了“让跟踪更平滑”,把 R 设得比实际噪声大一个数量级,结果是在目标转弯时跟踪框迟迟不跟随。量测噪声应当反映传感器真实误差,而不是被当作平滑参数使用。

2.2.1 从检测框到量测向量:框中心的转换代价

当检测器输出的是[x, y, w, h]形式的边界框时,量测向量取框中心是个自然选择,但要注意框宽高不稳定对大目标和小目标的影响不同。一个更好的做法是把量测向量扩展为[cx, cy, s],其中s代表框面积的对数,这样滤波器可以同时估计目标尺寸变化,减少因车辆远近导致的框尺寸跳变对跟踪稳定性的影响。扩展后观测矩阵 H 变成 3x5(如果状态向量包含尺寸项),处理预报时效果更稳。

3. Matlab 手写卡尔曼滤波实现车辆预测跟踪:单目标最小可运行系统

3.1 用类封装一个可复用的跟踪滤波器

不借助工具箱,直接基于面向对象编程实现一个简单的卡尔曼滤波跟踪器,有利于看清预测和更新两个步骤的数据流动。下面这段代码定义了一个继承自matlab.System的类,也可以直接用普通类配合step方法,便于集成进 Simulink 或作为独立脚本调用。类中只实现 CV 模型,核心逻辑全部在predictcorrect两个方法里。

classdef ConstantVelocityTracker < handle properties F (4,4) double % 状态转移矩阵 H (2,4) double % 观测矩阵 Q (4,4) double % 过程噪声 R (2,2) double % 量测噪声 xhat (4,1) double % 状态估计 [px; py; vx; vy] P (4,4) double % 估计误差协方差 hasPrior logical = false end methods function obj = ConstantVelocityTracker(dt, Q, R) obj.F = [1 0 dt 0; 0 1 0 dt; 0 0 1 0; 0 0 0 1]; obj.H = [1 0 0 0; 0 1 0 0]; obj.Q = Q; obj.R = R; end function obj = init(obj, z) % 用首帧量测初始化位置,速度置 0 obj.xhat = [z(1); z(2); 0; 0]; obj.P = 10 * eye(4); % 初始协方差取较大值 obj.hasPrior = true; end function obj = predict(obj, dt) % 更新状态转移矩阵(支持可变帧间隔) obj.F(1,3) = dt; obj.F(2,4) = dt; obj.xhat = obj.F * obj.xhat; obj.P = obj.F * obj.P * obj.F' + obj.Q; end function [obj, y] = correct(obj, z) % 卡尔曼增益修正,y 为返回的新息 y = z - obj.H * obj.xhat; S = obj.H * obj.P * obj.H' + obj.R; K = obj.P * obj.H' / S; obj.xhat = obj.xhat + K * y; obj.P = (eye(4) - K * obj.H) * obj.P; end end end

使用该滤波器的最小调用流程如下:设置QR,首帧调用init,随后每个新帧先predictcorrect。预测后的状态向量在检测缺失时可以继续输出,最多外推 5~8 帧,具体帧数取决于场景对误报的容忍度。correct方法返回的新息y和协方差S很重要,它们正是下一步数据关联和有效性判断的输入。

3.1.1 可变帧间隔的处理与时间校准

雷达和摄像头通常以不同频率输出数据,导致跟踪器每步的dt并不恒定。代码里通过更新obj.F(1,3)obj.F(2,4)来适配实际帧间隔,这一细节在多人协作项目中常被忽略。时序上,如果传感器数据带有时间戳,而跟踪器采用固定dt递推,长时间运行后帧率漂移会累计产生不可忽略的位置偏差。因此量产代码里一律使用时间戳差值作为dt

3.2 完整仿真脚本:生成含噪轨迹并观察跟踪收敛

下面这段脚本模拟一辆匀速直线运动车辆,叠加零均值高斯噪声作为量测,用上面的滤波器进行跟踪,并绘制真实轨迹、量测和滤波结果的对比图。

rng(2024); dt = 0.1; T = 200; t = (0:T-1) * dt; % 真实轨迹:vx=12m/s, vy=0, 起始点 (0,0) truePos = zeros(2, T); truePos(1,:) = t * 12; % 量测:位置加上标准差 1.0m 的高斯白噪声 sigma_z = 1.0; z = truePos + sigma_z * randn(2, T); % 初始化跟踪器,Q、R 定义见 2.2 sigma_a = 2.0; Q = zeros(4,4); Q(3:4,3:4) = sigma_a^2 * [dt^4/4 0; 0 dt^4/4]; R = sigma_z^2 * eye(2); tracker = ConstantVelocityTracker(dt, Q, R); % 递推滤波 estPos = zeros(2, T); for k = 1:T if ~tracker.hasPrior tracker.init(z(:,k)); estPos(:,k) = tracker.xhat(1:2); continue; end tracker.predict(dt); tracker.correct(z(:,k)); estPos(:,k) = tracker.xhat(1:2); end % 计算 RMS 误差 rmse_raw = sqrt(mean(sum((z - truePos).^2, 1))); rmse_fil = sqrt(mean(sum((estPos - truePos).^2, 1))); fprintf('量测 RMSE: %.3f m\n', rmse_raw); fprintf('滤波 RMSE: %.3f m\n', rmse_fil);

运行这段脚本会看到滤波后的 RMSE 显著小于原始量测。如果调大sigma_a,滤波轨迹会更贴近量测,RMSE 反而上升,这就是 Q 设置过大的直接表现。反过来把sigma_a降到 0.1,滤波轨迹几乎是一条直线,在真正转弯场景中会出现系统性滞后。这个仿真本身就可以当作 Q 调整的测试台。

3.2.1 从单目标到多目标:协方差和门控先于关联算法

单目标滤波做通后,多目标跟踪的复杂度不在滤波器本身,而在量测和轨迹的对应关系判定。多目标场景下每个检测量测都有三种可能:关联到已存在轨迹、成为新轨迹、或判定为虚警。Matlab 的assignDetectionsToTracks函数封装了匈牙利算法,但前提是你先提供“代价矩阵”,而代价矩阵的每个元素就是新息和马氏距离的计算结果。这也是为什么我在上一节的类里保留返回新息和协方差的接口。

4. 多目标关联与跟踪生命周期:车辆跟踪进入真实场景

4.1 马氏距离与门控:把关联问题变成带约束的分配问题

当画面里出现多辆车时,跟踪器不能简单地把最近的检测框当作同一个目标。常见的做法是计算预测状态与每个检测量测之间的马氏距离:

d(k,j) = (z_j - H*xhat_k)' * S_k^(-1) * (z_j - H*xhat_k)

其中S_k是量测预测的协方差矩阵,xhat_k是第 k 条轨迹的预测状态。马氏距离相比欧氏距离的优点是考虑了每个维度的不确定性在不同方向上的差异——例如纵向位置误差通常大于横向位置误差,马氏距离可以自动加权。随后用卡方分布表选择阈值:量测维度为 2(位置 px, py)时,取 95% 置信度对应的门控值为 5.99,取 99% 时为 9.21。一个检测量测如果与所有轨迹的马氏距离都超过门控值,就认为它来自新目标或虚警。

在 Matlab 中实现门控过滤和分配极为简洁:

% 预测协方差需要从 P 转换到量测空间 S = tracker.P * tracker.H' * inv(tracker.H * tracker.P * tracker.H' + tracker.R); % 实际上这个值在 correct() 内部已经算过,正式实现中应当复用 % 马氏距离,z 为当前帧所有检测框中心 [2 x N] for i = 1:size(z,2) innov = z(:,i) - H * tracker.xhat; d(i) = innov' / S * innov; end % 门控阈值,2 自由度下 95% 置信度 gateThreshold = chi2inv(0.95, 2); validIdx = find(d < gateThreshold);

chi2inv是 Matlab 内置函数,用来直接计算门控阈值,避免查表。在实际项目中,门控阈值会被做成可调参数,因为不同场景对误关联的容忍度不同。高速公路场景车辆轨迹稀疏,阈值可以放宽到 95% 甚至 90%;城市交叉口目标密集,需要收紧到 99%,否则相邻车道的车容易被错误关联。

4.1.1 全局最近邻(GNN)与联合概率数据关联(JPDA)的工程取舍

有了门控和代价矩阵之后,最简单的关联策略是每次分配让全局总代价最小,这就是 GNN,Matlab 中直接用assignDetectionsToTracks(costMatrix, costOfNonAssignment)实现。GNN 适合目标数量小于 20 的城市场景,计算量和实现复杂度都低,但是当两个目标靠近且轨迹交叉时,GNN 容易出现“轨迹互换”。更稳的方案是 JPDA,它考虑一个量测可能属于多个目标的所有假设,并进行概率加权。

工程上我一般建议先上 GNN,然后用轨迹的“存活期”来抵抗换轨——如果某条轨迹连续若干帧量测都落在门控边缘,就降低其置信度而不是立刻删除。JPDA 在 Matlab 中也有官方支持,但由于其计算量随目标数量指数增长,实际部署时往往是最后的选择。

4.2 轨迹生命周期的四个状态:初始化、确认、保持、删除

多目标跟踪器不能只做“当前帧分配”,还必须管理轨迹的生命周期。每一条轨迹有四个阶段:

  1. 初始态(Tentative):首次检测到目标时创建,此时不对外发布轨迹,因为单个检测框可能是虚警。
  2. 确认态(Confirmed):连续 N 帧(通常 3~5 帧)都关联到量测后转为确认态,只有确认态轨迹才送入下游融合模块。
  3. 保持态(Holding):目标被短暂遮挡(如被大车挡住)时,通过预测外推维持轨迹,但不再更新协方差。这个阶段应限制最长维持时间,通常不超过 1 秒,否则轨迹会漂移。
  4. 删除态:持续未关联到量测,且超过维持次数后直接删除。

以下代码段展示了如何用一段简洁的循环来更新每个轨迹的属性:

% tracksStatus 保存每条轨迹累计量测命中的次数和连续未命中次数 for i = 1:numTracks if ~tracksStatus(i).hitThisFrame tracksStatus(i).missCount = tracksStatus(i).missCount + 1; else tracksStatus(i).missCount = 0; tracksStatus(i).hitCount = tracksStatus(i).hitCount + 1; end if tracksStatus(i).hitCount >= 3 tracksStatus(i).isConfirmed = true; end if tracksStatus(i).missCount > 10 tracks(i).delete(); % 超出维持帧数,删除轨迹 end end
4.2.1 检测框与轨迹的 IoU 交互:另一种被低估的关联信号

基于位置的马氏距离只考虑中心坐标,完全忽略了框尺寸信息。当两个目标在横向上距离很近但速度方向不同时,只用位置距离很难区分。一个实用的补强手段是在代价矩阵里加入一个“尺寸代价”项:计算轨迹预测框和当前检测框的交并比,当 IoU 过小时,即使中心距离很近也给予惩罚。代码上可以用bboxOverlapRatio函数快速计算 IoU,乘以一个比例因子加到代价矩阵中。这样做在雨雾天或者检测框噪声大的情况下效果提升很明显。

5. 预测跟踪参数校准的进阶技巧:让滤波器状态评估可解释

当跟踪算法在真实路测中表现异常时,第一步不是改参数,而是确认异常来自检测器还是滤波器。一个最直接的手段是把“新息序列”拉出来做自相关分析。理想情况下,卡尔曼滤波的新息应当是不相关的零均值白噪声,如果新息序列出现持续偏置,说明模型失配;如果新息方差远大于S的理论值,说明 Q 设置偏小或量测噪声被低估。

在 Matlab 里做这个验证非常方便,只需把每帧 correct 步骤中的新息存下来:

innovations = zeros(2, T); % 在滤波循环中收集 [tracker, innovations(:,k)] = tracker.correct(z(:,k)); % 检查自相关,若 lag>0 处相关性显著,说明模型没有完全吸收系统偏差 [acf, lags] = xcorr(innovations(1,:) - mean(innovations(1,:)), 10, 'normalized'); stem(lags(11:end), acf(11:end));

如果自相关图在 lag=1 处明显超出 95% 置信带,通常不是调 Q 能解决的,而是运动模型选型错了——例如目标在持续转弯而你还在用 CV 模型。

最后一个容易被忽视的参数是时间基准的同步。如果预测步骤用了上一帧的dt,但检测器输出实际来自稍早时刻的数据(传感器内部延迟),跟踪输出的平滑性会显著下降。在量产架构里,我会为每个传感器数据包打上时间戳,并让跟踪器运行在统一的周期调度中,而不是检测到一帧处理一帧。对于延期到达的数据包,用预测步骤把轨迹推进到该时间戳,再做一次 correct,回到当前时间做下一次预测。

这套“时间戳对齐 + 新息自相关检查 + 门控与生命周期管理”的组合,基本覆盖了预测跟踪落地到路测的 80% 问题。如果你正在用trackerGNN做方案验证,我建议依然保留一个手写的单目标滤波器专门用来标定 Q 和 R 数量级——工具箱给出的默认值在真实场景里通常过于乐观。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询