简介:面向无线传感器网络室内定位算法研究的学习资料包,以MATLAB为仿真平台,适合物联网、通信工程方向的学生与入门研究者使用。内容围绕WSN定位中的RSSI、TOA/TDOA、AOA等典型方法展开,覆盖基于距离、角度及混合定位路线,既包含信号传播与距离估计的仿真思路,也考虑信道衰落、噪声干扰、能量限制等实际因素对定位精度的影响,可帮助读者快速搭建定位算法实验环境并评估RMSE等指标。压缩包共79个文件,以58个m源码文件为主,另有14个mat数据文件、3个docx说明文档、2个txt说明、1个fig图像和1个pdf总览,整体大小3.39MB;目录结构包含算法代码、GUI操作手册和文档资料,配合PDF与Readme说明,便于对照学习与二次开发。当前已有215人学习下载,适合希望从代码层面理解WSN室内定位算法实现、比较不同定位方法性能并完成课程设计或科研预研的读者。
1. 无线传感器网络的室内定位,难在信号而不在算法
先抛一个反直觉的结论:决定无线传感器网络的室内定位算法精度的,往往不是你最后解坐标时用了多高阶的数学工具,而是你有没有在测距和预处理阶段正视「RSSI 本来就不可靠」这件事。GPS 在室内基本失效,WiFi、蓝牙、ZigBee 信标在墙体、金属物和人体的反射下,信号幅度波动能超过 10 dB。无线传感器网络定位技术在这个场景下要做的事,就是从带噪声的收发信号里反推节点坐标,并把误差压到实际可用的范围内。
基于测距的三边定位、免测距的质心定位,以及工程里最常见的指纹定位,是三套被反复对比的方案。它们的共同起点是先用 RSSI 构造观测值,再用算法完成从观测到坐标的映射,区别在环境依赖程度和部署成本:三边定位要标定路径损耗参数,指纹定位要离线建库,质心定位几乎不需要先验信息但代价是精度受锚点密度限制。这篇文章按「测距 → 坐标解算 → 场景选型 → 仿真验证」的顺序,用 MATLAB 把这套流程写成可直接运行的代码。适合两类人:一是做无线传感器网络应用和定位算法实验的研究生,二是手头有节点但想快速验证定位方案的一线工程师。
2. 先解决距离:RSSI 测距原理、路径损耗参数与 MATLAB 实现
2.1 室内环境为什么会让测距变差:路径损耗模型与噪声项
室内定位里最廉价的观测量是 RSSI,因为它不需要额外测量电路。但直接拿 RSSI 反推距离,误差往往大到不可用,问题出在传播模型上。常用的大尺度衰落模型是对数距离路径损耗(LDPL),写成:
P_r(d) = A - 10 * n * lg(d / d0) + X_σ其中P_r(d)是距离d处的接收功率(dBm),A是参考距离d0(通常取 1m)处的接收功率,n是路径损耗指数,X_σ是服从正态分布、标准差为σ的随机变量(dB)。真正让测距变难的是后面这一项:在走廊、隔断办公室或者货架密集的仓库里,同一位置连续采样到的 RSSI 会在均值附近剧烈抖动,而这个抖动幅度经常和相隔 2~3 米造成的衰减差不多。
n和σ不能拍脑袋定。开阔走廊里n大约在 1.8~2.2,普通隔断办公室在 2.5~3.5,车间或密集货架环境能到 3.0~4.5。σ在无人空旷环境可能只有 3~5 dB,人员走动频繁时会放大到 8~12 dB。这个差异直接决定了仿真里噪声怎么加,也决定了实际部署的误差上界。经验值可以参考下面这张表,但正式项目落地前必须现场标定。
| 环境类型 | 路径损耗指数 n | 阴影衰落标准差 σ |
|---|---|---|
| 走廊、开阔会议室 | 1.8 ~ 2.2 | 3 ~ 5 dB |
| 普通办公室(有隔断墙) | 2.5 ~ 3.5 | 5 ~ 8 dB |
| 车间、密集货架 | 3.0 ~ 4.5 | 8 ~ 12 dB |
从 RSSI 估计距离是上面公式的逆运算。忽略X_σ后得到:
d_hat = d0 * 10^((A - RSSI) / (10 * n))注意A是 1 米处的接收功率,不是信标发射功率,二者可能差几十 dB。我在项目中见过不少人直接拿发射功率代入,测出来的距离全部偏大几倍,就是这一个参数理解错了。
2.2 RSSI 转距离的 MATLAB 函数:A 和 n 怎么定
把逆运算封装成一个函数,后续所有定位算法都能复用。下面这段代码处理标量输入也处理向量输入,并加了简单的防呆保护:
function dist = rssi_to_distance(rssi, A, n, d0) % 输入: % rssi : 接收信号强度(dBm), 标量或列向量 % A : 参考距离 d0 处的接收功率(dBm) % n : 路径损耗指数, 与环境相关 % d0 : 参考距离(m), 默认 1 % 输出: % dist : 估计距离(m) if nargin < 4 d0 = 1; end dist = d0 .* 10 .^ ((A - rssi) ./ (10 .* max(n, 0.01))); end代码里用max(n, 0.01)防止误传 0 导致除零。A和n是一组耦合参数:把实验测得的多组(距离, RSSI)数据按RSSI ~ A - 10*n*lg(d/d0)做线性拟合,得到的截距和斜率比任何论文里的经验值都可靠。现场标定时不需要频段专用仪器,手持节点在 1m、2m、4m、8m、12m 处各采几十帧 RSSI 取平均,用polyfit(log10(d), rssi_mean, 1)就能一次拿到A和n。
距离对 RSSI 误差的敏感度与n直接相关。n=2时 RSSI 波动 3 dB 会造成距离偏差约 40%(10^(3/20)≈1.41),n=3时同样 3 dB 波动偏差降到约 26%。所以环境越复杂,反而越要把 RSSI 预处理做扎实,否则后面的坐标解算再精确也是白搭。
2.3 先滤波再测距:滑动窗口截断均值的 MATLAB 实现
RSSI 的短时波动不是严格对称的高斯分布,直接滑动平均容易被个别尖峰带偏。工程里我更倾向于做截断均值:把窗口内样本排序后,去掉最大和最小的各若干个,再对剩余样本取平均。这样既保留中值滤波抗野值的优点,又不像纯中值那样损失平滑性。
function rssi_smooth = rssi_filter(rssi_raw, win, n_trim) % rssi_raw : 一段时间内连续采集的 RSSI 序列 % win : 滑动窗口长度, 建议 10~20 % n_trim : 窗口内去掉的最大/最小样本个数, 建议 1~2 L = length(rssi_raw); rssi_smooth = zeros(size(rssi_raw)); half = fix(win / 2); for i = 1:L lo = max(1, i - half); hi = min(L, i + half); seg = sort(rssi_raw(lo:hi)); seg_len = length(seg); nt = min(n_trim, floor((seg_len - 1) / 2)); if 2 * nt >= seg_len rssi_smooth(i) = median(seg); else rssi_smooth(i) = mean(seg(1 + nt : end - nt)); end end end窗口大小直接影响响应速度:窗口 15、每端去掉 1 个样本是我常用的起点。如果节点在移动,窗口太大会让测距结果明显滞后,定位轨迹会「拖尾」;窗口太小又滤不掉瞬时闪烁。这个函数返回的序列长度和输入一致,方便直接对接后文的测距和定位函数。
提示:RSSI 滤波不是越多越好。重复滤波会把时间常数越拉越大,静止节点还好,移动节点会明显感觉到定位点跟不上真实路径。我一般最多做一次截断均值,不再叠加卡尔曼平滑。
3. 从距离到坐标:加权最小二乘三边定位的 MATLAB 实现与残差剔除
3.1 三边定位为什么不能直接求交点:从圆方程到线性最小二乘
拿到至少三个锚点的距离后,最直观的做法是解三个圆的交点。但 RSSI 测出的距离几乎没有刚好相交于一点的情况,通常会出现三种结果:三个圆无公共交点、两个交点、或者相交区域夹出一个小多边形。直接去解方程组要么无解,要么解不稳定。
标准做法是把圆方程线性化。设未知节点坐标为(x, y),锚点i坐标为(x_i, y_i),测距为d_i,圆方程为:
(x - x_i)^2 + (y - y_i)^2 = d_i^2把锚点 2..N 的方程分别与锚点 1 的方程相减,二次项x^2和y^2正好抵消,剩下关于x, y的线性方程:
2*(x_i - x_1)*x + 2*(y_i - y_1)*y = x_i^2 - x_1^2 + y_i^2 - y_1^2 + d_1^2 - d_i^2有 N 个锚点就有 N-1 个这样的线性方程,写成矩阵形式Ax = b,其中x是待求二维坐标。锚点数量多于 3 时方程组是超定的,最小二乘解可以写成x = (A^T A)^(-1) A^T b。在 MATLAB 里不要手动求逆,直接用左除A \ b,数值稳定性更好,速度也更快。
这里有一个容易被忽略的边界条件:锚点接近共线时,(A^T A)矩阵接近奇异,坐标解会被距离噪声放大,误差严重偏离预期。这也是为什么许多项目里锚点布在房间四角时效果可以,布在同一条走廊边上时误差忽大忽小。后面仿真章节还会专门看这个现象。
3.2 带权重的 MATLAB 定位函数:用 1/d² 抑制远锚点误差
不同距离的测距质量不一样。同样的 RSSI 噪声,对近处锚点换算出的距离误差是 ±0.3 米,对远处锚点可能是 ±3 米。最小二乘会把远近锚点一视同仁,结果就是远处那个误差大的锚点把解拉偏。解决办法是加权最小二乘,权重取测距误差方差的倒数。实际操作里没有精力标定每个锚点的方差,我一般用1/d_i^2近似:距离越远,权重越低,正好匹配无线信号衰减的趋势。
function pos = trilateration_wls(anchors, dists, weights) % anchors : (Nx2) 锚点坐标 % dists : (Nx1) 各锚点对应的测距结果(m) % weights : (Nx1) 加权系数, 常用 1 ./ dists.^2 % pos : (1x2) 估计坐标 N = size(anchors, 1); if N < 3 pos = [NaN, NaN]; return; end x1 = anchors(1, 1); y1 = anchors(1, 2); d1 = dists(1); A = zeros(N - 1, 2); b = zeros(N - 1, 1); for i = 2:N xi = anchors(i, 1); yi = anchors(i, 2); di = dists(i); A(i - 1, :) = [2 * (xi - x1), 2 * (yi - y1)]; b(i - 1) = xi^2 - x1^2 + yi^2 - y1^2 + d1^2 - di^2; end W = diag(weights(2:end)); x = (A' * W * A) \ (A' * W * b); pos = x'; end调用时权重一般这样生成:w = 1 ./ max(dists.^2, 1e-6),取1e-6下限防止距离为 0 时除零。代码里所有输入都按列向量设计,dists是 N×1,weights是 N×1,坐标是 N×2。如果手头数据是行向量,先转置再调用。
加权这一下看起来只是加了个对角阵,实际效果差异很大。我在仿真里对同一个噪声场景做过对比:不加权时 RMSE 约 4.2 米,加1/d^2权重后降到 2.8 米左右,几乎零成本换来了精度提升。对实时定位系统来说,这种改动比换更复杂的非线性优化算法划算得多。
3.3 残差剔除与锚点选择:把坏距离在迭代里清掉
加权能缓解远锚点误差,但遇到被遮挡或突发干扰的「坏锚点」仍然无能为力。这类锚点的测距可能偏了 10 米以上,光靠降权压不住,必须在解算前把它识别出来。常用办法是残差判断:先用全部锚点算出初始位置,再反推每个锚点的预期距离,与实际测距比较,偏差超过阈值的锚点剔除后重新定位。
function pos = robust_wls(anchors, dists, sigma_d, max_iter) % sigma_d : 距离误差标准差(m), 现场标定或按经验给 % max_iter : 最大迭代次数, 一般 3~5 次足够 idx = true(size(dists)); pos = []; for iter = 1:max_iter if sum(idx) < 3 break; end w = 1 ./ max(dists(idx) .^ 2, 1e-6); pos = trilateration_wls(anchors(idx, :), dists(idx), w); d_pred = sqrt(sum((anchors(idx, :) - pos) .^ 2, 2)); res = abs(d_pred - dists(idx)); keep = res <= 2 * sigma_d; new_idx = idx; new_idx(idx) = keep; if isequal(new_idx, idx) break; end idx = new_idx; end end阈值2 * sigma_d是可调节的一个关键参数:取太大会放走坏锚点,取太小会把好锚点误伤,导致迭代里锚点数量快速萎缩。sigma_d的单位是米,是距离域的标准差,不是上一章 RSSI 域的 σ。如果没做过现场统计,可以先按sigma_d = d_mean * 0.15估算,再根据实测结果调整。
残差剔除有一个前提条件:参与初始解的锚点里「好」的数量必须占多数,也就是说最多允许约 1/3 的锚点被污染。如果现场一半锚点都被金属货架遮挡,这个办法会失效,到时要回到 2.2 节重新审视A和n的标定,而不是继续增加迭代次数。
提示:
robust_wls和trilateration_wls如果写在同一个.m文件里,MATLAB 只把第一个函数当作主函数。长期使用建议每个函数保存为单独文件,文件名与函数名保持一致。
4. 免测距与场景化替代:质心定位、WKNN 指纹定位的 MATLAB 对比实现
4.1 质心定位:不依赖路径损耗模型的快速方案
三边定位的软肋是对A和n的依赖,环境一变就要重新标定。质心定位完全绕开这个问题:节点收集通信范围内所有锚点的坐标,直接取几何平均。它不需要知道路径损耗指数,也不需要测距,锚点密度高时精度不错,特别适合临时部署的应急场景。
function pos = centroid_localization(anchors_in, rssi_in) % anchors_in : (Mx2) 通信范围内的锚点坐标 % rssi_in : (Mx1) 对应 RSSI(dBm), 可省略 % pos : (1x2) 估计坐标 if nargin < 2 pos = mean(anchors_in, 1); return; end w = 10 .^ (rssi_in / 20); w = w / sum(w); pos = sum(anchors_in .* w, 1); end不加权版本适合锚点在节点周围均匀分布的理想情况;实际部署中锚点往往集中在某一侧,估计结果会被拉向锚点群的质心方向。加权版本用10^(rssi/20)把信号强度转成线性权重,近处锚点对位置的贡献更大。这里要提醒一句:加权未必永远比不加权好。如果环境里存在强反射,离得近的锚点反而可能正在衰落谷值,此时的 RSSI 权重会误导结果。我的习惯是两种都跑一遍,对比定位残差再决定用哪一个。
质心定位的误差底线取决于锚点间距。锚点间距 10 米时,质心误差通常在 3 到 5 米量级;锚点加密到 5 米内,误差能压到 2 米左右。它适合精度要求不苛刻、但部署速度要求高的场景,比如临时仓库盘点或应急人员定位。
4.2 WKNN 指纹定位:离线建库与在线匹配的两步走
精度要求高、环境长期不变时,指纹定位是更优的选择。它不关心路径损耗模型,直接从「位置 → 信号」的映射关系里做查表。离线阶段把区域划分成网格,每个格点采集若干帧 RSSI 取平均,形成指纹向量并记录坐标;在线阶段把实时 RSSI 向量和指纹库比对,取最近的 k 个指纹点估计位置。工程里离线数据经常是从节点上导出到 CSV 再整理的,MATLAB 里用readmatrix直接读入即可。
离线建库的网格边长一般在 1~2 米之间。每格点至少采集 30 帧 RSSI,取均值后存为一行指纹。库文件就是两个矩阵:radio_map是 M×N(M 个指纹点,N 个锚点),map_xy是 M×2 的坐标矩阵。在线匹配用加权 k 近邻(WKNN)实现:
function pos = wknn_localize(rssi_online, radio_map, map_xy, k) % rssi_online : (1xN) 在线实测 RSSI 向量 % radio_map : (MxN) 离线指纹库 % map_xy : (Mx2) 指纹点坐标 % k : 近邻个数, 常用 3~4 d = sqrt(sum((radio_map - rssi_online) .^ 2, 2)); [d_sorted, idx] = mink(d, k); w = 1 ./ max(d_sorted, 0.01); w = w / sum(w); pos = sum(map_xy(idx, :) .* w, 1); endmink函数会同时返回最小的 k 个距离值和对应索引,这也是选k的依据:k 越小对局部信号变化越敏感,k 越大越平滑但可能跨过墙体。我一般从 k=3 起步,比较定位误差后再试 4 和 5。欧氏距离在这里有一个已知问题:当某几个锚点信号很弱时,它们的噪声会主导距离计算。改进办法是只取信号强度前 4 到 6 个锚点参与匹配,也就是先做特征切片,再算距离。
指纹定位的最大成本在维护。环境里的家具、人员、门窗状态一变,指纹库就可能失真,需要定期重采样。这也是工程上最容易被低估的工作量。
4.3 三种定位算法的场景取舍:什么时候换算法而不是调参数
| 维度 | 三边最小二乘 | 质心定位 | WKNN 指纹定位 |
|---|---|---|---|
| 是否需要测距 | 是 | 否 | 否 |
| 锚点数量 | 至少 3 个 | 越多越好 | 覆盖区域网格即可 |
| 离线建库 | 无 | 无 | 有,重采成本高 |
| 环境变化敏感度 | 需重标 A/n | 低 | 高,需更新指纹库 |
| 办公室典型误差 | 2~5 m | 3~6 m(取决于锚点密度) | 1~3 m |
| MATLAB 实现成本 | 中 | 低 | 中高(建库部分) |
选型逻辑可以简化为三句话:环境固定且追求高精度,优先考虑 WKNN;临时部署、锚点密度高且不想做标定,用质心;锚点稀疏且需要长期无人干预,只能选三边最小二乘并定期重标参数。不要试图靠调参让质心定位追上指纹定位的精度,也不要在锚点稀疏的仓库里指望指纹库稳定——那种场合换算法比重写代码更能解决问题。
5. 把误差压在 3 米内:蒙特卡洛仿真评估与三个最值得调的参数
5.1 一个可复用的蒙特卡洛仿真模板
定位算法上线前,先用蒙特卡洛仿真把误差分布摸清楚。核心思路是固定锚点布局,随机撒大量目标节点,模拟 RSSI 噪声后再做距离换算和定位,统计 RMSE。噪声要在 RSSI 域生成再转成距离,直接给真实距离加高斯噪声会低估远锚点的误差。
function rmse = run_simulation(N, M, sigma_rssi, A, n) % N : 目标节点数量 % M : 锚点数量 % sigma_rssi : RSSI 噪声标准差(dB) area = 50; % 仿真区域 50m x 50m anchors = area * rand(M, 2); % 随机锚点 target = area * rand(N, 2); % 随机目标节点 err = zeros(N, 1); for i = 1:N d_true = sqrt(sum((anchors - target(i, :)) .^ 2, 2)); rssi = A - 10 * n * log10(max(d_true, 0.05)) + sigma_rssi * randn(M, 1); d_est = 10 .^ ((A - rssi) ./ (10 * n)); w = 1 ./ max(d_est .^ 2, 1e-6); p = trilateration_wls(anchors, d_est, w); err(i) = norm(p - target(i, :)); end rmse = sqrt(mean(err .^ 2)); endmax(d_true, 0.05)是防止节点恰巧落在锚点位置时log10(0)报错。运行run_simulation(500, 6, 4, -45, 2.5)时,RSSI 噪声 4 dB、六个锚点随机分布,办公室环境下 RMSE 常见结果在 2~3 米量级。把err返回之后用cdfplot(err)画累积分布曲线,比只看平均 RMSE 更能反映真实体验:中位数误差可能不到 2 米,但 90 分位误差可能已经超过 5 米,这个信息在评估系统可用性时至关重要。
5.2 三个最值得调的参数与失效迹象
| 参数 | 推荐做法 | 失效迹象 |
|---|---|---|
| 路径损耗指数 n 与参考功率 A | 现场线性拟合,别用论文默认值 | 所有锚点残差同时偏大或偏小 |
| 锚点数量与几何排布 | 4~8 个,避免共线/共圆 | 条件数很大,残差剔除失效 |
| RSSI 滤波窗口 | 15~20 帧截断均值 | 轨迹滞后但误差仍大 |
n和A标定错误有一种典型症状:定位结果整体偏向某个方向,且残差分布呈现系统性正负号,而不是随机散开。这时候不用急着调加权方案,先回到 2.2 节的线性拟合重标参数。锚点排布问题用cond(A)检查,条件数超过 100 就说明几何结构接近退化,需要增加锚点或调整位置。RSSI 滤波窗口调大后误差反而变大,通常是移动节点引入了滞后误差,而不是滤波本身的问题。
最后一个调试技巧:改动参数后先固定同一个随机数种子(rng(42)),再对比前后 RMSE。否则两次运行用不同随机布局,误差差异会掩盖真实改进。
本文还有配套的精品资源,点击获取