356 lines
15 KiB
Matlab
356 lines
15 KiB
Matlab
|
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 四个标签的坐标矩阵,x,y,z
|
|||
|
% | ├── tof 飞行时间,4*17200,表示4个基站在连续时间点上对同一移动标签的测距时间
|
|||
|
% | └── cnt 基站个数
|
|||
|
% └── pos
|
|||
|
|
|||
|
N = length(dataset.imu.time); % 时间序列的长度
|
|||
|
dt = mean(diff(dataset.imu.time)); % dt:IMU数据的平均采样周期。通过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 100Hz,UWB 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是零偏,(1:3)和(4:6)分别代表加速度和陀螺仪的零偏
|
|||
|
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
|