├── .gitignore ├── Doc ├── Algorithm Description.html ├── Algorithm Description.md ├── Manual.html ├── Manual.md └── dataflow.svg ├── README.md ├── Ref ├── A Drift Eliminated Attitude & Position Estimation Algorithm In 3D.pdf ├── Adaptive Zero Velocity Update Based On Velocity Classification For Pedestrian.pdf ├── Drift reduction in IMU-only pedestrian navigation system in unstructured environment.pdf └── Using Inertial Sensors for Position and Orientation Estimation.pdf ├── butter.py ├── data_receiver.py ├── main.ipynb ├── main.py ├── mathlib.py └── plotlib.py /.gitignore: -------------------------------------------------------------------------------- 1 | /__pycache__/ 2 | /.ipynb_checkpoints/ 3 | .vscode/.ropeproject/config.py 4 | .vscode/.ropeproject/objectdb 5 | data.txt 6 | -------------------------------------------------------------------------------- /Doc/Algorithm Description.md: -------------------------------------------------------------------------------- 1 | # Algorithm 2 | ## Definitions 3 | Quaternion is defined as $q=[q_{scalar}\ q_{vector}^T]^T=[q_0\ q_1\ q_2\ q_3]^T$ 4 | 5 | The rotation matrix of a quaternion is 6 | $$ 7 | R(q)= 8 | \begin{bmatrix} 9 | q_0^2+q_1^2-q_2^2-q_3^2 & 2q_1q_2-2q_0q_3 & 2q_1q_3+2q_0q_2 \\ 10 | 2q_1q_2+2q_0q_3 & q_0^2-q_1^2+q_2^2-q_3^2 & 2q_2q_3-2q_0q_1 \\ 11 | 2q_1q_3-2q_0q_2 & 2q_2q_3+2q_0q_1 & q_0^2-q_1^2-q_2^2+q_3^2 12 | \end{bmatrix} 13 | $$ 14 | 15 | Define 16 | $$ 17 | \Omega(\omega)= 18 | \begin{bmatrix} 19 | 0 & -\omega_x & -\omega_y & -\omega_z \\ 20 | \omega_x & 0 & \omega_z & -\omega_y \\ 21 | \omega_y & -\omega_z & 0 & \omega_x \\ 22 | \omega_z & \omega_y & -\omega_x & 0 23 | \end{bmatrix} 24 | $$ 25 | 26 | $$ 27 | G(q)=\frac{1}{2} 28 | \begin{bmatrix} 29 | -q_1 & -q_2 & -q_3 \\ 30 | q_0 & -q_3 & q_2 \\ 31 | q_3 & q_0 & -q_1 \\ 32 | -q_2 & q_1 & q_0 33 | \end{bmatrix} 34 | $$ 35 | 36 | ## Initialization 37 | $$q=[1\ 0\ 0\ 0]^T$$ 38 | $$P=1\times10^{-8}*I_4$$ 39 | 40 | I assume the device stays still for a certain period of time during initialization, so we can get a initial gravity vector 41 | $$g_n$$ 42 | and magnetic field vector 43 | $$m_n$$ 44 | The notation $\cdot_n$ means navigation frame. 45 | 46 | This can also be done using QUEST algorithm and many other algorithms. 47 | 48 | 49 | ## Propagation 50 | The state vector is quaternion $q$ 51 | 52 | State transfer matrix 53 | $$F_t=I_4+\dfrac{1}{2}dt*\Omega(\omega_t)$$ 54 | 55 | Then we derive process noise 56 | $$Q=(GyroNoise*dt)^2*GG^T$$ 57 | 58 | Make estimation: 59 | $$q=F_tq$$ 60 | $$P=F_tPF_t^T+Q$$ 61 | 62 | and finally normalize $q$ to reduce error: 63 | $$q=\dfrac{q}{||q||_2}$$ 64 | 65 | ## Measurement Update 66 | 67 | Here we only use the unit vector of our measurements to avoid accumulation of noise errors. 68 | 69 | $$ea=\dfrac{a_t}{||a_t||_2}$$ 70 | $$em=\dfrac{m_t}{||m_t||_2}$$ 71 | 72 | and the estimation(predition) of is as follow: 73 | $$pa=Normalize(-R(q)g_n)$$ 74 | $$pm=Normalize(R(q)m_n)$$ 75 | 76 | so error: 77 | $$ 78 | \epsilon_t= 79 | \begin{bmatrix} 80 | ea\\em 81 | \end{bmatrix}_{6\times1} 82 | - 83 | \begin{bmatrix} 84 | pa\\pm 85 | \end{bmatrix}_{6\times1} 86 | $$ 87 | 88 | note that $a_t$ and $m_t$ come in as $3\times1$ vectors, so don't get confused over dimensions. 89 | 90 | And we use the measurement matrix to calculate kalman gain. The measurent matrix $H$ is defined as 91 | $$ 92 | H= 93 | \begin{bmatrix} 94 | -\dfrac{\partial}{\partial q}(R(q)g_n) \\ 95 | \dfrac{\partial}{\partial q}(R(q)m_n) 96 | \end{bmatrix}_{6\times4} 97 | $$ 98 | which is annoying to calculate but it's done in `mathlib.py`. 99 | 100 | As for the sensor noise $R$, you can either just use $R=C*I_6$ or some other fancy definitions. 101 | 102 | Then it's just usual kalman filter stuff: 103 | $$S=HPH^T+R$$ 104 | $$K=PH^TS^{-1}$$ 105 | $$q=q+K\epsilon_t$$ 106 | $$P=P-KHP$$ 107 | 108 | ## Post Correction 109 | 110 | Normalize $q$ again 111 | $$q=\dfrac{q}{||q||_2}$$ 112 | and make sure P is symmetrical 113 | $$P=\dfrac{1}{2}(P+P^T)$$ 114 | 115 | ## Double Integration 116 | 117 | body frame acceleration: 118 | $$a_b=a_t+R(q)g_n$$ 119 | and we can update position and velocity now 120 | $$position=position + velocity*dt + \dfrac{1}{2}a_bdt^2$$ 121 | $$velocity=velocity+a_bdt$$ -------------------------------------------------------------------------------- /Doc/Manual.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |main.py
:主算法,带有一个从手机接收数据并可视化的示例。每次运行会自动保存数据到data.txt
中,receive_data()
的参数设为mode='file'
即可从文件读取。plotlib.py
:可视化的简单封装。mathlib.py
:矩阵操作和滤波器的封装。butter.py
:实时巴特沃斯滤波器,详情见这里。目前没有使用。main.ipynb
:开发使用。/Ref
:
68 | __init__(self, sampling, data_order={'w': 1, 'a': 2, 'm': 3})
sampling
82 | order
87 | w
、加速度为a
、磁力计为m
,默认为:{'w': 1, 'a': 2, 'm': 3}
。initialize(self, data, noise_coefficient={'w': 100, 'a': 100, 'm': 10})
返回一个list,包含EKF算法需要的所有初始化值。
96 |data
98 | noise_coefficient
104 | attitudeTrack(self, data, init_list)
使用Extended Kalman Filter(EKF) 计算姿态,算法描述在/Doc/Algorithm Description.html
。返回地面坐标系下的加速度(去除了重力成分)和设备朝向。
朝向由3个numpy数组表示,分别是轴的方向向量(单位向量),设备初始状态是:
115 |117 |
绕轴右手方向旋转后:
118 |120 |
data
122 | init_list
127 | initialize
的返回值,也可自定义。removeAccErr(self, a_nav, threshold=0.2, filter=False, wn=(0.01, 15))
假设设备测量前后都静止,去除地面坐标系加速度的偏差,并通过一个带通滤波器(可选)。返回修正后的加速度数据。
147 |a_nav
149 | threshold
154 | filter
159 | wn
165 | zupt(self, a_nav, threshold)
使用Zero velocity UPdaTe算法来修正速度。返回修正后的速度数据。
174 |a_nav
176 | threshold
181 | positionTrack(self, a_nav, velocities)
使用加速度和速度进行积分得到位移。返回位移数据。
190 |a_nav
192 | velocities
197 | IMUTracker
类initialize
方法initialize
返回的list,同传感器数据一起传入attitudeTrack
方法removeAccErr
方法修正attitudeTrack
返回的加速度数据zupt
方法计算速度positionTrack
方法对速度和加速度数据进行积分,得到位移data = sensor.getData()
215 | tracker = IMUTracker(sampling=100)
216 |
217 | # init
218 | init_list = tracker.initialize(data[5:30])
219 | # EKF
220 | a_nav, orix, oriy, oriz = tracker.attitudeTrack(data[30:], init_list)
221 | # filter a_nav
222 | a_nav_filtered = tracker.removeAccErr(a_nav, filter=False)
223 | # get velocity
224 | v = tracker.zupt(a_nav_filtered, threshold=0.2)
225 | # get position
226 | p = tracker.positionTrack(a_nav_filtered, v)
227 |
228 | plot3(data, ax=None, lims=None, labels=None, show=False, show_legend=False)
接受多个的数据,如方向加速度,将3个分量分别画在3张图中。默认不设置其他参数。返回使用的matplotlib axes对象。
238 |data
240 | [data1, data2, ...]
。每个元素都是的numpy数组。ax
245 | lims
250 | [[[xl, xh], [yl, yh]], ...]
。labels
256 | [[x_label1, y_label1, z_label1], [x_label2, y_label2, z_label2], ...]
。data
中数据一一对应。show
262 | plt.show()
,用处不大可以忽略。show_legend
267 | plot3D(data, lim=None, ax=None)
接受多个的数据,画出3维图像。
277 |data
279 | [[data1, label1], [data2, label2], ...]
。lim
285 | [[xl, xh], [yl, yh], [zl, zh]]
。None
则使用默认值,如:[[xl, xh], [yl, yh], None]
。ax
291 | plot3DAnimated(data, lim=[[-1, 1], [-1, 1], [-1, 1]], label=None, interval=10, show=True, repeat=False)
生成一个3D动画。
300 |视角调整比较麻烦,见axes3d.view_init。
301 |如果需要保存动画,则需要ffmpeg库,然后使用ani.save()
,见matplotlib文档示例。
data
304 | lim
309 | label
315 | interval
320 | show
325 | plt.show()
。plt.show()
repeat
331 | 直接输入数据即可画图:
340 |plot3([acc1, acc2])
341 | plot3D([[position, 'position'], [orientation, 'orientation']])
342 |
343 | plot3()
默认使用3行布局,也可以使用3列布局:
import matplotlib.pyplot as plt
345 | fig, ax = plt.subplots(nrows=1, ncols=3)
346 | plot3([acc1, acc2], ax=ax)
347 |
348 |
349 |
350 |
--------------------------------------------------------------------------------
/Doc/Manual.md:
--------------------------------------------------------------------------------
1 | - [说明](#说明)
2 | - [依赖库](#依赖库)
3 | - [项目结构](#项目结构)
4 | - [main.py](#mainpy)
5 | - [接口](#接口)
6 | - [IMUTracker类](#imutracker类)
7 | - [使用](#使用)
8 | - [示例](#示例)
9 | - [流程图](#流程图)
10 | - [plotlib.py](#plotlibpy)
11 | - [接口](#接口-1)
12 | - [示例](#示例-1)
13 |
14 | # 说明
15 |
16 | ## 依赖库
17 | - numpy
18 | - scipy
19 | - matplotlib
20 |
21 | ## 项目结构
22 | - `main.py`:主算法,带有一个从手机接收数据并可视化的示例。每次运行会自动保存数据到`data.txt`中,`receive_data()`的参数设为`mode='file'`即可从文件读取。
23 | - `plotlib.py`:可视化的简单封装。
24 | - `mathlib.py`:矩阵操作和滤波器的封装。
25 | - `butter.py`:实时巴特沃斯滤波器,详情见[这里](https://github.com/keikun555/Butter)。目前没有使用。
26 | - `main.ipynb`:开发使用。
27 | - `/Ref`:
28 | - *Using Inertial Sensors for Position and Orientation Estimation*是一个基本教程,包含了卡尔曼滤波的较为详细的描述。
29 | - 其它为修正算法相关文献。
30 |
31 |
32 | # main.py
33 |
34 | ## 接口
35 | ### IMUTracker类
36 | - `__init__(self, sampling, data_order={'w': 1, 'a': 2, 'm': 3})`
37 | - `sampling`
38 | - 采样率,单位:Hz。
39 | - `order`
40 | - 数据的顺序,角速度为`w`、加速度为`a`、磁力计为`m`,默认为:`{'w': 1, 'a': 2, 'm': 3}`。
41 |
42 |
43 | - `initialize(self, data, noise_coefficient={'w': 100, 'a': 100, 'm': 10})`
44 |
45 | 返回一个list,包含EKF算法需要的所有初始化值。
46 |
47 | - `data`
48 | - 传感器数据,$(n\times 9)$numpy数组。
49 | - 有时传感器刚开始测量时会产生无用的数据,可能需要预处理去掉明显不符合实际的数据点,如去掉刚开始测量时的前10个点。
50 | - `noise_coefficient`
51 | - 传感器噪声值包括了实际噪声和测量误差,噪声由初始化数据的方差算出,然后乘上一个系数作为算法中的传感器噪声。
52 | - 这个值越大,代表算法越不信任传感器,那么在滤波补偿时就会减小这个传感器的权值。
53 |
54 |
55 | - `attitudeTrack(self, data, init_list)`
56 |
57 | 使用Extended Kalman Filter(EKF) 计算姿态,算法描述在`/Doc/Algorithm Description.html`。返回地面坐标系下的加速度(去除了重力成分)和设备朝向。
58 |
59 | 朝向由3个$n\times 3$numpy数组表示,分别是$XYZ$轴的方向向量(单位向量),设备初始状态是:
60 | $$\hat{x}=[1,0,0]^T\ \hat{y}=[0,1,0]^T\ \hat{z}=[0,0,1]^T$$
61 |
62 | 绕$Z$轴右手方向旋转$90\degree$后:
63 | $$\hat{x}=[0,1,0]^T\ \hat{y}=[-1,0,0]^T\ \hat{z}=[0,0,1]^T$$
64 |
65 |
66 |
67 | - `data`
68 | - 传感器数据,$(n\times 9)$numpy数组。
69 | - `init_list`
70 | - 初始化值列表,可以直接使用`initialize`的返回值,也可自定义。
71 | - 顺序:
72 | - 地面坐标系重力向量
73 | - 重力大小
74 | - 磁场方向**单位**向量
75 | - 陀螺仪噪声
76 | - 陀螺仪偏差
77 | - 加速度计噪声
78 | - 地磁计噪声
79 |
80 |
81 | - `removeAccErr(self, a_nav, threshold=0.2, filter=False, wn=(0.01, 15))`
82 |
83 | 假设设备测量前后都静止,去除地面坐标系加速度的偏差,并通过一个带通滤波器(可选)。返回修正后的加速度数据。
84 |
85 | - `a_nav`
86 | - 地面坐标系加速度数据,$(n\times 3)$numpy数组。
87 | - `threshold`
88 | - 检测静止状态的加速度阈值,不建议太低。
89 | - `filter`
90 | - 滤波开关。
91 | - 低通滤波去除毛刺,高通滤波去除直流分量(固定的偏差),但有时滤波会起反作用,应视实际情况使用。
92 | - `wn`
93 | - 滤波器的截止频率。
94 |
95 |
96 | - `zupt(self, a_nav, threshold)`
97 |
98 | 使用Zero velocity UPdaTe算法来修正速度。返回修正后的速度数据。
99 |
100 | - `a_nav`
101 | - 地面坐标系加速度数据,$(n\times 3)$numpy数组。
102 | - `threshold`
103 | - 检测静止状态的加速度阈值,运动越激烈阈值应越高。
104 |
105 |
106 | - `positionTrack(self, a_nav, velocities)`
107 |
108 | 使用加速度和速度进行积分得到位移。返回位移数据。
109 |
110 | - `a_nav`
111 | - 地面坐标系加速度数据,$(n\times 3)$numpy数组。
112 | - `velocities`
113 | - 地面坐标系速度数据,$(n\times 3)$numpy数组。
114 |
115 |
116 | ## 使用
117 | 1. 初始化`IMUTracker`类
118 | 2. 将初始化数据输入`initialize`方法
119 | 3. 将`initialize`返回的list,同传感器数据一起传入`attitudeTrack`方法
120 | 4. 使用`removeAccErr`方法修正`attitudeTrack`返回的加速度数据
121 | 5. 使用`zupt`方法计算速度
122 | 6. 使用`positionTrack`方法对速度和加速度数据进行积分,得到位移
123 |
124 | ## 示例
125 | ```python
126 | data = sensor.getData()
127 | tracker = IMUTracker(sampling=100)
128 |
129 | # init
130 | init_list = tracker.initialize(data[5:30])
131 | # EKF
132 | a_nav, orix, oriy, oriz = tracker.attitudeTrack(data[30:], init_list)
133 | # filter a_nav
134 | a_nav_filtered = tracker.removeAccErr(a_nav, filter=False)
135 | # get velocity
136 | v = tracker.zupt(a_nav_filtered, threshold=0.2)
137 | # get position
138 | p = tracker.positionTrack(a_nav_filtered, v)
139 | ```
140 |
141 | ## 流程图
142 |