|
@@ -11,6 +11,10 @@
|
|
|
|
|
|
#include <glog/logging.h>
|
|
#include <glog/logging.h>
|
|
|
|
|
|
|
|
+#include <geometry_msgs/TransformStamped.h>
|
|
|
|
+#include <geometry_msgs/PoseWithCovarianceStamped.h>
|
|
|
|
+
|
|
|
|
+
|
|
hdl_backend* instance = nullptr; // 初始化静态单例(分配内存, 调用hdl_backend 的默认构造函数)
|
|
hdl_backend* instance = nullptr; // 初始化静态单例(分配内存, 调用hdl_backend 的默认构造函数)
|
|
// hdl_backend& hdl_opt = hdl_backend::getInstance(); // 因为 静态成员变量 instance 是私有的,需要 静态成员函数 getInstance() 拿到 instance, 并且起个别名(引用)
|
|
// hdl_backend& hdl_opt = hdl_backend::getInstance(); // 因为 静态成员变量 instance 是私有的,需要 静态成员函数 getInstance() 拿到 instance, 并且起个别名(引用)
|
|
|
|
|
|
@@ -34,7 +38,7 @@ void hdl_backend::init() {
|
|
max_keyframes_per_update = 10;
|
|
max_keyframes_per_update = 10;
|
|
keyframe_updater.reset(new hdl_graph_slam::KeyframeUpdater(nh));
|
|
keyframe_updater.reset(new hdl_graph_slam::KeyframeUpdater(nh));
|
|
points_topic = "/velodyne_points";
|
|
points_topic = "/velodyne_points";
|
|
- graph_slam.reset(new hdl_graph_slam::GraphSLAM("lm_var")); // 智能指针,用reset重新初始化,让它重新指向新分配的对象
|
|
|
|
|
|
+ graph_slam.reset(new hdl_graph_slam::GraphSLAM("lm_var_cholmod")); // 智能指针,用reset重新初始化,让它重新指向新分配的对象
|
|
// graph_slam.reset(new GraphSLAM(private_nh.param<std::string>("g2o_solver_type", "lm_var"))); // 智能指针,用reset重新初始化,让它重新指向新分配的对象
|
|
// graph_slam.reset(new GraphSLAM(private_nh.param<std::string>("g2o_solver_type", "lm_var"))); // 智能指针,用reset重新初始化,让它重新指向新分配的对象
|
|
|
|
|
|
auto voxelgrid = new pcl::VoxelGrid<PointT>();
|
|
auto voxelgrid = new pcl::VoxelGrid<PointT>();
|
|
@@ -48,7 +52,9 @@ void hdl_backend::init() {
|
|
anchor_edge = nullptr;
|
|
anchor_edge = nullptr;
|
|
odom_sub.reset(new message_filters::Subscriber<nav_msgs::Odometry>(nh, "/laser_odom", 256)); // 创建 message_filters::Subscriber 对象,用于订阅nav_msgs::Odometry类型的消息
|
|
odom_sub.reset(new message_filters::Subscriber<nav_msgs::Odometry>(nh, "/laser_odom", 256)); // 创建 message_filters::Subscriber 对象,用于订阅nav_msgs::Odometry类型的消息
|
|
// cloud_sub.reset(new message_filters::Subscriber<sensor_msgs::PointCloud2>(nh, "/keyframe_points", 32)); // 创建 message_filters::Subscriber 对象,用于订阅sensor_msgs::PointCloud2类型的消息。
|
|
// cloud_sub.reset(new message_filters::Subscriber<sensor_msgs::PointCloud2>(nh, "/keyframe_points", 32)); // 创建 message_filters::Subscriber 对象,用于订阅sensor_msgs::PointCloud2类型的消息。
|
|
- cloud_sub.reset(new message_filters::Subscriber<sensor_msgs::PointCloud2>(nh, "/filtered_points", 32)); // 创建 message_filters::Subscriber 对象,用于订阅sensor_msgs::PointCloud2类型的消息。
|
|
|
|
|
|
+ // cloud_sub.reset(new message_filters::Subscriber<sensor_msgs::PointCloud2>(nh, "/velodyne_points", 32)); // 三维
|
|
|
|
+ cloud_sub.reset(new message_filters::Subscriber<sensor_msgs::PointCloud2>(nh, "/filtered_points", 32)); // 二维
|
|
|
|
+
|
|
// 现在的 /filtered_points 是原始点云数据,没有做坐标转换, 滤波, 将采样等
|
|
// 现在的 /filtered_points 是原始点云数据,没有做坐标转换, 滤波, 将采样等
|
|
// keyframe_points 在前端做了降采样
|
|
// keyframe_points 在前端做了降采样
|
|
|
|
|
|
@@ -61,7 +67,8 @@ void hdl_backend::init() {
|
|
map_points_pub2 = nh.advertise<sensor_msgs::PointCloud2>("/hdl_graph_slam/map_points2", 1, true); // 这个话题会保留以前扫描过的点云!! 扫过的地方的点云就是
|
|
map_points_pub2 = nh.advertise<sensor_msgs::PointCloud2>("/hdl_graph_slam/map_points2", 1, true); // 这个话题会保留以前扫描过的点云!! 扫过的地方的点云就是
|
|
|
|
|
|
read_until_pub = nh.advertise<std_msgs::Header>("/hdl_graph_slam/read_until", 32); // 这种类型的消息通常用于指示读取操作应该持续到何时
|
|
read_until_pub = nh.advertise<std_msgs::Header>("/hdl_graph_slam/read_until", 32); // 这种类型的消息通常用于指示读取操作应该持续到何时
|
|
-
|
|
|
|
|
|
+ odom_pub = nh.advertise<geometry_msgs::PoseStamped>("/opt_odom", 32);
|
|
|
|
+
|
|
// save_map_service_server = nh.advertiseService("/hdl_graph_slam/save_map", &hdl_backend::save_map_service, this); // 保存地图
|
|
// save_map_service_server = nh.advertiseService("/hdl_graph_slam/save_map", &hdl_backend::save_map_service, this); // 保存地图
|
|
|
|
|
|
graph_updated = false;
|
|
graph_updated = false;
|
|
@@ -107,7 +114,7 @@ void hdl_backend::optimization_timer_callback() {
|
|
}
|
|
}
|
|
if(!keyframe_updated) {
|
|
if(!keyframe_updated) {
|
|
// 检查地板、GPS、IMU等队列,分别调用相应的 flush 函数(例如 flush_floor_queue、flush_gps_queue、flush_imu_queue),如果这些数据没有更新,则函数直接返回,不进行后续操作。
|
|
// 检查地板、GPS、IMU等队列,分别调用相应的 flush 函数(例如 flush_floor_queue、flush_gps_queue、flush_imu_queue),如果这些数据没有更新,则函数直接返回,不进行后续操作。
|
|
- // LOG(INFO) << "关键帧没有更新,退出 optimization_timer_callback 函数";
|
|
|
|
|
|
+ // LOG(INFO) << "关键帧没有更新,usleep(30000); & continue;";
|
|
// return;
|
|
// return;
|
|
usleep(30000);
|
|
usleep(30000);
|
|
continue;
|
|
continue;
|
|
@@ -122,10 +129,8 @@ void hdl_backend::optimization_timer_callback() {
|
|
Eigen::Isometry3d relpose(loop->relative_pose.cast<double>());
|
|
Eigen::Isometry3d relpose(loop->relative_pose.cast<double>());
|
|
Eigen::MatrixXd information_matrix = inf_calclator->calc_information_matrix(loop->key1->cloud, loop->key2->cloud, relpose);
|
|
Eigen::MatrixXd information_matrix = inf_calclator->calc_information_matrix(loop->key1->cloud, loop->key2->cloud, relpose);
|
|
auto edge = graph_slam->add_se3_edge(loop->key1->node, loop->key2->node, relpose, information_matrix); // 添加边,位姿之间的关系构成边(edge)
|
|
auto edge = graph_slam->add_se3_edge(loop->key1->node, loop->key2->node, relpose, information_matrix); // 添加边,位姿之间的关系构成边(edge)
|
|
- graph_slam->add_robust_kernel(edge, "Tukey", 0.05);
|
|
|
|
|
|
+ graph_slam->add_robust_kernel(edge, "Huber", 2);
|
|
// graph_slam->add_robust_kernel(edge, "NONE", 0.01); // Huber Cauchy Tukey None
|
|
// graph_slam->add_robust_kernel(edge, "NONE", 0.01); // Huber Cauchy Tukey None
|
|
-
|
|
|
|
- // loop_count++;
|
|
|
|
}
|
|
}
|
|
std::copy(new_keyframes.begin(), new_keyframes.end(), std::back_inserter(keyframes)); // 将新关键帧 new_keyframes 合并到已有的关键帧列表 keyframes 中
|
|
std::copy(new_keyframes.begin(), new_keyframes.end(), std::back_inserter(keyframes)); // 将新关键帧 new_keyframes 合并到已有的关键帧列表 keyframes 中
|
|
new_keyframes.clear(); // 清空 new_keyframes
|
|
new_keyframes.clear(); // 清空 new_keyframes
|
|
@@ -139,7 +144,7 @@ void hdl_backend::optimization_timer_callback() {
|
|
// 如果启用了自适应固定第一帧的功能(参数 "fix_first_node_adaptive"),则将第一个关键帧的锚点位置更新为当前估计的位置,以允许它自由移动但仍然保持在原点附近。
|
|
// 如果启用了自适应固定第一帧的功能(参数 "fix_first_node_adaptive"),则将第一个关键帧的锚点位置更新为当前估计的位置,以允许它自由移动但仍然保持在原点附近。
|
|
}
|
|
}
|
|
// optimize the pose graph
|
|
// optimize the pose graph
|
|
- int num_iterations = 10000000; // launch文件中都是设置成512
|
|
|
|
|
|
+ int num_iterations = 5000; // launch文件中都是设置成512
|
|
|
|
|
|
// for(int i = 0; i < keyframes.size(); i++){
|
|
// for(int i = 0; i < keyframes.size(); i++){
|
|
// // std::cout << "before optimize, odom[" << i << "] = \n" << keyframes[i]->odom.matrix() << std::endl;
|
|
// // std::cout << "before optimize, odom[" << i << "] = \n" << keyframes[i]->odom.matrix() << std::endl;
|
|
@@ -185,12 +190,42 @@ void hdl_backend::optimization_timer_callback() {
|
|
markers_pub.publish(markers);
|
|
markers_pub.publish(markers);
|
|
// LOG(INFO) << "markers_pub 发布完成";
|
|
// LOG(INFO) << "markers_pub 发布完成";
|
|
// }
|
|
// }
|
|
|
|
+ publishMatrixAsPose(odom_pub, keyframes[keyframes.size() - 1]->node->estimate().matrix().cast<float>());
|
|
|
|
+ // nav_msgs::Odometry
|
|
usleep(30000);
|
|
usleep(30000);
|
|
}
|
|
}
|
|
|
|
|
|
LOG(INFO) << "end()";
|
|
LOG(INFO) << "end()";
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+
|
|
|
|
+void hdl_backend::publishMatrixAsPose(ros::Publisher& publisher, const Eigen::Matrix4f& matrix) {
|
|
|
|
+ // 提取平移部分
|
|
|
|
+ Eigen::Vector3f translation = matrix.block<3, 1>(0, 3);
|
|
|
|
+
|
|
|
|
+ // 提取旋转部分
|
|
|
|
+ Eigen::Matrix3f rotation_matrix = matrix.block<3, 3>(0, 0);
|
|
|
|
+ Eigen::Quaternionf quaternion(rotation_matrix);
|
|
|
|
+
|
|
|
|
+ // 创建并填充 PoseStamped 消息
|
|
|
|
+ geometry_msgs::PoseStamped pose_msg;
|
|
|
|
+ pose_msg.header.stamp = ros::Time
|
|
|
|
+ ::now();
|
|
|
|
+ pose_msg.header.frame_id = "odom"; // 设置坐标系
|
|
|
|
+
|
|
|
|
+ pose_msg.pose.position.x = translation.x();
|
|
|
|
+ pose_msg.pose.position.y = translation.y();
|
|
|
|
+ pose_msg.pose.position.z = translation.z();
|
|
|
|
+ pose_msg.pose.orientation.x = quaternion.x();
|
|
|
|
+ pose_msg.pose.orientation.y = quaternion.y();
|
|
|
|
+ pose_msg.pose.orientation.z = quaternion.z();
|
|
|
|
+ pose_msg.pose.orientation.w = quaternion.w();
|
|
|
|
+
|
|
|
|
+ // 发布消息
|
|
|
|
+ publisher.publish(pose_msg);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* @brief generate map point cloud and publish it
|
|
* @brief generate map point cloud and publish it
|
|
* @param event
|
|
* @param event
|
|
@@ -198,7 +233,8 @@ void hdl_backend::optimization_timer_callback() {
|
|
void hdl_backend::map_points_publish_timer_callback(const ros::WallTimerEvent& event) {
|
|
void hdl_backend::map_points_publish_timer_callback(const ros::WallTimerEvent& event) {
|
|
// 源码中,发布是图优化完之后发布才有内容的
|
|
// 源码中,发布是图优化完之后发布才有内容的
|
|
// LOG(INFO) << graph_updated;
|
|
// LOG(INFO) << graph_updated;
|
|
- if(!map_points_pub.getNumSubscribers() || !graph_updated) {
|
|
|
|
|
|
+ if(!graph_updated) {
|
|
|
|
+ // if(!map_points_pub.getNumSubscribers() || !graph_updated) {
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -242,7 +278,6 @@ void hdl_backend::map_points_publish_timer_callback(const ros::WallTimerEvent& e
|
|
// LOG(INFO) << "map_points_publish_timer_callback 函数执行完毕";
|
|
// LOG(INFO) << "map_points_publish_timer_callback 函数执行完毕";
|
|
}
|
|
}
|
|
|
|
|
|
-
|
|
|
|
/**
|
|
/**
|
|
* @brief downsample a point cloud
|
|
* @brief downsample a point cloud
|
|
* @param cloud input cloud
|
|
* @param cloud input cloud
|
|
@@ -271,22 +306,12 @@ pcl::PointCloud<PointT>::ConstPtr hdl_backend::outlier_removal(const pcl::PointC
|
|
return filtered;
|
|
return filtered;
|
|
}
|
|
}
|
|
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-
|
|
|
|
/**
|
|
/**
|
|
* @brief 接收点云数据并放到 keyframe_queue 中
|
|
* @brief 接收点云数据并放到 keyframe_queue 中
|
|
* @param odom_msg 前端的激光里程计数据
|
|
* @param odom_msg 前端的激光里程计数据
|
|
* @param cloud_msg 前端滤波后的点云数据
|
|
* @param cloud_msg 前端滤波后的点云数据
|
|
*/
|
|
*/
|
|
void hdl_backend::cloud_callback(const nav_msgs::OdometryConstPtr& odom_msg, const sensor_msgs::PointCloud2::ConstPtr& cloud_msg) {
|
|
void hdl_backend::cloud_callback(const nav_msgs::OdometryConstPtr& odom_msg, const sensor_msgs::PointCloud2::ConstPtr& cloud_msg) {
|
|
-
|
|
|
|
- // // 点云下采样 & 滤波
|
|
|
|
- // pcl::PointCloud<PointT>::Ptr filtered(new pcl::PointCloud<PointT>());
|
|
|
|
- // downsample_filter->setInputCloud(cloud);
|
|
|
|
- // downsample_filter->filter(*filtered);
|
|
|
|
-
|
|
|
|
-
|
|
|
|
const ros::Time& stamp = cloud_msg->header.stamp; // 获取点云数据的时间戳
|
|
const ros::Time& stamp = cloud_msg->header.stamp; // 获取点云数据的时间戳
|
|
Eigen::Isometry3d odom = hdl_graph_slam::odom2isometry(odom_msg); // 将里程计消息转换为Eigen库中的Isometry3d类型,它表示一个3D刚体变换(包括旋转和平移)
|
|
Eigen::Isometry3d odom = hdl_graph_slam::odom2isometry(odom_msg); // 将里程计消息转换为Eigen库中的Isometry3d类型,它表示一个3D刚体变换(包括旋转和平移)
|
|
|
|
|
|
@@ -299,7 +324,6 @@ void hdl_backend::cloud_callback(const nav_msgs::OdometryConstPtr& odom_msg, con
|
|
auto filtered = downsample(cloud);
|
|
auto filtered = downsample(cloud);
|
|
filtered = outlier_removal(filtered);
|
|
filtered = outlier_removal(filtered);
|
|
|
|
|
|
-
|
|
|
|
// 更新关键帧判断
|
|
// 更新关键帧判断
|
|
if(!keyframe_updater->update(odom)) { // 根据当前的里程计信息 odom 判断是否需要生成新的关键帧
|
|
if(!keyframe_updater->update(odom)) { // 根据当前的里程计信息 odom 判断是否需要生成新的关键帧
|
|
// std::lock_guard<std::mutex> lock(keyframe_queue_mutex); // 这行代码的作用是确保线程安全,防止多个线程同时访问或修改 keyframe_queue 队列
|
|
// std::lock_guard<std::mutex> lock(keyframe_queue_mutex); // 这行代码的作用是确保线程安全,防止多个线程同时访问或修改 keyframe_queue 队列
|
|
@@ -362,19 +386,20 @@ bool hdl_backend::flush_keyframe_queue() {
|
|
|
|
|
|
// fix the first node 固定第一个节点,整段代码只运行一次
|
|
// fix the first node 固定第一个节点,整段代码只运行一次
|
|
if(keyframes.empty() && new_keyframes.size() == 1) {
|
|
if(keyframes.empty() && new_keyframes.size() == 1) {
|
|
- if(nh.param<bool>("fix_first_node", false)) {
|
|
|
|
- Eigen::MatrixXd inf = Eigen::MatrixXd::Identity(6, 6);
|
|
|
|
- std::stringstream sst(nh.param<std::string>("fix_first_node_stddev", "1 1 1 1 1 1"));
|
|
|
|
- for(int i = 0; i < 6; i++) {
|
|
|
|
- double stddev = 1.0;
|
|
|
|
- sst >> stddev;
|
|
|
|
- inf(i, i) = 1.0 / stddev;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- anchor_node = graph_slam->add_se3_node(Eigen::Isometry3d::Identity()); // 添加节点,在graph-based SLAM中,机器人的位姿是一个节点(node)
|
|
|
|
- anchor_node->setFixed(true);
|
|
|
|
- anchor_edge = graph_slam->add_se3_edge(anchor_node, keyframe->node, Eigen::Isometry3d::Identity(), inf); // 添加边,位姿之间的关系构成边(edge)
|
|
|
|
|
|
+ // if(nh.param<bool>("fix_first_node", false)) { // launch 中是 true
|
|
|
|
+ Eigen::MatrixXd inf = Eigen::MatrixXd::Identity(6, 6);
|
|
|
|
+ // std::stringstream sst(nh.param<std::string>("fix_first_node_stddev", "1 1 1 1 1 1")); // 这个是什么意思?
|
|
|
|
+ std::stringstream sst("10 10 10 1 1 1"); // 这个是什么意思?
|
|
|
|
+
|
|
|
|
+ for(int i = 0; i < 6; i++) {
|
|
|
|
+ double stddev = 1.0;
|
|
|
|
+ sst >> stddev;
|
|
|
|
+ inf(i, i) = 1.0 / stddev;
|
|
}
|
|
}
|
|
|
|
+ anchor_node = graph_slam->add_se3_node(Eigen::Isometry3d::Identity()); // 添加节点,在graph-based SLAM中,机器人的位姿是一个节点(node)
|
|
|
|
+ anchor_node->setFixed(true);
|
|
|
|
+ anchor_edge = graph_slam->add_se3_edge(anchor_node, keyframe->node, Eigen::Isometry3d::Identity(), inf); // 添加边,位姿之间的关系构成边(edge)
|
|
|
|
+ // }
|
|
}
|
|
}
|
|
|
|
|
|
if(i == 0 && keyframes.empty()) {
|
|
if(i == 0 && keyframes.empty()) {
|
|
@@ -389,6 +414,9 @@ bool hdl_backend::flush_keyframe_queue() {
|
|
auto edge = graph_slam->add_se3_edge(keyframe->node, prev_keyframe->node, relative_pose, information); // 添加边,位姿之间的关系构成边(edge)
|
|
auto edge = graph_slam->add_se3_edge(keyframe->node, prev_keyframe->node, relative_pose, information); // 添加边,位姿之间的关系构成边(edge)
|
|
graph_slam->add_robust_kernel(edge, "NONE", 1.0);
|
|
graph_slam->add_robust_kernel(edge, "NONE", 1.0);
|
|
// graph_slam->add_robust_kernel(edge, "Tukey", 0.1);
|
|
// graph_slam->add_robust_kernel(edge, "Tukey", 0.1);
|
|
|
|
+
|
|
|
|
+ std::cout << "keyframe->node = \n" << keyframe->node->estimate().matrix() << std::endl;
|
|
|
|
+ std::cout << "prev_keyframe->node = \n" << prev_keyframe->node->estimate().matrix() << std::endl;
|
|
}
|
|
}
|
|
|
|
|
|
std_msgs::Header read_until;
|
|
std_msgs::Header read_until;
|