激光雷达在ROS2中的使用

5.1 ROS2里的激光雷达数据

激光雷达的旋转部分搭载激光测距探头,其在旋转的过程中每隔一定角度就会测量一次距离值。当其旋转一圈时,刚好可以对周围障碍物进行均匀的测距采样,这么一组测距值就是激光雷达的输出数值。      

消息类型

ROS2中的激光雷达的节点会在指定名称的话题里发布消息。话题名称在REP-138中推荐为“/scan”。消息的类型为sensor_msgs::LaserScan,其格式如图所示:      

在速度控制中,我们的发布者节点也是定义了类似的消息格式的

适用的时候要提前定义:

  geometry_msgs::msg::Twist vel_msg;
  vel_msg.linear.x = 0.1;
  vel_msg.linear.y = 0.0;
  vel_msg.linear.z = 0.0;
  vel_msg.angular.x = 0.0;
  vel_msg.angular.y = 0.0;
  vel_msg.angular.z = 0.0;

5.2 在RViz2中查看激光雷达数据

在ROS2中,提供了一个图形化工具,可以直观的查看传感器的数值。这个工具名字叫“RViz2”,其中“RViz”是“ROS Visualization Tool”的缩写,“2”表示第二代。      

保持之前的WPR仿真机器人窗口不关。

开一个新终端(ctrl+ALT+T)

cd ros2_ws
source ~/ros2_ws/install/setup.bash
rviz2

打开后,选择坐标系为base_footprint

然后现在画面上啥也没有,需要添加机器人和激光雷达

找到左下角add

添加robotmodel

现在还是啥也没有,别着急,点击这个Descriptiontopic

选择这个

出现啦!

然后再点击Add,把Laserscan点出来

一开始也是禁用状态,你必须为他添加话题,点击Topic

 这个点可能不是很明显

可以把这个size调大一些,就能看清楚1

看到这样的画面就是成功了

你可以点击File保存为XXX.rviz,下次就能rviz2 -d ~/XXX.rviz启动了

5.3 激光雷达数据获取(订阅者节点编写)

激光雷达数据的获取是通过订阅激光雷达发布的话题,从话题中获取激光雷达发出的消息包来实现的。如图所示,将会实现一个订阅者节点,订阅"/scan"话题。从此话题中接收sensor_msgs::LaserScan类型的消息包,并解析出雷达的测距数值。

同理,先建包

cd ~/ros2_ws/src
ros2 pkg create lidar_pkg

在包内的src中创建lidar_data.cpp,复制以下内容

#include <rclcpp/rclcpp.hpp>
#include <sensor_msgs/msg/laser_scan.hpp>
std::shared_ptr<rclcpp::Node> node;
void LidarCallback(const sensor_msgs::msg::LaserScan::SharedPtr msg)
{
    int nNum = msg->ranges.size();    
    int nMid = nNum / 2;
    float fMidDist = msg->ranges[nMid];
    RCLCPP_INFO(node->get_logger(), "ranges[%d] = %f m", nMid, fMidDist);
}
int main(int argc, char** argv)
{
    rclcpp::init(argc, argv);
    node = std::make_shared<rclcpp::Node>("lidar_data_node");
    auto lidar_sub = node->create_subscription<sensor_msgs::msg::LaserScan>("/scan", 10, LidarCallback);
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

修改MakeList

在第一个实验中我们已经了解,所有的include都需要在MakeList中的find_package添加。

引入了sensor_msgs::Twist,因此要

find_package(rclcpp REQUIRED) 
find_package(sensor_msgs REQUIRED)

同时要添加这个ament_target,才能自动链接这两个库。

这两步就是先找到库在哪里,再链接上用到这两个库的文件。

ament_target_dependencies(lidar_data "rclcpp" "sensor_msgs")

然后需要创建该节点的可执行文件exe,命名为publisher_node,引用src下的publisher_node.cpp

add_executable(lidar_data src/lidar_data.cpp)

接着要把上一步这个可执行文件(节点Node)安装到指定文件夹(package)下。这个文件夹ros2是能够访问的,这样才能通过ros2去启动他。

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

完整Cmakelist

cmake_minimum_required(VERSION 3.8)
project(lidar_pkg)

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(sensor_msgs REQUIRED)
# ==================================================

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

# ==================== 新增可执行文件配置 ====================
add_executable(lidar_data src/lidar_data.cpp)
ament_target_dependencies(lidar_data "rclcpp" "sensor_msgs")

install(TARGETS lidar_data
  DESTINATION lib/${PROJECT_NAME})
# ==========================================================

ament_package()

修改package.xml

还记得第一个示例的“注意”吧?

CMakeLists.txt 里的 find_package 是给编译器看的;

package.xml 里的 <depend> 是给包管理器(rosdep)和构建系统(colcon)看的。两者缺一不可,必须保持同步。

<depend>rclcpp</depend>
<depend>sensor_msgs</depend>

完整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>lidar_pkg</name>
  <version>0.0.0</version>
  <description>TODO: Package description</description>
  <maintainer email="unicorn@todo.todo">unicorn</maintainer>
  <license>TODO: License declaration</license>

  <buildtool_depend>ament_cmake</buildtool_depend>

  <depend>rclcpp</depend>
  <depend>sensor_msgs</depend>

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

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

在ros2_ws colcon build一下

colcon build

保持之前的wpr别关,关了就新开一个终端

source install/setup.bash
ros2 launch wpr_simulation2 wpb_simple.launch.py

在之前colconbuild的终端source一下

source install/setup.bash

执行

ros2 run lidar_pkg lidar_data

可以看到数据源源不断传入了

5.4 基于激光雷达的避障实现(发布者-订阅者闭环)

下面将激光雷达数据获取和速度控制进行结合,实现一个避障程序。程序订阅雷达数据的“/scan”话题,从中接收激光雷达节点发来的sensor_msgs::LaserScan类型消息包,并解析出雷达测距值。根据雷达测距值让机器人做出反应:前方没有障碍物就前进,前方遇到障碍物就转向。

在lidar_data.cpp下面新建lidar_behaviour.cpp

很好理解,main中创建pub和sub两个节点,注意由于pub需要发布速度消息包的,而发布速度是要在接收到激光雷达后通过回调函数触发的,因此要在外声明。

然后LidarCallback中处理避障逻辑。

#include <rclcpp/rclcpp.hpp>
#include <sensor_msgs/msg/laser_scan.hpp>
#include <geometry_msgs/msg/twist.hpp>
std::shared_ptr<rclcpp::Node> node;
rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr vel_pub;
int nCount = 0;
void LidarCallback(const sensor_msgs::msg::LaserScan::SharedPtr msg)
{  int nNum = msg->ranges.size();    
    int nMid = nNum / 2;
    float fMidDist = msg->ranges[nMid];
    RCLCPP_INFO(node->get_logger(), "ranges[%d] = %f m", nMid, fMidDist);
    if (nCount > 0)
    {  nCount--;
        return;    }
    geometry_msgs::msg::Twist vel_msg;
    if (fMidDist < 1.5f)
    {  vel_msg.angular.z = 0.3;
        nCount = 100;    }
    else
    {  vel_msg.linear.x = 0.1;    }
    vel_pub->publish(vel_msg);
}
int main(int argc, char** argv)
{
    rclcpp::init(argc, argv);

    node = std::make_shared<rclcpp::Node>("lidar_behavior_node");

    vel_pub = node->create_publisher<geometry_msgs::msg::Twist>("/cmd_vel", 10);
    auto lidar_sub = node->create_subscription<sensor_msgs::msg::LaserScan>("/scan", 10, LidarCallback);

    rclcpp::spin(node);

    rclcpp::shutdown();

    return 0;
}

每收到一帧激光雷达数据,ROS 2 就会自动调用这个函数。

int nNum = msg->ranges.size();
int nMid = nNum / 2;
float fMidDist = msg->ranges[nMid];
  • ranges 是激光雷达各个角度的测距数组,单位是米。

  • nMid = nNum / 2 取数组中间的下标,一般对应雷达的正前方(前提是雷达扫描角度对称)。

  • fMidDist 就是正前方障碍物的距离。

RCLCPP_INFO(node->get_logger(), "ranges[%d] = %f m", nMid, fMidDist);

打印正前方的距离,方便调试。

if (nCount > 0)
{
    nCount--;
    return;
}

这是一个冷却计数器。如果 nCount > 0,就减 1 并直接返回,不发布任何速度指令。
它的作用是:当机器人决定转向后,让后续 100 次雷达回调都“跳过控制”,相当于保持上一次发布的转向指令一段时间。

geometry_msgs::msg::Twist vel_msg;

创建一个速度指令消息,包含线速度 linear 和角速度 angular。

if (fMidDist < 1.5f)
{
    vel_msg.angular.z = 0.3;
    nCount = 100;
}

如果正前方距离小于 1.5 米,说明有障碍物:

  • 设置角速度 angular.z = 0.3 rad/s(原地左转或右转,取决于正负号,这里是正数)。

  • 设置 nCount = 100,表示接下来 100 次雷达回调都跳过控制,让机器人持续转向一段时间。

else
{
    vel_msg.linear.x = 0.1;
}

如果前方没有障碍物:

  • 设置线速度 linear.x = 0.1 m/s,让机器人缓慢前进。

  • 角速度默认为 0,所以是直行。

vel_pub->publish(vel_msg);

把速度指令发布出去,底盘收到后就会执行。

整体逻辑

收到雷达数据
  ↓
取正前方距离
  ↓
如果正在冷却(nCount > 0),跳过本次控制
  ↓
如果正前方 < 1.5m:
    发布角速度 0.3,并设置冷却 100 次
否则:
    发布线速度 0.1

修改MakeList

在第一个实验中我们已经了解,所有的include都需要在MakeList中的find_package添加。

引入了sensor_msgs::Twist,因此要

find_package(rclcpp REQUIRED) 
find_package(sensor_msgs REQUIRED) 
find_package(geometry_msgs REQUIRED)

同时要添加这个ament_target,才能自动链接这两个库。

这两步就是先找到库在哪里,再链接上用到这两个库的文件。

ament_target_dependencies(lidar_behavior "rclcpp" "sensor_msgs" "geometry_msgs")

然后需要创建该节点的可执行文件exe,命名为publisher_node,引用src下的publisher_node.cpp

add_executable(lidar_behavior src/lidar_behavior.cpp)

接着要把上一步这个可执行文件(节点Node)安装到指定文件夹(package)下。这个文件夹ros2是能够访问的,这样才能通过ros2去启动他。

install(TARGETS  lidar_data  lidar_behavior
DESTINATION  lib/${PROJECT_NAME})

完整Cmakelist

cmake_minimum_required(VERSION 3.8)
project(lidar_pkg)

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(sensor_msgs REQUIRED)
find_package(geometry_msgs REQUIRED)  # <--- 新增 geometry_msgs
# ====================================================

if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  set(ament_cmake_copyright_FOUND TRUE)
  set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

# ==================== 可执行文件配置 ====================
# 1. 原有的 lidar_data 节点
add_executable(lidar_data src/lidar_data.cpp)
ament_target_dependencies(lidar_data "rclcpp" "sensor_msgs")

# 2. 新增的 lidar_behavior 节点
add_executable(lidar_behavior src/lidar_behavior.cpp)
ament_target_dependencies(lidar_behavior "rclcpp" "sensor_msgs" "geometry_msgs")

# ==================== 安装规则 ====================
# 将两个可执行文件合并到同一个 install 指令中
install(TARGETS 
  lidar_data 
  lidar_behavior
  DESTINATION lib/${PROJECT_NAME})
# ====================================================

ament_package()

修改package.xml

还记得第一个示例的“注意”吧?

CMakeLists.txt 里的 find_package 是给编译器看的;

package.xml 里的 <depend> 是给包管理器(rosdep)和构建系统(colcon)看的。两者缺一不可,必须保持同步。

<depend>rclcpp</depend>
<depend>sensor_msgs</depend>
<depend>geometry_msgs</depend>

完整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>lidar_pkg</name>
  <version>0.0.0</version>
  <description>TODO: Package description</description>
  <maintainer email="unicorn@todo.todo">unicorn</maintainer>
  <license>TODO: License declaration</license>

  <buildtool_depend>ament_cmake</buildtool_depend>

  <depend>rclcpp</depend>
  <depend>sensor_msgs</depend>
  <depend>geometry_msgs</depend>


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

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

在ros2_ws colcon build一下

colcon build

保持之前的wpr别关,关了就新开一个终端

source install/setup.bash
ros2 launch wpr_simulation2 wpb_simple.launch.py

在之前colconbuild的终端source一下

source install/setup.bash

执行

ros2 run lidar_pkg lidar_behavior

可以看到小车避开了橱柜:

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐