四元数及姿态解算 Mahony算法
https://www.cnblogs.com/TIANHUAHUA/p/8031606.html
AHRS(attitude and heading reference system)称为航姿参考系统。
首先,我们明确一下四元数的知识。
四元数(quaternion)是由我们的威廉·哈密顿提出的。哈密顿就是那个‘哈密顿最小作用原理’的提出者。
四元数可视为复数的扩展。在复数中,定义了,而四元数中则定义了。也就是说四元数是一个具有三个虚部的虚数。把这些虚部理解为一个复数空间的话,四元数就是一个四位空间。这个是纯数学问题,有兴趣可以了解群论,本人也不是很理解所以也不多讲啦。
但是四元数本身最多的就是使用单位四元数来表示三维空间的旋转(rotation)及定向(orientation)。
为了方便,我们下面使用q = ((x, y, z),w) = (v, w),其中v是向量,w是实数,这样的式子来表示一个四元数。下述内容采自知乎。
首先,定义一个你需要做的旋转。旋转轴为向量,旋转角度为(右手法则的旋转)。如下图所示:
此图中,
那么与此相对应的四元数(下三行式子都是一个意思,只是不同的表达形式)
这时它的共轭(下三行式子都是一个意思,只是不同的表达形式),
如果你想算一个点在这个旋转下新的坐标,需要进行如下操作,
1.定义纯四元数
2.进行四元数运算
3.产生的一定是纯四元数,也就是说它的第一项为0,有如下形式:
4.中的后三项就是:
这样,就完成了一次四元数旋转运算。
同理,如果你有一个四元数:
那么,它对应一个以向量为轴旋转角度的旋转操作(右手法则的旋转)。
这里基本解释了部分四元数的基本原理,如果想要更深入了解可以去看原帖:https://www.zhihu.com/question/23005815
其他的数学性质:
四元数是描述了B相对于A的旋转,其中的rx,ry,rz组成的向量也是处于A中的向量。
如果要一个共轭四元数,也就是A对B的四元数,即 。我们有:
注意,这里的当四元数表示由B相对于A坐标的四元数为时,他的共轭四元数则表示A相对于B坐标的四元数。
当我们想求相对四元数时,可以使用。详情见笔记11012018A01。
四元数的乘法,
- 封闭性:易证明,p和p的共轭相乘即可,|p*q|=1。
- 结合律:这条也很好证明,只要证明(p*q)*r=p*(q*r)。
四元数对欧拉角转换,
更多资料可以参考:https://zhuanlan.zhihu.com/p/28189289
我们下一步来看一下通过陀螺仪输出的角加速度估计姿态问题。
数学操作部分:
如果那些角速度被分配到向量的时候,即
的时候。
表示传感器所在坐标相对于地磁坐标的姿态改变速率的四元数微分可以表示为下边公式:
为从E坐标到S坐标的预测值。
而在时间t,地球坐标系相对于传感器坐标系的四元数可以同过的积分获得。而是通过预测值与时间t时刻形成的角速度的叉乘获得的。
在不在对q进行二次处理的话,姿态预测值就是现在的姿态值。
下面就是一个求解传感器所在坐标系的姿态。
开始,基于一个初始为[1 0 0 0]的四元数得到 q = [1 0 0 0], 根据加速度求得加速度的单位向量,根据q求转换到b系的值v。
而 v = [2*(q(2)*q(4)-q(1)*q(3)) 2*(q(1)*q(2)+q(3)*q(4)) q(1)^2 -q(2)^2 -q(3)^2+q(4)^2]
而后计算误差,其中加速度与计算得到的v的向量积可以得到误差值。
计算得到的积分误差为误差的积分,即误差和加上误差乘以采样时间。
经过PI降低陀螺仪的误差。陀螺仪数据为自身数据加上P乘以误差加I乘以积分误差。
而后计算四元数的微分值等于0.5*上一次采样的四元数乘角速度。
最后在对四元数进行积分得到当前四元数。最后再将四元数单位化为单位向量。
下面是这个过程的matlab源码。
View Code
在获得的姿态上减去带上方向的重力加速度,而后通过重力加速度进行积分,可以获得速度。然而由于积分误差的累计,会导致信号失真,并漂移。
所以通过高通滤波处理速度。
位移可以通过同样的方法获得。
代码如下:
%% Housekeeping addpath('ximu_matlab_library'); % include x-IMU MATLAB library addpath('quaternion_library'); % include quatenrion library close all; % close all figures clear; % clear all variables clc; % clear the command terminal %% Import data xIMUdata = xIMUdataClass('LoggedData/LoggedData'); samplePeriod = 1/256; gyr = [xIMUdata.CalInertialAndMagneticData.Gyroscope.X... xIMUdata.CalInertialAndMagneticData.Gyroscope.Y... xIMUdata.CalInertialAndMagneticData.Gyroscope.Z]; % gyroscope acc = [xIMUdata.CalInertialAndMagneticData.Accelerometer.X... xIMUdata.CalInertialAndMagneticData.Accelerometer.Y... xIMUdata.CalInertialAndMagneticData.Accelerometer.Z]; % accelerometer % Plot figure('NumberTitle', 'off', 'Name', 'Gyroscope'); hold on; plot(gyr(:,1), 'r'); plot(gyr(:,2), 'g'); plot(gyr(:,3), 'b'); xlabel('sample'); ylabel('dps'); title('Gyroscope'); legend('X', 'Y', 'Z'); figure('NumberTitle', 'off', 'Name', 'Accelerometer'); hold on; plot(acc(:,1), 'r'); plot(acc(:,2), 'g'); plot(acc(:,3), 'b'); xlabel('sample'); ylabel('g'); title('Accelerometer'); legend('X', 'Y', 'Z'); %% Process data through AHRS algorithm (calcualte orientation) % See: http://www.x-io.co.uk/open-source-imu-and-ahrs-algorithms/ R = zeros(3,3,length(gyr)); % rotation matrix describing sensor relative to Earth ahrs = MahonyAHRS('SamplePeriod', samplePeriod, 'Kp', 1); for i = 1:length(gyr) ahrs.UpdateIMU(gyr(i,:) * (pi/180), acc(i,:)); % gyroscope units must be radians R(:,:,i) = quatern2rotMat(ahrs.Quaternion)'; % transpose because ahrs provides Earth relative to sensor end %% Calculate 'tilt-compensated' accelerometer tcAcc = zeros(size(acc)); % accelerometer in Earth frame for i = 1:length(acc) tcAcc(i,:) = R(:,:,i) * acc(i,:)'; end % Plot figure('NumberTitle', 'off', 'Name', '''Tilt-Compensated'' accelerometer'); hold on; plot(tcAcc(:,1), 'r'); plot(tcAcc(:,2), 'g'); plot(tcAcc(:,3), 'b'); xlabel('sample'); ylabel('g'); title('''Tilt-compensated'' accelerometer'); legend('X', 'Y', 'Z'); %% Calculate linear acceleration in Earth frame (subtracting gravity) linAcc = tcAcc - [zeros(length(tcAcc), 1), zeros(length(tcAcc), 1), ones(length(tcAcc), 1)]; linAcc = linAcc * 9.81; % convert from 'g' to m/s/s % Plot figure('NumberTitle', 'off', 'Name', 'Linear Acceleration'); hold on; plot(linAcc(:,1), 'r'); plot(linAcc(:,2), 'g'); plot(linAcc(:,3), 'b'); xlabel('sample'); ylabel('g'); title('Linear acceleration'); legend('X', 'Y', 'Z'); %% Calculate linear velocity (integrate acceleartion) linVel = zeros(size(linAcc)); for i = 2:length(linAcc) linVel(i,:) = linVel(i-1,:) + linAcc(i,:) * samplePeriod; end % Plot figure('NumberTitle', 'off', 'Name', 'Linear Velocity'); hold on; plot(linVel(:,1), 'r'); plot(linVel(:,2), 'g'); plot(linVel(:,3), 'b'); xlabel('sample'); ylabel('g'); title('Linear velocity'); legend('X', 'Y', 'Z'); %% High-pass filter linear velocity to remove drift order = 1; filtCutOff = 0.1; [b, a] = butter(order, (2*filtCutOff)/(1/samplePeriod), 'high'); linVelHP = filtfilt(b, a, linVel); % Plot figure('NumberTitle', 'off', 'Name', 'High-pass filtered Linear Velocity'); hold on; plot(linVelHP(:,1), 'r'); plot(linVelHP(:,2), 'g'); plot(linVelHP(:,3), 'b'); xlabel('sample'); ylabel('g'); title('High-pass filtered linear velocity'); legend('X', 'Y', 'Z'); %% Calculate linear position (integrate velocity) linPos = zeros(size(linVelHP)); for i = 2:length(linVelHP) linPos(i,:) = linPos(i-1,:) + linVelHP(i,:) * samplePeriod; end % Plot figure('NumberTitle', 'off', 'Name', 'Linear Position'); hold on; plot(linPos(:,1), 'r'); plot(linPos(:,2), 'g'); plot(linPos(:,3), 'b'); xlabel('sample'); ylabel('g'); title('Linear position'); legend('X', 'Y', 'Z'); %% High-pass filter linear position to remove drift order = 1; filtCutOff = 0.1; [b, a] = butter(order, (2*filtCutOff)/(1/samplePeriod), 'high'); linPosHP = filtfilt(b, a, linPos); % Plot figure('NumberTitle', 'off', 'Name', 'High-pass filtered Linear Position'); hold on; plot(linPosHP(:,1), 'r'); plot(linPosHP(:,2), 'g'); plot(linPosHP(:,3), 'b'); xlabel('sample'); ylabel('g'); title('High-pass filtered linear position'); legend('X', 'Y', 'Z'); %% Play animation SamplePlotFreq = 8; SixDOFanimation(linPosHP, R, ... 'SamplePlotFreq', SamplePlotFreq, 'Trail', 'Off', ... 'Position', [9 39 1280 720], ... 'AxisLength', 0.1, 'ShowArrowHead', false, ... 'Xlabel', 'X (m)', 'Ylabel', 'Y (m)', 'Zlabel', 'Z (m)', 'ShowLegend', false, 'Title', 'Unfiltered',... 'CreateAVI', false, 'AVIfileNameEnum', false, 'AVIfps', ((1/samplePeriod) / SamplePlotFreq)); %% End of script
而最后的SixDOFanimation函数如下:
View Code
其中可以通过更改PI部分参数,重力补偿值和滤波频率,来适应自己的信号。
如果想要了解更多细节获得源程序,可以查看原官方资料网站:
http://x-io.co.uk/open-source-imu-and-ahrs-algorithms/
参考资料: