├── ukf
├── config
│ └── ukf.yaml
├── launch
│ └── ukf.launch
├── src
│ ├── ukf_node.cpp
│ └── ukf.cpp
├── CMakeLists.txt
├── package.xml
└── include
│ └── ukf
│ └── ukf.h
└── README.md
/ukf/config/ukf.yaml:
--------------------------------------------------------------------------------
1 | init_state_provided: false
2 |
3 | imu_topic: /imu/data
4 | gnss_topic: /fix
5 | odom_topic: /odometry/car
6 |
--------------------------------------------------------------------------------
/ukf/launch/ukf.launch:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ukf/src/ukf_node.cpp:
--------------------------------------------------------------------------------
1 | /**********************************
2 |
3 | Created on : 24th April 2020
4 | Author : Krishna Sandeep
5 |
6 | **********************************/
7 |
8 | #include
9 |
10 | #include "ukf/ukf.h"
11 |
12 | int main(int argc, char** argv)
13 | {
14 | ros::init(argc, argv, "ukf_node");
15 | ros::NodeHandle node;
16 | ros::NodeHandle private_nh("~");
17 |
18 | UKF ukf(node, private_nh); //instance of UKF class
19 |
20 | ros::Rate loop_rate(30);
21 |
22 | while(ros::ok())
23 | {
24 | ros::spinOnce(); //invokes callback
25 | ukf.estimateVehicleState(); //runs the UKF estimation
26 | loop_rate.sleep();
27 | }
28 |
29 | return 0;
30 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # UKF
2 | Unscented Kalman Filter using IMU and GNSS data for vehicle or mobile robot localization.
3 |
4 | Requirements: Eigen3, ROS
5 |
6 | Input: sensor_msgs/NavSatFix, sensor_msgs/Imu
7 |
8 | Output: nav_msgs/Odometry
9 |
10 | Key Features:
11 |
12 | 1. Assumes 2D motion.
13 | 2. Initializes the state{position x, position y, heading angle, velocity x, velocity y} to (0.0, 0.0, yaw, 0.0, 0.0) with the yaw from IMU at the start of the program if no initial state is provided.
14 | 3. Uses acceleration and yaw rate data from IMU in the prediction step.
15 | 4. GNSS data is used for correction.
16 | 5. Yaw from the IMU is only used to initialize and not in correction step.
17 |
18 | How to use:
19 |
20 | 1. Clone the repository in to your workspace
21 | 2. catkin_make #compile
22 | 3. Edit the parameters in config/ukf.yaml to your suiting. Note that not all the parameters used in the code are not present in the yaml file.
23 | 4. roslaunch ukf ukf.launch #to run the code
24 |
25 |
--------------------------------------------------------------------------------
/ukf/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 2.8.3)
2 | project(ukf)
3 |
4 | ## Compile as C++11, supported in ROS Kinetic and newer
5 | add_compile_options(-std=c++11)
6 |
7 | ## Find catkin macros and libraries
8 | ## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz)
9 | ## is used, also find other catkin packages
10 | find_package(catkin REQUIRED COMPONENTS
11 | roscpp
12 | std_msgs
13 | geometry_msgs
14 | sensor_msgs
15 | nav_msgs
16 | tf2
17 | )
18 |
19 | ## System dependencies are found with CMake's conventions
20 | find_package(Eigen3 REQUIRED)
21 |
22 | ###################################
23 | ## catkin specific configuration ##
24 | ###################################
25 | ## The catkin_package macro generates cmake config files for your package
26 | ## Declare things to be passed to dependent projects
27 | ## INCLUDE_DIRS: uncomment this if your package contains header files
28 | ## LIBRARIES: libraries you create in this project that dependent projects also need
29 | ## CATKIN_DEPENDS: catkin_packages dependent projects also need
30 | ## DEPENDS: system dependencies of this project that dependent projects also need
31 | catkin_package(
32 | INCLUDE_DIRS include
33 | # LIBRARIES ukf
34 | CATKIN_DEPENDS roscpp std_msgs
35 | DEPENDS system_lib
36 | )
37 |
38 | ###########
39 | ## Build ##
40 | ###########
41 |
42 | ## Specify additional locations of header files
43 | ## Your package locations should be listed before other locations
44 | include_directories(
45 | include
46 | ${catkin_INCLUDE_DIRS}
47 | ${EIGEN3_INCLUDE_DIRS}
48 | )
49 |
50 | add_executable(ukf_node src/ukf_node.cpp src/ukf.cpp)
51 | target_link_libraries(ukf_node ${catkin_LIBRARIES} ${EIGEN3_LIBRARIES})
52 | add_dependencies(ukf_node ${catkin_EXPORTED_TARGETS})
53 |
54 |
--------------------------------------------------------------------------------
/ukf/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | ukf
4 | 0.0.0
5 | The ukf package
6 |
7 |
8 |
9 |
10 | user
11 |
12 |
13 |
14 |
15 |
16 | TODO
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | catkin
52 | roscpp
53 | std_msgs
54 | roscpp
55 | std_msgs
56 | roscpp
57 | std_msgs
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/ukf/include/ukf/ukf.h:
--------------------------------------------------------------------------------
1 | /**********************************
2 |
3 | Created on : 24th April 2020
4 | Author : Krishna Sandeep
5 |
6 | **********************************/
7 |
8 | #ifndef UKF_UKF_H
9 | #define UKF_UKF_H
10 |
11 | #include
12 | #include
13 | #include
14 | #include
15 |
16 | #include
17 | #include
18 |
19 | #include
20 |
21 | using namespace std;
22 | using namespace Eigen;
23 |
24 | /*
25 | --------- Assuming 2D motion, Vehicle state {x,y,theta,vx,vy} i.e., {position x, position y, heading angle, velocity x, velocity y}
26 | */
27 |
28 | typedef Matrix Matrix5d;
29 | typedef Matrix Vector5d;
30 |
31 | /* state consists of 5 elements [x, y, theta, vx, vy] in order and a 5x5 covariance matrix
32 | (x,y) : position in map frame
33 | theta : heading angle (ENU convention)
34 | (vx,vy) : velocity in base_link frame
35 | */
36 | class VehicleState
37 | {
38 | public:
39 | VehicleState();
40 | ~VehicleState(){}
41 |
42 | Vector5d vec;
43 | Matrix5d cov_mat; //covariance matrix
44 | };
45 |
46 | class MMInput
47 | {
48 | public:
49 | MMInput();
50 | ~MMInput(){}
51 |
52 | double ax; //acceleration
53 | double ay;
54 | double omega; //yaw rate
55 | double delT; //time interval
56 | };
57 |
58 | class UKF
59 | {
60 | public:
61 | UKF(ros::NodeHandle node, ros::NodeHandle private_nh);
62 | ~UKF(){}
63 |
64 | void estimateVehicleState(); //runs the UKF estimation
65 |
66 | private:
67 | void imuCallback(const sensor_msgs::Imu::ConstPtr& msg); //IMU callback
68 | void gnssCallback(const sensor_msgs::NavSatFix::ConstPtr& fix); //GNSS callback
69 |
70 | void initVehicleState(const double yaw); //Initializes the Vehicle state to default values
71 | void predict(); //the prediction step
72 | Vector5d applyMotionModel(Vector5d vec); //applies the model model
73 | void correct(const double x, const double y); //the correction step
74 | void publishVehicleState(); //publishes the vehicle state as a nav_msgs::Odometry message
75 |
76 | ros::Subscriber gnss_sub; //GNSS subscriber
77 | ros::Subscriber imu_sub; //IMU data subscriber
78 | ros::Publisher odom_pub; //publishes nav_msgs::Odometry msgs
79 | string _imu_topic, _gnss_topic, _odom_topic; //topic names to subscribe/publish
80 | double _noise_var_pos, _noise_var_yaw, _noise_var_vel, _noise_var_meas; //noise variances for position, yaw, velocity and measurement
81 | bool _init_state_provided; //True if initial state is provided
82 | double _init_x, _init_y, _init_yaw, _init_velx, _init_vely; //initial values for state
83 | double _init_var_pos, _init_var_yaw, _init_var_vel; //initial variances for position, yaw, velocity
84 |
85 | VehicleState _curr_state, _predicted_state;
86 | pair _init_utm, _curr_utm; //Initial and current UTM reading in (easting, northing)
87 | pair _vp_at_first_gnss; //vehicle position when the 1st GNSS reading is recieved
88 | bool _is_first_imu, _is_first_gnss; //boolean to verify if this is the first IMU, GNSS reading respectively
89 | bool _imu_available, _gnss_available; //true if a new imu/gnss reading is available
90 | double _prev_imu_time, _curr_imu_time; //Timestamps of two consecutive IMU readings
91 | MMInput _mmi; //current motion model input
92 | Matrix _propagated_sigma_pt_mat; //matrix to store the sigma points propagated using the motion model
93 | Matrix5d _process_noise_mat; //Covariance matrix for the process noise
94 | Matrix2d _meas_noise_mat; //Covariance matrix for the measurement noise
95 | };
96 |
97 | #endif
--------------------------------------------------------------------------------
/ukf/src/ukf.cpp:
--------------------------------------------------------------------------------
1 | /**********************************
2 |
3 | Created on : 24th April 2020
4 | Author : Krishna Sandeep
5 |
6 | **********************************/
7 |
8 | #include "ukf/ukf.h"
9 |
10 | #include
11 | #include
12 | #include
13 |
14 | #include
15 |
16 | #include
17 |
18 | #include
19 |
20 | /* @brief Converts quaternion to euler */
21 | double getYaw(geometry_msgs::Quaternion q_msg)
22 | {
23 | tf2::Quaternion quat;
24 | tf2::fromMsg(q_msg, quat);
25 |
26 | tf2::Matrix3x3 mat(quat);
27 | double roll, pitch, yaw;
28 | mat.getRPY(roll, pitch, yaw);
29 |
30 | return yaw;
31 | }
32 |
33 | /* @brief Constructor */
34 | VehicleState::VehicleState()
35 | {
36 | vec = Vector5d::Zero();
37 | cov_mat = 1e-4*Matrix5d::Identity(); //small variances
38 | }
39 |
40 | /* @brief Constructor */
41 | MMInput::MMInput()
42 | {
43 | ax = 0.0;
44 | ay = 0.0;
45 | omega = 0.0;
46 | delT = 0.0;
47 | }
48 |
49 | /* @brief Constructor */
50 | UKF::UKF(ros::NodeHandle node, ros::NodeHandle private_nh)
51 | {
52 | /* Loading parameters */
53 | private_nh.param("noise_var_pos", _noise_var_pos, 1e-4);
54 | ROS_INFO("noise_var_pos: %f", _noise_var_pos);
55 | private_nh.param("noise_var_yaw", _noise_var_yaw, 1e-4);
56 | ROS_INFO("noise_var_yaw: %f", _noise_var_yaw);
57 | private_nh.param("noise_var_vel", _noise_var_vel, 1e-4);
58 | ROS_INFO("noise_var_vel: %f", _noise_var_vel);
59 | private_nh.param("noise_var_meas", _noise_var_meas, 1e-4);
60 | ROS_INFO("noise_var_meas: %f", _noise_var_meas);
61 | private_nh.param("init_state_provided", _init_state_provided, false);
62 | ROS_INFO("init_state_provided: %d", _init_state_provided);
63 | private_nh.param("init_x", _init_x, 0.0);
64 | ROS_INFO("init_x: %f", _init_x);
65 | private_nh.param("init_y", _init_y, 0.0);
66 | ROS_INFO("init_y: %f", _init_y);
67 | private_nh.param("init_yaw", _init_yaw, 0.0);
68 | ROS_INFO("init_yaw: %f", _init_yaw);
69 | private_nh.param("init_velx", _init_velx, 0.0);
70 | ROS_INFO("init_velx: %f", _init_velx);
71 | private_nh.param("init_vely", _init_vely, 0.0);
72 | ROS_INFO("init_vely: %f", _init_vely);
73 | private_nh.param("init_var_pos", _init_var_pos, 1e-4);
74 | ROS_INFO("init_var_pos: %f", _init_var_pos);
75 | private_nh.param("init_var_yaw", _init_var_yaw, 1e-4);
76 | ROS_INFO("init_var_yaw: %f", _init_var_yaw);
77 | private_nh.param("init_var_vel", _init_var_vel, 1e-4);
78 | ROS_INFO("init_var_vel: %f", _init_var_vel);
79 |
80 | private_nh.param("imu_topic", _imu_topic, "/imu/data");
81 | ROS_INFO("imu_topic: %s", _imu_topic.c_str());
82 | private_nh.param("gnss_topic", _gnss_topic, "/fix");
83 | ROS_INFO("gnss_topic: %s", _gnss_topic.c_str());
84 | private_nh.param("odom_topic", _odom_topic, "/odometry/car");
85 | ROS_INFO("odom_topic: %s", _odom_topic.c_str());
86 |
87 | imu_sub = node.subscribe(_imu_topic, 1, &UKF::imuCallback, this);
88 | gnss_sub = node.subscribe(_gnss_topic, 1, &UKF::gnssCallback, this);
89 | odom_pub = node.advertise(_odom_topic, 1);
90 |
91 | //initializing values
92 | if(_init_state_provided)
93 | {
94 | _curr_state.vec(0) = _init_x;
95 | _curr_state.vec(1) = _init_y;
96 | _curr_state.vec(2) = _init_yaw;
97 | _curr_state.vec(3) = _init_velx;
98 | _curr_state.vec(4) = _init_vely;
99 | _curr_state.cov_mat.diagonal() << _init_var_pos, _init_var_pos, _init_var_yaw,
100 | _init_var_vel, _init_var_vel;
101 | }
102 |
103 | _process_noise_mat.diagonal() << _noise_var_pos, _noise_var_pos, _noise_var_yaw,
104 | _noise_var_vel, _noise_var_vel;
105 |
106 | _meas_noise_mat.diagonal() << _noise_var_meas, _noise_var_meas;
107 |
108 | _is_first_imu = true;
109 | _is_first_gnss = true;
110 | _imu_available = false;
111 | _gnss_available = false;
112 |
113 | _prev_imu_time = 0.0;
114 | _curr_imu_time = 0.0;
115 |
116 | _init_utm = make_pair(0.0, 0.0);
117 | _curr_utm = make_pair(0.0, 0.0);
118 | _vp_at_first_gnss = make_pair(0.0, 0.0);
119 |
120 | _propagated_sigma_pt_mat = Matrix::Zero();
121 |
122 | //cout<<"Constructor done"<header.stamp.toSec();
133 | _curr_imu_time = msg->header.stamp.toSec();
134 |
135 | if(!_init_state_provided)
136 | initVehicleState(getYaw(msg->orientation));
137 |
138 | _is_first_imu = false;
139 | }
140 |
141 | else
142 | {
143 | _imu_available =true;
144 |
145 | _prev_imu_time = _curr_imu_time;
146 | _curr_imu_time = msg->header.stamp.toSec();
147 |
148 | //fetching the motion model input
149 | _mmi.ax = msg->linear_acceleration.x;
150 | _mmi.ay = msg->linear_acceleration.y;
151 | _mmi.omega = msg->angular_velocity.z;
152 | _mmi.delT = _curr_imu_time - _prev_imu_time;
153 | }
154 |
155 | return;
156 | }
157 |
158 | /* @brief Callback for GNSS data */
159 | void UKF::gnssCallback(const sensor_msgs::NavSatFix::ConstPtr& fix)
160 | {
161 | //cout<<"In gnss callback"<latitude, fix->longitude, _init_utm.second, _init_utm.first, zone);
170 |
171 | //saving position of the vehicle when 1st GNSS reading is received
172 | _vp_at_first_gnss.first = _curr_state.vec(0);
173 | _vp_at_first_gnss.second = _curr_state.vec(1);
174 |
175 | cout<<"Pos at 1st gnss, x : "<<_curr_state.vec(0)<<", y : "<<_curr_state.vec(1)<latitude, fix->longitude, _curr_utm.second, _curr_utm.first, zone);
186 | }
187 |
188 | return;
189 | }
190 |
191 | /* @brief Initializes the Vehicle state to default values i.e., (0.0, 0.0, heading, 0.0, 0.0)
192 | where yaw is the initial yaw angle obtained from IMU when the program starts*/
193 | void UKF::initVehicleState(const double yaw)
194 | {
195 | cout<<"Initializing vehicle state with yaw = "< rot1(del_theta);
211 | Vector2d del_vec(_mmi.ax*_mmi.delT, _mmi.ay*_mmi.delT);
212 | propagated_vec(2) = vec(2) + del_theta; //updating yaw
213 | //bounding yaw between -PI and PI
214 | if(propagated_vec(2) > M_PI)
215 | propagated_vec(2) = propagated_vec(2) - 2*M_PI;
216 | else if(propagated_vec(2) <= -M_PI)
217 | propagated_vec(2) = propagated_vec(2) + 2*M_PI;
218 |
219 | /* Transforming velocities in previous base_link frame to current one and add acc*delT */
220 | propagated_vec.segment(3,2) = rot1.toRotationMatrix()*vec.segment(3,2) + del_vec;
221 |
222 | /*
223 | This might look like using predicted velocity rather than velocity at previous time instance, but it is
224 | done to reduce computations, using v=u+at, x=ut+0.5at^2 is same as y=vt-0.5at^2
225 | */
226 | del_vec(0) = propagated_vec(3)*_mmi.delT - _mmi.ax*pow(_mmi.delT,2);
227 | del_vec(1) = propagated_vec(4)*_mmi.delT - _mmi.ay*pow(_mmi.delT,2);
228 | Rotation2D rot2(propagated_vec(2));
229 | propagated_vec.segment(0,2) = rot2.inverse().toRotationMatrix()*del_vec + vec.segment(0,2);
230 |
231 | return propagated_vec;
232 | }
233 |
234 | /* @brief The prediction step */
235 | void UKF::predict()
236 | {
237 | //cout<<"In prediction step"< del_state_mat = _propagated_sigma_pt_mat.colwise() - _predicted_state.vec;
256 |
257 | _predicted_state.cov_mat = (-2.0/3.0)*(del_state_mat.col(0))*(del_state_mat.col(0).transpose());
258 | for(int i=1;i<11;i++)
259 | _predicted_state.cov_mat += (1.0/6.0)*(del_state_mat.col(i))*(del_state_mat.col(i).transpose());
260 |
261 | _predicted_state.cov_mat += _process_noise_mat; //adding process noise covariance
262 |
263 | return;
264 | }
265 |
266 | /* @brief The correction step */
267 | void UKF::correct(const double x, const double y)
268 | {
269 | //cout<<"In correction step"< predicted_meas_mat = _propagated_sigma_pt_mat.block<2,11>(0,0);
272 | Vector2d predicted_meas_vec(_predicted_state.vec(0), _predicted_state.vec(1)); //since measurement is the position itself
273 | Matrix del_meas_mat = predicted_meas_mat.colwise() - predicted_meas_vec;
274 | Matrix del_state_mat = _propagated_sigma_pt_mat.colwise() - _predicted_state.vec;
275 |
276 | Matrix2d predicted_cov_mat;
277 | Matrix cross_cov_mat; //cross covariance matrix
278 |
279 | predicted_cov_mat = (-2.0/3.0)*del_meas_mat.col(0)*(del_meas_mat.col(0).transpose());
280 | cross_cov_mat = (-2.0/3.0)*del_state_mat.col(0)*(del_meas_mat.col(0).transpose());
281 | for(int i=1;i<11;i++)
282 | {
283 | predicted_cov_mat += (1.0/6.0)*del_meas_mat.col(i)*(del_meas_mat.col(i).transpose());
284 | cross_cov_mat += (1.0/6.0)*del_state_mat.col(i)*(del_meas_mat.col(i).transpose());
285 | }
286 |
287 | predicted_cov_mat += _meas_noise_mat; //adding measurement noise covariance
288 |
289 | Matrix kalman_gain = cross_cov_mat*predicted_cov_mat.inverse();
290 |
291 | Vector2d meas_vec(x,y); //actual measurements
292 |
293 | _curr_state.vec = _predicted_state.vec + kalman_gain*(meas_vec - predicted_meas_vec);
294 | _curr_state.cov_mat = _predicted_state.cov_mat - kalman_gain*predicted_cov_mat*(kalman_gain.transpose());
295 |
296 | return;
297 | }
298 |
299 | /* @brief Publishes the vehicle state as a nav_msgs::Odometry message*/
300 | void UKF::publishVehicleState()
301 | {
302 | //cout<<"Publishing vehicle state"<