4、UWB定位算法:最小二乘法、卡尔曼滤波、粒子滤波、Chan算法、Taylor算法

各位,这一章咱们来啃硬骨头——定位算法。说实话,UWB的硬件再好,天线再准,没有合适的算法,数据就是一堆乱码。我在项目里见过太多人,买了最好的模块,结果定位飘得跟喝醉了一样,最后发现是算法选错了。

UWB定位算法,说白了就是「用数学把距离换算成坐标」。你想想看,基站告诉你「标签离我5米」,但5米是个圆,三个圆交在一起才是点。怎么交?怎么让这个点更准?这就是算法的活。

我按工程实用度,把这五种算法分成两类:解析类和滤波类。咱们一个一个过。

核心观点:没有最好的算法,只有最合适的场景。静态场景用最小二乘法,动态场景用卡尔曼滤波,NLOS严重时上粒子滤波,基站布局好就用Chan+Taylor组合。

UWB定位算法分类框架 UWB定位算法 解析类算法 滤波类算法 最小二乘法(LS) Chan算法 Taylor算法 卡尔曼滤波(KF) 粒子滤波(PF) 工程建议: 静态标定 → 最小二乘法;动态跟踪 → 卡尔曼滤波;强NLOS/多模态 → 粒子滤波; 基站≥4且布局规则 → Chan算法做初值 + Taylor算法迭代求精。

4.1 最小二乘法(LS)—— 最朴素的定位算法

最小二乘法,我习惯叫它「硬算」。它的思路特别直白:已知N个基站坐标和N个距离测量值,找一个点,让这个点到所有基站的理论距离和实测距离的误差平方和最小。

数学上就是解一个超定线性方程组。我给大家写个简化版的代码框架,实际项目中我经常这么用:

// 最小二乘法定位(C语言伪代码)
typedef struct {
    double x, y;
} Point;

Point least_squares_localization(Point* anchors, double* ranges, int n) {
    // n >= 3,基站数量
    // 构造线性方程组 Ax = b
    // 取第一个基站作为参考,做差分消除二次项
    double A[(n-1) * 2];
    double b[(n-1)];
    
    for (int i = 1; i < n; i++) {
        A[(i-1)*2 + 0] = 2 * (anchors[i].x - anchors[0].x);
        A[(i-1)*2 + 1] = 2 * (anchors[i].y - anchors[0].y);
        b[i-1] = ranges[0]*ranges[0] - ranges[i]*ranges[i] 
               + anchors[i].x*anchors[i].x - anchors[0].x*anchors[0].x
               + anchors[i].y*anchors[i].y - anchors[0].y*anchors[0].y;
    }
    
    // 解最小二乘:x = (A^T A)^{-1} A^T b
    // 实际工程用QR分解或SVD,别直接求逆
    Point result;
    // ... 矩阵运算代码
    return result;
}

我的经验:最小二乘法对测量噪声敏感。我在一个仓库项目里,直接用LS算出来位置跳了2米多,后来加了加权(WLS),给信号强的基站更高权重,立马稳到30厘米以内。加权矩阵一般取距离倒数的平方。

适用场景:静态标定、基站数量多且分布均匀、对实时性要求不高。

4.2 卡尔曼滤波(KF)—— 动态定位的标配

卡尔曼滤波,做定位的没人不知道。它不单独算位置,而是「预测+更新」两步走。你想想看,上一秒标签在A点,速度是v,下一秒大概率在A+v·Δt附近,然后拿UWB测量值去修正这个预测。

我刚开始用卡尔曼的时候,老觉得状态矩阵难调。后来总结了一个口诀:「状态要全,噪声要准,初值要稳」。

标准卡尔曼滤波只适用于线性高斯系统。UWB定位模型是非线性的(距离和坐标是平方关系),所以实际用的是扩展卡尔曼滤波(EKF)或无迹卡尔曼滤波(UKF)。

// EKF 预测-更新流程(伪代码)
// 状态向量: [x, y, vx, vy]
// 观测向量: [d1, d2, ..., dn]  (n个基站的距离)

void ekf_predict(State* s, double dt) {
    // 状态预测:匀速模型
    s->x += s->vx * dt;
    s->y += s->vy * dt;
    // 协方差预测:P = F * P * F^T + Q
    // F是状态转移矩阵,Q是过程噪声协方差
}

void ekf_update(State* s, double* ranges, Point* anchors, int n) {
    // 计算预测距离
    double h[n];
    for (int i = 0; i < n; i++) {
        h[i] = sqrt(pow(s->x - anchors[i].x, 2) + pow(s->y - anchors[i].y, 2));
    }
    // 计算雅可比矩阵 H (n x 4)
    // 卡尔曼增益: K = P * H^T * (H * P * H^T + R)^{-1}
    // 状态更新: s += K * (z - h)
    // 协方差更新: P = (I - K * H) * P
}

避坑指南:我曾经在一个AGV小车项目里,卡尔曼滤波的Q矩阵设得太小,导致滤波器「过于自信」,测量值来了也不怎么更新,小车拐弯时定位滞后了半秒。后来把Q调大了一倍,跟踪就灵敏了。记住:Q控制平滑度,R控制信任度。

4.3 粒子滤波(PF)—— 暴力美学

粒子滤波,说白了就是「撒豆子」。在可能的位置撒几千个粒子,每个粒子带一个权重,然后根据测量值更新权重,最后加权平均得到位置。粒子多的区域就是最可能的位置。

为什么需要它?因为卡尔曼滤波假设噪声是高斯分布,但UWB在非视距(NLOS)环境下,误差分布可能是多峰的——比如信号穿墙后,测量值可能偏大2米,也可能偏大5米,没有固定规律。这时候卡尔曼就懵了,粒子滤波反而能扛住。

粒子滤波的步骤我总结为四步:

  1. 初始化:在初始位置附近撒N个粒子,权重均等
  2. 预测:每个粒子按运动模型随机移动(加一点噪声)
  3. 更新:根据UWB测量值,计算每个粒子的似然,更新权重
  4. 重采样:权重低的粒子淘汰,权重高的粒子复制,保持粒子总数不变

工程对比:粒子滤波精度高,但计算量大。我在一个室内定位项目里,用500个粒子,在STM32F4上跑,帧率只有10Hz。后来优化到200个粒子,配合重要性采样,帧率提到25Hz,精度损失不到5%。

4.4 Chan算法 —— 解析法的天花板

Chan算法,全名Chan氏算法,是TDOA(到达时间差)定位的经典解法。它分两步:第一步假设所有基站误差独立,得到一个初始解;第二步利用第一步的结果和基站几何关系,得到更优解。

Chan算法的好处是——一次计算,不需要迭代。在基站数量≥4且布局不太差的情况下,精度接近克拉美罗下界(CRLB)。

特性 Chan算法 最小二乘法
输入类型 TDOA(时间差) TOA(绝对距离)
计算方式 两步加权最小二乘 线性最小二乘
基站要求 ≥4个 ≥3个
噪声鲁棒性 较好(加权处理) 一般
计算复杂度 O(n) O(n)

我的习惯:Chan算法做初值,然后用Taylor算法迭代3-5次,精度能再提升一个量级。这个组合我在十几个项目里验证过,稳定可靠。

4.5 Taylor算法 —— 迭代求精

Taylor算法,也叫泰勒级数展开法。它的思路是:先猜一个初始位置,然后在这个位置附近做泰勒展开,忽略高阶项,得到一个线性方程组,解出位置修正量,然后迭代。

说白了就是「摸着石头过河」——每一步都往误差减小的方向走一小步,直到收敛。

Taylor算法的关键点有两个:

  • 初值要准:初值离真实位置越近,收敛越快。我一般用Chan算法或最小二乘法给初值
  • 迭代次数要控制:一般3-5次就够了,多了反而可能震荡。设置一个阈值,比如位置修正量小于1厘米就停止
// Taylor算法迭代流程
Point taylor_localization(Point init, Point* anchors, double* ranges, int n) {
    Point pos = init;
    double delta = 999;
    int iter = 0;
    
    while (delta > 0.01 && iter < 10) {
        // 计算雅可比矩阵 G (n x 2)
        // 计算残差向量 w (n x 1): 实测距离 - 预测距离
        // 解: [dx, dy]^T = (G^T * Q^{-1} * G)^{-1} * G^T * Q^{-1} * w
        // Q是测量噪声协方差矩阵
        
        pos.x += dx;
        pos.y += dy;
        delta = sqrt(dx*dx + dy*dy);
        iter++;
    }
    return pos;
}

避坑指南:我曾经在基站布局呈直线(共线)的情况下用Taylor算法,结果雅可比矩阵奇异,直接算崩了。后来加了正则化项(Levenberg-Marquardt修正),才稳住。记住:基站几何布局不好时,任何解析算法都会出问题。

4.6 算法选型对照表

最后,我给大家整理了一张选型表。项目里怎么选,看这张表就够了:

场景 推荐算法 理由
静态标定/部署调试 最小二乘法 简单、快速、不需要初值
动态跟踪(LOS环境) 扩展卡尔曼滤波 实时性好、平滑、适合嵌入式
强NLOS/多径环境 粒子滤波 能处理非高斯、多模态噪声
基站≥4、布局规则 Chan + Taylor 精度高、计算快、不需要滤波
资源受限MCU 最小二乘法 或 Chan 无迭代、内存占用小

嗯,这一章的内容就到这儿。五种算法,各有各的脾气。你只要记住:解析算法求快,滤波算法求稳,组合算法求准。项目里多试试,找到最适合你场景的那一款。