ROS1到ROS2迁移教程(以rslidar_to_velodyne功能包为例)+ ROS2版本LIO-SAM试跑

一、引言

1、本博文主要目的是将rslidar_to_velodyne功能包的ros1版本转换为ros2版本
2、内容会包含ROS1到ROS2迁移技巧,是自己总结的一套简单的流程,可以保证ROS2下的代码试跑成功,如果需要将代码进一步转化为类的实现的方式,自己稍作修改就可以了
3、最终会放原始ROS1版本以及修改过后的ROS2版本的代码配置文件和CPP文件供大家对比参考
4、本来是想用现成的开源的ROS2版本的rslidar_to_velodyne,但是没有找到,不知道是因为大家没有需求还是没有开源出来,所以我干脆就自己修改下并开源出来给大家使用,希望能给各位提供帮助(给我一键三连即可())
5、最后,ros2是真的比ros1方便很多(#.#)

二、整体思路与流程

1、安装虚拟机或者双系统并配置ROS2环境
双系统的话参考我以前的博文,然后ROS2环境大家用小鱼的一键安装即可,网上应该能搜到
2、根据功能包package.xml文件确定所需要的依赖,并根据该依赖创建新的ROS2功能包
3、创建CPP文件并修改对应的CmakeList文件配置
4、修改CPP文件内容,将ROS1的API转化为ROS2
5、配置安装编译LIO-SAM的ROS2版本并用一个速腾雷达bag包进行算法测试

三、ROS1迁移ROS2

3.1 配置ros2安装环境
一键安装,然后你需要同时安装ROS1 Noetic版本和ROS2 Galactic版本,根据自己需要吧,反正ros1和ros2一起安装就可以了,这个之后有用处,也不用担心环境会冲突,这个东西就是配置下环境变量的事儿

wget http://fishros.com/install -O fishros && . fishros

3.2 创建功能包
ROS1版本rslidar_to_velodyne代码文件核心的有三个:CMakeLists.txt、package.xml、rs_to_velodyne.cpp
首先我们看一下ROS1版本package.xml文件:

<?xml version="1.0"?>
<package format="2">
  <name>rs_to_velodyne</name>
  <version>0.0.0</version>
  <description>The rs_to_velodyne package</description>

  <!-- One maintainer tag required, multiple allowed, one person per tag -->
  <!-- Example:  -->
  <!-- <maintainer email="jane.doe@example.com">Jane Doe</maintainer> -->
  <maintainer email="hvt@todo.todo">hvt</maintainer>


  <!-- One license tag required, multiple allowed, one license per tag -->
  <!-- Commonly used license strings: -->
  <!--   BSD, MIT, Boost Software License, GPLv2, GPLv3, LGPLv2.1, LGPLv3 -->
  <license>TODO</license>


  <!-- Url tags are optional, but multiple are allowed, one per tag -->
  <!-- Optional attribute type can be: website, bugtracker, or repository -->
  <!-- Example: -->
  <!-- <url type="website">http://wiki.ros.org/rs_to_velodyne</url> -->


  <!-- Author tags are optional, multiple are allowed, one per tag -->
  <!-- Authors do not have to be maintainers, but could be -->
  <!-- Example: -->
  <!-- <author email="jane.doe@example.com">Jane Doe</author> -->


  <!-- The *depend tags are used to specify dependencies -->
  <!-- Dependencies can be catkin packages or system dependencies -->
  <!-- Examples: -->
  <!-- Use depend as a shortcut for packages that are both build and exec dependencies -->
  <!--   <depend>roscpp</depend> -->
  <!--   Note that this is equivalent to the following: -->
  <!--   <build_depend>roscpp</build_depend> -->
  <!--   <exec_depend>roscpp</exec_depend> -->
  <!-- Use build_depend for packages you need at compile time: -->
  <!--   <build_depend>message_generation</build_depend> -->
  <!-- Use build_export_depend for packages you need in order to build against this package: -->
  <!--   <build_export_depend>message_generation</build_export_depend> -->
  <!-- Use buildtool_depend for build tool packages: -->
  <!--   <buildtool_depend>catkin</buildtool_depend> -->
  <!-- Use exec_depend for packages you need at runtime: -->
  <!--   <exec_depend>message_runtime</exec_depend> -->
  <!-- Use test_depend for packages you need only for testing: -->
  <!--   <test_depend>gtest</test_depend> -->
  <!-- Use doc_depend for packages you need only for building documentation: -->
  <!--   <doc_depend>doxygen</doc_depend> -->
  <buildtool_depend>catkin</buildtool_depend>
  <build_depend>message_filters</build_depend>
  <build_depend>pcl_conversions</build_depend>
  <build_depend>pcl_ros</build_depend>
  <build_depend>roscpp</build_depend>
  <build_depend>sensor_msgs</build_depend>
  <build_depend>std_msgs</build_depend>
  <build_export_depend>message_filters</build_export_depend>
  <build_export_depend>pcl_conversions</build_export_depend>
  <build_export_depend>pcl_ros</build_export_depend>
  <build_export_depend>roscpp</build_export_depend>
  <build_export_depend>sensor_msgs</build_export_depend>
  <build_export_depend>std_msgs</build_export_depend>
  <exec_depend>message_filters</exec_depend>
  <exec_depend>pcl_conversions</exec_depend>
  <exec_depend>pcl_ros</exec_depend>
  <exec_depend>roscpp</exec_depend>
  <exec_depend>sensor_msgs</exec_depend>
  <exec_depend>std_msgs</exec_depend>


  <!-- The export tag contains other, unspecified, tags -->
  <export>
    <!-- Other tools can request additional information be placed here -->

  </export>
</package>

里面的<build_depend>重点关注一下,可以发现其依赖为message_filters、pcl_conversions、pcl_ros、roscpp、sensor_msgs、std_msgs一共六个需要配置的依赖包记住他们,后面我们创建新的功能包可以用到。

这个时候我们就可以开始创建功能包了

ros2 pkg create rs_to_velodyne --build-type ament_cmake --dependencies rclcpp std_msgs sensor_msgs message_filters pcl_conversions pcl_ros 

3.3 修改ROS2版本功能包的package.xml文件
上一步创建功能包其实我们已经把依赖关系添加了,所以这一步并不需要修改了,如果是直接创建功能包的话,需要手动添加自己用到的依赖,这样比较麻烦,所以才先看下ROS1的package.xml文件提前知道需要用到的依赖包这样子给后面节省工作量,这里放一下ROS2版本功能包的package.xml文件

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>rs_to_velodyne</name>
  <version>0.0.0</version>
  <description>TODO: Package description</description>
  <maintainer email="lixushi@todo.todo">lixushi</maintainer>
  <license>TODO: License declaration</license>

  <buildtool_depend>ament_cmake</buildtool_depend>

  <depend>rclcpp</depend>
  <depend>std_msgs</depend>
  <depend>sensor_msgs</depend>
  <depend>message_filters</depend>
  <depend>pcl_conversions</depend>
  <depend>pcl_ros</depend>

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

3.4 在新的功能包src文件夹下创建rs_to_velodyne.cpp文件,内容就先把ROS1的代码放进去,之后我们再一步一步修改

//#include "utility.h"
#include <ros/ros.h>

#include <sensor_msgs/PointCloud2.h>

#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl_conversions/pcl_conversions.h>

std::string output_type;

static int RING_ID_MAP_RUBY[] = {
        3, 66, 33, 96, 11, 74, 41, 104, 19, 82, 49, 112, 27, 90, 57, 120,
        35, 98, 1, 64, 43, 106, 9, 72, 51, 114, 17, 80, 59, 122, 25, 88,
        67, 34, 97, 0, 75, 42, 105, 8, 83, 50, 113, 16, 91, 58, 121, 24,
        99, 2, 65, 32, 107, 10, 73, 40, 115, 18, 81, 48, 123, 26, 89, 56,
        7, 70, 37, 100, 15, 78, 45, 108, 23, 86, 53, 116, 31, 94, 61, 124,
        39, 102, 5, 68, 47, 110, 13, 76, 55, 118, 21, 84, 63, 126, 29, 92,
        71, 38, 101, 4, 79, 46, 109, 12, 87, 54, 117, 20, 95, 62, 125, 28,
        103, 6, 69, 36, 111, 14, 77, 44, 119, 22, 85, 52, 127, 30, 93, 60
};
static int RING_ID_MAP_16[] = {
        0, 1, 2, 3, 4, 5, 6, 7, 15, 14, 13, 12, 11, 10, 9, 8
};

// rslidar和velodyne的格式有微小的区别
// rslidar的点云格式
struct RsPointXYZIRT {
    PCL_ADD_POINT4D
    PCL_ADD_INTENSITY;
    uint16_t ring = 0;
    double timestamp = 0;

    EIGEN_MAKE_ALIGNED_OPERATOR_NEW
} EIGEN_ALIGN16;
POINT_CLOUD_REGISTER_POINT_STRUCT(RsPointXYZIRT,
                                  (float, x, x)(float, y, y)(float, z, z)(float, intensity, intensity)
                                          (uint16_t, ring, ring)(double, timestamp, timestamp))

// velodyne的点云格式
struct VelodynePointXYZIRT {
    PCL_ADD_POINT4D

    PCL_ADD_INTENSITY;
    uint16_t ring;
    float time;

    EIGEN_MAKE_ALIGNED_OPERATOR_NEW
} EIGEN_ALIGN16;

POINT_CLOUD_REGISTER_POINT_STRUCT (VelodynePointXYZIRT,
                                   (float, x, x)(float, y, y)(float, z, z)(float, intensity, intensity)
                                           (uint16_t, ring, ring)(float, time, time)
)

struct VelodynePointXYZIR {
    PCL_ADD_POINT4D

    PCL_ADD_INTENSITY;
    uint16_t ring;

    EIGEN_MAKE_ALIGNED_OPERATOR_NEW
} EIGEN_ALIGN16;

POINT_CLOUD_REGISTER_POINT_STRUCT (VelodynePointXYZIR,
                                   (float, x, x)(float, y, y)
                                           (float, z, z)(float, intensity, intensity)
                                           (uint16_t, ring, ring)
)

ros::Subscriber subRobosensePC;
ros::Publisher pubRobosensePC;

template<typename T>
bool has_nan(T point) {

    // remove nan point, or the feature assocaion will crash, the surf point will containing nan points
    // pcl remove nan not work normally
    // ROS_ERROR("Containing nan point!");
    if (pcl_isnan(point.x) || pcl_isnan(point.y) || pcl_isnan(point.z)) {
        return true;
    } else {
        return false;
    }
}

template<typename T>
void publish_points(T &new_pc, const sensor_msgs::PointCloud2 &old_msg) {
    // pc properties
    new_pc->is_dense = true;

    // publish
    sensor_msgs::PointCloud2 pc_new_msg;
    pcl::toROSMsg(*new_pc, pc_new_msg);
    pc_new_msg.header = old_msg.header;
    pc_new_msg.header.frame_id = "velodyne";
    pubRobosensePC.publish(pc_new_msg);
}

void rsHandler_XYZI(sensor_msgs::PointCloud2 pc_msg) {
    pcl::PointCloud<pcl::PointXYZI>::Ptr pc(new pcl::PointCloud<pcl::PointXYZI>());
    pcl::PointCloud<VelodynePointXYZIR>::Ptr pc_new(new pcl::PointCloud<VelodynePointXYZIR>());
    pcl::fromROSMsg(pc_msg, *pc);

    // to new pointcloud
    for (int point_id = 0; point_id < pc->points.size(); ++point_id) {
        if (has_nan(pc->points[point_id]))
            continue;

        VelodynePointXYZIR new_point;
        new_point.x = pc->points[point_id].x;
        new_point.y = pc->points[point_id].y;
        new_point.z = pc->points[point_id].z;
        new_point.intensity = pc->points[point_id].intensity;
        // remap ring id
        if (pc->height == 16) {
            new_point.ring = RING_ID_MAP_16[point_id / pc->width];
        } else if (pc->height == 128) {
            new_point.ring = RING_ID_MAP_RUBY[point_id % pc->height];
        }
        pc_new->points.push_back(new_point);
    }

    publish_points(pc_new, pc_msg);
}


template<typename T_in_p, typename T_out_p>
void handle_pc_msg(const typename pcl::PointCloud<T_in_p>::Ptr &pc_in,
                   const typename pcl::PointCloud<T_out_p>::Ptr &pc_out) {

    // to new pointcloud
    for (int point_id = 0; point_id < pc_in->points.size(); ++point_id) {
        if (has_nan(pc_in->points[point_id]))
            continue;
        T_out_p new_point;
//        std::copy(pc->points[point_id].data, pc->points[point_id].data + 4, new_point.data);
        new_point.x = pc_in->points[point_id].x;
        new_point.y = pc_in->points[point_id].y;
        new_point.z = pc_in->points[point_id].z;
        new_point.intensity = pc_in->points[point_id].intensity;
//        new_point.ring = pc->points[point_id].ring;
//        // 计算相对于第一个点的相对时间
//        new_point.time = float(pc->points[point_id].timestamp - pc->points[0].timestamp);
        pc_out->points.push_back(new_point);
    }
}

template<typename T_in_p, typename T_out_p>
void add_ring(const typename pcl::PointCloud<T_in_p>::Ptr &pc_in,
              const typename pcl::PointCloud<T_out_p>::Ptr &pc_out) {
    // to new pointcloud
    int valid_point_id = 0;
    for (int point_id = 0; point_id < pc_in->points.size(); ++point_id) {
        if (has_nan(pc_in->points[point_id]))
            continue;
        // 跳过nan点
        pc_out->points[valid_point_id++].ring = pc_in->points[point_id].ring;
    }
}

template<typename T_in_p, typename T_out_p>
void add_time(const typename pcl::PointCloud<T_in_p>::Ptr &pc_in,
              const typename pcl::PointCloud<T_out_p>::Ptr &pc_out) {
    // to new pointcloud
    int valid_point_id = 0;
    for (int point_id = 0; point_id < pc_in->points.size(); ++point_id) {
        if (has_nan(pc_in->points[point_id]))
            continue;
        // 跳过nan点
        pc_out->points[valid_point_id++].time = float(pc_in->points[point_id].timestamp - pc_in->points[0].timestamp);
    }
}

void rsHandler_XYZIRT(const sensor_msgs::PointCloud2 &pc_msg) {
    pcl::PointCloud<RsPointXYZIRT>::Ptr pc_in(new pcl::PointCloud<RsPointXYZIRT>());
    pcl::fromROSMsg(pc_msg, *pc_in);

    if (output_type == "XYZIRT") {
        pcl::PointCloud<VelodynePointXYZIRT>::Ptr pc_out(new pcl::PointCloud<VelodynePointXYZIRT>());
        handle_pc_msg<RsPointXYZIRT, VelodynePointXYZIRT>(pc_in, pc_out);
        add_ring<RsPointXYZIRT, VelodynePointXYZIRT>(pc_in, pc_out);
        add_time<RsPointXYZIRT, VelodynePointXYZIRT>(pc_in, pc_out);
        publish_points(pc_out, pc_msg);
    } else if (output_type == "XYZIR") {
        pcl::PointCloud<VelodynePointXYZIR>::Ptr pc_out(new pcl::PointCloud<VelodynePointXYZIR>());
        handle_pc_msg<RsPointXYZIRT, VelodynePointXYZIR>(pc_in, pc_out);
        add_ring<RsPointXYZIRT, VelodynePointXYZIR>(pc_in, pc_out);
        publish_points(pc_out, pc_msg);
    } else if (output_type == "XYZI") {
        pcl::PointCloud<pcl::PointXYZI>::Ptr pc_out(new pcl::PointCloud<pcl::PointXYZI>());
        handle_pc_msg<RsPointXYZIRT, pcl::PointXYZI>(pc_in, pc_out);
        publish_points(pc_out, pc_msg);
    }
}

int main(int argc, char **argv) {
    ros::init(argc, argv, "rs_converter");
    ros::NodeHandle nh;
    if (argc < 3) {
        ROS_ERROR(
                "Please specify input pointcloud type( XYZI or XYZIRT) and output pointcloud type(XYZI, XYZIR, XYZIRT)!!!");
        exit(1);
    } else {
        // 输出点云类型
        output_type = argv[2];

        if (std::strcmp("XYZI", argv[1]) == 0) {
            subRobosensePC = nh.subscribe("/rslidar_points", 1, rsHandler_XYZI);
        } else if (std::strcmp("XYZIRT", argv[1]) == 0) {
            subRobosensePC = nh.subscribe("/rslidar_points", 1, rsHandler_XYZIRT);
        } else {
            ROS_ERROR(argv[1]);
            ROS_ERROR("Unsupported input pointcloud type. Currently only support XYZI and XYZIRT.");
            exit(1);
        }
    }
    pubRobosensePC = nh.advertise<sensor_msgs::PointCloud2>("/velodyne_points", 1);

    ROS_INFO("Listening to /rslidar_points ......");
    ros::spin();
    return 0;
}

3.5 修改新功能包的CMakeLists.txt文件
首先看一下ROS1版本的:

cmake_minimum_required(VERSION 3.0.2)
project(rs_to_velodyne)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -O3")

find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  pcl_ros
  pcl_conversions

  std_msgs
  sensor_msgs
)

catkin_package(
  INCLUDE_DIRS 
  DEPENDS PCL
)

include_directories(
	include
	${catkin_INCLUDE_DIRS}
	${PCL_INCLUDE_DIRS}
)

link_directories(
	include
	${PCL_LIBRARY_DIRS}
)


add_executable(rs_to_velodyne src/rs_to_velodyne.cpp)
add_dependencies(rs_to_velodyne ${catkin_EXPORTED_TARGETS})
target_link_libraries(rs_to_velodyne ${catkin_LIBRARIES} ${PCL_LIBRARIES} ${OpenCV_LIBRARIES})

再看一下ROS2版本的:

cmake_minimum_required(VERSION 3.8)
project(rs_to_velodyne)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
find_package(sensor_msgs REQUIRED)
find_package(message_filters REQUIRED)
find_package(pcl_conversions REQUIRED)
find_package(pcl_ros REQUIRED)

add_executable(rs_to_velodyne src/rs_to_velodyne.cpp)
ament_target_dependencies(
  rs_to_velodyne
  "rclcpp"
  "std_msgs"
  "sensor_msgs"
  "message_filters"
  "pcl_conversions"
  "pcl_ros"
)

install(TARGETS 
  rs_to_velodyne
  DESTINATION lib/${PROJECT_NAME})


if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # the following line skips the linter which checks for copyrights
  # uncomment the line when a copyright and license is not present in all source files
  #set(ament_cmake_copyright_FOUND TRUE)
  # the following line skips cpplint (only works in a git repo)
  # uncomment the line when this package is not in a git repo
  #set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

ament_package()

其实只是在新功能包的CMakeLists.txt文件中添加了下面这些内容,如果有其他CPP文件同理创建就可以了

add_executable(rs_to_velodyne src/rs_to_velodyne.cpp)
ament_target_dependencies(
  rs_to_velodyne
  "rclcpp"
  "std_msgs"
  "sensor_msgs"
  "message_filters"
  "pcl_conversions"
  "pcl_ros"
)

install(TARGETS 
  rs_to_velodyne
  DESTINATION lib/${PROJECT_NAME})

3.6 最后最关键的就是修改CPP文件了,老规矩先放ROS1版本的,然后我们一步一步去对应修改,会发现其实挺简单的

//#include "utility.h"
#include <ros/ros.h>

#include <sensor_msgs/PointCloud2.h>

#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl_conversions/pcl_conversions.h>

std::string output_type;

static int RING_ID_MAP_RUBY[] = {
        3, 66, 33, 96, 11, 74, 41, 104, 19, 82, 49, 112, 27, 90, 57, 120,
        35, 98, 1, 64, 43, 106, 9, 72, 51, 114, 17, 80, 59, 122, 25, 88,
        67, 34, 97, 0, 75, 42, 105, 8, 83, 50, 113, 16, 91, 58, 121, 24,
        99, 2, 65, 32, 107, 10, 73, 40, 115, 18, 81, 48, 123, 26, 89, 56,
        7, 70, 37, 100, 15, 78, 45, 108, 23, 86, 53, 116, 31, 94, 61, 124,
        39, 102, 5, 68, 47, 110, 13, 76, 55, 118, 21, 84, 63, 126, 29, 92,
        71, 38, 101, 4, 79, 46, 109, 12, 87, 54, 117, 20, 95, 62, 125, 28,
        103, 6, 69, 36, 111, 14, 77, 44, 119, 22, 85, 52, 127, 30, 93, 60
};
static int RING_ID_MAP_16[] = {
        0, 1, 2, 3, 4, 5, 6, 7, 15, 14, 13, 12, 11, 10, 9, 8
};

// rslidar和velodyne的格式有微小的区别
// rslidar的点云格式
struct RsPointXYZIRT {
    PCL_ADD_POINT4D
    PCL_ADD_INTENSITY;
    uint16_t ring = 0;
    double timestamp = 0;

    EIGEN_MAKE_ALIGNED_OPERATOR_NEW
} EIGEN_ALIGN16;
POINT_CLOUD_REGISTER_POINT_STRUCT(RsPointXYZIRT,
                                  (float, x, x)(float, y, y)(float, z, z)(float, intensity, intensity)
                                          (uint16_t, ring, ring)(double, timestamp, timestamp))

// velodyne的点云格式
struct VelodynePointXYZIRT {
    PCL_ADD_POINT4D

    PCL_ADD_INTENSITY;
    uint16_t ring;
    float time;

    EIGEN_MAKE_ALIGNED_OPERATOR_NEW
} EIGEN_ALIGN16;

POINT_CLOUD_REGISTER_POINT_STRUCT (VelodynePointXYZIRT,
                                   (float, x, x)(float, y, y)(float, z, z)(float, intensity, intensity)
                                           (uint16_t, ring, ring)(float, time, time)
)

struct VelodynePointXYZIR {
    PCL_ADD_POINT4D

    PCL_ADD_INTENSITY;
    uint16_t ring;

    EIGEN_MAKE_ALIGNED_OPERATOR_NEW
} EIGEN_ALIGN16;

POINT_CLOUD_REGISTER_POINT_STRUCT (VelodynePointXYZIR,
                                   (float, x, x)(float, y, y)
                                           (float, z, z)(float, intensity, intensity)
                                           (uint16_t, ring, ring)
)

ros::Subscriber subRobosensePC;
ros::Publisher pubRobosensePC;

template<typename T>
bool has_nan(T point) {

    // remove nan point, or the feature assocaion will crash, the surf point will containing nan points
    // pcl remove nan not work normally
    // ROS_ERROR("Containing nan point!");
    if (pcl_isnan(point.x) || pcl_isnan(point.y) || pcl_isnan(point.z)) {
        return true;
    } else {
        return false;
    }
}

template<typename T>
void publish_points(T &new_pc, const sensor_msgs::PointCloud2 &old_msg) {
    // pc properties
    new_pc->is_dense = true;

    // publish
    sensor_msgs::PointCloud2 pc_new_msg;
    pcl::toROSMsg(*new_pc, pc_new_msg);
    pc_new_msg.header = old_msg.header;
    pc_new_msg.header.frame_id = "velodyne";
    pubRobosensePC.publish(pc_new_msg);
}

void rsHandler_XYZI(sensor_msgs::PointCloud2 pc_msg) {
    pcl::PointCloud<pcl::PointXYZI>::Ptr pc(new pcl::PointCloud<pcl::PointXYZI>());
    pcl::PointCloud<VelodynePointXYZIR>::Ptr pc_new(new pcl::PointCloud<VelodynePointXYZIR>());
    pcl::fromROSMsg(pc_msg, *pc);

    // to new pointcloud
    for (int point_id = 0; point_id < pc->points.size(); ++point_id) {
        if (has_nan(pc->points[point_id]))
            continue;

        VelodynePointXYZIR new_point;
        new_point.x = pc->points[point_id].x;
        new_point.y = pc->points[point_id].y;
        new_point.z = pc->points[point_id].z;
        new_point.intensity = pc->points[point_id].intensity;
        // remap ring id
        if (pc->height == 16) {
            new_point.ring = RING_ID_MAP_16[point_id / pc->width];
        } else if (pc->height == 128) {
            new_point.ring = RING_ID_MAP_RUBY[point_id % pc->height];
        }
        pc_new->points.push_back(new_point);
    }

    publish_points(pc_new, pc_msg);
}


template<typename T_in_p, typename T_out_p>
void handle_pc_msg(const typename pcl::PointCloud<T_in_p>::Ptr &pc_in,
                   const typename pcl::PointCloud<T_out_p>::Ptr &pc_out) {

    // to new pointcloud
    for (int point_id = 0; point_id < pc_in->points.size(); ++point_id) {
        if (has_nan(pc_in->points[point_id]))
            continue;
        T_out_p new_point;
//        std::copy(pc->points[point_id].data, pc->points[point_id].data + 4, new_point.data);
        new_point.x = pc_in->points[point_id].x;
        new_point.y = pc_in->points[point_id].y;
        new_point.z = pc_in->points[point_id].z;
        new_point.intensity = pc_in->points[point_id].intensity;
//        new_point.ring = pc->points[point_id].ring;
//        // 计算相对于第一个点的相对时间
//        new_point.time = float(pc->points[point_id].timestamp - pc->points[0].timestamp);
        pc_out->points.push_back(new_point);
    }
}

template<typename T_in_p, typename T_out_p>
void add_ring(const typename pcl::PointCloud<T_in_p>::Ptr &pc_in,
              const typename pcl::PointCloud<T_out_p>::Ptr &pc_out) {
    // to new pointcloud
    int valid_point_id = 0;
    for (int point_id = 0; point_id < pc_in->points.size(); ++point_id) {
        if (has_nan(pc_in->points[point_id]))
            continue;
        // 跳过nan点
        pc_out->points[valid_point_id++].ring = pc_in->points[point_id].ring;
    }
}

template<typename T_in_p, typename T_out_p>
void add_time(const typename pcl::PointCloud<T_in_p>::Ptr &pc_in,
              const typename pcl::PointCloud<T_out_p>::Ptr &pc_out) {
    // to new pointcloud
    int valid_point_id = 0;
    for (int point_id = 0; point_id < pc_in->points.size(); ++point_id) {
        if (has_nan(pc_in->points[point_id]))
            continue;
        // 跳过nan点
        pc_out->points[valid_point_id++].time = float(pc_in->points[point_id].timestamp - pc_in->points[0].timestamp);
    }
}

void rsHandler_XYZIRT(const sensor_msgs::PointCloud2 &pc_msg) {
    pcl::PointCloud<RsPointXYZIRT>::Ptr pc_in(new pcl::PointCloud<RsPointXYZIRT>());
    pcl::fromROSMsg(pc_msg, *pc_in);

    if (output_type == "XYZIRT") {
        pcl::PointCloud<VelodynePointXYZIRT>::Ptr pc_out(new pcl::PointCloud<VelodynePointXYZIRT>());
        handle_pc_msg<RsPointXYZIRT, VelodynePointXYZIRT>(pc_in, pc_out);
        add_ring<RsPointXYZIRT, VelodynePointXYZIRT>(pc_in, pc_out);
        add_time<RsPointXYZIRT, VelodynePointXYZIRT>(pc_in, pc_out);
        publish_points(pc_out, pc_msg);
    } else if (output_type == "XYZIR") {
        pcl::PointCloud<VelodynePointXYZIR>::Ptr pc_out(new pcl::PointCloud<VelodynePointXYZIR>());
        handle_pc_msg<RsPointXYZIRT, VelodynePointXYZIR>(pc_in, pc_out);
        add_ring<RsPointXYZIRT, VelodynePointXYZIR>(pc_in, pc_out);
        publish_points(pc_out, pc_msg);
    } else if (output_type == "XYZI") {
        pcl::PointCloud<pcl::PointXYZI>::Ptr pc_out(new pcl::PointCloud<pcl::PointXYZI>());
        handle_pc_msg<RsPointXYZIRT, pcl::PointXYZI>(pc_in, pc_out);
        publish_points(pc_out, pc_msg);
    }
}

int main(int argc, char **argv) {
    ros::init(argc, argv, "rs_converter");
    ros::NodeHandle nh;
    if (argc < 3) {
        ROS_ERROR(
                "Please specify input pointcloud type( XYZI or XYZIRT) and output pointcloud type(XYZI, XYZIR, XYZIRT)!!!");
        exit(1);
    } else {
        // 输出点云类型
        output_type = argv[2];

        if (std::strcmp("XYZI", argv[1]) == 0) {
            subRobosensePC = nh.subscribe("/rslidar_points", 1, rsHandler_XYZI);
        } else if (std::strcmp("XYZIRT", argv[1]) == 0) {
            subRobosensePC = nh.subscribe("/rslidar_points", 1, rsHandler_XYZIRT);
        } else {
            ROS_ERROR(argv[1]);
            ROS_ERROR("Unsupported input pointcloud type. Currently only support XYZI and XYZIRT.");
            exit(1);
        }
    }
    pubRobosensePC = nh.advertise<sensor_msgs::PointCloud2>("/velodyne_points", 1);

    ROS_INFO("Listening to /rslidar_points ......");
    ros::spin();
    return 0;
}

第一步修改头文件
将:

#include <ros/ros.h>
#include <sensor_msgs/PointCloud2.h>
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl_conversions/pcl_conversions.h>

修改为:

#include <rclcpp/rclcpp.hpp>
#include <sensor_msgs/msg/point_cloud2.hpp>
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl_conversions/pcl_conversions.h>

第二步修改变量定义
将:

ros::Subscriber subRobosensePC;
ros::Publisher pubRobosensePC;

修改为:

rclcpp::Subscription<sensor_msgs::msg::PointCloud2>::SharedPtr subRobosensePC;
rclcpp::Publisher<sensor_msgs::msg::PointCloud2>::SharedPtr pubRobosensePC;

第三步修改API
将:

ros::init(argc, argv, "rs_converter");
ros::NodeHandle nh;

修改为:

rclcpp::init(argc, argv);
auto node = rclcpp::Node::make_shared("rs_converter");

将:

ROS_ERROR("Please specify input pointcloud type( XYZI or XYZIRT) and output pointcloud type(XYZI, XYZIR, XYZIRT)!!!");

修改为:

RCLCPP_ERROR(node->get_logger(),"Please specify input pointcloud type( XYZI or XYZIRT) and output pointcloud type(XYZI, XYZIR, XYZIRT)!!!");

当然这里ROS_ERROR不止一个地方哈,都修改掉就行,这里就是举个例子,后文同理
将:

subRobosensePC = nh.subscribe("/rslidar_points", 1, rsHandler_XYZI);

修改为:

subRobosensePC = node->create_subscription<sensor_msgs::msg::PointCloud2>("/rslidar_points", 1, rsHandler_XYZI);

将:

pubRobosensePC = nh.advertise<sensor_msgs::PointCloud2>("/velodyne_points", 1);

修改为:

pubRobosensePC = node->create_publisher<sensor_msgs::msg::PointCloud2>("/velodyne_points", 1);

将:

ROS_INFO("Listening to /rslidar_points ......");
ros::spin();

修改为:

RCLCPP_INFO(node->get_logger(), "Listening to /rslidar_points ......");
rclcpp::spin(node);

然后因为这里的订阅者和发布者都是指针类型,所以其成员函数的调用得用"->“的方式替换到”."的方式,所以要将:

pubRobosensePC.publish(pc_new_msg);

修改为:

pubRobosensePC->publish(pc_new_msg);

以上就是需要修改的全部内容了,如果是其他开源代码需要做移植也是同理,但是呢ROS2其实要把发布者订阅者用继承node的方式进行调用,需要打包成一个类,所以后续还是需要优化一下的,但是我这样修改的代码也是可以使用的(只是我暂时没空改┭┮﹏┭┮)
最后放一下ROS2版本完整的CPP代码叭:

//#include "utility.h"
#include <rclcpp/rclcpp.hpp>
#include <cstring>
#include <cstdlib>
#include <sensor_msgs/msg/point_cloud2.hpp>

#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl_conversions/pcl_conversions.h>

std::string output_type;

static int RING_ID_MAP_RUBY[] = {
        3, 66, 33, 96, 11, 74, 41, 104, 19, 82, 49, 112, 27, 90, 57, 120,
        35, 98, 1, 64, 43, 106, 9, 72, 51, 114, 17, 80, 59, 122, 25, 88,
        67, 34, 97, 0, 75, 42, 105, 8, 83, 50, 113, 16, 91, 58, 121, 24,
        99, 2, 65, 32, 107, 10, 73, 40, 115, 18, 81, 48, 123, 26, 89, 56,
        7, 70, 37, 100, 15, 78, 45, 108, 23, 86, 53, 116, 31, 94, 61, 124,
        39, 102, 5, 68, 47, 110, 13, 76, 55, 118, 21, 84, 63, 126, 29, 92,
        71, 38, 101, 4, 79, 46, 109, 12, 87, 54, 117, 20, 95, 62, 125, 28,
        103, 6, 69, 36, 111, 14, 77, 44, 119, 22, 85, 52, 127, 30, 93, 60
};
static int RING_ID_MAP_16[] = {
        0, 1, 2, 3, 4, 5, 6, 7, 15, 14, 13, 12, 11, 10, 9, 8
};

// rslidar和velodyne的格式有微小的区别
// rslidar的点云格式
struct RsPointXYZIRT {
    PCL_ADD_POINT4D
    PCL_ADD_INTENSITY;
    uint16_t ring = 0;
    double timestamp = 0;

    EIGEN_MAKE_ALIGNED_OPERATOR_NEW
} EIGEN_ALIGN16;
POINT_CLOUD_REGISTER_POINT_STRUCT(RsPointXYZIRT,
                                  (float, x, x)(float, y, y)(float, z, z)(float, intensity, intensity)
                                          (uint16_t, ring, ring)(double, timestamp, timestamp))

// velodyne的点云格式
struct VelodynePointXYZIRT {
    PCL_ADD_POINT4D

    PCL_ADD_INTENSITY;
    uint16_t ring;
    float time;

    EIGEN_MAKE_ALIGNED_OPERATOR_NEW
} EIGEN_ALIGN16;

POINT_CLOUD_REGISTER_POINT_STRUCT (VelodynePointXYZIRT,
                                   (float, x, x)(float, y, y)(float, z, z)(float, intensity, intensity)
                                           (uint16_t, ring, ring)(float, time, time)
)

struct VelodynePointXYZIR {
    PCL_ADD_POINT4D

    PCL_ADD_INTENSITY;
    uint16_t ring;

    EIGEN_MAKE_ALIGNED_OPERATOR_NEW
} EIGEN_ALIGN16;

POINT_CLOUD_REGISTER_POINT_STRUCT (VelodynePointXYZIR,
                                   (float, x, x)(float, y, y)
                                           (float, z, z)(float, intensity, intensity)
                                           (uint16_t, ring, ring)
)

rclcpp::Subscription<sensor_msgs::msg::PointCloud2>::SharedPtr subRobosensePC;
rclcpp::Publisher<sensor_msgs::msg::PointCloud2>::SharedPtr pubRobosensePC;

template<typename T>
bool has_nan(T point) {

    // remove nan point, or the feature assocaion will crash, the surf point will containing nan points
    // pcl remove nan not work normally
    // ROS_ERROR("Containing nan point!");
    if (pcl_isnan(point.x) || pcl_isnan(point.y) || pcl_isnan(point.z)) {
        return true;
    } else {
        return false;
    }
}

template<typename T>
void publish_points(T &new_pc, const sensor_msgs::msg::PointCloud2 &old_msg) {
    // pc properties
    new_pc->is_dense = true;

    // publish
    sensor_msgs::msg::PointCloud2 pc_new_msg;
    pcl::toROSMsg(*new_pc, pc_new_msg);
    pc_new_msg.header = old_msg.header;
    pc_new_msg.header.frame_id = "velodyne";
    pubRobosensePC->publish(pc_new_msg);
}

void rsHandler_XYZI(sensor_msgs::msg::PointCloud2 pc_msg) {
    pcl::PointCloud<pcl::PointXYZI>::Ptr pc(new pcl::PointCloud<pcl::PointXYZI>());
    pcl::PointCloud<VelodynePointXYZIR>::Ptr pc_new(new pcl::PointCloud<VelodynePointXYZIR>());
    pcl::fromROSMsg(pc_msg, *pc);

    // to new pointcloud
    for (int point_id = 0; point_id < pc->points.size(); ++point_id) {
        if (has_nan(pc->points[point_id]))
            continue;

        VelodynePointXYZIR new_point;
        new_point.x = pc->points[point_id].x;
        new_point.y = pc->points[point_id].y;
        new_point.z = pc->points[point_id].z;
        new_point.intensity = pc->points[point_id].intensity;
        // remap ring id
        if (pc->height == 16) {
            new_point.ring = RING_ID_MAP_16[point_id / pc->width];
        } else if (pc->height == 128) {
            new_point.ring = RING_ID_MAP_RUBY[point_id % pc->height];
        }
        pc_new->points.push_back(new_point);
    }

    publish_points(pc_new, pc_msg);
}


template<typename T_in_p, typename T_out_p>
void handle_pc_msg(const typename pcl::PointCloud<T_in_p>::Ptr &pc_in,
                   const typename pcl::PointCloud<T_out_p>::Ptr &pc_out) {

    // to new pointcloud
    for (int point_id = 0; point_id < pc_in->points.size(); ++point_id) {
        if (has_nan(pc_in->points[point_id]))
            continue;
        T_out_p new_point;
//        std::copy(pc->points[point_id].data, pc->points[point_id].data + 4, new_point.data);
        new_point.x = pc_in->points[point_id].x;
        new_point.y = pc_in->points[point_id].y;
        new_point.z = pc_in->points[point_id].z;
        new_point.intensity = pc_in->points[point_id].intensity;
//        new_point.ring = pc->points[point_id].ring;
//        // 计算相对于第一个点的相对时间
//        new_point.time = float(pc->points[point_id].timestamp - pc->points[0].timestamp);
        pc_out->points.push_back(new_point);
    }
}

template<typename T_in_p, typename T_out_p>
void add_ring(const typename pcl::PointCloud<T_in_p>::Ptr &pc_in,
              const typename pcl::PointCloud<T_out_p>::Ptr &pc_out) {
    // to new pointcloud
    int valid_point_id = 0;
    for (int point_id = 0; point_id < pc_in->points.size(); ++point_id) {
        if (has_nan(pc_in->points[point_id]))
            continue;
        // 跳过nan点
        pc_out->points[valid_point_id++].ring = pc_in->points[point_id].ring;
    }
}

template<typename T_in_p, typename T_out_p>
void add_time(const typename pcl::PointCloud<T_in_p>::Ptr &pc_in,
              const typename pcl::PointCloud<T_out_p>::Ptr &pc_out) {
    // to new pointcloud
    int valid_point_id = 0;
    for (int point_id = 0; point_id < pc_in->points.size(); ++point_id) {
        if (has_nan(pc_in->points[point_id]))
            continue;
        // 跳过nan点
        pc_out->points[valid_point_id++].time = float(pc_in->points[point_id].timestamp - pc_in->points[0].timestamp);
    }
}

void rsHandler_XYZIRT(const sensor_msgs::msg::PointCloud2 &pc_msg) {
    pcl::PointCloud<RsPointXYZIRT>::Ptr pc_in(new pcl::PointCloud<RsPointXYZIRT>());
    pcl::fromROSMsg(pc_msg, *pc_in);

    if (output_type == "XYZIRT") {
        pcl::PointCloud<VelodynePointXYZIRT>::Ptr pc_out(new pcl::PointCloud<VelodynePointXYZIRT>());
        handle_pc_msg<RsPointXYZIRT, VelodynePointXYZIRT>(pc_in, pc_out);
        add_ring<RsPointXYZIRT, VelodynePointXYZIRT>(pc_in, pc_out);
        add_time<RsPointXYZIRT, VelodynePointXYZIRT>(pc_in, pc_out);
        publish_points(pc_out, pc_msg);
    } else if (output_type == "XYZIR") {
        pcl::PointCloud<VelodynePointXYZIR>::Ptr pc_out(new pcl::PointCloud<VelodynePointXYZIR>());
        handle_pc_msg<RsPointXYZIRT, VelodynePointXYZIR>(pc_in, pc_out);
        add_ring<RsPointXYZIRT, VelodynePointXYZIR>(pc_in, pc_out);
        publish_points(pc_out, pc_msg);
    } else if (output_type == "XYZI") {
        pcl::PointCloud<pcl::PointXYZI>::Ptr pc_out(new pcl::PointCloud<pcl::PointXYZI>());
        handle_pc_msg<RsPointXYZIRT, pcl::PointXYZI>(pc_in, pc_out);
        publish_points(pc_out, pc_msg);
    }
}

int main(int argc, char **argv) {
    //ros::init(argc, argv, "rs_converter");
    //ros::NodeHandle nh;
    rclcpp::init(argc, argv);
    auto node = rclcpp::Node::make_shared("rs_converter");
    if (argc < 3) {
        RCLCPP_ERROR(node->get_logger(),
            "Please specify input pointcloud type( XYZI or XYZIRT) and output pointcloud type(XYZI, XYZIR, XYZIRT)!!!");
        exit(1);
    } else {
        // 输出点云类型
        output_type = argv[2];

        if (std::strcmp("XYZI", argv[1]) == 0) {
            //subRobosensePC = nh.subscribe("/rslidar_points", 1, rsHandler_XYZI);
            subRobosensePC = node->create_subscription<sensor_msgs::msg::PointCloud2>("/rslidar_points", 1, rsHandler_XYZI);
        } else if (std::strcmp("XYZIRT", argv[1]) == 0) {
            //subRobosensePC = nh.subscribe("/rslidar_points", 1, rsHandler_XYZIRT);
            subRobosensePC = node->create_subscription<sensor_msgs::msg::PointCloud2>("/rslidar_points", 1, rsHandler_XYZIRT);       
        } else {
            RCLCPP_ERROR(node->get_logger(), argv[1]);
            RCLCPP_ERROR(node->get_logger(), "Unsupported input pointcloud type. Currently only support XYZI and XYZIRT.");
            exit(1);
        }
    }
    //pubRobosensePC = nh.advertise<sensor_msgs::PointCloud2>("/velodyne_points", 1);
    pubRobosensePC = node->create_publisher<sensor_msgs::msg::PointCloud2>("/velodyne_points", 1);
    RCLCPP_INFO(node->get_logger(), "Listening to /rslidar_points ......");
    rclcpp::spin(node);
    return 0;
}

大家只需要将我的博客内容的:CMakeLists.txt、package.xml、rs_to_velodyne.cpp文件内容拷贝到到自己的文件就可以直接使用了

四、ROS2版本LIO-SAM试跑

4.1 抓包获取速腾雷达的IP地址
这一小节可以直接跳过,我自己记录用的,因为我的雷达被别人使用过,但是我又不知道IP地址是设置的啥,所以记录一下用抓包的方式获取IP

#wireshark进行抓包测试
sudo apt-get install wireshark
sudo wireshark

在这里插入图片描述

可以获取我的雷达ip 为192.168.1.123
主机地址 192.168.1.101(电脑ip需要设置成这个)
ping 192.168.1.123 可以检测是否正常通信
可视化一下看看是否能正常出点云图:

#ros+rviz可视化
roslaunch rslidar_sdk start.launch  

在这里插入图片描述

4.2 数据集准备
可能大家手上只有ROS1的bag包,这样子ros2下的算法肯定不能直接用,这个时候就需要将ROS1的bag包转换到ROS2下面去
第一步要确定自己同时安装了ROS1和ROS2
第二步配置环境变量,添加:

source /opt/ros/galactic/setup.bash
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/ros/noetic/lib

第三步安装依赖并运行bag包并订阅查看效果:

sudo apt install -y libroscpp-serialization0d ros-galactic-nmea-msgs ros-noetic-nmea-msgs
sudo apt install -y ros-galactic-rosbag2-bag-v2-plugins  ros-galactic-rosbag2-storage ros-galactic-rosbag2-storage-default-plugins ros-galactic-ros2bag ros-galactic-rosbag2-transport 

ros2 bag info -s rosbag_v2 hdl_400.bag #查看bag包信息
ros2 bag play -s rosbag_v2 hdl_400.bag #这一步就可以直接将ROS1话题转ROS2了

4.3 配置LIO-SAM

sudo apt install ros-galactic-perception-pcl \
                 ros-galactic-pcl-msgs \
                 ros-galactic-vision-opencv \
                 ros-galactic-xacro
  	   
  	   
sudo add-apt-repository ppa:borglab/gtsam-release-4.0
sudo apt install libgtsam-dev libgtsam-unstable-dev

mkdir -p ~/ros2_slam_3d_ws/src
cd ~/ros2_slam_3d_ws/src
git clone -b ros2  https://github.com/TixiaoShan/LIO-SAM.git
colcon build

这里我遇到了一个报错,额就是我把.hpp文件改成.h就可以了,具体报错内容没有记录下来
4.4 运行LIO-SAM
这一步包含启动lio-sam,启动雷达数据转换节点以及bag包播放、保存地图的操作

ros2 launch lio_sam run.launch.py
ros2 run rs_to_velodyne rs_to_velodyne XYZIRT XYZIRT
ros2 bag play -s rosbag_v2 33.bag
ros2 service call /lio_sam/save_map lio_sam/srv/SaveMap "{resolution: 0.2, destination: /Downloads/LOAM2}"

4.5 效果展示
这是我自己在园区采集的数据集,有问题随时私信我~~看到的话会回的
在这里插入图片描述
在这里插入图片描述

五、参考文献

[1]https://blog.youkuaiyun.com/qq_16539009/article/details/124060944
[2]https://www.ncnynl.com/archives/202210/5482.html
[3]https://www.ncnynl.com/archives/202209/5469.html
[4]https://blog.youkuaiyun.com/kinderkindme/article/details/127862204
[5]https://www.guyuehome.com/36725
[6]传感器标定参考:https://blog.youkuaiyun.com/weixin_42141088/article/details/118000544
[7]算法源码注释:https://blog.youkuaiyun.com/zkk9527/article/details/117957067

<think>好的,我现在需要回答用户关于FastLIO激光导航是否能部署到ROS2上的问题。首先,我得确认自己对FastLIO的理解是否正确。FastLIO是一个基于紧耦合激光雷达惯性里程计的SLAM算法,它结合了IMU和激光雷达的数据,实现高频率的状态估计,适用于高速或动态环境下的定位与建图。这点我记得没错吧? 接下来,用户想知道能否将其部署到ROS2。我需要先了解FastLIO原本支持的ROS版本。根据之前的了解,FastLIO主要是基于ROS1(比如Noetic)开发的,而ROS2ROS1在架构和通信机制上有较大差异,比如ROS1使用roscore,而ROS2去中心化了,使用DDS中间件。所以,直接兼容可能有问题,但可能存在移植的版本或方法。 然后,我需要查证是否有社区已经进行了ROS2的移植工作。如,GitHub上可能有fork的项目或者分支,将FastLIO迁移到了ROS2。如果存在这样的项目,用户就可以直接使用或者参考。如果不存在,用户可能需要自行移植,这会涉及到将代码中的ROS1 API替换为ROS2的,比如节点、话题、服务的初始化方式,消息类型的更新等。 另外,还需要考虑依赖库的情况。FastLIO可能依赖某些ROS1,比如PCL、Eigen、OpenCV等,这些在ROS2中可能已经有对应的版本,或者需要调整编译脚本。此外,像rclcpp代替roscpp,消息定义的变化(如std_msgs vs. builtin_interfaces)也需要处理。 还有,编译系统的不同。ROS1使用catkin,而ROS2使用ament或colcon。用户需要将原有的CMakeLists.txt和package.xml适配到ROS2的构建系统中,这可能涉及到依赖项的更新和编译选项的调整。 另外,实时性能也是需要考虑的因素。ROS2相比ROS1在实时性方面有所改进,可能更适合需要高频率数据处理的FastLIO。但具体部署时,还需要优化节点间的通信效率,确保数据同步和低延迟。 最后,我应该建议用户先查找现有的ROS2移植版本,如果找不到,再考虑自行移植,并提供一些步骤和注意事项,比如逐步替换API、测试节点功能、处理可能的依赖问题等。同时,提醒用户注意ROS2ROS1在消息传递、参数服务器等方面的差异,可能需要详细查阅ROS2的官方迁移指南。 总结一下,回答的结构应该是:确认FastLIO的基本情况,分析ROS1ROS2的差异,指出已有的移植可能性,给出自行移植的步骤和注意事项,最后提供参考资料或社区资源。需要确保信息准确,步骤清晰,帮助用户逐步解决问题。</think>### FastLIO激光导航与ROS2部署的逐步分析 #### 1. **FastLIO简介** FastLIO(Fast LiDAR-Inertial Odometry)是一种基于紧耦合的激光雷达-惯性里程计(LiDAR-IMU)融合算法,特点括: - **高频状态估计**:利用IMU高频数据提升激光雷达点云配准效率。 - **紧耦合优化**:直接融合IMU预积分与激光雷达观测,避免松耦合的信息损失。 - **抗运动模糊**:适合高速或剧烈运动场景(如无人机、自动驾驶)。 #### 2. **ROS1ROS2的兼容性差异** - **通信机制**:ROS1依赖`roscore`中心节点,ROS2采用去中心化的DDS(数据分发服务)。 - **API变更**:ROS2的节点、话题、服务接口(如`rclcpp`替代`roscpp`)与ROS1不兼容。 - **编译系统**:ROS1使用`catkin`,ROS2使用`colcon`和`ament`构建工具。 - **消息格式**:ROS2消息定义更严格(如时间戳类型为`builtin_interfaces/Time`,而非`std_msgs/Header`)。 #### 3. **现有ROS2移植情况** - **官方支持**:截至2023年,FastLIO官方仓库([HKUST-Aerial-Robotics/FastLIO](https://github.com/hku-mars/FAST_LIO))主要支持ROS1(Noetic)。 - **社区移植**: - 部分开发者已尝试移植到ROS2如通过GitHub分支(如搜索`FastLIO ROS2`可找到非官方版本)。 - 需验证代码可靠性,可能存在功能缺失或性能差异。 #### 4. **自行移植到ROS2的关键步骤** 若需手动移植,需逐步完成以下操作: **步骤1:代码结构适配** -ROS1的`package.xml`依赖替换为ROS2版本(如`rclcpp`、`sensor_msgs`)。 - 更新`CMakeLists.txt`,替换`find_package(catkin)`为`find_package(ament_cmake)`。 **步骤2:API替换** - 节点初始化:`ros::init()` → `rclcpp::init()`,`ros::NodeHandle` → `rclcpp::Node`。 - 话题发布/订阅: ```cpp // ROS1 ros::Publisher pub = nh.advertise<sensor_msgs::PointCloud2>("topic", 10); // ROS2 auto pub = node->create_publisher<sensor_msgs::msg::PointCloud2>("topic", 10); ``` - 时间处理:`ros::Time` → `rclcpp::Clock`,消息时间戳使用`builtin_interfaces/msg/Time`。 **步骤3:消息类型更新** - 替换所有ROS1消息头(如`std_msgs/Header`)为ROS2格式: ```cpp // ROS1 #include <std_msgs/Header.h> // ROS2 #include <std_msgs/msg/header.hpp> ``` **步骤4:编译与调试** - 使用`colcon build`替代`catkin_make`,解决依赖项冲突。 - 测试关键功能:IMU数据同步、点云处理、地图输出。 #### 5. **潜在问题与优化建议** - **实时性**:ROS2支持实时节点(`rclcpp::RealTime`),可优化数据流水线。 - **依赖冲突**:检查第三方库(如`PCL`、`Eigen`)是否与ROS2兼容。 - **性能测试**:对比移植前后算法精度与计算资源占用。 #### 6. **替代方案** 若移植成本过高,可考虑: - 通过`ros1_bridge`在ROS2中桥接ROS1节点(需同时运行ROS1ROS2环境)。 - 使用其他ROS2原生LiDAR-IMU算法(如`LIO-SAM`的ROS2分支)。 #### 参考资料 - FastLIO官方文档:[FAST_LIO GitHub](https://github.com/hku-mars/FAST_LIO) - ROS2迁移指南:[ROS2 Migration Guide](https://docs.ros.org/en/rolling/Contributing/Migration-Guide.html)
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值