HoloLib
High-performance holonomic (X-Drive) control library for VEX V5
Loading...
Searching...
No Matches
pose_ekf.h
Go to the documentation of this file.
1#pragma once
2
3#include "Eigen/Core"
4#include "Eigen/Dense"
6#include <cmath>
7#include <vector>
8
9/**
10 * @brief Extended Kalman Filter implementation for maintaining the robot's
11 * pose.
12 *
13 * Fuses IMU heading and tracking wheel (or motor encoder) deltas to estimate
14 * X, Y, and Theta coordinates on the field.
15 */
16class PoseEKF {
17private:
18 Eigen::Vector3f x;
19 Eigen::Matrix3f P;
20 Eigen::Matrix<float, 1, 3> H;
21 float measurementNoise;
22 float xProcessNoise = 0.01f, yProcessNoise = 0.01f,
23 thetaProcessNoise = 0.001f;
24
25public:
26 EIGEN_MAKE_ALIGNED_OPERATOR_NEW
27
28 /**
29 * @brief Set the process noise values for the Kalman filter matrices.
30 *
31 * @param xNoise Process noise in the X direction.
32 * @param yNoise Process noise in the Y direction.
33 * @param thetaNoise Process noise for the heading.
34 * @param measNoise Measurement noise parameter.
35 */
36 void setProcessNoise(float xNoise, float yNoise, float thetaNoise,
37 float measNoise) {
38 xProcessNoise = xNoise;
39 yProcessNoise = yNoise;
40 thetaProcessNoise = thetaNoise;
41 measurementNoise = measNoise;
42 }
43
44 /**
45 * @brief Construct a new Pose EKF object.
46 *
47 * @param initial_x Starting X position.
48 * @param initial_y Starting Y position.
49 * @param initial_theta Starting heading.
50 */
51 PoseEKF(float initial_x, float initial_y, float initial_theta) {
52 x << initial_x, initial_y, initial_theta;
53 P.setZero();
54 H << 0, 0, 1;
55 measurementNoise = 0.00005f;
56 }
57
58 /**
59 * @brief The prediction step of the EKF based on local relative movements.
60 *
61 * @param dx_local Change in local X position.
62 * @param dy_local Change in local Y position.
63 * @param dtheta Change in heading.
64 */
65 void predict(float dx_local, float dy_local, float dtheta) {
66 float s, c;
67 if (std::abs(dtheta) < 1e-4f) {
68 s = 1.0f - (dtheta * dtheta) / 6.0f;
69 c = dtheta / 2.0f;
70 } else {
71 s = std::sin(dtheta) / dtheta;
72 c = (1.0f - std::cos(dtheta)) / dtheta;
73 }
74
75 Eigen::Vector2f local_d(dx_local, dy_local);
76 Eigen::Matrix2f R_arc;
77 R_arc << s, c, -c, s;
78 Eigen::Vector2f arc_d = R_arc * local_d;
79
80 float prev_theta = x(2);
81 float cos_t = std::cos(prev_theta);
82 float sin_t = std::sin(prev_theta);
83
84 Eigen::Matrix2f R_global;
85 R_global << cos_t, sin_t, -sin_t, cos_t;
86 Eigen::Vector2f global_d = R_global * arc_d;
87
88 x.head<2>() += global_d;
89 x(2) += dtheta;
90 x(2) = std::remainder(x(2), 2.0f * M_PI);
91
92 Eigen::Matrix3f F = Eigen::Matrix3f::Identity();
93 F(0, 2) = global_d.y();
94 F(1, 2) = -global_d.x();
95
96 Eigen::Matrix3f Q_step = Eigen::Matrix3f::Zero();
97 float var_x = xProcessNoise * std::abs(dx_local) + 1e-6f;
98 float var_y = yProcessNoise * std::abs(dy_local) + 1e-6f;
99 Eigen::Matrix2f Q_local;
100 Q_local << var_x, 0, 0, var_y;
101
102 Q_step.block<2, 2>(0, 0) = R_global * Q_local * R_global.transpose();
103 Q_step(2, 2) = thetaProcessNoise * std::abs(dtheta) + 1e-7f;
104
105 P = F * P * F.transpose() + Q_step;
106 }
107
108 /**
109 * @brief Update the state using absolute heading measured by an IMU.
110 *
111 * @param measured_theta Heading read from the IMU.
112 * @param dynamic_R Dynamic measurement noise.
113 */
114 void updateIMU(float measured_theta, float dynamic_R) {
115 float y = measured_theta - x(2);
116 y = std::remainder(y, 2.0f * M_PI);
117 float S = (H * P * H.transpose())(0, 0) + dynamic_R;
118 Eigen::Vector3f K = P * H.transpose() / S;
119 x = x + K * y;
120 x(2) = std::remainder(x(2), 2.0f * M_PI);
121 P = (Eigen::Matrix3f::Identity() - K * H) * P;
122 }
123
124 /**
125 * @brief Update the state using unpowered tracking wheels data.
126 *
127 * @param configs Vector of tracking wheel configurations.
128 * @param measured_deltas Measurement differences for each wheel since the
129 * last step.
130 * @param dx_local Estimated local X change.
131 * @param dy_local Estimated local Y change.
132 * @param dtheta Estimated heading change.
133 * @param wheel_noise Sensor noise for the tracking wheels.
134 */
135 void updateTrackingWheels(const std::vector<TrackingWheelConfig> &configs,
136 const Eigen::VectorXf &measured_deltas,
137 float dx_local, float dy_local, float dtheta,
138 float wheel_noise) {
139 const int n = static_cast<int>(configs.size());
140 if (n == 0 || measured_deltas.size() != n)
141 return;
142 float theta = x(2);
143 float cos_t = std::cos(theta);
144 float sin_t = std::sin(theta);
145
146 Eigen::MatrixXf H_tw(n, 3);
147 Eigen::VectorXf z_pred(n);
148
149 for (int i = 0; i < n; ++i) {
150 float ox = configs[i].xOffset;
151 float oy = configs[i].yOffset;
152
153 if (configs[i].orientation == TrackingWheelOrientation::VERTICAL) {
154 z_pred(i) = dy_local - ox * dtheta;
155 H_tw(i, 0) = -sin_t;
156 H_tw(i, 1) = cos_t;
157 H_tw(i, 2) = -ox;
158 } else {
159
160 z_pred(i) = dx_local + oy * dtheta;
161 H_tw(i, 0) = cos_t;
162 H_tw(i, 1) = sin_t;
163 H_tw(i, 2) = oy;
164 }
165 }
166
167 Eigen::VectorXf y_innov = measured_deltas - z_pred;
168 Eigen::MatrixXf R_tw = Eigen::MatrixXf::Identity(n, n) * wheel_noise;
169 Eigen::MatrixXf S = H_tw * P * H_tw.transpose() + R_tw;
170 Eigen::MatrixXf K = P * H_tw.transpose() * S.inverse();
171 x += K * y_innov;
172 x(2) = std::remainder(x(2), 2.0f * static_cast<float>(M_PI));
173
174 Eigen::Matrix3f I3 = Eigen::Matrix3f::Identity();
175 Eigen::Matrix3f IKH = I3 - K * H_tw;
176 P = IKH * P * IKH.transpose() + K * R_tw * K.transpose();
177 }
178
179 /**
180 * @brief Set specific process noises when using tracking wheels.
181 *
182 * @param xNoise Process noise in X direction.
183 * @param yNoise Process noise in Y direction.
184 * @param thetaNoise Process noise for the heading.
185 */
186 void setTrackingWheelNoise(float xNoise, float yNoise, float thetaNoise) {
187 xProcessNoise = xNoise;
188 yProcessNoise = yNoise;
189 thetaProcessNoise = thetaNoise;
190 }
191
192 /**
193 * @brief Get current X position.
194 * @return float The X position.
195 */
196 float getX() const { return x(0); }
197
198 /**
199 * @brief Get current Y position.
200 * @return float The Y position.
201 */
202 float getY() const { return x(1); }
203
204 /**
205 * @brief Get current heading angle.
206 * @return float The heading in radians.
207 */
208 float getTheta() const { return x(2); }
209
210 /**
211 * @brief Reset the state to a specific pose.
212 *
213 * @param new_x New X position.
214 * @param new_y New Y position.
215 * @param new_theta New heading angle.
216 */
217 void setPose(float new_x, float new_y, float new_theta) {
218 x << new_x, new_y, new_theta;
219 P.setZero();
220 }
221};
Extended Kalman Filter implementation for maintaining the robot's pose.
Definition pose_ekf.h:16
float getTheta() const
Get current heading angle.
Definition pose_ekf.h:208
float getY() const
Get current Y position.
Definition pose_ekf.h:202
EIGEN_MAKE_ALIGNED_OPERATOR_NEW void setProcessNoise(float xNoise, float yNoise, float thetaNoise, float measNoise)
Set the process noise values for the Kalman filter matrices.
Definition pose_ekf.h:36
void setPose(float new_x, float new_y, float new_theta)
Reset the state to a specific pose.
Definition pose_ekf.h:217
PoseEKF(float initial_x, float initial_y, float initial_theta)
Construct a new Pose EKF object.
Definition pose_ekf.h:51
void updateTrackingWheels(const std::vector< TrackingWheelConfig > &configs, const Eigen::VectorXf &measured_deltas, float dx_local, float dy_local, float dtheta, float wheel_noise)
Update the state using unpowered tracking wheels data.
Definition pose_ekf.h:135
void setTrackingWheelNoise(float xNoise, float yNoise, float thetaNoise)
Set specific process noises when using tracking wheels.
Definition pose_ekf.h:186
float getX() const
Get current X position.
Definition pose_ekf.h:196
void updateIMU(float measured_theta, float dynamic_R)
Update the state using absolute heading measured by an IMU.
Definition pose_ekf.h:114
void predict(float dx_local, float dy_local, float dtheta)
The prediction step of the EKF based on local relative movements.
Definition pose_ekf.h:65