4、RSSI测距实战:RSSI数据采集、滤波处理与距离估算
RSSI测距,说白了就是利用信号强度来猜距离。听起来简单,但实际做起来坑不少。我记得刚入行那会儿,拿着蓝牙模块在实验室里测,数据跳得跟心电图似的,根本没法用。后来才明白,原始RSSI不经过处理,直接拿去算距离,结果基本是废的。
这一章,我们就来把RSSI测距的完整流程走一遍。从数据采集开始,到三种常用滤波方法,再到距离估算和误差分析。每一步我都会把项目里踩过的坑讲清楚。
核心流程: 原始RSSI采集 → 滤波预处理 → 路径损耗模型估算 → 误差分析与校准
4.1 RSSI数据采集:别急着上手
数据采集看起来最简单,但恰恰是出错最多的地方。我曾经有个项目,部署了6个网关,结果采集回来的数据完全对不上,排查了两天才发现是采样时间窗口没对齐。
采集时要注意几个关键点:
- 采样频率:建议10-20Hz,太低捕捉不到信号波动,太高数据冗余太大
- 采样时长:每个位置至少采集3-5秒,保证有足够样本做滤波
- 多网关同步:所有网关必须在同一时间窗口内采样,否则距离估算会乱套
- 环境记录:记下当时的温度、湿度、遮挡物情况,这些后面分析误差时用得上
我的习惯:每次采集前先发一个同步广播包,所有网关收到后开始采样。这样能保证时间窗口偏差在10ms以内。
下面是一个简单的数据采集代码示例,我用的是ESP32 + 蓝牙Beacon的方案:
// RSSI数据采集示例(Arduino框架)
#define SAMPLE_COUNT 100
int rssi_samples[SAMPLE_COUNT];
void collect_rssi_samples() {
for (int i = 0; i < SAMPLE_COUNT; i++) {
// 扫描并获取最近的Beacon RSSI
rssi_samples[i] = get_beacon_rssi("BEACON_MAC");
delay(50); // 20Hz采样
}
}
int get_beacon_rssi(String mac) {
// 实际项目中这里调用蓝牙扫描API
// 返回RSSI值,单位dBm
return -65; // 示例返回值
}
4.2 三种滤波方法:各有各的脾气
原始RSSI数据拿到手,第一件事就是滤波。你想想看,信号在室内反射、绕射、吸收,RSSI值能稳定才怪。我试过不滤波直接算距离,误差能到10米以上,完全没法用。
4.2.1 均值滤波:简单但有效
均值滤波是最基础的方法。把N个样本加起来除以N,就得到一个平均值。优点是计算量小,缺点是对突发干扰很敏感。
// 均值滤波
float mean_filter(int* samples, int len) {
long sum = 0;
for (int i = 0; i < len; i++) {
sum += samples[i];
}
return (float)sum / len;
}
适用场景:信号相对稳定的环境,比如空旷的仓库、走廊。
4.2.2 中值滤波:抗干扰能力强
中值滤波的原理很简单:把样本排序,取中间那个值。这样做的好处是,哪怕有几个样本因为干扰跳到了-30dBm或-90dBm,对结果影响也不大。
// 中值滤波
int median_filter(int* samples, int len) {
// 先排序(这里用简单的冒泡,实际项目用快速排序)
int temp[len];
memcpy(temp, samples, len * sizeof(int));
for (int i = 0; i < len - 1; i++) {
for (int j = 0; j < len - i - 1; j++) {
if (temp[j] > temp[j + 1]) {
int t = temp[j];
temp[j] = temp[j + 1];
temp[j + 1] = t;
}
}
}
// 取中值
return temp[len / 2];
}
我曾经踩过的坑:有一次在工厂车间做测试,均值滤波出来的结果完全不对,后来发现是因为叉车经过时产生了强烈的电磁干扰。换成中值滤波后,问题立刻解决了。
4.2.3 高斯滤波:保留有效信号
高斯滤波是我个人比较喜欢的方法。它假设RSSI值服从正态分布,只保留概率较高的样本,剔除那些偏离太远的异常值。
// 高斯滤波
float gaussian_filter(int* samples, int len) {
// 第一步:计算均值和标准差
float mean = mean_filter(samples, len);
float variance = 0;
for (int i = 0; i < len; i++) {
variance += pow(samples[i] - mean, 2);
}
variance /= len;
float std_dev = sqrt(variance);
// 第二步:只保留均值±1.5σ范围内的样本
int valid_count = 0;
float sum_valid = 0;
for (int i = 0; i < len; i++) {
if (abs(samples[i] - mean) <= 1.5 * std_dev) {
sum_valid += samples[i];
valid_count++;
}
}
return (valid_count > 0) ? (sum_valid / valid_count) : mean;
}
为什么用1.5σ? 我做过大量实测,1.5倍标准差能保留约87%的有效样本,同时剔除掉那些明显异常的跳变点。这个参数可以根据实际环境微调。
4.3 三种滤波效果对比
为了让你更直观地看到区别,我拿一组实际采集的数据做了对比。这是在办公室环境下,距离网关5米处采集的100个样本。
| 滤波方法 | 输出值(dBm) | 标准差(dBm) | 计算耗时(ms) | 抗干扰能力 |
|---|---|---|---|---|
| 原始数据 | -68.3 | 4.7 | - | 无 |
| 均值滤波 | -68.3 | 2.1 | 0.02 | 弱 |
| 中值滤波 | -67.0 | 1.8 | 0.15 | 强 |
| 高斯滤波 | -68.8 | 1.2 | 0.35 | 较强 |
从表中能看出来:高斯滤波的标准差最小,说明稳定性最好。但计算耗时也最长。实际项目中怎么选?我的建议是:
- 资源受限的嵌入式设备:用中值滤波,效果好且计算量适中
- 服务器端处理:用高斯滤波,精度优先
- 快速原型验证:用均值滤波,简单直接
4.4 距离估算:从RSSI到米
滤波完的RSSI值,怎么换算成距离?这里要用到路径损耗模型。最常用的是对数距离路径损耗模型:
// 距离估算函数
float rssi_to_distance(float rssi, float tx_power, float n) {
// tx_power: 1米处的参考RSSI值(通常-59dBm)
// n: 环境衰减因子(自由空间2.0,室内2.5-4.0)
// 公式: distance = 10^((tx_power - rssi) / (10 * n))
return pow(10, (tx_power - rssi) / (10.0 * n));
}
// 使用示例
float filtered_rssi = gaussian_filter(samples, 100);
float distance = rssi_to_distance(filtered_rssi, -59, 3.0);
printf("估算距离: %.2f 米\n", distance);
这里有两个关键参数需要校准:
- tx_power:1米处的参考RSSI。这个值每个设备都不一样,必须实测。我习惯在1米处采集200个样本取均值作为tx_power。
- n(环境衰减因子):这个值决定了信号随距离衰减的速度。自由空间是2.0,普通办公室大概2.5-3.0,有隔断的仓库可能到3.5-4.0。
避坑指南:千万不要直接用芯片手册上写的tx_power。我遇到过好几次,手册写-59dBm,实际测出来是-63dBm,差4个dBm,算出来的距离能差30%。
4.5 误差分析:为什么测不准?
RSSI测距的误差来源很多,我总结了几类:
| 误差类型 | 典型误差范围 | 主要原因 | 缓解方法 |
|---|---|---|---|
| 多径效应 | 2-8米 | 信号反射叠加 | 多网关协同、天线分集 |
| 阴影衰落 | 1-5米 | 障碍物遮挡 | 动态环境建模 |
| 设备差异 | 1-3米 | 天线增益不同 | 逐个设备校准 |
| 温度漂移 | 0.5-2米 | 射频器件温漂 | 温度补偿 |
我记得有一次在停车场做测试,估算距离一直在3-8米之间跳,怎么都稳定不下来。后来发现是旁边有根金属柱子,信号反射特别严重。换了位置之后,误差立刻降到了1米以内。
所以,做RSSI测距一定要有心理准备:绝对精度很难做到1米以下。但在多网关协同定位中,我们通过多个网关的数据融合,可以大幅提升定位精度。这个后面几章会详细讲。
4.6 本章小结
这一章我们把RSSI测距的完整流程走了一遍。从数据采集的注意事项,到三种滤波方法的原理和代码实现,再到距离估算和误差分析。核心就一句话:原始RSSI不可信,滤波是必须的,参数校准是关键。