在VINS-Mono代码阅读笔记(一):从Euroc数据集的运行开始 中我们已经初步知道了vins_estimator中订阅和发布的topic,那么,在研究vins_estimator模块的代码时,一个很有用的思路就是关注接收到的每一个topic是怎么进行处理的,发送的topic的message信息有哪些。把这些搞清楚了,这个模块的主要工作也就搞清楚了。
1.vins_estimator的main函数
main函数代码路径为:src/VINS-Mono/vins_estimator/src/estimator_node.cpp,该函数代码如下:
int main(int argc, char **argv)
{
//1.相关初始化
ros::init(argc, argv, "vins_estimator");
ros::NodeHandle n("~");
ros::console::set_logger_level(ROSCONSOLE_DEFAULT_NAME, ros::console::levels::Info);
//2.参数读取
readParameters(n);
//3.设置状态估计器的参数
estimator.setParameter();
#ifdef EIGEN_DONT_PARALLELIZE
ROS_DEBUG("EIGEN_DONT_PARALLELIZE");
#endif
ROS_WARN("waiting for image and imu...");
//4.注册发布器
registerPub(n);
/**
* ros::TransportHints().tcpNoDelay()允许指定hints到roscpp的传输层,这里使用没延迟的TCP。其实也可以使用UPD传输,
*/
//5.订阅topic
ros::Subscriber sub_imu = n.subscribe(IMU_TOPIC, 2000, imu_callback, ros::TransportHints().tcpNoDelay());
ros::Subscriber sub_image = n.subscribe("/feature_tracker/feature", 2000, feature_callback);
ros::Subscriber sub_restart = n.subscribe("/feature_tracker/restart", 2000, restart_callback);
ros::Subscriber sub_relo_points = n.subscribe("/pose_graph/match_points", 2000, relocalization_callback);
//6.创建process线程,这个是主线程
std::thread measurement_process{process};
ros::spin();
return 0;
}
可以看出,上边main函数的代码从逻辑功能上可以分为六部分:ROS相关初始化、参数读取、estimator中相关参数的设置、topic发布器注册、订阅topic、创建measurement_process线程。
下面看看以上代码中都具体做了什么。
2.estimator中的参数读取
main函数中readParameters函数中读取了配置文件中设置的相关参数。这个配置文件路径为:src/VINS-Mono/config/euroc/euroc_config.yaml,所以可以直接打开该文件来查看各个参数的值。
//读取参数
void readParameters(ros::NodeHandle &n)
{
std::string config_file;
config_file = readParam<std::string>(n, "config_file");
cv::FileStorage fsSettings(config_file, cv::FileStorage::READ);
if(!fsSettings.isOpened())
{
std::cerr << "ERROR: Wrong path to settings" << std::endl;
}
fsSettings["imu_topic"] >> IMU_TOPIC;
SOLVER_TIME = fsSettings["max_solver_time"];
NUM_ITERATIONS = fsSettings["max_num_iterations"];
MIN_PARALLAX = fsSettings["keyframe_parallax"];//10
MIN_PARALLAX = MIN_PARALLAX / FOCAL_LENGTH;//最小视差=最小视差/焦距=10.0/460.0
std::string OUTPUT_PATH;
fsSettings["output_path"] >> OUTPUT_PATH;
VINS_RESULT_PATH = OUTPUT_PATH + "/vins_result_no_loop.csv";
std::cout << "result path " << VINS_RESULT_PATH << std::endl;
// create folder if not exists
//创建输出目录:output_path: "/home/shaozu/output/"
FileSystemHelper::createDirectoryIfNotExists(OUTPUT_PATH.c_str());
std::ofstream fout(VINS_RESULT_PATH, std::ios::out);
fout.close();
ACC_N = fsSettings["acc_n"];
ACC_W = fsSettings["acc_w"];
GYR_N = fsSettings["gyr_n"];
GYR_W = fsSettings["gyr_w"];
G.z() = fsSettings["g_norm"];
ROW = fsSettings["image_height"];
COL = fsSettings["image_width"];
ROS_INFO("ROW: %f COL: %f ", ROW, COL);
//IMU和camera之间的外参
ESTIMATE_EXTRINSIC = fsSettings["estimate_extrinsic"];
if (ESTIMATE_EXTRINSIC == 2)
{
ROS_WARN("have no prior about extrinsic param, calibrate extrinsic param");
RIC.push_back(Eigen::Matrix3d::Identity());
TIC.push_back(Eigen::Vector3d::Zero());
EX_CALIB_RESULT_PATH = OUTPUT_PATH + "/extrinsic_parameter.csv";
}
else
{
if ( ESTIMATE_EXTRINSIC == 1)
{
ROS_WARN(" Optimize extrinsic param around initial guess!");
EX_CALIB_RESULT_PATH = OUTPUT_PATH + "/extrinsic_parameter.csv";
}
if (ESTIMATE_EXTRINSIC == 0)
ROS_WARN(" fix extrinsic param ");
cv::Mat cv_R, cv_T;
fsSettings["extrinsicRotation"] >> cv_R;
fsSettings["extrinsicTranslation"] >> cv_T;
Eigen::Matrix3d eigen_R;
Eigen::Vector3d eigen_T;
cv::cv2eigen(cv_R, eigen_R);
cv::cv2eigen(cv_T, eigen_T);
Eigen::Quaterniond Q(eigen_R);
eigen_R = Q.normalized();
RIC.push_back(eigen_R);
TIC.push_back(eigen_T);
ROS_INFO_STREAM("Extrinsic_R : " << std::endl << RIC[0]);
ROS_INFO_STREAM("Extrinsic_T : " << std::endl << TIC[0].transpose());
}
INIT_DEPTH = 5.0;
BIAS_ACC_THRESHOLD = 0.1;
BIAS_GYR_THRESHOLD = 0.1;
//获取TD,图像和IMU数据在时间上的偏移量,这里配置文件中为0.0
TD = fsSettings["td"];
ESTIMATE_TD = fsSettings["estimate_td"];
if (ESTIMATE_TD)
ROS_INFO_STREAM("Unsynchronized sensors, online estimate time offset, initial td: " << TD);
else
ROS_INFO_STREAM("Synchronized sensors, fix time offset: " << TD);
ROLLING_SHUTTER = fsSettings["rolling_shutter"];
if (ROLLING_SHUTTER)
{
TR = fsSettings["rolling_shutter_tr"];
ROS_INFO_STREAM("rolling shutter camera, read out time per line: " << TR);
}
else
{
TR = 0;
}
fsSettings.release();
}
3.estimator中的参数设置
setParameter代码如下:
void Estimator::setParameter()
{
//单目的情况下相机个数为1,所以NUM_OF_CAN值为1
for (int i = 0; i < NUM_OF_CAM; i++)
{
tic[i] = TIC[i];
ric[i] = RIC[i];
}
f_manager.setRic(ric);
//FOCAL_LENGTH=460
ProjectionFactor::sqrt_info = FOCAL_LENGTH / 1.5 * Matrix2d::Identity();
ProjectionTdFactor::sqrt_info = FOCAL_LENGTH / 1.5 * Matrix2d::Identity();
// 配置文件中td的解释:initial value of time offset. unit: s. readed image clock + td = real image clock (IMU clock)
td = TD;
}
4.创建estimator中的topic发布器
registerPub函数代码如下,estimator中要发布多少个topic这里就创建多少个topic发布器。
void registerPub(ros::NodeHandle &n)
{
pub_latest_odometry = n.advertise<nav_msgs::Odometry>("imu_propagate", 1000);
pub_path = n.advertise<nav_msgs::Path>("path", 1000);
pub_relo_path = n.advertise<nav_msgs::Path>("relocalization_path", 1000);
pub_odometry = n.advertise<nav_msgs::Odometry>("odometry", 1000);
pub_point_cloud = n.advertise<sensor_msgs::PointCloud>("point_cloud", 1000);
pub_margin_cloud = n.advertise<sensor_msgs::PointCloud>("history_cloud", 1000);
pub_key_poses = n.advertise<visualization_msgs::Marker>("key_poses", 1000);
pub_camera_pose = n.advertise<nav_msgs::Odometry>("camera_pose", 1000);
pub_camera_pose_visual = n.advertise<visualization_msgs::MarkerArray>("camera_pose_visual", 1000);
pub_keyframe_pose = n.advertise<nav_msgs::Odometry>("keyframe_pose", 1000);
pub_keyframe_point = n.advertise<sensor_msgs::PointCloud>("keyframe_point", 1000);
pub_extrinsic = n.advertise<nav_msgs::Odometry>("extrinsic", 1000);
pub_relo_relative_pose= n.advertise<nav_msgs::Odometry>("relo_relative_pose", 1000);
//可视化的相关设置
cameraposevisual.setScale(1);
cameraposevisual.setLineWidth(0.05);
keyframebasevisual.setScale(0.1);
keyframebasevisual.setLineWidth(0.01);
}
5.订阅topic,并处理相关消息
1)订阅IMU消息
ros::Subscriber sub_imu = n.subscribe(IMU_TOPIC, 2000, imu_callback, ros::TransportHints().tcpNoDelay());
imu订阅的回调函数为imu_callback,在imu_callback函数中对接收到的imu消息进行处理。
//imu回调函数,将imu_msg存入imu_buf,递推IMU的PQV并发布"imu_propagate”
void imu_callback(const sensor_msgs::ImuConstPtr &imu_msg)
{
//用时间戳来判断IMU message是否乱序
if (imu_msg->header.stamp.toSec() <= last_imu_t)
{
ROS_WARN("imu message in disorder!");
return;
}
last_imu_t = imu_msg->header.stamp.toSec();
//在修改多个线程共享的变量的时候要进行上锁,防止多个线程同时访问该变量
m_buf.lock();
imu_buf.push(imu_msg);//新来的imu_msg放入imu_buf队列当中
m_buf.unlock();
//这里调用notify_one唤醒的是process线程
con.notify_one();
last_imu_t = imu_msg->header.stamp.toSec();
{
std::lock_guard<std::mutex> lg(m_state);
//预测函数,这里推算的是tmp_P,tmp_Q,tmp_V
predict(imu_msg);
std_msgs::Header header = imu_msg->header;
header.frame_id = "world";
//发布imu_propagate topic
if (estimator.solver_flag == Estimator::SolverFlag::NON_LINEAR)
pubLatestOdometry(tmp_P, tmp_Q, tmp_V, header);
}
}
imu_callback中调用的predict函数代码如下:
//从IMU测量值imu_msg和上一个PVQ递推得到当前PVQ
void predict(const sensor_msgs::ImuConstPtr &imu_msg)
{
double t = imu_msg->header.stamp.toSec();
//init_imu初始化的值为1
if (init_imu)
{
latest_time = t;
init_imu = 0;
return;
}
//计算当前imu_msg距离上一个imu_msg的时间间隔
double dt = t - latest_time;
latest_time = t;
//获取x,y,z三个方向上的线加速度
double dx = imu_msg->linear_acceleration.x;
double dy = imu_msg->linear_acceleration.y;
double dz = imu_msg->linear_acceleration.z;
Eigen::Vector3d linear_acceleration{dx, dy, dz};
//获取x,y,z三个方向上的角速度
double rx = imu_msg->angular_velocity.x;
double ry = imu_msg->angular_velocity.y;
double rz = imu_msg->angular_velocity.z;
Eigen::Vector3d angular_velocity{rx, ry, rz};
Eigen::Vector3d un_acc_0 = tmp_Q * (acc_0 - tmp_Ba) - estimator.g;
Eigen::Vector3d un_gyr = 0.5 * (gyr_0 + angular_velocity) - tmp_Bg;
tmp_Q = tmp_Q * Utility::deltaQ(un_gyr * dt);
Eigen::Vector3d un_acc_1 = tmp_Q * (linear_acceleration - tmp_Ba) - estimator.g;
Eigen::Vector3d un_acc = 0.5 * (un_acc_0 + un_acc_1);
//利用位移公式计算位移
tmp_P = tmp_P + dt * tmp_V + 0.5 * dt * dt * un_acc;
//加速度情况下速度计算
tmp_V = tmp_V + dt * un_acc;
//线加速度
acc_0 = linear_acceleration;
//角速度
gyr_0 = angular_velocity;
}
imu_callback中调用的pubLatestOdometry函数如下,该函数中主要是发布predict中计算出来的PVQ信息。
/**
* 发布最新的由imu直接递推得到的PQV
*/
void pubLatestOdometry(const Eigen::Vector3d &P, const Eigen::Quaterniond &Q, const Eigen::Vector3d &V, const std_msgs::Header &header)
{
Eigen::Quaterniond quadrotor_Q = Q ;
nav_msgs::Odometry odometry;
odometry.header = header;
odometry.header.frame_id = "world";
odometry.pose.pose.position.x = P.x();
odometry.pose.pose.position.y = P.y();
odometry.pose.pose.position.z = P.z();
odometry.pose.pose.orientation.x = quadrotor_Q.x();
odometry.pose.pose.orientation.y = quadrotor_Q.y();
odometry.pose.pose.orientation.z = quadrotor_Q.z();
odometry.pose.pose.orientation.w = quadrotor_Q.w();
odometry.twist.twist.linear.x = V.x();
odometry.twist.twist.linear.y = V.y();
odometry.twist.twist.linear.z = V.z();
pub_latest_odometry.publish(odometry);
}
2)订阅feature消息
ros::Subscriber sub_image = n.subscribe("/feature_tracker/feature", 2000, feature_callback);
回调函数feature_callback函数代码如下:
//feature回调函数,将feature_msg放入feature_buf
void feature_callback(const sensor_msgs::PointCloudConstPtr &feature_msg)
{
//如果是第一个检测到的特征则直接忽略掉,这里直接return了二没有将该feature加入到feature_buf中
if (!init_feature)
{
//skip the first detected feature, which doesn't contain optical flow speed
init_feature = 1;
return;
}
m_buf.lock();
feature_buf.push(feature_msg);
m_buf.unlock();
//唤醒process线程,处理feature_buf中的
con.notify_one();
}
3)订阅restart消息
ros::Subscriber sub_restart = n.subscribe("/feature_tracker/restart", 2000, restart_callback);
回调函数restart_callback函数代码如下,这里的restart消息处理中主要是将estimator中的相关参数和存储数据的buf清除掉。
//restart回调函数,收到restart消息时清空feature_buf和imu_buf,估计器重置,时间重置
void restart_callback(const std_msgs::BoolConstPtr &restart_msg)
{
if (restart_msg->data == true)
{
ROS_WARN("restart the estimator!");
//重启estimator的时候需要先上锁将feature_buf、imu_buf都清空,
m_buf.lock();
while(!feature_buf.empty())
feature_buf.pop();
while(!imu_buf.empty())
imu_buf.pop();
m_buf.unlock();
m_estimator.lock();
estimator.clearState();
estimator.setParameter();
m_estimator.unlock();
current_time = -1;
last_imu_t = 0;
}
return;
}
4)订阅match_points消息
ros::Subscriber sub_relo_points = n.subscribe("/pose_graph/match_points", 2000, relocalization_callback);
回调函数relocalization_callback代码如下,该函数对于接收到的匹配的地图点信息存放在relo_buf中,为后边的重定位提供数据支持。
//relocalization回调函数,将points_msg放入relo_buf
void relocalization_callback(const sensor_msgs::PointCloudConstPtr &points_msg)
{
//printf("relocalization callback! \n");
m_buf.lock();
relo_buf.push(points_msg);
m_buf.unlock();
}
6.创建measurement_process线程
std::thread measurement_process{process};
在这里创建了measurement_process线程,线程的入口函数为process,这个线程也就运行了起来。该线程为estimator中最重要的逻辑处理函数,IMU预计分和图像处理等重要的操作都是在该线程中实现的。
由于process函数中的内容太多了,对process的代码分析放到下一篇博客中进行介绍。