ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

可见光通信室内定位仿真:三边定位算法精度与MATLAB实现

可见光通信室内定位仿真:三边定位算法精度与MATLAB实现

简介:该仿真程序基于三边定位算法,面向可见光通信与数字信号处理方向的研究者和开发者,用于验证室内定位方案。压缩包共2个文件,均为MATLAB脚本,体积仅2KB,包含定位主程序和配套执行文件,结构精简,便于直接运行或按需修改。目前已有852人学习下载。程序覆盖发射端LED光信号建模、接收端解调与噪声抑制、以及三边定位核心模块,完整实现距离估计、坐标计算和误差校正流程。距离估计可选时间差或强度衰减方式,坐标计算利用三角几何并配以最小二乘或非线性优化,误差校正则可集成卡尔曼滤波或粒子滤波,有助于深入理解调制解调、信号恢复等数字信号处理环节。该实现还可作为智能建筑、物联网设备及导航系统的定位算法参考,适合光通信定位、算法仿真等各类研究与教学场景。

1. 可见光通信室内定位仿真:三边定位算法能落到什么精度

做室内定位的人多半有过这种体验:蓝牙、WiFi、UWB 都试了一圈,不是指纹库维护成本高,就是硬件价格劝退。可见光通信(VLC)室内定位的切入点很直接——把天花板上本来就要装的 LED 当成基站,用光电二极管接收光信号,既完成照明又顺带把位置算出来。这套思路在仿真里跑通并不难,难的是把“测距—解算—误差校正”整条链路在 MATLAB 里串起来,并且知道哪一步在拖精度后腿。这篇笔记要拆的资源是一套基于三边定位算法的可见光通信室内定位仿真程序,压缩包里是 positioning.m 与 Positioning_main.m 两个脚本,适合正在做课程设计、毕设预研或者想快速验证 VLC 定位方案的研究者。它给你的不是现成产品,而是一套可以改参数、换噪声模型、看误差分布的参考实现。

2. 定位链路与距离估计:RSS 和 TOA 怎么选出三个距离值

2.1 VLC 定位链路的发射端与接收端构成

先把整条链路看明白,后面调参才有方向。VLC 室内定位系统分成四段:信源编码与调制、LED 驱动与光发射、自由空间光信道、光电接收与数字信号处理。

在发射端,LED 并不只是发光,它还要把信息调制到光强度上。常见的做法是强度调制直接检测(IM/DD),也就是用电流驱动 LED,让光功率跟随信号变化。为了让照明不闪烁,调制频率要高于人眼可感知的范围,一般至少几百 kHz,实际系统里常做到 MHz 级别。仿真程序里通常不会把 PWM 波形画出来,而是直接用光功率、调制指数、LED 数量这些参数去等效。

接收端则是光电二极管(PD)加跨阻放大器,把光信号变成电压信号,再经过 ADC 采样送入数字信号处理模块。数字信号处理在这里干的活包括解调、去噪声、从接收信号里估计距离。很多人拿到仿真程序先去看定位解算代码,这是不对的——定位误差的根子往往在前面:距离估得不准,后面三边定位算得再精细也是白搭。

2.2 RSS 测距的传播模型与噪声敏感性

RSS(接收信号强度)测距是 VLC 定位里最省事的方案,不需要时间同步,只要把接收光功率和距离的关系标定好就行。VLC 信道常用朗伯辐射模型描述,LED 通常近似为一阶或二阶朗伯辐射体。接收功率大致可以写成:

接收功率 = 发射功率 × (朗伯阶数 + 1) / (2π × 距离平方) × 发射角方向系数 × 接收角方向系数 × 接收面积

室内定位默认 LED 和 PD 都朝下安装,发射角和入射角可以近似看成同一个角度,这个角度的余弦恰好等于垂直高度除以真实距离。把几何关系代入后会发现:接收功率与距离的高次方成反比,这意味着距离越大,光功率衰减越剧烈。

RSS 测距的优点是实现简单,缺点是模型误差大。仿真中如果只用自由空间路径损耗,不考虑墙面反射和遮挡,算出来的距离会偏乐观;而实际环境中,桌面反光、灯具老化、PD 朝向偏移都会改变接收功率。所以跑仿真时不要盯着理想曲线看,要主动往接收功率里加百分比噪声,比如 5% 到 10% 的随机扰动,观察定位误差如何放大。

2.3 TOA/TDOA 测距的同步条件

TOA(到达时间)测距通过测量光信号从 LED 到 PD 的飞行时间来算距离。光速是 3×10^8 米每秒,室内距离只有几米,飞行时间只有十几纳秒。要在仿真里体现 TOA 测距,采样率至少要 GHz 级别,这对 ADC 是非常大的压力,实际系统很少在消费级硬件上做纯 TOA。

更现实的是 TDOA(到达时间差),它不要求 LED 和 PD 之间绝对同步,只要多个 LED 之间同步即可。但 LED 之间的时钟同步同样需要专门机制去保证。在 MATLAB 仿真程序里,TOA 测距通常会简化成理想定时加上一个高斯时间误差,再乘光速转成距离误差。要留意的是,如果你看到仿真的定位精度特别高,先看它的时间误差设了多少——ns 级别的误差在室内定位里几乎不可实现,仿真参数要往实际硬件水平靠。

测距方式是否需硬件同步误差主来源仿真实现复杂度精度潜力
RSS不需要反射、遮挡、器件非线性低中
TOA收发端严格同步时钟偏差、多径中高高
TDOA发射端同步即可基站间同步精度中较高

2.4 三边定位的几何求交与最小二乘求解

拿到三个 LED 到接收机的距离后,定位问题就变成一个几何问题:已知三个圆心(LED 坐标)和三个半径(测量距离),求交点。理想情况下三个圆交于一点,但仿真和实测里几乎没有这种好事——测距误差、坐标误差、模型误差都会让三个圆交成一个区域,甚至根本不交。

教科书里常用解析几何求解,做法是把其中一个圆作为参考圆,用另外两个圆与参考圆的差值消去二次项,变成线性方程组。设 LED 坐标为 (x_i, y_i),测量距离为 d_i,以第一个 LED 为参考,可以得到:

2(x_i - x_1)Δx + 2(y_i - y_1)Δy = d_1² - d_i² + x_i² + y_i² - x_1² - y_1²

这里有一个工程上的关键选择:不要用两个差值方程直接解二元一次方程组,而应该把三个以上的 LED 数据全部放进最小二乘框架里。原因很简单,两个方程刚好求出唯一解,但任何测距误差都会完整保留在结果里;而三个以上方程时,最小二乘会把误差按平方和最小的原则分配到多个方向上,定位结果稳定性明显好。用 MATLAB 实现时,核心就是 mldivide 的左除操作,不需要手写矩阵求逆。

3. 仿真程序的主流程与函数划分:positioning.m 怎么调用

3.1 主脚本与核心功能函数的职责划分

拿到压缩包后先别急着整体运行,把两个文件的职责先分开。最合理的结构通常是这样:位置函数是核心算法函数,负责“输入 LED 坐标和距离,输出估计坐标”;主脚本负责“配置参数、生成测试点、叠加噪声、循环调用位置函数、统计误差”。

我在给参考程序做复刻时,最常用的做法是让位置函数保持纯计算属性,不掺任何随机因素。这样便于单元测试,也方便对比不同测距噪声下的表现。举个例子,位置函数我一般写成这样:

function [pos] = positioning(led_xyz, dist_meas) % led_xyz: 3x3 矩阵,每行是一个 LED 的三维坐标 % dist_meas: 3x1 向量,三个 LED 到接收机的测量距离 % pos: 1x2 向量,估算出的接收机二维坐标 A = 2 * (led_xyz(2:end, 1:2) - led_xyz(1, 1:2)); b = dist_meas(1)^2 - dist_meas(2:end).^2 ... + sum(led_xyz(2:end, 1:2).^2, 2) ... - sum(led_xyz(1:2, 1:2).^2); pos = (A' * A) \ (A' * b); pos = pos'; end

这段代码的逻辑是先把第二、第三个 LED 与第一个 LED 的坐标差构造矩阵,再算右侧常数项。最后一个左除操作完成最小二乘求解。需要留意,如果只需要二维定位,LED 的 z 坐标不参与求解矩阵,只用 x、y;如果做三维定位,LED 数量至少得四个,且 A 矩阵的构造要延伸到三维。

主脚本里最要紧的是随机数种子和噪声模型。很多翻车现场都源于没设随机种子,每次运行结果都不同,调参数时根本看不出是参数变化带来的影响还是随机波动。

3.2 房间、LED 布局与接收机网格的构建

室内定位仿真第一步要布设房间。常见做法是把房间定义成一个 4m × 4m × 3m 的空间,LED 安装在天花板,接收机放在离地面 0.85m 左右的高度,这个高度参考了桌面设备或者手持终端的典型位置。LED 的数量和位置直接影响定位精度覆盖范围。

布局方面,三颗 LED 是最低配置,排成三角形覆盖房间中心区域。但要注意,三角形布局在房间边缘误差会迅速变大,原因后面避坑章会展开。如果希望整个房间的误差更均匀,四颗 LED 按矩形或菱形布局是更稳妥的方案。主脚本里一般会有类似这样的参数块:

% 房间尺寸与 LED 位置 room_size = [4, 4, 3]; % 长宽高 led_pos = [0, 0, 3; ... % LED1 坐标 4, 0, 3; ... % LED2 坐标 2, 3.5, 3]; % LED3 坐标 z_pd = 0.85; % 接收机高度

接收机的测试方式有两种:一是固定几个特殊位置点,比如房间中心、角落、LED 正下方,看单个点的定位误差;二是把房间地面按 0.1m 间隔划分成网格,每个网格点都做一次定位,最后画出误差热力图。第二种方式更能暴露布局问题,但计算量也更大,建议先跑粗网格,比如 0.5m 间隔,确认参数合理后再加密。

3.3 跑通一次定位的 MATLAB 代码骨架

有了位置函数和参数,主脚本就可以按蒙特卡洛方式统计定位误差了。下面这套骨架我在看参考程序时非常推荐的逻辑,它把测距噪声彻底暴露出来:

clear; clc; rng(2025); % 固定随机种子,保证结果可复现 % 三颗 LED 的天花板坐标 led = [0 0 3; 4 0 3; 2 3.5 3]; target = [2, 1.2, 0.85]; % 接收机真实位置 % 真实距离 d_true = sqrt(sum((led - target).^2, 2)); d_meas = d_true .* (1 + 0.05 * randn(3, 1)); % 距离加入 5% 高斯噪声 pos_estimated = positioning(led, d_meas); err = norm(pos_estimated - target(1:2)); fprintf('定位误差:%.3f m\n', err);

这段代码先用真实坐标算出三个 LED 到目标的距离,然后给距离乘上一个加噪因子,模拟 RSS 测距里接收功率波动带来的距离估计偏差。5% 的比例噪声在这个场景里算是比较温和的,实际环境中由于反光和阴影,距离误差比例可能会到 10% 以上。

跑通一次只是第一步。单次结果没有统计意义,必须做蒙特卡洛循环,跑几百次,把误差累积下来再统计 RMSE。这也是为什么主脚本里通常会带一个 for 循环的原因。要判断一个定位方案好坏,至少得看三个指标:平均误差、均方根误差(RMSE)、P95 误差。

4. 参数调优与误差评估:接口参数和 RMSE 这样盯

4.1 几个影响定位精度的关键输入参数

仿真程序不是拿来直接出结果的,它是拿来回答“如果参数变成这样,精度会怎样”这个问题。下面这几个参数是优先要动的。

LED 发射功率直接决定了接收端的信噪比底子。功率提高 3dB,接收信号幅度增加约 41%,在噪声不变的情况下距离测量误差会明显下降。但仿真里有个陷阱:发射功率提上去了,接收机的前端饱和效应却没建模,导致仿真结果偏乐观,实际硬件里 PD 早就饱和了。

LED 个数和布局是最值得调的。三颗 LED 能定位,但精度分布完全由位置决定;四颗 LED 才能给最小二乘提供冗余,角落区域的误差能压下来不少。朗伯阶数也很关键,这个参数描述 LED 发光的集中程度,阶数越高,光束越窄,正下方信号强、周边信号弱,距离估计对位置更敏感。

还有一个常被忽略的参数是接收机高度。很多人默认固定 z 坐标,但如果模拟的是手持设备,高度变化会直接改变 LED 到 PD 的垂直距离,进而改变接收功率。仿真时最好把接收机高度也当作变量扫一遍。

4.2 给仿真叠加噪声:信噪比与距离误差模型的取舍

噪声模型选得对不对,直接决定仿真结论有没有参考价值。最常见的做法是加高斯白噪声,这个没有错,但要分清楚是在哪个环节加。在信号域加噪声,也就是在接收功率上叠加噪声,是最接近物理实际的做法;但如果你的仿真程序只提供距离域接口,那就在距离上加比例噪声。

我常用的信号域噪声加法是这样的思路:先算理想接收功率,加上热噪声和散粒噪声后换算回距离。热噪声的功率和带宽、跨阻增益有关,散粒噪声和光电流有关。在简化仿真里,可以用一个等效 SNR 来涵盖所有噪声源:

snr_db = 30; % 等效信噪比 noise_power = p_rx / (10^(snr_db / 10)); p_rx_noisy = p_rx + sqrt(noise_power) * randn(size(p_rx));

用 SNR 控制噪声的好处是直观,30dB 对应比较好但现实的链路,20dB 已经开始反映比较恶劣的情况。坏处是它掩盖了噪声的频谱特性,实空间里噪声往往不是平坦白噪声,主要是 100Hz 的市电工频干扰。要模拟得更细,可以在接收功率序列里加入 100Hz 正弦分量,这比单纯加大噪声功率更能体现真实场景。

4.3 定位结果的评估指标:RMSE、P95 与误差分布

评估指标上,报告里只写一个平均误差是不严谨的。平均误差会被中间区域的高精度稀释,角落的差表现完全看不出来。业界更认 RMSE 和 P95。

RMSE 对大的离群误差敏感,能够反映最坏情况的影响;P95 则告诉你 95% 的定位点误差在多少米以内,这个指标对导航类应用更实际。计算方式不复杂:把所有蒙特卡洛误差排序,取 95% 分位。如果 RMSE 和平均误差差得太大,说明误差分布有肥尾,也就是某些位置的定位严重失效。

误差热力图是定位仿真里最直观的调试工具。用 surf 或 pcolor 把每个网格点的误差画出来,马上就能看出盲区在哪里。正常情况是房间中心误差小、边缘误差大,LED 正下方误差最小。如果热力图出现奇怪的规律,比如某个 LED 正下方反而误差大,优先怀疑距离模型的方向角计算错了。

提示:跑误差热力图前,先单独检查三个 LED 的距离误差均值。如果某个 LED 的距离误差显著大于其他两个,定位结果会被这个“差基站”拖垮,此时调定位算法不如调这个 LED 的功率模型。

5. 仿真常见问题与避坑:四个典型翻车现场

5.1 三个圆交不到一点:最小二乘与测距失配

现象:跑定位时,某些测试点算出的位置完全偏离真实坐标,甚至跑到房间外面去。

原因:三个距离圆在几何上根本不相交,或者交出的区域极大。RSS 测距误差大的时候,测量距离和实际位置之间根本不满足三角不等式,强行解析求解就会产生离谱结果。

解决:不要用手写的二元方程精确求解,改用最小二乘。同时看看测距噪声是不是加得太大——有时候为了展示算法鲁棒性,仿真里噪声加得过于夸张,导致距离已经有了 30% 以上的误差,这种条件下三边定位从数学上看本来就无能为力。我在这种情况下的习惯是把噪声比例降到 5% 左右,先把主流程跑通,再逐步加大噪声找极限。

5.2 LED 正下方坐标抖动:入射角模型病态

现象:把接收机放在某颗 LED 正下方,理论上应该定位最准,但蒙特卡洛统计出的误差反而不稳定,有时比其他位置还要大。

原因:正下方时发射角和入射角都接近零度,余弦值接近 1,角度变化对接收功率的影响趋近于零。也就是说,接收机在 LED 正下方附近移动时,接收功率基本不变,从功率反推距离几乎失效。这个区域在数学上叫病态区。

解决:别把单颗 LED 正下方当作精度保障点。实际部署时,接收机出现在 LED 正下方的概率不高,但如果你的应用场景是固定工位上方的灯具,那就应该主动增加 LED 数量或者引入 PD 倾斜角测量。仿真里要验证这一点,可以在 LED 正下方旁边密集撒点,看误差分布是否出现原点附近的跳跃。

5.3 数据量大但误差没变小:网格划分与墙角盲区

现象:把接收机网格间隔从 0.5m 缩小到 0.1m,计算结果更精确了,但 RMSE 没什么实质改善。

原因:网格加密只增加了采样密度,没有改变定位算法的客观误差来源。如果在墙角区域接收功率本身就弱、信噪比低,网格再密,墙角那个点的距离估计误差还是大。这是系统性问题,不是统计量够不够的问题。

解决:先按区域分别统计误差。把房间分成中心区和边缘区,分别看 RMSE,如果边缘区误差三倍于中心区,就要考虑改 LED 布局,把靠墙的 LED 往中间挪一点,或者增加一个墙角处的辅助 LED。这个结论不是玄学,数学上就是病态几何加上低信噪比的双重效应。

5.4 精度高得离谱:坐标系与角度单位埋雷

现象:仿真结果 RMSE 低于 1 毫米,比真实硬件高两个数量级,一看就不对劲。

原因:最常见的是角度单位错误。MATLAB 的 cos、sin 默认用弧度,如果手动输入的角度是度数没转弧度,方向余弦就完全错了。另一种是坐标单位没统一,LED 坐标用米,接收机坐标却填了毫米,代入公式后距离量级混乱。

解决:在参数区统一加注释,强制所有长度用米、所有角度用弧度。顺手做一个合理性检查:跑一个理想无噪声的定位,估计输出应当和真实坐标完全相同,偏差应该小于 1e-10。如果这个检查都过不了,任何统计指标都没有意义。

注意:无噪声自检必须通过,才能开始研究噪声下的误差统计。这是仿真程序调试的第一优先级。

6. 更进一步:加权最小二乘与连续轨迹定位

拿到基础版本的三边定位参考程序后,有两个方向能带来实质精度提升,值得自己动手改一改。

第一个方向是加权最小二乘。基础版本里所有 LED 的距离误差被一视同仁,但实际上离接收机越远的 LED,RSS 测距的相对误差越大。给每个 LED 的距离方程加一个与距离成反比的权重,让远处 LED 对定位结果的影响减小,角落区域的误差会明显收敛。MATLAB 实现上不需要改多少,左除前面乘上一个权重矩阵就行:

W = diag(1 ./ ((d_meas + 0.1) .^ 2)); % 距离大的权重小 pos_w = (A' * W * A) \ (A' * W * b);

第二个方向是对连续运动轨迹做滤波。单次定位结果是独立的,上一帧到下一帧的跳动没有约束;而室内定位的接收机通常是移动的,比如手持终端或者自动导引车。用卡尔曼滤波把位置的历史信息结合进来,让轨迹平滑化,比单纯提高单点定位精度更有效果。仿真程序里先按时间序列生成一条移动轨迹,每帧都做一次三边定位,再把定位结果输入卡尔曼滤波器,输出的轨迹会比原始定位平滑得多。

如果想把参考程序改得更贴近实际部署,还可以做一件事:把光信道模型从自由空间路径损耗换成包含一次反射的模型。墙面反射会对接收功率产生一个偏置分量,让 RSS 测距的系统性偏差更明显。仿真里只要在理想接收功率上叠加一个占直射功率 5% 到 15% 的反射分量,就能复现出真实环境里定位漂移的现象。做好这一步,仿真结论的说服力会明显上一个台阶,也会对实际系统的误差预算有更准确的预期。从我开始用这类程序做定位预研以后,每次拿到新的仿真代码都会先做三个动作:固定随机种子、跑无噪声自检、再扫一遍关键参数。这套流程看起来很笨,但几乎能过滤掉所有低级错误。希望帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进