参考博文链接:vins-fusion代码解读
VINS-Fusion/vins-estimator/rosNodeTest.cpp
程序顺序解读
首先,定义了几个全局变量:
1.Estimator estimator;
自动执行该类的构造函数,其中两条重要的语句如下:
initThreadFlag = false;
clearState();
本质上不仅清除了状态,而且为诸多变量赋初值。
(这里std::mutex
似乎和C++11 的并发和多线程编程有关,留待后续了解)
2.queue
然后,进入了main()
函数:
readParameters(config_file);
estimator.setParameter();
读取参数并配置参数
.yaml
文件中,通过parameters.h
文件中的readParameters()
函数(该文件中只有这一个函数),将该文件中声明的所有变量赋值(这些变量定义在parameters.cpp
文件中!且变量名全部大写,然后使用extern
修饰并放在parameters.h
中,可以想象当该.h
文件被其他文件#include
则可以直接使用这些值了),而estimator.setParameter()
是从parameters.h
文件中获得相应的配置参数。processThread = std::thread(&Estimator::processMeasurements, this);
开启了一个新线程。最后,订阅了几个topic
,分别是IMU_TOPIC
,两个相机图像IMAGE0_TOPIC, IMAGE1_TOPIC
,和feature_tracker
所提供的跟踪光流点?
同时开启了一个新线程sync_process
。??线程的作用:如果图像buffer里面有数据的话,读入数据并且添加到estimator中。为什么不在相机图像的回调函数就input,我的理解在于对于双目的话,能够检测同步问题,能够将同样时间戳的两帧图片同时放入estimator中。所以对于Imu以及feature直接在回调函数中进行添加。
这里有几句ros
的指令需要进行了解!至此,main()
函数就结束了,一直在此处循环处理。
registerPub(n);
ros::Subscriber sub_imu = n.subscribe(IMU_TOPIC, 2000, imu_callback, ros::TransportHints().tcpNoDelay());
ros::Subscriber sub_feature = n.subscribe("/feature_tracker/feature", 2000, feature_callback);
ros::Subscriber sub_img0 = n.subscribe(IMAGE0_TOPIC, 100, img0_callback);
ros::Subscriber sub_img1 = n.subscribe(IMAGE1_TOPIC, 100, img1_callback);
ros::Subscriber sub_restart = n.subscribe("/vins_restart", 100, restart_callback);
ros::Subscriber sub_imu_switch = n.subscribe("/vins_imu_switch", 100, imu_switch_callback);
ros::Subscriber sub_cam_switch = n.subscribe("/vins_cam_switch", 100, cam_switch_callback);
std::thread sync_thread{sync_process};
ros::spin();
下面,恶补一下关于ROS中的几行关键指令的含义:
roscpp是C++语言的ROS接口。直接调用它所提供的函数就可以实现topic、service等通信功能。(ROS为机器人开发者们提供了不同语言的编程接口,比如C++接口叫做roscpp,Python接口叫做rospy,Java接口叫做rosjava)
可以简单地把roscpp就当作一个C++的库,当我们创建一个工程,并在其中#include
,这样就可以使用ROS提供的函数了。
初始化节点
一个C++写的ROS程序,和普通的C++程序之间的区别:
1.1. 调用了ros::init()
函数,初始化节点的名称和其他信息(节点什么时候存在的?)
1.2. 创建ros::NodeHandle
对象,即节点的句柄;
关闭节点
直接在终端上ctrl+c
,(等价于调用ros::shutdown()
)
源代码里的节点初始化、关闭框架:
#include
int main(int argc, char** argv){
ros::init(argc, argv, "vins_estimator"); // node_name,初始化一个节点
ros::NodeHandle n("~"); // 获取节点的句柄,此处似乎有个命名空间的问题
// 节点功能
//
ros::spin(); // 用于触发topic, service的响应队列
return 0;
}
NodeHandle是node的句柄,用来对当前节点进行各种操作。
在ROS中,NodeHandle是一个定义好的类,我们可以创建这个类,并使用它的成员函数。它的成员函数有(按照用到的介绍)roscpp官方文档:
ros::Subscriber subscribe (const std::string &topic, uint32_t queue_size, void(*fp)(M), const TransportHints &transport_hints=TransportHints())
第一个参数是订阅话题的名称;
第二个参数是订阅队列的长度;(如果收到的消息都没来得及处理,那么新消息入队,旧消息就会出队);
第三个参数是回调函数的指针,指向回调函数来处理接收到的消息!
第四个参数:似乎与延迟有关系,暂时不关心。(该成员函数有13重载)
例如:
ros::Subscriber sub_imu = n.subscribe(IMU_TOPIC, 2000, imu_callback, ros::TransportHints().tcpNoDelay());
其中,std::string IMU_TOPIC;
是在parameters.cpp
中定义的一个参数。
ros::Subscriber sub_feature = n.subscribe("/feature_tracker/feature", 2000, feature_callback);
这几个回调函数,分别定义在了该main()
函数的开头位置。
简单看一下:
void imu_callback(const sensor_msgs::ImuConstPtr &imu_msg)
{
double t = imu_msg->header.stamp.toSec();
double dx = imu_msg->linear_acceleration.x;
double dy = imu_msg->linear_acceleration.y;
double dz = imu_msg->linear_acceleration.z;
double rx = imu_msg->angular_velocity.x;
double ry = imu_msg->angular_velocity.y;
double rz = imu_msg->angular_velocity.z;
Vector3d acc(dx, dy, dz);
Vector3d gyr(rx, ry, rz);
estimator.inputIMU(t, acc, gyr);
return;
}
不难看出,传感器输出的的原始数据,经过该函数进入到了程序中。实际上此处可以看作为一个消息接收节点??!
void Estimator::inputIMU(double t, const Vector3d &linearAcceleration, const Vector3d &angularVelocity)
{
mBuf.lock();
accBuf.push(make_pair(t, linearAcceleration));
gyrBuf.push(make_pair(t, angularVelocity));
//printf("input imu with time %f \n", t);
mBuf.unlock();
fastPredictIMU(t, linearAcceleration, angularVelocity); // 计算最新的PVQ
if (solver_flag == NON_LINEAR)
pubLatestOdometry(latest_P, latest_Q, latest_V, t); //此处会发布最新的信息
}
此处,消息稍加处理,然后又发布出去。关于最后一句pubLatestOdometry()
:
void pubLatestOdometry(const Eigen::Vector3d &P, const Eigen::Quaterniond &Q, const Eigen::Vector3d &V, double t)
{
nav_msgs::Odometry odometry; // 信息打包成约定格式
odometry.header.stamp = ros::Time(t);
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 = Q.x();
odometry.pose.pose.orientation.y = Q.y();
odometry.pose.pose.orientation.z = Q.z();
odometry.pose.pose.orientation.w = 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); // 发布
}
其中,通常我们会把消息的收发的两端分成两个节点来写,一个节点就是一个完整的C++程序,此处是“发节点”
ros::Publisher pub_latest_odometry;
pub_latest_odometry = n.advertise
nav_msgs::Odometry odometry;
pub_latest_odometry.publish(odometry);
感叹,ROS的架构真的很清晰!!
图像的两个类似(很简单,这个回调函数仅仅接收保存数据而已):
void img0_callback(const sensor_msgs::ImageConstPtr &img_msg)
{
m_buf.lock();
img0_buf.push(img_msg);
m_buf.unlock();
}
void img1_callback(const sensor_msgs::ImageConstPtr &img_msg)
{
m_buf.lock();
img1_buf.push(img_msg);
m_buf.unlock();
}
注意,这个收节点有些不同,前面三个收节点接收的都是原始传感器的数据,而此处的收节点接收的是已经经过处理的信息sensor_msgs::PointCloudConstPtr
。那么是如何从原始数据到处理好的信息的呢?怎么继续执行下去呢?
void feature_callback(const sensor_msgs::PointCloudConstPtr &feature_msg)
{
map>>> featureFrame;
for (unsigned int i = 0; i < feature_msg->points.size(); i++)
{
int feature_id = feature_msg->channels[0].values[i];
int camera_id = feature_msg->channels[1].values[i];
double x = feature_msg->points[i].x;
double y = feature_msg->points[i].y;
double z = feature_msg->points[i].z;
double p_u = feature_msg->channels[2].values[i];
double p_v = feature_msg->channels[3].values[i];
double velocity_x = feature_msg->channels[4].values[i];
double velocity_y = feature_msg->channels[5].values[i];
if(feature_msg->channels.size() > 5)
{
double gx = feature_msg->channels[6].values[i];
double gy = feature_msg->channels[7].values[i];
double gz = feature_msg->channels[8].values[i];
pts_gt[feature_id] = Eigen::Vector3d(gx, gy, gz);
//printf("receive pts gt %d %f %f %f\n", feature_id, gx, gy, gz);
}
ROS_ASSERT(z == 1);
Eigen::Matrix xyz_uv_velocity;
xyz_uv_velocity << x, y, z, p_u, p_v, velocity_x, velocity_y;
featureFrame[feature_id].emplace_back(camera_id, xyz_uv_velocity);
}
double t = feature_msg->header.stamp.toSec();
estimator.inputFeature(t, featureFrame);
return;
}