UWBIns/UWBInsFusion/eskf_uwb_imu.m

356 lines
15 KiB
Matlab
Raw Permalink Normal View History

2025-04-16 20:15:33 +08:00
clc
clear
close all
addpath './lib';
addpath './lib/gnss';
addpath './lib/calbiration';
addpath './lib/plot';
addpath './lib/rotation';
addpath './lib/geo';
savepath
%% 说明
% UWB IMU 融合算法采用误差卡尔曼15维经典模型伪距组合
% PR(TOF) 伪距: UWB硬件给出的原始测量距离值
% IMU: 加速度(3) 3轴陀螺(3) 共6维,,
% noimal_state: 名义状态: 导航方程状态: 位置(3) 速度(3) 四元数(4) 共10维
% err_state: KF误差状态: 位置误差(3) 速度误差(3) 失准角(3) 加速度计零偏(3) 陀螺零偏(3) 共15维
% du: 零偏反馈: 加速度计零偏(3) 陀螺零偏(3) 共6维
% 单位说明:
% 加速度,加速度零偏: m/s^(2)
% 角速度, 角速度(陀螺)零偏: rad/s
% 角度 rad
% 速度: m/s
%% 读取数据集
load datas2
dataset = datas;
% dataset的数据结构如下
% dataset
% ├── imu
% | ├── acc 加速度计三维数据
% | ├── gyr 陀螺仪三维数据
% | └── time 时间戳数组
% ├── uwb
% | ├── time 时间戳数组 1*17200
% | ├── anchor 四个标签的坐标矩阵xyz
% | ├── tof 飞行时间4*17200表示4个基站在连续时间点上对同一移动标签的测距时间
% | └── cnt 基站个数
% └── pos
N = length(dataset.imu.time); % 时间序列的长度
dt = mean(diff(dataset.imu.time)); % dtIMU数据的平均采样周期。通过diff方法计算相邻时间戳的差值并求平均值得到dt
% 故意删除一些基站及数据,看看算法在基站数量很少的时候能否有什么奇迹。。
% dataset.uwb.anchor(:,1) = [];
% dataset.uwb.tof(1,:) = [];
% EKF融合使用的基站个数融合算法最少2个基站就可以2D定位
%dataset.uwb.cnt = size(dataset.uwb.anchor, 2);
m_div_cntr = 0; % 量测分频器计数器用于记录已累计的IMU更新次数。当计数器达到 m_div 时触发UWB量测更新并重置计数器。
m_div = 1; % 量测分频系数每m_div次量测才更新一次EKF量测(UWB更新), 可以节约计算量 或者 做实验看效果
% UWB的采样率通常远低于IMU例如IMU 100HzUWB 10Hz直接每次融合会导致冗余计算。通过分频系数可以匹配两者的实际更新频率避免不必要的计算。
UWB_LS_MODE = 3; % 2 纯UWB解算采用2D模式 3纯UWB解算采用3D模式
UWB_EKF_UPDATE_MODE = 3; % EKF 融合采用2D模式 3: EKF融合采用3D模式
%% 数据初始化
out_data.uwb = []; % 初始化输出数据结构
out_data.uwb.time = dataset.uwb.time; % 存储UWB时间戳
out_data.imu.time = dataset.imu.time; % 存储IMU时间戳
out_data.uwb.anchor = dataset.uwb.anchor; % 存储基站坐标
% pr = 0;
% last_pr = 0;
%% 滤波参数初始化
settings = uwb_imu_example_settings(); % 获取滤波器设置参数使用uwb_imu_example_settings()函数获取滤波器的配置参数
% 返回值是一个结构体settings包含滤波器的各种参数
R = diag(ones(dataset.uwb.cnt, 1)*settings.sigma_uwb^(2)); % 量测噪声协方差矩阵
noimal_state = init_navigation_state(settings) ; % 初始化名义状态10维位置x,y,z;速度vx,vy,vz;姿态四元数q0,q1,q2,q3
% 名义状态:系统在理想条件下的理论状态,忽略噪声、扰动和模型误差
err_state = zeros(15, 1); % 初始化误差状态,15维位置误差3维速度误差3维姿态误差3维加速度计零偏3维陀螺仪零偏3维
% 误差状态:实际状态与名义状态之间的偏差,由噪声、外部干扰或模型不确定性导致
%使用第一帧伪距作为初始状态
pr = dataset.uwb.tof(:, 1); % 获取第一帧UWB测量数据
% 初始位置解算
% 测量值=真实值+零偏+噪声
% 零偏是传感器在无输入信号时输出的非零偏移量,反应其基准点的系统性偏差
% 伪距是UWB超宽带等无线定位系统中通过信号传播时间计算出的等效距离测量值
% noimal_state前三项:位置坐标,使用最小二乘法解算
noimal_state(1:3) = multilateration(dataset.uwb.anchor, [ 1 1 0.99]', pr', UWB_LS_MODE);
du = zeros(6, 1); % 初始化零偏反馈
[P, Q1, Q2, ~, ~] = init_filter(settings); % 初始化滤波器参数,此处初始化时使用~占位符,因为这个地方这两个参数没有意义,这样占位可以不占用内存
% 输出提示
fprintf("共%d帧数据, IMU采样频率:%d Hz 共运行时间 %d s\n", N, 1 / dt, N * dt);
fprintf("UWB基站个数:%d\n", dataset.uwb.cnt);
fprintf("UWB量测更新频率为:%d Hz\n", (1 / dt) / m_div);
fprintf("UWB EKF量测更新模式: %dD模式\n", UWB_EKF_UPDATE_MODE);
fprintf("纯UWB最小二乘解算: %dD模式\n", UWB_LS_MODE);
fprintf("EKF 滤波参数:\n");
settings
fprintf("开始滤波...\n");
for k=1:N
acc = dataset.imu.acc(:,k);
gyr = dataset.imu.gyr(:,k);
% 反馈 加速度bias, 陀螺bias
% 测量值 = 真实值 + 零偏 + 噪声
acc = acc - du(1:3); % du是零偏1346分别代表加速度和陀螺仪的零偏
gyr = gyr - du(4:6); % 陀螺仪
% 捷联惯导解算
p = noimal_state(1:3); % 位置坐标 [x; y; z](单位:米)
v = noimal_state(4:6); % 速度 [vx; vy; vz]单位m/s
q = noimal_state(7:10); % 姿态四元数 [q0; q1; q2; q3]
% 捷联惯导解算输入当前状态和IMU测量值输出更新后的位置、速度和姿态
% pvq是当前的状态acc、gyr是加速度和陀螺仪的测量值dt为时间步长后面为重力向量
[p, v, q] = nav_equ_local_tan(p, v, q, acc, gyr, dt, [0, 0, -9.8]'); % 东北天坐标系,重力简化为-9.8
% 小车假设基本做平面运动N系下Z轴速度基本为0直接给0
v(3) = 0;
% 更新数值
noimal_state(1:3) = p;
noimal_state(4:6) = v;
noimal_state(7:10) = q;
out_data.eul(k,:) = q2eul(q); % 四元数转欧拉角并存入数据表
% 生成F阵 G阵
% 看函数注释,根据当前的状态、加速度测量值 acc 和采样时间 dt生成状态转移矩阵 F 和过程噪声耦合矩阵 G
[F, G] = state_space_model(noimal_state, acc, dt);
% 卡尔曼P阵预测公式
P = F*P*F' + G*blkdiag(Q1, Q2)*G';
% log数据
out_data.x(k,:) = noimal_state;
out_data.delta_u(k,:) = du'; % 位置方差
out_data.diag_P(k,:) = trace(P); % 姿态四元数方差
%% EKF UWB量测更新
m_div_cntr = m_div_cntr+1; %量测分频器
% 每m_div次量测才更新一次EKF量测(UWB更新)此时m_div设置为1
if m_div_cntr == m_div
m_div_cntr = 0;
pr = dataset.uwb.tof(1:dataset.uwb.cnt, k); % pr是当前时刻第k帧所有UWB基站的TOF
%判断两次PR 差如果差太大则认为这个基站PR比较烂不要了。相当于GNSS里的挑星
% arr = find(abs(pr - last_pr) < 0.05);
% last_pr = pr;
% out_data.good_anchor_cnt(k,:) = length(arr); %记录挑出来的基站数
%
% if(isempty(arr))
% continue;
% end
%
% %构建 剔除不好的基站之后的基站位置矩阵和R矩阵
% pr = pr(arr);
% anch = dataset.uwb.anchor(:, arr);
% R1 = R(arr, arr);
% 算了不挑基站了,所有基站直接参与定位,其实也差不太多
anch = dataset.uwb.anchor;
R1 = R; % 测量噪声协方差矩阵
% 量测方程
% Y是根据当前状态 noimal_state 预测出来的各个基站到目标的距离
% H是观测矩阵表示距离对状态的偏导
[Y, H] = uwb_hx(noimal_state, anch, UWB_EKF_UPDATE_MODE);
% 卡尔曼公式计算K
S = H*P*H'+R1; % system uncertainty 系统不确定度,用于衡量测量是否可信
residual = pr - Y; % residual 或者叫新息 测量与预测的差值
%% 根据量测置信度给R一些增益 Self-Calibrating Multi-Sensor Fusion with Probabilistic
%Measurement Validation for Seamless Sensor Switching on a UAV, 计算量测可靠性
% 衡量量测可信度L越大代表当前量测越不可信Mahalanobis 距离L
L = (residual'*S^(-1)*residual);
out_data.L(k,:) = L;
% if(L > 20 ) %如果量测置信度比较大,则更不相信量测
% S = H*P*H'+R1*5;
% end
K = (P*H')/(S); % 卡尔曼增益K
err_state = [zeros(9,1); du] + K*(residual); % 状态误差向量
% 反馈速度位置
noimal_state(1:6) = noimal_state(1:6) + err_state(1:6);
% 反馈姿态
q = noimal_state(7:10); % 提取姿态四元数
q = qmul(rv2q(err_state(7:9)), q);
noimal_state(7:10) = q; % 姿态反馈(使用小角度修正旋转)
%存储加速度计零偏,陀螺零偏
du = err_state(10:15);
% P阵后验更新
P = (eye(15)-K*H)*P;
end
%% 车载约束Z轴速度约束 B系下 Z轴速度必须为0(不能钻地).. 可以有效防止Z轴位置跳动 参考https://kth.instructure.com/files/677996/download?download_frd=1 和 https://academic.csuohio.edu/simond/pubs/IETKalman.pdf
%% 车辆在 B系Z轴上不应该有速度如果估计有就认为估计错了通过观测去修正
R2 = eye(1)*0.5;
Cn2b = q2m(qconj(noimal_state(7:10))); % 计算四元数的共轭,得到从导航系到机体系的旋转;再转换成方向余弦矩阵
H = [zeros(1,3), [0 0 1]* Cn2b, zeros(1,9)]; % 构造观测矩阵H用于观测当前状态中的速度
K = (P*H')/(H*P*H'+R2);
z = Cn2b*noimal_state(4:6); % 当前估计的B系Z轴速度
err_state = [zeros(9,1); du] + K*(0-z(3:3)); % 目标观测值是0因为车不能在B系Z轴上移动当前估计值是z(3),差值为残差
% 反馈速度位置 更新状态
noimal_state(1:6) = noimal_state(1:6) + err_state(1:6);
% 反馈姿态
q = noimal_state(7:10);
q = qmul(ch_rv2q(err_state(7:9)), q); % 更新原始四元数
noimal_state(7:10) = q;
%存储加速度计零偏,陀螺零偏
% du = err_state(10:15);
% P阵后验更新
P = (eye(15)-K*H)*P;
end
fprintf("开始纯UWB最小二乘位置解算...\n");
%% 纯 UWB 位置解算
j = 1;
uwb_pos = [1 1 1]';
N = length(dataset.uwb.time);
for i=1:N
pr = dataset.uwb.tof(:, i);
% 去除NaN点
%if all(~isnan(pr)) == true
uwb_pos = multilateration(dataset.uwb.anchor, uwb_pos, pr', UWB_LS_MODE);
out_data.uwb.pos(:,j) = uwb_pos;
j = j+1;
%end
end
fprintf("计算完成...\n");
%% plot 数据
out_data.uwb.tof = dataset.uwb.tof;
out_data.uwb.fusion_pos = out_data.x(:,1:3)';
demo_plot(dataset, out_data);
%% 初始化nomial state
function x = init_navigation_state(~)
% 简单点, 全部给 0 。10维位置x,y,z;速度vx,vy,vz;姿态四元数q0,q1,q2,q3
q = eul2q(deg2rad([0 0 0])); % eul2q函数用于将欧拉角转换为四元数
x = [zeros(6,1); q]; % 拼接6维零向量与四元数
end
%% 初始化滤波器参数
function [P, Q1, Q2, R, H] = init_filter(settings)
% Kalman filter state matrix
P = zeros(15);
P(1:3,1:3) = settings.factp(1)^2*eye(3);
P(4:6,4:6) = settings.factp(2)^2*eye(3);
P(7:9,7:9) = diag(settings.factp(3:5)).^2;
P(10:12,10:12) = settings.factp(6)^2*eye(3);
P(13:15,13:15) = settings.factp(7)^2*eye(3);
% Process noise covariance
Q1 = zeros(6);
Q1(1:3,1:3) = diag(settings.sigma_acc).^2*eye(3);
Q1(4:6,4:6) = diag(settings.sigma_gyro).^2*eye(3);
Q2 = zeros(6);
Q2(1:3,1:3) = settings.sigma_acc_bias^2*eye(3);
Q2(4:6,4:6) = settings.sigma_gyro_bias^2*eye(3);
R =0;
H = 0;
end
%% 生成F阵G阵
% 根据当前的状态 x、加速度测量值 acc 和采样时间 dt生成状态转移矩阵 F 和过程噪声耦合矩阵 G
function [F,G] = state_space_model(x, acc, dt)
Cb2n = q2m(x(7:10)); % 把四元数转换成方向余弦矩阵从机体坐标系body转到导航系nav
sk = askew(Cb2n * acc); % 对一个向量v构造出反对称矩阵用于表示向量叉乘的线性化形式
O = zeros(3);
I = eye(3);
% P V theta 加计零偏 陀螺零偏
F = [
O I O O O;
O O -sk -Cb2n O;
O O O O -Cb2n;
O O O O O;
O O O O O];
% Approximation of the discret time state transition matrix
% 将连续时间的状态转移矩阵做一次一阶近似离散化
F = eye(15) + dt*F;
% Noise gain matrix
G=dt*[
O O O O;
Cb2n O O O;
O -Cb2n O O;
O O I O;
O O O I];
end
%% UWB量测过程
% Y 根据当前状态和UWB基站坐标预测出来的伪距
% H 量测矩阵
% anchor: 基站坐标 M x N: M:3(三维坐标) N:基站个数
% dim: 2: 二维 3三维
function [Y, H] = uwb_hx(x, anchor, dim)
N = size(anchor,2); % 基站个数
% 三维取xyz二维取xy
position = x(1:3);
if(dim)== 2
position = position(1:2);
anchor = anchor(1:2, 1:N);
% uwb.anchor
end
Y = []; % Y是伪距预测即目标位置到每个锚点的欧几里得距离
H = []; % 雅可比矩阵的每一行
% 计算预测的伪距s所有预测的位置与各个锚点的向量差
perd_pr = repmat(position,1,N) - anchor(:,1:N);
for i=1:N
if(dim)== 2
H = [H ;perd_pr(:,i)'/norm(perd_pr(:,i)),zeros(1,13)];
else
H = [H ;perd_pr(:,i)'/norm(perd_pr(:,i)),zeros(1,12)];
end
Y = [Y ;norm(perd_pr(:,i))];
end
end