室内定位与测距:RSSI测距、三角定位、指纹定位、Kalman滤波
说到室内定位,很多朋友第一反应就是「用GPS不就行了?」。但做过实际项目的都知道,GPS在室内基本就是个摆设——信号被楼板一挡,定位精度直接掉到几十米开外。
那BLE能做室内定位吗?能,但没那么简单。我最早在做一个商场导购项目时,天真地以为拿到RSSI值就能算出精确位置。结果呢?数据抖得像心电图,定位点满屏乱跳。后来才明白,室内定位是个系统工程,得把测距、滤波、定位算法串起来才行。
今天我们就来拆解这套流程。说白了,就是三步:先测距,再滤波,最后定位。
RSSI测距:信号强度与距离的关系
RSSI(Received Signal Strength Indicator)是BLE芯片直接上报的数值,单位是dBm。理论上,距离越近,RSSI越大(负值越小)。但实际环境里,信号会被墙壁、人体、金属货架吸收反射,导致RSSI剧烈波动。
常用的测距模型是对数距离路径损耗模型:
RSSI = A - 10 * n * log10(d)
其中:
- A:距离1米时的参考RSSI值(通常取-55 ~ -65 dBm)
- n:环境衰减因子(自由空间取2,室内有障碍取3~4)
- d:待求距离(米)
反推距离公式:
d = 10 ^ ((A - RSSI) / (10 * n))
关键点:A值和n值必须根据实际环境校准。我在项目中吃过亏——直接用了网上找的默认参数,结果测距误差超过5米。后来老老实实在现场采集了100组数据做拟合,才把误差压到1米以内。
校准小技巧:在1米、2米、3米、5米、8米处各采集20组RSSI数据,取均值后用最小二乘法拟合出A和n。别偷懒,这一步省不了。
Kalman滤波:让RSSI不再「抽风」
原始RSSI值波动非常大。同一位置、同一设备,两次采样可能差10个dBm。直接拿这个值去算距离,结果就是定位点满屏乱跳。
Kalman滤波就是用来解决这个问题的。它本质上是一个预测-校正的过程:
- 预测:根据上一时刻的状态,预测当前时刻的RSSI值
- 校正:用当前实际测量值,修正预测值
核心公式就五个,但我不建议你死记硬背。直接看代码实现更直观:
class KalmanFilter(private val processNoise: Float = 0.01f,
private val measurementNoise: Float = 0.1f) {
private var estimate = 0f // 估计值
private var errorCovariance = 1f // 误差协方差
private var kalmanGain = 0f // 卡尔曼增益
fun filter(measurement: Float): Float {
// 预测阶段
errorCovariance += processNoise
// 计算卡尔曼增益
kalmanGain = errorCovariance / (errorCovariance + measurementNoise)
// 校正阶段
estimate += kalmanGain * (measurement - estimate)
errorCovariance *= (1 - kalmanGain)
return estimate
}
}
参数调优:processNoise和measurementNoise需要根据实际数据调整。processNoise越大,滤波器响应越快但波动也大;measurementNoise越大,滤波越平滑但延迟也大。我一般先设processNoise=0.01,measurementNoise=0.1,然后看效果微调。
用了Kalman滤波之后,RSSI曲线明显平滑了很多。定位点不再满屏乱跳,用户体验好了不止一个档次。
三角定位:三个信标定天下
三角定位的原理很简单:已知三个信标的坐标,以及设备到每个信标的距离,就能算出设备的位置。这其实就是几何里的三圆交点问题。
但实际中,三个圆往往不会交于一点,而是形成一个误差区域。这时候需要用最小二乘法来求解:
data class Point(val x: Double, val y: Double)
fun trilateration(beacons: List<Triple<Point, Double>>): Point? {
if (beacons.size < 3) return null
// 构建线性方程组 Ax = b
// 用第一个信标作为参考点,减去其他信标的方程
val (p1, r1) = beacons[0]
val A = Array(beacons.size - 1) { i ->
val (p, _) = beacons[i + 1]
doubleArrayOf(2 * (p.x - p1.x), 2 * (p.y - p1.y))
}
val b = DoubleArray(beacons.size - 1) { i ->
val (p, r) = beacons[i + 1]
r1 * r1 - r * r - p1.x * p1.x + p.x * p.x - p1.y * p1.y + p.y * p.y
}
// 解最小二乘:x = (A^T * A)^-1 * A^T * b
// 这里用简化实现,实际项目建议用矩阵库
val a11 = A.sumOf { it[0] * it[0] }
val a12 = A.sumOf { it[0] * it[1] }
val a22 = A.sumOf { it[1] * it[1] }
val b1 = A.indices.sumOf { A[it][0] * b[it] }
val b2 = A.indices.sumOf { A[it][1] * b[it] }
val det = a11 * a22 - a12 * a12
if (Math.abs(det) < 1e-10) return null
val x = (b1 * a22 - b2 * a12) / det
val y = (b2 * a11 - b1 * a12) / det
return Point(x, y)
}
实战经验:三角定位对测距精度要求很高。如果RSSI测距误差超过2米,定位结果基本不可用。我建议在部署信标时,尽量让三个信标呈等边三角形分布,且间距在5-10米之间。太近了定位精度差,太远了信号太弱。
指纹定位:用「指纹」代替公式
三角定位依赖精确的测距模型,但室内环境太复杂了——一堵墙、一个货架、甚至一个人走过,都会改变信号传播。这时候,指纹定位就派上用场了。
指纹定位的思路是:不建模,直接采样。具体分两步:
- 离线采集:在室内划分网格,每个网格点记录所有信标的RSSI值,形成指纹库
- 在线匹配:实时采集RSSI向量,与指纹库匹配,找到最相似的位置
匹配算法常用KNN(K近邻):
data class Fingerprint(val position: Point, val rssiVector: Map<String, Int>)
fun knnMatch(currentRssi: Map<String, Int>,
fingerprintDb: List<Fingerprint>,
k: Int = 3): Point {
// 计算欧氏距离
val distances = fingerprintDb.map { fp ->
val dist = fp.rssiVector.entries.sumOf { (mac, rssi) ->
val cur = currentRssi[mac] ?: -100
(cur - rssi).toDouble().let { it * it }
}
Pair(fp.position, Math.sqrt(dist))
}
// 取距离最小的K个点,求平均
val nearest = distances.sortedBy { it.second }.take(k)
val avgX = nearest.map { it.first.x }.average()
val avgY = nearest.map { it.first.y }.average()
return Point(avgX, avgY)
}
指纹库维护:指纹库不是一成不变的。环境变化(比如重新装修、家具移动)会导致指纹失效。我建议每季度重新采集一次,或者用众包方式让用户设备帮忙更新。
指纹定位的优点是精度高(1-3米),缺点是前期工作量巨大。一个1000平米的商场,网格间距1米,就要采集1000个点,每个点记录5-8个信标的RSSI。想想就头大。
四种方法对比
| 方法 | 精度 | 部署成本 | 维护成本 | 适用场景 |
|---|---|---|---|---|
| RSSI测距 | 3-5米 | 低 | 低 | 粗略定位、区域判断 |
| 三角定位 | 2-4米 | 中 | 中 | 开阔空间、信标部署规则 |
| 指纹定位 | 1-3米 | 高 | 高 | 复杂环境、高精度需求 |
| Kalman滤波 | 提升稳定性 | 无 | 低 | 所有场景的预处理 |
知识体系总览
下面这张图把室内定位的核心逻辑串起来了。你可以看到,从原始RSSI到最终定位坐标,中间经历了测距、滤波、定位三个环节。每个环节都有多种方案可选,具体选哪个,取决于你的精度要求和成本预算。
说实话,没有哪种方法能包打天下。我做过的一个仓储项目,货架区用指纹定位(精度要求高),过道区用三角定位(成本低),两种方法混合使用,效果反而最好。
嗯,室内定位这块水挺深的。但只要你把RSSI测距、Kalman滤波、定位算法这三个环节吃透了,大部分场景都能应付。剩下的,就是根据实际环境做调优了。
公众号:蓝海资料掘金营,微信deep3321