☰
卡尔曼滤波结合RSSI实现分布式定位轨迹预测与校正
2026/10/15 23:41:19 网站建设 项目流程

前阵子帮一位朋友调试一套室内定位演示系统,场景不复杂:几十平方米的空间里部署了七八个低功耗传感器节点,一个可移动的小车带着无线信标来回走,节点把各自收到的信号强度(RSSI)传回上位机,要在上位机上实时画出小车的运动轨迹。最初他们拿 RSSI 直接画散点,轨迹抖得像心电图,后来把卡尔曼滤波加进去,轨迹才变得像人能走出来的样子。这个项目标题讲的“基于卡尔曼滤波实现分布式传感器采集目标位置或 RSSI 数据进行轨迹预测与校正,输出平均距离误差和 RMSE 并可视化”,本质上就是把这件事从头到尾做一遍,顺便把误差量化出来。

这类需求在室内定位、安防监控、无人机编队、工业巡检里非常常见。传感器节点本身很便宜,不能指望单点测距有多准,但多个节点把观测拼起来,再用卡尔曼滤波去平滑校正,完全可以在工程上达到实用水平。这篇文章会把建模思路、每一步代码、调参心得和踩过的坑按项目全程梳理出来,适合正在做定位跟踪相关课题的同学、准备用低精度传感器做轨迹还原的工程师,以及想系统了解卡尔曼滤波如何落地的初学者。

1. 核心思路:RSSI 加卡尔曼滤波这套方案的底气在哪

1.1 分布式传感器跟踪,本质是“弱观测、多来源”

分布式传感器网络这个“分布式”三个字,容易让人想到分布式卡尔曼滤波或一致性算法,但本项目里它更多是指物理部署架构:传感器节点散布在区域各处,各自独立测量同一个目标,测量数据回传到上位机后集中处理。节点只负责感知和转发,不负责解算,这降低了节点硬件要求,也让算法升级只需要改上位机。

这种架构下有个现实问题:单个节点的观测质量很不稳定。RSSI 受路径损耗、阴影衰落、多径效应影响,同一位置同一目标,换个墙角可能就是几 dBm 的起伏。换算成距离误差,10 米外 3 dBm 的波动就能带来接近 30% 的距离偏差。单独拿任何一个节点去定位,结果都不可信。

但卡尔曼滤波最擅长的恰恰就是你给它一堆“不太靠谱但统计特性可知”的观测,它按噪声协方差给每个观测分配权重,把可靠的信息吸收进来,把不可靠的信息平滑掉。多个弱观测节点加一个合适的滤波器,综合效果往往比任何一个单节点都稳。这也是分布式传感器组网做目标跟踪最常见的工程范式。

1.2 直接位置观测和 RSSI 观测,两条路线怎么选

项目标题里写了“目标的位置或信号强度 RSSI 数据”,这两种数据源在滤波里对应不同的观测模型,需要先厘清。

如果传感器直接输出目标位置,比如通过 AOA 测角交叉定位、TOA 测距解算等手段得到坐标,那观测方程的矩阵就是常数矩阵,滤波器直接吃坐标观测就行。这种情况最简单,代码里观测更新那一步完全不用改。

如果传感器只给 RSSI 原始强度值,那就必须先做一层转换:RSSI 根据路径损耗模型换算成距离,再由多个距离值用最小二乘解算出目标坐标,最后把这个坐标当成“观测”喂给卡尔曼滤波。

两条路的对比如下:

数据源硬件成本解算复杂度精度上限典型场景
直接位置观测较高低取决于传感器本身雷达、视觉、激光
RSSI 观测低中中,受环境干扰大蓝牙/WiFi 信标定位

本项目的代码以 RSSI 路线为主,因为这条链路更通用,也更能体现“弱观测也能把轨迹磨出来”的价值。直接位置观测的代码只需要把观测生成部分换成现成坐标,其余滤波逻辑原封不动。

1.3 为什么偏偏是卡尔曼滤波

这个场景里卡尔曼滤波几乎是天然选择,原因有三。

第一,计算量小。状态维数只有 4 维,观测维数 2 维,矩阵运算都是 4×4 级别,普通上位机每个采样周期跑几百上千个点毫无压力,实时性完全撑得住。

第二,在系统满足线性高斯假设时,标准卡尔曼滤波是方差意义下的最优估计器。RSSI 定位后误差近似高斯分布,运动模型误差用过程噪声吸收,假设基本站得住。

第三,实现门槛低。五步递推公式固定,MATLAB 里二三十行就能写清,调试周期比粒子滤波、无迹卡尔曼短得多。粒子滤波确实能处理非线性非高斯,但粒子数取多少、重采样怎么做、收敛怎么判断都是坑,没有足够精力没必要在这里上。

当然,这套方案的代价是它不擅长强机动目标。目标突然大转角、急加速时,恒定速度模型会有一段跟不上,后面调参部分我会专门说这个现象。

1.4 从仿真到评估的完整链路

整个项目可以拆成五个模块,顺序跑通后就构成了一个可评估的闭环:

  1. 场景生成:设计目标真实运动轨迹、部署传感器节点。
  2. 观测生成:按路径损耗模型算每个节点收到的 RSSI,加上阴影衰落噪声,按接收灵敏度筛掉无效节点。
  3. 位置解算:用可见节点的距离估计做最小二乘定位,得到带噪声的目标坐标。
  4. 卡尔曼滤波:对坐标观测做预测与校正,输出平滑后的轨迹。
  5. 误差评估与可视化:用真实轨迹与滤波轨迹计算平均距离误差和 RMSE,绘制轨迹对比和误差曲线。

这套流程的好处是每一步都可以单独替换,比如把运动模型换成匀加速模型、把定位算法换成三边加权质心,看整体指标变化。下面按这个链路逐步展开。

2. 建模细节:状态方程、观测方程和噪声怎么定

2.1 状态向量与运动模型设计

目标在二维平面运动,状态向量取:

x = [位置x; 位置y; 速度vx; 速度vy]

用恒定速度模型,采样周期为 dt,状态转移矩阵为:

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

预测方程就是 x(k|k-1) = F * x(k-1|k-1)。很多读者会问:真实目标明明走的不是直线,为什么还用恒定速度模型?答案是模型失配的部分交给过程噪声 Q 去吸收。只要目标机动不是太剧烈、Q 设置合理,滤波器仍然能跟上平均速度变化;如果目标长期做匀加速运动,那就应该升级为带加速度项的状态向量。本项目仿真轨迹是一条曲线,恒定速度模型加适当 Q 已经足够。

观测方程取:

H = [1 0 0 0; 0 1 0 0]

表示观测向量是位置坐标,速度和观测没有直接关系。

2.2 RSSI 路径损耗模型与距离估计换算

RSSI 与距离常用对数距离路径损耗模型描述:

RSSI(d) = RSSI0 - 10*n*log10(d/d0) + W

其中 RSSI0 是参考距离 d0 处的信号强度,n 是路径损耗指数,W 是阴影衰落,一般建模为零均值高斯噪声,标准差记为 sigma_RSSI。工程上常取 d0=1m,RSSI0=-40dBm,n 在 1.8 到 3 之间,sigma_RSSI 在 1 到 5dB 之间。

由测量到的 RSSI 反推距离:

d_est = d0 * 10^((RSSI0 - RSSI_meas) / (10*n))

注意这条公式要求 RSSI 噪声加在 dB 域,也就是对数域,再转换到线性距离域。这个顺序不能反,原因见后面避坑章节。对数域的固定标准差转换到距离域后,距离越远误差越大,这也是 RSSI 定位“近处准、远处飘”的直接原因。

2.3 最小二乘定位:把距离变成坐标

假设某时刻有 m 个传感器可见,位置分别是 (xi, yi),由 RSSI 换算得到目标到各节点的距离估计为 di。对每个传感器列圆方程:

(x - xi)^2 + (y - yi)^2 = di^2

两个圆方程相减可以消掉二次项,得到一个线性方程。取第一个可见传感器为基准,对第 i 个传感器整理可得:

2*(xi - x1)*x + 2*(yi - y1)*y = xi^2 + yi^2 - x1^2 - y1^2 - di^2 + d1^2

把所有可见节点(除基准外)的方程堆起来,就得到形如 A*z = b 的超定方程组,最小二乘解为:

z = (A'*A) \ (A'*b)

这个解就是当前时刻目标位置的观测值。可见节点越多、几何分布越分散,定位结果越稳定;如果三个传感器几乎在一条直线上,矩阵会病态,后面会专门讲。

2.4 Q 与 R 矩阵设置原则

卡尔曼滤波里两个矩阵直接决定滤波“信谁”:

  • 过程噪声协方差 Q:代表运动模型的不确定性。目标机动强就把 Q 调大,告诉滤波器预测不太可靠、要多相信观测;目标运动平稳就把 Q 调小,让轨迹更平滑。
  • 观测噪声协方差 R:代表位置观测的噪声方差。R 大说明观测不靠谱、滤波更依赖预测;R 小说明观测可信、滤波更跟观察测值走。

本项目里用简化形式:

Q = diag([0.3, 0.3, 0.5, 0.5]); R = diag([2.5^2, 2.5^2]);

R 取 2.5 米方差是因为 RSSI 最小二乘定位在中等距离下标准差大致在这个量级。更严谨的做法是用连续白噪声模型构造 Q,把位置噪声和速度噪声的交叉项也考虑进去,但工程上对角线形式参数直观、方便调,大多数场景够用。

3. MATLAB 实现:关键代码和每一步在做什么

3.1 场景生成:目标轨迹与传感器部署

仿真总时长 40 秒,采样周期 0.1 秒,共 401 个时刻。目标轨迹用正弦和余弦组合成一条闭合曲线,让目标在传感器覆盖区域内反复运动、改变方向,这样能测试滤波器对曲线运动的跟踪能力。

dt = 0.1; T = 40; t = 0:dt:T; N = length(t); x_true = zeros(4, N); x_true(1,:) = 8 * cos(0.15 * t); x_true(2,:) = 5 * sin(0.12 * t) + 8 * sin(0.05 * t); x_true(3,:) = [diff(x_true(1,:))/dt, (x_true(1,end)-x_true(1,end-1))/dt]; x_true(4,:) = [diff(x_true(2,:))/dt, (x_true(2,end)-x_true(2,end-1))/dt];

速度分量由位置差分近似。传感器部署 8 个节点,坐标覆盖区域周边:

sensorPos = [30 -25; -25 -20; 20 25; -30 20; 5 30; -20 10; 25 -5; 0 -30];

目标轨迹主体在原点附近约 ±15 米范围,传感器散布在 ±30 米范围,这样目标运动过程中与各节点的距离动态变化,有的节点能稳定可见,有的节点会因距离过远而丢包,更贴近真实系统。

3.2 观测生成:RSSI 采样与可见节点筛选

每个采样时刻,先计算目标到各传感器的真实距离,按路径损耗模型生成带阴影衰落的 RSSI 观测值,再根据接收灵敏度阈值筛掉不可见节点:

d0 = 1; RSSI0 = -40; nPL = 2.2; sigma_RSSI = 2.5; sensThresh = -70; z_obs = nan(2, N); valid_obs = false(1, N); for k = 1:N targetPos = [x_true(1,k); x_true(2,k)]; delta = sensorPos - targetPos'; d_true = sqrt(sum(delta.^2, 2)); rssi_true = RSSI0 - 10 * nPL * log10(max(d_true, d0) / d0); rssi_meas = rssi_true + sigma_RSSI * randn(length(sensorPos), 1); visible = rssi_meas >= sensThresh; end

灵敏度阈值取 -70dBm,对应约二十几米的可靠接收距离。目标靠近某个节点时该节点必然可见,目标跑到区域边缘时远端节点自动退出,模拟传感器网络的覆盖变化。

3.3 最小二乘定位函数的实现

对每个时刻,如果可见节点数不少于 3,就调用最小二乘定位:

idxVis = find(visible); if length(idxVis) >= 3 baseIdx = idxVis(1); A = zeros(length(idxVis)-1, 2); b = zeros(length(idxVis)-1, 1); row = 0; for ii = 2:length(idxVis) ci = idxVis(ii); d_est = d0 * 10.^((RSSI0 - rssi_meas) / (10 * nPL)); row = row + 1; A(row,:) = 2 * (sensorPos(ci,:) - sensorPos(baseIdx,:)); b(row) = sum(sensorPos(ci,:).^2) - sum(sensorPos(baseIdx,:).^2) ... - d_est(ci)^2 + d_est(baseIdx)^2; end zPos = (A'*A) \ (A'*b); z_obs(:,k) = zPos; valid_obs(k) = true; end

这段代码的核心是消去二次项后构建线性方程组。实际跑下来,可见节点 3 到 5 个时定位误差普遍在 2 到 4 米,节点多且分散时误差会小一些,几何差时误差会明显变大。

3.4 卡尔曼滤波主循环:预测、更新、丢包处理

滤波初始化给一个带小误差的起始位置,速度置零,协方差矩阵给一个中等置信度:

x_est = zeros(4, N); x_est(:,1) = [x_true(1,1) + 0.5*randn; x_true(2,1) + 0.5*randn; 0; 0]; P = diag([1, 1, 1, 1]); F = [1 0 dt 0; 0 1 0 dt; 0 0 1 0; 0 0 0 1]; H = [1 0 0 0; 0 1 0 0]; Q = diag([0.3, 0.3, 0.5, 0.5]); R = diag([2.5^2, 2.5^2]);

主循环从第二个时刻开始,每个时刻先做预测,再判断当前是否有有效观测。有观测就计算卡尔曼增益并更新,没有观测就直接用预测结果:

for k = 2:N x_pred = F * x_est(:,k-1); P_pred = F * P * F' + Q; if valid_obs(k) K = P_pred * H' / (H * P_pred * H' + R); x_est(:,k) = x_pred + K * (z_obs(:,k) - H * x_pred); P = (eye(4) - K * H) * P_pred; else x_est(:,k) = x_pred; P = P_pred; end end

这个“无观测时只预测不更新”的分支很容易被初学者忽略,但在真实分布式系统里非常关键。传感器丢包、节点故障、目标跑到覆盖盲区都可能导致某时刻没有观测,如果这时候还强行拿旧观测更新,滤波器会给出错误结果,甚至导致发散。

3.5 误差统计与可视化输出

统计两个核心指标:平均距离误差和位置 RMSE。为了避免初始收敛段拉高误差,统计时跳过前 2 秒:

startIdx = find(t >= 2, 1); trackErr = sqrt((x_est(1,startIdx:end) - x_true(1,startIdx:end)).^2 + ... (x_est(2,startIdx:end) - x_true(2,startIdx:end)).^2); meanErr = mean(trackErr); rmse = sqrt(mean(trackErr.^2)); fprintf('平均距离误差: %.3f m\n', meanErr); fprintf('位置RMSE: %.3f m\n', rmse);

可视化用六个子图分别展示轨迹对比、X/Y 分量、距离误差、可见节点数和观测分布:

figure('Position', [100 100 1400 800]); subplot(2,3,1); plot(x_true(1,:), x_true(2,:), 'k-', 'LineWidth', 1.8); hold on; plot(x_est(1,:), x_est(2,:), 'r--', 'LineWidth', 1.4); plot(z_obs(1,valid_obs), z_obs(2,valid_obs), 'b.', 'MarkerSize', 5); plot(sensorPos(:,1), sensorPos(:,2), 'g^', 'MarkerSize', 10, 'MarkerFaceColor', 'g'); legend('真实轨迹', '滤波轨迹', 'RSSI定位观测', '传感器节点', 'Location', 'best'); axis equal; grid on; title('轨迹跟踪效果对比'); subplot(2,3,2); plot(t, x_true(1,:), 'k-', 'LineWidth', 1.5); hold on; plot(t, x_est(1,:), 'r--', 'LineWidth', 1.2); xlabel('时间/s'); ylabel('X/m'); grid on; legend('真实X', '滤波X'); title('X方向分量'); subplot(2,3,3); plot(t, x_true(2,:), 'k-', 'LineWidth', 1.5); hold on; plot(t, x_est(2,:), 'r--', 'LineWidth', 1.2); xlabel('时间/s'); ylabel('Y/m'); grid on; legend('真实Y', '滤波Y'); title('Y方向分量'); subplot(2,3,4); plot(t, trackErr, 'b-', 'LineWidth', 1.2); xlabel('时间/s'); ylabel('距离误差/m'); grid on; title(sprintf('距离误差 (均值 %.2fm, RMSE %.2fm)', meanErr, rmse)); subplot(2,3,5); plot(t, sum(valid_obs_array), 'r-', 'LineWidth', 1.2); xlabel('时间/s'); ylabel('节点数'); grid on; title('可见传感器节点数量变化'); subplot(2,3,6); histogram(z_obs(1,valid_obs)-x_true(1,valid_obs), 30); xlabel('X方向定位误差/m'); ylabel('频次'); grid on; title('RSSI定位误差分布');

这里 visible 数组如果只在循环内产生,后面要统计节点数变化需要把它存下来,实际操作时用 visible_cnt 数组记录每个时刻的可见数量即可。

4. 实测效果与调参经验

4.1 一组典型结果:误差数值和图形怎么读

用默认参数跑一次,我实测得到的典型结果是:平均距离误差 1.5 米左右,位置 RMSE 2 米左右。这个数值放在室内定位场景里属于“能看出运动趋势、能区分大致路径”的水平,距离米级高精度还有差距,但作为信号强度定位的滤波结果已经很合理。

看图上轨迹对比会发现几个特征:滤波轨迹比 RSSI 观测散点平滑很多,在目标速度变化平缓的区段几乎贴着真实轨迹;在轨迹曲率较大的位置,滤波轨迹会“切弯”,也就是沿着弯道内侧走,误差曲线在这些位置会出现小尖峰。这个切弯现象是恒定速度模型对曲线运动的固有滞后,通过适当增大 Q 可以压缩,但不能完全消除。

4.2 不同 RSSI 噪声强度下的误差对比

sigma_RSSI 是环境阴影衰落的直接体现,它越大,单点距离估计越差,定位误差越大。我用同一目标轨迹做了三组对比:

sigma_RSSI平均距离误差位置 RMSE
1 dB约 0.8 m约 1.1 m
2.5 dB约 1.5 m约 2.0 m
5 dB约 2.8 m约 3.6 m

这个趋势说明 R 的选取应该跟随环境噪声水平动态调整。实测中如果环境噪声变大而 R 还保持小值,滤波会过度信任观测,误差曲线会出现明显锯齿。反过来,如果环境噪声其实很小但 R 给得很大,轨迹会过度平滑、响应迟钝。

4.3 Q 和 R 的调节手感与典型误区

调 Q 和 R 的直观手感可以总结为“Q 决定轨迹的灵活度,R 决定轨迹的平滑度”。

Q 太小,滤波器对机动的反应很慢,目标转弯时轨迹明显滞后,误差曲线出现大幅摆动的趋势;Q 太大,滤波器几乎不信任自己预测的运动趋势,轨迹会跟着观测乱跳,平滑作用减弱。

R 的调节方向正好相反。R 太小时,观测里的噪声直接穿透滤波器,轨迹抖动加剧;R 太大时,滤波器更依赖预测,轨迹看起来很平滑,但真实突变位置会反应迟钝。

一个常见的误区是把 Q 和 R 同时调得很小,以为这样“噪声低、效果好”,结果卡尔曼增益既不相信预测又不相信观测,滤波结果变得很飘忽。实际调试时一般固定 R 按物理估计,然后只调 Q,看轨迹的灵活度是否匹配目标机动。也可以用模拟退火等简单方式扫参数,但人工观察误差曲线往往更直接。

4.4 观测丢包场景下滤波的韧性

这套仿真里灵敏度阈值设成 -70dBm 后,可见节点数并不恒定。目标跑向区域远端时,个别节点会低于阈值,可见节点数可能从 6 个掉到 4 个;极少数时刻可能出现少于 3 个可见节点,此时没有观测,滤波退化为纯预测。

我特意统计过丢包时刻的误差变化:连续一两秒无观测时,由于目标运动相对规律,预测轨迹仍然能保持在真实轨迹附近,误差增加约半米;重新获得观测后,滤波器能在几个周期内把误差拉回来。这说明卡尔曼滤波的预测能力在分布式系统中是很有价值的——就算某个时刻观测全部丢失,系统仍然能给出一段时间的位置估计,而不是直接黑屏。

5. 常见坑与排查速查

5.1 RSSI 噪声必须加在 dB 域,别加在线性域

这是最容易被带偏的点。正确的观测生成方式是在 dBm 域加高斯噪声,再转换成距离。有人直接在真实距离上加高斯噪声模拟 RSSI 测量误差,相当于把整个误差模型搞反了。

从数学上看,dB 域的固定标准差在线性域对应的是乘性对数正态噪声,远处误差大、近处误差小。如果在线性域加固定标准差的高斯噪声,近处可能变成负距离,远处误差又体现不出来,整个定位链路都会失真。RSSI 转距离的公式决定了两者的换算关系完全不同。

5.2 最小二乘矩阵奇异或病态怎么办

最小二乘定位的 A 矩阵如果接近奇异,通常是因为可见传感器几乎共线,或者基准传感器和其他传感器离得太近。这时 (A'*A) 的行列式接近零,求逆结果会非常离谱,可能算出几十米外的错误坐标。

工程上有两个简单对策:一是可见节点多时,把所有可见节点轮流作为基准,取误差最小的组合;二是对定位结果加一个合理性检查,如果位置解离所有传感器太远或速度突变异常,就丢弃该观测,本时刻按无观测处理。后一种方法实现成本低、见效快,在真实系统里非常实用。

5.3 滤波器发散的三大症状与对策

卡尔曼滤波发散最常见的三个症状是:

  • 轨迹在某几个点出现大幅跳变,然后又很快拉回,多半是 R 给得太小,观测噪声穿透了滤波器。
  • 轨迹整体偏离真实轨迹,更新基本不起作用,多半是 R 给得太大,滤波器太依赖预测。
  • 误差持续增长不收敛,考虑是不是 Q 太小无法覆盖运动模型失配,或者状态矩阵写错了导致 P 计算异常。

排查时可以先把 P 的每一个时刻输出画出来,如果协方差数值异常膨胀,或者卡尔曼增益趋近于零,基本可以锁定问题出在噪声矩阵的设置上。也可以把真实观测单独画出来和滤波结果对比,先确认定位观测本身不是发散的,再谈滤波调参。

5.4 误差指标统计时别忘了观测丢失段

平均距离误差和 RMSE 的统计要覆盖所有滤波输出时刻,包括无观测的预测段。有些实现只统计有观测时刻的误差,这会高估滤波器性能,因为丢包时预测段误差通常更大,丢掉这些样本显然不公平。

真正交付项目时,建议把无观测段的误差单独统计出来,和正常时段并排展示。这样既能体现卡尔曼滤波的预测能力,也能直观反映系统对覆盖盲区的容忍度。这也是向非技术角色解释“系统什么时候会变差”的最好方式。

5.5 代码调试层面的几个小建议

第一,在跑整条链路前,先把真实轨迹画出来,确认轨迹范围和传感器部署区域匹配。我调试初期就遇到过目标轨迹跑出传感器覆盖范围的情况,结果后面每个时刻都几乎无观测,一排查发现是轨迹幅度设置太大。

第二,每一步都留输出存档。RSSI 观测、距离估计、最小二乘定位结果、滤波结果分别存成变量,出了问题可以定位到具体环节,不用从头猜。

第三,随机种子固定下来。RSSI 噪声是随机的,不固定种子每次跑的结果都不一样,调参时很难判断改参数的效果是真实改善还是随机波动。在代码开头固定 rng,等参数调完再放开随机性验证稳定性,是最省事的做法。

如果把这套代码继续往深做,可以考虑几个方向:把运动模型升级为匀加速模型或交互多模型来应对更复杂的机动目标;在观测端引入加权融合,按节点距离质量动态调整每个节点的权重;也可以把 RSSI 原值直接作为非线性观测接入无迹卡尔曼滤波,省掉最小二乘定位这一层,减少误差传播。每条路都有各自的适用场景,但核心的“弱观测多来源加滤波平滑”这套思路,几乎所有分布式跟踪问题都能复用上。

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

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

立即咨询