|
@@ -70,12 +70,13 @@ public:
|
|
|
HdlGraphSlamNodelet() {}
|
|
|
virtual ~HdlGraphSlamNodelet() {}
|
|
|
|
|
|
- virtual void onInit() {
|
|
|
+ virtual void onInit() { // 初始化函数
|
|
|
+ // 申请节点句柄
|
|
|
nh = getNodeHandle();
|
|
|
mt_nh = getMTNodeHandle();
|
|
|
private_nh = getPrivateNodeHandle();
|
|
|
|
|
|
- // init parameters
|
|
|
+ // init parameters 初始化参数
|
|
|
published_odom_topic = private_nh.param<std::string>("published_odom_topic", "/odom");
|
|
|
map_frame_id = private_nh.param<std::string>("map_frame_id", "map");
|
|
|
odom_frame_id = private_nh.param<std::string>("odom_frame_id", "odom");
|
|
@@ -84,80 +85,100 @@ public:
|
|
|
|
|
|
max_keyframes_per_update = private_nh.param<int>("max_keyframes_per_update", 10);
|
|
|
|
|
|
- //
|
|
|
- anchor_node = nullptr;
|
|
|
+ // 一些指针的初始化
|
|
|
+ anchor_node = nullptr; // 普通指针,直接给 nullptr
|
|
|
anchor_edge = nullptr;
|
|
|
floor_plane_node = nullptr;
|
|
|
- graph_slam.reset(new GraphSLAM(private_nh.param<std::string>("g2o_solver_type", "lm_var")));
|
|
|
+ graph_slam.reset(new GraphSLAM(private_nh.param<std::string>("g2o_solver_type", "lm_var"))); // 智能指针,用reset重新初始化,让它重新指向新分配的对象
|
|
|
keyframe_updater.reset(new KeyframeUpdater(private_nh));
|
|
|
loop_detector.reset(new LoopDetector(private_nh));
|
|
|
map_cloud_generator.reset(new MapCloudGenerator());
|
|
|
inf_calclator.reset(new InformationMatrixCalculator(private_nh));
|
|
|
nmea_parser.reset(new NmeaSentenceParser());
|
|
|
|
|
|
- gps_time_offset = private_nh.param<double>("gps_time_offset", 0.0);
|
|
|
- gps_edge_stddev_xy = private_nh.param<double>("gps_edge_stddev_xy", 10000.0);
|
|
|
- gps_edge_stddev_z = private_nh.param<double>("gps_edge_stddev_z", 10.0);
|
|
|
- floor_edge_stddev = private_nh.param<double>("floor_edge_stddev", 10.0);
|
|
|
+ // 从参数服务器中读取参数
|
|
|
+ // 第一个参数是参数服务器中的变量名,第二个参数是如果参数服务器中没有这个变量的默认取值
|
|
|
+ gps_time_offset = private_nh.param<double>("gps_time_offset", 0.0); // GPS时间戳与SLAM系统内部时间戳之间的偏移量,用于同步GPS数据和SLAM系统的时间
|
|
|
+ gps_edge_stddev_xy = private_nh.param<double>("gps_edge_stddev_xy", 10000.0); // GPS测量在XY平面(水平方向)的标准差,用于量化GPS测量的不确定性,通常用于优化算法中权重的计算
|
|
|
+ gps_edge_stddev_z = private_nh.param<double>("gps_edge_stddev_z", 10.0); // GPS测量在Z轴(垂直方向)的标准差,用于量化GPS测量的不确定性
|
|
|
+ floor_edge_stddev = private_nh.param<double>("floor_edge_stddev", 10.0); // 与地面相关的测量或估计的标准差,例如在检测到地面时用于优化算法
|
|
|
|
|
|
- imu_time_offset = private_nh.param<double>("imu_time_offset", 0.0);
|
|
|
- enable_imu_orientation = private_nh.param<bool>("enable_imu_orientation", false);
|
|
|
- enable_imu_acceleration = private_nh.param<bool>("enable_imu_acceleration", false);
|
|
|
- imu_orientation_edge_stddev = private_nh.param<double>("imu_orientation_edge_stddev", 0.1);
|
|
|
- imu_acceleration_edge_stddev = private_nh.param<double>("imu_acceleration_edge_stddev", 3.0);
|
|
|
+ imu_time_offset = private_nh.param<double>("imu_time_offset", 0.0); // IMU(惯性测量单元)时间戳与SLAM系统内部时间戳之间的偏移量,用于同步IMU数据和SLAM系统的时间
|
|
|
+ enable_imu_orientation = private_nh.param<bool>("enable_imu_orientation", false); // 指示是否启用IMU的方位数据
|
|
|
+ enable_imu_acceleration = private_nh.param<bool>("enable_imu_acceleration", false); // 指示是否启用IMU的加速度数据
|
|
|
+ imu_orientation_edge_stddev = private_nh.param<double>("imu_orientation_edge_stddev", 0.1); // IMU方位测量的标准差,用于量化IMU方位测量的不确定性
|
|
|
+ imu_acceleration_edge_stddev = private_nh.param<double>("imu_acceleration_edge_stddev", 3.0); // IMU加速度测量的标准差,用于量化IMU加速度测量的不确定性
|
|
|
|
|
|
- points_topic = private_nh.param<std::string>("points_topic", "/velodyne_points");
|
|
|
+ points_topic = private_nh.param<std::string>("points_topic", "/velodyne_points"); // 点云数据的ROS话题名称,SLAM系统将订阅这个话题来获取点云数据
|
|
|
|
|
|
// subscribers
|
|
|
- odom_sub.reset(new message_filters::Subscriber<nav_msgs::Odometry>(mt_nh, published_odom_topic, 256));
|
|
|
- cloud_sub.reset(new message_filters::Subscriber<sensor_msgs::PointCloud2>(mt_nh, "/filtered_points", 32));
|
|
|
- sync.reset(new message_filters::Synchronizer<ApproxSyncPolicy>(ApproxSyncPolicy(32), *odom_sub, *cloud_sub));
|
|
|
- sync->registerCallback(boost::bind(&HdlGraphSlamNodelet::cloud_callback, this, _1, _2));
|
|
|
- imu_sub = nh.subscribe("/gpsimu_driver/imu_data", 1024, &HdlGraphSlamNodelet::imu_callback, this);
|
|
|
- floor_sub = nh.subscribe("/floor_detection/floor_coeffs", 1024, &HdlGraphSlamNodelet::floor_coeffs_callback, this);
|
|
|
-
|
|
|
- if(private_nh.param<bool>("enable_gps", true)) {
|
|
|
- gps_sub = mt_nh.subscribe("/gps/geopoint", 1024, &HdlGraphSlamNodelet::gps_callback, this);
|
|
|
- nmea_sub = mt_nh.subscribe("/gpsimu_driver/nmea_sentence", 1024, &HdlGraphSlamNodelet::nmea_callback, this);
|
|
|
- navsat_sub = mt_nh.subscribe("/gps/navsat", 1024, &HdlGraphSlamNodelet::navsat_callback, this);
|
|
|
+ // 话题通信的接受方
|
|
|
+ odom_sub.reset(new message_filters::Subscriber<nav_msgs::Odometry>(mt_nh, published_odom_topic, 256)); // 创建一个新的message_filters::Subscriber对象,用于订阅nav_msgs::Odometry类型的消息
|
|
|
+ cloud_sub.reset(new message_filters::Subscriber<sensor_msgs::PointCloud2>(mt_nh, "/filtered_points", 32)); // 创建一个新的message_filters::Subscriber对象,用于订阅sensor_msgs::PointCloud2类型的消息。
|
|
|
+ // ApproxSyncPolicy是一个同步策略,允许在一定时间窗口内的消息进行同步,这里设置的窗口大小为32
|
|
|
+ sync.reset(new message_filters::Synchronizer<ApproxSyncPolicy>(ApproxSyncPolicy(32), *odom_sub, *cloud_sub)); // 创建一个新的message_filters::Synchronizer对象,用于同步上面创建的两个订阅者(odom_sub和cloud_sub)的消息。
|
|
|
+ sync->registerCallback(boost::bind(&HdlGraphSlamNodelet::cloud_callback, this, _1, _2)); // 为同步器注册了一个回调函数,当同步条件满足时,会调用HdlGraphSlamNodelet类的cloud_callback成员函数
|
|
|
+ // 调用 cloud_callback 函数的条件:
|
|
|
+ // 消息同步:odom_sub 和 cloud_sub 两个订阅者各自收到消息后,sync 会根据 ApproximateTime 策略尝试同步它们的时间戳。
|
|
|
+ // 时间戳接近:两个消息的时间戳不需要完全一致,只要在一定范围内(由 ApproximateTime 策略自动决定),就会被认为是同步的。
|
|
|
+ // 队列不为空:odom_sub 和 cloud_sub 的消息队列必须都各自至少包含一条消息,以便同步器能进行匹配。
|
|
|
+ // 队列中找到匹配的消息对:当订阅者的队列中找到符合同步条件的消息对(即时间戳接近的 Odometry 和 PointCloud2 消息),就会触发 cloud_callback。
|
|
|
+
|
|
|
+ imu_sub = nh.subscribe("/gpsimu_driver/imu_data", 1024, &HdlGraphSlamNodelet::imu_callback, this); // 接受并处理 imu 数据(小车没有imu,所以这个回调函数可以不看)
|
|
|
+ floor_sub = nh.subscribe("/floor_detection/floor_coeffs", 1024, &HdlGraphSlamNodelet::floor_coeffs_callback, this); // 接受并处理 地板检测 数据(小车没有地板检测,所以这个回调函数可以不看)
|
|
|
+
|
|
|
+ if(private_nh.param<bool>("enable_gps", true)) { // !!!因为我们的小车没有GPS,所以这几个 callback 函数可以不看 !!!
|
|
|
+ gps_sub = mt_nh.subscribe("/gps/geopoint", 1024, &HdlGraphSlamNodelet::gps_callback, this); // 接受并处理 GPS 数据
|
|
|
+ nmea_sub = mt_nh.subscribe("/gpsimu_driver/nmea_sentence", 1024, &HdlGraphSlamNodelet::nmea_callback, this); //
|
|
|
+ navsat_sub = mt_nh.subscribe("/gps/navsat", 1024, &HdlGraphSlamNodelet::navsat_callback, this); //
|
|
|
}
|
|
|
|
|
|
// publishers
|
|
|
- markers_pub = mt_nh.advertise<visualization_msgs::MarkerArray>("/hdl_graph_slam/markers", 16);
|
|
|
- odom2map_pub = mt_nh.advertise<geometry_msgs::TransformStamped>("/hdl_graph_slam/odom2pub", 16);
|
|
|
- map_points_pub = mt_nh.advertise<sensor_msgs::PointCloud2>("/hdl_graph_slam/map_points", 1, true);
|
|
|
- read_until_pub = mt_nh.advertise<std_msgs::Header>("/hdl_graph_slam/read_until", 32);
|
|
|
-
|
|
|
- load_service_server = mt_nh.advertiseService("/hdl_graph_slam/load", &HdlGraphSlamNodelet::load_service, this);
|
|
|
- dump_service_server = mt_nh.advertiseService("/hdl_graph_slam/dump", &HdlGraphSlamNodelet::dump_service, this);
|
|
|
+ // 话题通信的发布方
|
|
|
+ markers_pub = mt_nh.advertise<visualization_msgs::MarkerArray>("/hdl_graph_slam/markers", 16); // 发布可视化数据(具体是什么还不知道) 这种类型的消息通常用于在RViz等可视化工具中显示标记(markers)
|
|
|
+ odom2map_pub = mt_nh.advertise<geometry_msgs::TransformStamped>("/hdl_graph_slam/odom2pub", 16); // 发布从里程计到地图的变换
|
|
|
+ map_points_pub = mt_nh.advertise<sensor_msgs::PointCloud2>("/hdl_graph_slam/map_points", 1, true); //
|
|
|
+ read_until_pub = mt_nh.advertise<std_msgs::Header>("/hdl_graph_slam/read_until", 32); // 这种类型的消息通常用于指示读取操作应该持续到何时
|
|
|
+
|
|
|
+ // 服务通信:一个节点(客户端)发送一个服务请求给服务服务器,服务服务器处理这个请求并返回一个响应。这是一种同步的、请求/响应模式的通信方式。
|
|
|
+ load_service_server = mt_nh.advertiseService("/hdl_graph_slam/load", &HdlGraphSlamNodelet::load_service, this); // 创建了一个服务服务器load_service_server,用于处理名为"/hdl_graph_slam/load"的服务请求,服务的处理函数是HdlGraphSlamNodelet类的load_service成员函数
|
|
|
+ dump_service_server = mt_nh.advertiseService("/hdl_graph_slam/dump", &HdlGraphSlamNodelet::dump_service, this); // 创建了一个服务服务器dump_service_server,用于处理名为"/hdl_graph_slam/dump"的服务请求,服务的处理函数是HdlGraphSlamNodelet类的dump_service成员函数
|
|
|
save_map_service_server = mt_nh.advertiseService("/hdl_graph_slam/save_map", &HdlGraphSlamNodelet::save_map_service, this);
|
|
|
|
|
|
- graph_updated = false;
|
|
|
- double graph_update_interval = private_nh.param<double>("graph_update_interval", 3.0);
|
|
|
- double map_cloud_update_interval = private_nh.param<double>("map_cloud_update_interval", 10.0);
|
|
|
- optimization_timer = mt_nh.createWallTimer(ros::WallDuration(graph_update_interval), &HdlGraphSlamNodelet::optimization_timer_callback, this);
|
|
|
- map_publish_timer = mt_nh.createWallTimer(ros::WallDuration(map_cloud_update_interval), &HdlGraphSlamNodelet::map_points_publish_timer_callback, this);
|
|
|
+ graph_updated = false; // 用于跟踪图优化是否已经更新
|
|
|
+ double graph_update_interval = private_nh.param<double>("graph_update_interval", 3.0); // 表示图优化更新的时间间隔,默认值为3.0秒 interval:间隔
|
|
|
+ double map_cloud_update_interval = private_nh.param<double>("map_cloud_update_interval", 10.0); // 地图点云更新的时间间隔,默认值为10.0秒
|
|
|
+ optimization_timer = mt_nh.createWallTimer(ros::WallDuration(graph_update_interval), &HdlGraphSlamNodelet::optimization_timer_callback, this); // 创建一个定时器,定时器会在每个 graph_update_interval 秒后触发,然后调用 optimization_timer_callback 函数
|
|
|
+ map_publish_timer = mt_nh.createWallTimer(ros::WallDuration(map_cloud_update_interval), &HdlGraphSlamNodelet::map_points_publish_timer_callback, this); // 创建一个定时器,定时器会在每个 map_cloud_update_interval 秒后触发,然后调用 map_points_publish_timer_callback 函数
|
|
|
+
|
|
|
+ // 函数入口都是 callback 函数,也即三个:cloud_callback 、 optimization_timer_callback 、 map_points_publish_timer_callback
|
|
|
}
|
|
|
|
|
|
private:
|
|
|
/**
|
|
|
* @brief received point clouds are pushed to #keyframe_queue
|
|
|
- * @param odom_msg
|
|
|
- * @param cloud_msg
|
|
|
+ * @param odom_msg // 前端的激光里程计数据
|
|
|
+ * @param cloud_msg // 前端滤波后的点云数据
|
|
|
*/
|
|
|
void cloud_callback(const nav_msgs::OdometryConstPtr& odom_msg, const sensor_msgs::PointCloud2::ConstPtr& cloud_msg) {
|
|
|
- const ros::Time& stamp = cloud_msg->header.stamp;
|
|
|
- Eigen::Isometry3d odom = odom2isometry(odom_msg);
|
|
|
+ const ros::Time& stamp = cloud_msg->header.stamp; // 获取点云数据的时间戳
|
|
|
+ Eigen::Isometry3d odom = odom2isometry(odom_msg); // 将里程计消息转换为Eigen库中的Isometry3d类型,它表示一个3D刚体变换(包括旋转和平移)
|
|
|
|
|
|
- pcl::PointCloud<PointT>::Ptr cloud(new pcl::PointCloud<PointT>());
|
|
|
- pcl::fromROSMsg(*cloud_msg, *cloud);
|
|
|
+ pcl::PointCloud<PointT>::Ptr cloud(new pcl::PointCloud<PointT>()); // 创建一个点云对象的指针
|
|
|
+ pcl::fromROSMsg(*cloud_msg, *cloud); // 将ROS的点云消息 cloud_msg 转换为PCL(Point Cloud Library)的点云格式
|
|
|
if(base_frame_id.empty()) {
|
|
|
- base_frame_id = cloud_msg->header.frame_id;
|
|
|
- }
|
|
|
-
|
|
|
- if(!keyframe_updater->update(odom)) {
|
|
|
- std::lock_guard<std::mutex> lock(keyframe_queue_mutex);
|
|
|
- if(keyframe_queue.empty()) {
|
|
|
+ base_frame_id = cloud_msg->header.frame_id; // 将当前点云消息的坐标系 frame_id 设置为基础坐标系 base_frame_id 是整个系统中关键帧数据的参考坐标系
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新关键帧判断
|
|
|
+ if(!keyframe_updater->update(odom)) { // 根据当前的里程计信息 odom 判断是否需要生成新的关键帧
|
|
|
+ std::lock_guard<std::mutex> lock(keyframe_queue_mutex); // 这行代码的作用是确保线程安全,防止多个线程同时访问或修改 keyframe_queue 队列
|
|
|
+ // std::lock_guard<std::mutex> 是 C++ 标准库中的一个类,用来简化互斥锁(std::mutex)的使用。它的作用是在其作用域内自动对给定的互斥量加锁,并在作用域结束时自动解锁
|
|
|
+ // keyframe_queue_mutex 是一个互斥量(std::mutex),用于保护关键帧队列 keyframe_queue。当多个线程试图同时访问或修改 keyframe_queue 时,互斥量确保只有一个线程能够访问该资源,避免数据竞争和并发问题
|
|
|
+ // 工作原理:当这行代码执行时,lock_guard 会锁住 keyframe_queue_mutex,使其他线程无法访问与之关联的资源(即 keyframe_queue) 当程序执行离开该作用域时,lock_guard 会自动释放锁,无需显式调用 unlock() 函数
|
|
|
+ // 为什么需要锁? 在多线程环境中(比如 ROS 回调函数可能被不同线程调用),如果多个线程同时读取或写入 keyframe_queue,可能导致数据不一致或崩溃。使用互斥量确保对 keyframe_queue 的操作是原子性的,即一个线程在操作时,其他线程必须等待
|
|
|
+
|
|
|
+ if(keyframe_queue.empty()) { // 如果关键帧队列是空的,发布一个 ROS 消息通知系统继续读取点云数据,直到指定时间点(stamp + ros::Duration(10, 0))。这段代码确保在没有关键帧生成时,系统能够继续读取点云数据
|
|
|
std_msgs::Header read_until;
|
|
|
read_until.stamp = stamp + ros::Duration(10, 0);
|
|
|
read_until.frame_id = points_topic;
|
|
@@ -166,16 +187,19 @@ private:
|
|
|
read_until_pub.publish(read_until);
|
|
|
}
|
|
|
|
|
|
- return;
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
- double accum_d = keyframe_updater->get_accum_distance();
|
|
|
- KeyFrame::Ptr keyframe(new KeyFrame(stamp, odom, accum_d, cloud));
|
|
|
+ double accum_d = keyframe_updater->get_accum_distance(); // 获取累计的运动距离,用于判断关键帧生成的条件
|
|
|
+ KeyFrame::Ptr keyframe(new KeyFrame(stamp, odom, accum_d, cloud)); // 创建一个新的关键帧 keyframe,其中包含当前的时间戳 stamp,里程计信息 odom,累计距离 accum_d,以及处理后的点云数据 cloud(这里的处理就是做了个消息类型的转换)
|
|
|
|
|
|
- std::lock_guard<std::mutex> lock(keyframe_queue_mutex);
|
|
|
- keyframe_queue.push_back(keyframe);
|
|
|
+ std::lock_guard<std::mutex> lock(keyframe_queue_mutex); // 使用 std::lock_guard 加锁,确保对关键帧队列 keyframe_queue 的操作是线程安全的
|
|
|
+ keyframe_queue.push_back(keyframe); // 将新生成的关键帧 keyframe 推入 keyframe_queue 队列,供后续的处理使用
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
/**
|
|
|
* @brief this method adds all the keyframes in #keyframe_queue to the pose graph (odometry edges)
|
|
|
* @return if true, at least one keyframe was added to the pose graph
|
|
@@ -192,7 +216,7 @@ private:
|
|
|
Eigen::Isometry3d odom2map(trans_odom2map.cast<double>());
|
|
|
trans_odom2map_mutex.unlock();
|
|
|
|
|
|
- std::cout << "flush_keyframe_queue - keyframes len:"<< keyframes.size() << std::endl;
|
|
|
+ std::cout << "flush_keyframe_queue - keyframes len:" << keyframes.size() << std::endl;
|
|
|
int num_processed = 0;
|
|
|
for(int i = 0; i < std::min<int>(keyframe_queue.size(), max_keyframes_per_update); i++) {
|
|
|
num_processed = i;
|
|
@@ -208,7 +232,6 @@ private:
|
|
|
|
|
|
// fix the first node
|
|
|
if(keyframes.empty() && new_keyframes.size() == 1) {
|
|
|
-
|
|
|
if(private_nh.param<bool>("fix_first_node", false)) {
|
|
|
Eigen::MatrixXd inf = Eigen::MatrixXd::Identity(6, 6);
|
|
|
std::stringstream sst(private_nh.param<std::string>("fix_first_node_stddev", "1 1 1 1 1 1"));
|
|
@@ -808,7 +831,6 @@ private:
|
|
|
return markers;
|
|
|
}
|
|
|
|
|
|
-
|
|
|
/**
|
|
|
* @brief load all data from a directory
|
|
|
* @param req
|
|
@@ -824,13 +846,12 @@ private:
|
|
|
|
|
|
// Load graph.
|
|
|
graph_slam->load(directory + "/graph.g2o");
|
|
|
-
|
|
|
- // Iterate over the items in this directory and count how many sub directories there are.
|
|
|
+
|
|
|
+ // Iterate over the items in this directory and count how many sub directories there are.
|
|
|
// This will give an upper limit on how many keyframe indexes we can expect to find.
|
|
|
boost::filesystem::directory_iterator begin(directory), end;
|
|
|
- int max_directory_count = std::count_if(begin, end,
|
|
|
- [](const boost::filesystem::directory_entry & d) {
|
|
|
- return boost::filesystem::is_directory(d.path()); // only return true if a direcotry
|
|
|
+ int max_directory_count = std::count_if(begin, end, [](const boost::filesystem::directory_entry& d) {
|
|
|
+ return boost::filesystem::is_directory(d.path()); // only return true if a direcotry
|
|
|
});
|
|
|
|
|
|
// Load keyframes by looping through key frame indexes that we expect to see.
|
|
@@ -847,8 +868,8 @@ private:
|
|
|
KeyFrame::Ptr keyframe(new KeyFrame(key_frame_directory, graph_slam->graph.get()));
|
|
|
keyframes.push_back(keyframe);
|
|
|
}
|
|
|
- std::cout << "loaded " << keyframes.size() << " keyframes" <<std::endl;
|
|
|
-
|
|
|
+ std::cout << "loaded " << keyframes.size() << " keyframes" << std::endl;
|
|
|
+
|
|
|
// Load special nodes.
|
|
|
std::ifstream ifs(directory + "/special_nodes.csv");
|
|
|
if(!ifs) {
|
|
@@ -863,21 +884,21 @@ private:
|
|
|
anchor_node = static_cast<g2o::VertexSE3*>(graph_slam->graph->vertex(id));
|
|
|
} else if(token == "anchor_edge") {
|
|
|
int id = 0;
|
|
|
- ifs >> id;
|
|
|
+ ifs >> id;
|
|
|
// We have no way of directly pulling the edge based on the edge ID that we have just read in.
|
|
|
- // Fortunatly anchor edges are always attached to the anchor node so we can iterate over
|
|
|
+ // Fortunatly anchor edges are always attached to the anchor node so we can iterate over
|
|
|
// the edges that listed against the node untill we find the one that matches our ID.
|
|
|
- if(anchor_node){
|
|
|
+ if(anchor_node) {
|
|
|
auto edges = anchor_node->edges();
|
|
|
|
|
|
for(auto e : edges) {
|
|
|
- int edgeID = e->id();
|
|
|
- if (edgeID == id){
|
|
|
+ int edgeID = e->id();
|
|
|
+ if(edgeID == id) {
|
|
|
anchor_edge = static_cast<g2o::EdgeSE3*>(e);
|
|
|
|
|
|
break;
|
|
|
}
|
|
|
- }
|
|
|
+ }
|
|
|
}
|
|
|
} else if(token == "floor_node") {
|
|
|
int id = 0;
|
|
@@ -900,7 +921,7 @@ private:
|
|
|
if(floor_plane_node->id()) {
|
|
|
std::cout << " floor_node: " << floor_plane_node->id();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// finish with a new line
|
|
|
std::cout << std::endl;
|
|
|
}
|
|
@@ -917,12 +938,11 @@ private:
|
|
|
|
|
|
res.success = true;
|
|
|
|
|
|
- std::cout << "snapshot updated" << std::endl << "loading successful" <<std::endl;
|
|
|
+ std::cout << "snapshot updated" << std::endl << "loading successful" << std::endl;
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
-
|
|
|
/**
|
|
|
* @brief dump all data to the current directory
|
|
|
* @param req
|
|
@@ -948,7 +968,7 @@ private:
|
|
|
}
|
|
|
|
|
|
std::cout << "dumping data to:" << directory << std::endl;
|
|
|
- // save graph
|
|
|
+ // save graph
|
|
|
graph_slam->save(directory + "/graph.g2o");
|
|
|
|
|
|
// save keyframes
|
|
@@ -1012,11 +1032,11 @@ private:
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
-private:
|
|
|
+private: // 一些参数的定义
|
|
|
// ROS
|
|
|
- ros::NodeHandle nh;
|
|
|
- ros::NodeHandle mt_nh;
|
|
|
- ros::NodeHandle private_nh;
|
|
|
+ ros::NodeHandle nh; // 节点句柄
|
|
|
+ ros::NodeHandle mt_nh; //
|
|
|
+ ros::NodeHandle private_nh; // 私有节点句柄 申请多几个节点句柄主要是为了:命名空间隔离、参数隔离、代码组织、责任分离、调试和测试、灵活性和扩展性、并行处理(kimi解释)
|
|
|
ros::WallTimer optimization_timer;
|
|
|
ros::WallTimer map_publish_timer;
|
|
|
|
|
@@ -1098,7 +1118,9 @@ private:
|
|
|
std::vector<KeyFrame::Ptr> keyframes;
|
|
|
std::unordered_map<ros::Time, KeyFrame::Ptr, RosTimeHash> keyframe_hash;
|
|
|
|
|
|
- std::unique_ptr<GraphSLAM> graph_slam;
|
|
|
+ // std::unique_ptr:一种独占所有权的智能指针,意味着同一时间内只能有一个std::unique_ptr指向某个对象
|
|
|
+ // 智能指针是一种自动管理动态分配内存的类,它们在对象不再使用时自动释放内存,从而帮助防止内存泄漏
|
|
|
+ std::unique_ptr<GraphSLAM> graph_slam; // 声明一个指向GraphSLAM 类型的智能指针 graph_slam
|
|
|
std::unique_ptr<LoopDetector> loop_detector;
|
|
|
std::unique_ptr<KeyframeUpdater> keyframe_updater;
|
|
|
std::unique_ptr<NmeaSentenceParser> nmea_parser;
|