ROS : Navigation 基于碰撞传感器、悬崖传感器的实时避障 [kobuki]

话题消息

碰撞传感器

~events/bumper ( kobuki_msgs/BumperEvent ) 提供一个保险杠事件,每当按下或释放保险杠时都会发生此事件。

~$ rostopic info /mobile_base/events/bumper
Type: kobuki_msgs/BumperEvent

Publishers:
 * /mobile_base_nodelet_manager (http://10.42.0.1:44834/)

Subscribers:
 * /mobile_base_nodelet_manager (http://10.42.0.1:44834/)

~$ rosmsg show kobuki_msgs/BumperEvent
uint8 LEFT=0
uint8 CENTER=1
uint8 RIGHT=2
uint8 RELEASED=0
uint8 PRESSED=1
uint8 bumper
uint8 state

~$ rostopic echo /mobile_base/events/bumper
bumper: 1  // 左前 0 ,正前 1 ,右前 2
state: 1  // 保险杠按压(撞击)为 1 ,未撞击为 0
---
bumper: 1
state: 0
---

悬崖传感器

~events/cliff ( kobuki_msgs/CliffEvent ) 提供悬崖传感器事件,每当机器人接近或离开悬崖时就会发生。

~$ rostopic info /mobile_base/events/cliff
Type: kobuki_msgs/CliffEvent

Publishers:
 * /mobile_base_nodelet_manager (http://10.42.0.1:44834/)

Subscribers: None
 * /mobile_base_nodelet_manager (http://10.42.0.1:44834/)


~$ rosmsg info kobuki_msgs/CliffEvent
uint8 LEFT=0
uint8 CENTER=1
uint8 RIGHT=2
uint8 FLOOR=0
uint8 CLIFF=1
uint8 sensor
uint8 state
uint16 bottom


~$ rostopic echo /mobile_base/events/cliff

---
sensor: 1
state: 1
bottom: 616
---
sensor: 2
state: 1
bottom: 571
---
sensor: 2
state: 0
bottom: 544
---

点云数据

~sensors/bumper_pointcloud ( sensor_msgs/PointCloud2 ) 触发碰撞传感器、悬崖传感器事件时转 点云数据 ,代码见 kobuki_bumper2pc.cpp

# The point cloud data may be organized 2d (image-like) or 1d
# (unordered). Point clouds organized as 2d images may be produced by
# camera depth sensors such as stereo or time-of-flight.

# Time of sensor data acquisition, and the coordinate frame ID (for 3d
# points).
Header header

# 2D structure of the point cloud. If the cloud is unordered, height is
# 1 and width is the length of the point cloud.
uint32 height
uint32 width

# Describes the channels and their layout in the binary data blob.
PointField[] fields

bool    is_bigendian # Is this data bigendian?
uint32  point_step   # Length of a point in bytes
uint32  row_step     # Length of a row in bytes
uint8[] data         # Actual point data, size is (row_step*height)

bool is_dense        # True if there are no invalid points
  • height 与 width 是指点云数据的高和宽,一般无序点云的高为 1 ,宽为点云中激光点的个数;结构化点云的高和宽均大于 1 。

  • sensor_msgs/PointField[] fields 由以下组成:

    # This message holds the description of one point entry in the
    # PointCloud2 message format.
    uint8 INT8    = 1
    uint8 UINT8   = 2
    uint8 INT16   = 3
    uint8 UINT16  = 4
    uint8 INT32   = 5
    uint8 UINT32  = 6
    uint8 FLOAT32 = 7
    uint8 FLOAT64 = 8
    
    string name      # Name of field
    uint32 offset    # Offset from start of point struct
    uint8  datatype  # Datatype enumeration, see above
    uint32 count     # How many elements in the field
    
    • name 是指点云包含的域的名称,如 x 、 y 、 z 、 intensity 、 ring 等;
    • offset 是指在 data 的每个数据中该域的偏移量;
    • datatype 是指以上 1~8 的数据类型;
    • count 是指该域有多少个元素,一般为 1 。
    • 每一帧点云均有该项数据。
  • is_bigendian 数据是大端存储还是小端存储的标志。

  • pointstep 表示每个点的字节长度,常见的为 32 。

  • rowstep 表示每行的字节长度。

  • data 表示所有的点的数据,以字节存储。 uint8[] data 代表 vector 类型,有 size 、 push_back 、 clear 等操作。

  • is_dense 若是 true ,代表点云数据中不包含无效点( nan 点);若是 false ,代表点云中包含无效点。

~$ rostopic info /mobile_base/sensors/bumper_pointcloud
Type: sensor_msgs/PointCloud2

Publishers:
 * /mobile_base_nodelet_manager (http://10.42.0.1:44834/)

Subscribers:
 * /move_base (http://10.42.0.1:42621/)


~$ rosmsg show sensor_msgs/PointCloud2
std_msgs/Header header
  uint32 seq
  time stamp
  string frame_id
uint32 height
uint32 width
sensor_msgs/PointField[] fields
  uint8 INT8=1
  uint8 UINT8=2
  uint8 INT16=3
  uint8 UINT16=4
  uint8 INT32=5
  uint8 UINT32=6
  uint8 FLOAT32=7
  uint8 FLOAT64=8
  string name
  uint32 offset
  uint8 datatype
  uint32 count
bool is_bigendian
uint32 point_step
uint32 row_step
uint8[] data
bool is_dense


~$ rostopic echo /mobile_base/sensors/bumper_pointcloud
header:  // 点云的头信息
  seq: 0
  stamp:  // 时间戳
    secs: 1455218508
    nsecs: 292744004
  frame_id: "/base_link"
height: 1  // 如果 cloud 是无序的,则 height 是 1
width: 3  // 点云的长度
fields:  // sensor_msgs/PointField[] fields
  -
    name: "x"
    offset: 0
    datatype: 7
    count: 1
  -
    name: "y"
    offset: 4
    datatype: 7
    count: 1
  -
    name: "z"
    offset: 8
    datatype: 7
    count: 1
is_bigendian: False
point_step: 12  // 一个点占的比特数
row_step: 36  // 一行的长度占用的比特数
data: [221, 206, 8, 66, 67, 240, 187, 66, 10, 215, 35, 61, 143, 194, 117, 62, 0, 0, 0, 0, 10, 215, 35, 61, 221, 206, 8, 66, 67, 240, 187, 194, 10, 215, 35, 61]  // Actual point data, size is (row_step*height) 这部分数据是点云的二进制数据流,必须要单独解析,直接读取没有任何意义
is_dense: True  // True 代表没有非法数据点
---
header:
  seq: 1
  stamp:
    secs: 1455218508
    nsecs: 347700570
  frame_id: "/base_link"
height: 1
width: 3
fields:
  -
    name: "x"
    offset: 0
    datatype: 7
    count: 1
  -
    name: "y"
    offset: 4
    datatype: 7
    count: 1
  -
    name: "z"
    offset: 8
    datatype: 7
    count: 1
is_bigendian: False
point_step: 12
row_step: 36
data: [221, 206, 8, 66, 67, 240, 187, 66, 10, 215, 35, 61, 143, 194, 117, 62, 0, 0, 0, 0, 10, 215, 35, 61, 221, 206, 8, 66, 67, 240, 187, 194, 10, 215, 35, 61]
is_dense: True
---

传感器详细信息

~sensors/core ( kobuki_msgs/SensorState ) kobuki 整体传感器详细信息, 50Hz 。

note :

kobuki_bumper2pc.cpp 是利用该话题消息将碰撞传感器和悬崖传感器数据转点云数据的。主要利用了 uint8 bumper 和 uint8 cliff , 这两个值在多个传感器触发时会将各个传感器对应的状态值求和得到。

~$ rostopic info /mobile_base/sensors/core
Type: kobuki_msgs/SensorState

Publishers:
 * /mobile_base_nodelet_manager (http://10.42.0.1:44834/)

Subscribers:
 * /mobile_base_nodelet_manager (http://10.42.0.1:44834/)


~$ rosmsg info kobuki_msgs/SensorState
uint8 BUMPER_RIGHT=1
uint8 BUMPER_CENTRE=2
uint8 BUMPER_LEFT=4
uint8 WHEEL_DROP_RIGHT=1
uint8 WHEEL_DROP_LEFT=2
uint8 CLIFF_RIGHT=1
uint8 CLIFF_CENTRE=2
uint8 CLIFF_LEFT=4
uint8 BUTTON0=1
uint8 BUTTON1=2
uint8 BUTTON2=4
uint8 DISCHARGING=0
uint8 DOCKING_CHARGED=2
uint8 DOCKING_CHARGING=6
uint8 ADAPTER_CHARGED=18
uint8 ADAPTER_CHARGING=22
uint8 OVER_CURRENT_LEFT_WHEEL=1
uint8 OVER_CURRENT_RIGHT_WHEEL=2
uint8 OVER_CURRENT_BOTH_WHEELS=3
uint8 DIGITAL_INPUT0=1
uint8 DIGITAL_INPUT1=2
uint8 DIGITAL_INPUT2=4
uint8 DIGITAL_INPUT3=8
uint8 DB25_TEST_BOARD_CONNECTED=64
std_msgs/Header header
  uint32 seq
  time stamp
  string frame_id
uint16 time_stamp
uint8 bumper
uint8 wheel_drop
uint8 cliff
uint16 left_encoder
uint16 right_encoder
int8 left_pwm
int8 right_pwm
uint8 buttons
uint8 charger
uint8 battery
uint16[] bottom
uint8[] current
uint8 over_current
uint16 digital_input
uint16[] analog_input


# 碰撞传感器
~$ rostopic echo /mobile_base/sensors/core
---
header:
  seq: 13711
  stamp:
    secs: 1455208898
    nsecs: 480690468
  frame_id: ''
time_stamp: 38084
bumper: 4
wheel_drop: 0
cliff: 0
left_encoder: 5062
right_encoder: 5013
left_pwm: 0
right_pwm: 0
buttons: 0
charger: 0
battery: 156
bottom: [1572, 1895, 1633]
current: [0, 0]
over_current: 0
digital_input: 8
analog_input: [4095, 4095, 4095, 4095]
---
header:
  seq: 13754
  stamp:
    secs: 1455208900
    nsecs: 856141130
  frame_id: ''
time_stamp: 40464
bumper: 6
wheel_drop: 0
cliff: 0
left_encoder: 5046
right_encoder: 5012
left_pwm: 0
right_pwm: 0
buttons: 0
charger: 0
battery: 156
bottom: [1573, 1893, 1653]
current: [0, 0]
over_current: 0
digital_input: 8
analog_input: [4095, 4095, 4095, 4095]
---
header:
  seq: 13757
  stamp:
    secs: 1455208900
    nsecs: 996265722
  frame_id: ''
time_stamp: 40604
bumper: 7
wheel_drop: 0
cliff: 0
left_encoder: 5044
right_encoder: 5012
left_pwm: 0
right_pwm: 0
buttons: 0
charger: 0
battery: 156
bottom: [1584, 1898, 1634]
current: [0, 0]
over_current: 0
digital_input: 8
analog_input: [4095, 4095, 4095, 4095]
---

# 悬崖传感器
~$ rostopic echo /mobile_base/sensors/core
---
header:
  seq: 20113
  stamp:
    secs: 1455209221
    nsecs: 398578496
  frame_id: ''
time_stamp: 33904
bumper: 0
wheel_drop: 3
cliff: 1
left_encoder: 5333
right_encoder: 5051
left_pwm: 0
right_pwm: 0
buttons: 0
charger: 0
battery: 156
bottom: [383, 636, 666]
current: [0, 0]
over_current: 0
digital_input: 8
analog_input: [4095, 4095, 4095, 4095]
---
header:
  seq: 20111
  stamp:
    secs: 1455209221
    nsecs: 316537941
  frame_id: ''
time_stamp: 33824
bumper: 0
wheel_drop: 3
cliff: 3
left_encoder: 5333
right_encoder: 5051
left_pwm: 0
right_pwm: 0
buttons: 0
charger: 0
battery: 156
bottom: [213, 519, 1202]
current: [0, 0]
over_current: 0
digital_input: 8
analog_input: [4095, 4095, 4095, 4095]
---
header:
  seq: 20175
  stamp:
    secs: 1455209224
    nsecs: 513620609
  frame_id: ''
time_stamp: 37024
bumper: 0
wheel_drop: 3
cliff: 7
left_encoder: 5333
right_encoder: 5051
left_pwm: 0
right_pwm: 0
buttons: 0
charger: 0
battery: 155
bottom: [191, 149, 78]
current: [0, 0]
over_current: 0
digital_input: 8
analog_input: [4095, 4095, 4095, 4095]
---

基于碰撞传感器、悬崖传感器的实时避障

通过碰撞传感器、悬崖传感器实时添加障碍物的方式是:通过修改 global_costmap 或者 local_costmap 配置文件,调用 Costmap 的障碍物层(也就是,自定义创建 costmap layer 的最后一步, costmap_2d 功能包已经完成前面几步)。示例如下:

  • 修改 costmap_common_params_nav.yaml 文件,在障碍物层添加障碍数据来源 bump ,指定数据类型是 PointCloud2 ,指定话题为 mobile_base/sensors/bumper_pointcloud 。

    由于触发碰撞传感器、悬崖传感器事件后均转为 topic 为 /mobile_base/sensors/bumper_pointcloud 的点云数据,因此,这里指定的数据来源 bump 不仅仅是碰撞传感器的,还包括悬崖传感器的。

    # ...
    
    obstacle_layer:
      # ...
      observation_sources:  scan bump
      scan:
        data_type: LaserScan
        topic: scan
        # ...
      bump:
        data_type: PointCloud2
        topic: mobile_base/sensors/bumper_pointcloud
        marking: true
        clearing: true
        min_obstacle_height: 0.0
        max_obstacle_height: 0.1
        expected_update_rate: 0
    # ...
    
  • 修改 global_costmap_params.yaml 、 local_costmap_params.yaml 文件,调用 Costmap 的障碍物层,这里指定的是 costmap_2d::VoxelLayer 。需要注意的是,插件的调用顺序是从最底层到最顶层。

    local_costmap:
       # ...
       plugins:
        - {name: static_layer,        type: "costmap_2d::StaticLayer"}
        - {name: obstacle_layer,      type: "costmap_2d::VoxelLayer"}
        - {name: inflation_layer,     type: "costmap_2d::InflationLayer"}
    
    global_costmap:
       # ...
       plugins:
         - {name: static_layer,            type: "costmap_2d::StaticLayer"}
         - {name: obstacle_layer,          type: "costmap_2d::VoxelLayer"}
         - {name: inflation_layer,         type: "costmap_2d::InflationLayer"}
    

转点云数据源码解读

kobuki_bumper2pc 是碰撞传感器和悬崖传感器点云 Nodelet 节点实现。

├── CHANGELOG.rst
├── CMakeLists.txt
├── include
│   └── kobuki_bumper2pc
│       └── kobuki_bumper2pc.hpp  # 碰撞传感器和悬崖传感器点云 Nodelet 类定义
├── launch
│   └── standalone.launch         # 独立启动保险杆和悬崖 Nodelet 节点
├── package.xml
├── plugins
│   └── nodelet_plugins.xml       # Nodelet 插件
└── src
    └── kobuki_bumper2pc.cpp      # 碰撞传感器和悬崖传感器点云 Nodelet 类实现

kobuki_bumper2pc.cpp

kobuki_bumper2pc.cpp 碰撞传感器和悬崖传感器点云 Nodelet 类实现:

  • 发布导航包可使用的点云话题
  • 在导航中作为 Nodelet 与 kobuki_node 一起工作
Bumper2PcNodelet::onInit
void Bumper2PcNodelet::onInit()
{
  ros::NodeHandle nh = this->getPrivateNodeHandle();

  // Bumper/cliff pointcloud distance to base frame; should be something like the robot radius plus
  // costmap resolution plus an extra to cope with robot inertia. This is a bit tricky parameter: if
  // it's too low, costmap will ignore this pointcloud (the robot footprint runs over the hit obstacle),
  // but if it's too big, hit obstacles will be mapped too far from the robot and the navigation around
  // them will probably fail.
  std::string base_link_frame;
  double r, h, angle;
  nh.param("pointcloud_radius", r, 0.25); pc_radius_ = r;  // 碰撞传感器/悬崖传感器点云到底座半径范围
  nh.param("pointcloud_height", h, 0.04); pc_height_ = h;  // 碰撞传感器/悬崖传感器点云高度
  nh.param("side_point_angle", angle, 0.34906585);    // 默认约 20 度
      nh.param<std::string>("base_link_frame", base_link_frame, "/base_link");  // 发布的点云数据是相对于 /base_link 的,也就是相对于机器人中心位置的

      // Lateral points x/y coordinates; we need to store float values to memcopy later  // 左右位置的传感器的坐标
      p_side_x_ = + pc_radius_*sin(angle); // angle degrees from vertical  // 左右位置的碰撞传感器/悬崖传感器发布的点云数据的 x 轴坐标是 pc_radius_*sin(angle)
      p_side_y_ = + pc_radius_*cos(angle); // angle degrees from vertical  // 左侧碰撞传感器/悬崖传感器发布的点云数据的 y 轴坐标是 pc_radius_*cos(angle)
      n_side_y_ = - pc_radius_*cos(angle); // angle degrees from vertical  // 右侧碰撞传感器/悬崖传感器发布的点云数据的 y 轴坐标是 -1.0*pc_radius_*cos(angle)

  // Prepare constant parts of the pointcloud message to be  published
  pointcloud_.header.frame_id = base_link_frame;
  pointcloud_.width  = 3;  // 点云的宽为 3 (左中右)
  pointcloud_.height = 1;  // 无序点云的高为 1
  pointcloud_.fields.resize(3);

  // Set x/y/z as the only fields  // 点云包含的域的名称
  pointcloud_.fields[0].name = "x";
  pointcloud_.fields[1].name = "y";
  pointcloud_.fields[2].name = "z";

  int offset = 0;
  // All offsets are *4, as all field data types are float32
  for (size_t d = 0; d < pointcloud_.fields.size(); ++d, offset += 4)
  {
    pointcloud_.fields[d].count    = 1;  // 指该域有多少个元素,一般为 1
    pointcloud_.fields[d].offset   = offset;  // offset 是在 data 的每个数据中该域的偏移量
    pointcloud_.fields[d].datatype = sensor_msgs::PointField::FLOAT32;
  }

  pointcloud_.point_step = offset;  // 每个点的字节长度
  pointcloud_.row_step   = pointcloud_.point_step * pointcloud_.width;  // 每行的字节长度

  pointcloud_.data.resize(3 * pointcloud_.point_step);  // data 表示所有的点的数据,以字节存储
  pointcloud_.is_bigendian = false;  // 数据是大端存储还是小端存储的标志
  pointcloud_.is_dense     = true;  // 点云数据中不包含无效点( nan 点)

  // Bumper/cliff "points" fix coordinates (the others depend on sensor activation/deactivation)

  // y: always 0 for central bumper  // 中间传感器的点云 y 的值总为 0
  memcpy(&pointcloud_.data[1 * pointcloud_.point_step + pointcloud_.fields[1].offset], &ZERO, sizeof(float));

  // z: constant elevation from base frame  // 点云 z 的值为 pc_height_
  memcpy(&pointcloud_.data[0 * pointcloud_.point_step + pointcloud_.fields[2].offset], &pc_height_, sizeof(float));
  memcpy(&pointcloud_.data[1 * pointcloud_.point_step + pointcloud_.fields[2].offset], &pc_height_, sizeof(float));
  memcpy(&pointcloud_.data[2 * pointcloud_.point_step + pointcloud_.fields[2].offset], &pc_height_, sizeof(float));

  // 这两个话题均做了重映射,见 standalone.launch
  pointcloud_pub_  = nh.advertise <sensor_msgs::PointCloud2> ("pointcloud", 10);  // 发布到 pointcloud 话题
  core_sensor_sub_ = nh.subscribe("core_sensors", 10, &Bumper2PcNodelet::coreSensorCB, this);  // 订阅 core_sensors 话题,数据类型是 SensorState ,进入回调函数 Bumper2PcNodelet::coreSensorCB

  ROS_INFO("Bumper/cliff pointcloud configured at distance %f and height %f from base frame", pc_radius_, pc_height_);
}

memcpy()

void *memcpy ( void *destination, const void *source, size_t num );

  • destination 指向用于存储复制内容的目标数组,类型强制转换为 void* 指针。
  • source 指向要复制的数据源,类型强制转换为 void* 指针。
  • num 要被复制的字节数。
Bumper2PcNodelet::coreSensorCB

Bumper2PcNodelet::onInit 中已经固定设置好中间传感器的 y 的值以及所有传感器的 z 的值。该回调函数将利用 SensorState 消息类型的数据设置中间传感器 x 的值以及两侧传感器 x y 的值。

void Bumper2PcNodelet::coreSensorCB(const kobuki_msgs::SensorState::ConstPtr& msg)
{
  if (pointcloud_pub_.getNumSubscribers() == 0)
    return;

  // We publish just one "no events" pc (with all three points far away) and stop spamming when bumper/cliff conditions disappear
  if (! msg->bumper && ! msg->cliff && ! prev_bumper && ! prev_cliff)
    return;

  prev_bumper = msg->bumper;
  prev_cliff  = msg->cliff;

  // We replicate the sensors order of bumper/cliff event messages: LEFT = 0, CENTER = 1 and RIGHT = 2
  // For any of {left/center/right} with no bumper/cliff event, we publish a faraway point that won't get used
  if ((msg->bumper & kobuki_msgs::SensorState::BUMPER_LEFT) ||
      (msg->cliff  & kobuki_msgs::SensorState::CLIFF_LEFT))  //左侧传感器
  {
    memcpy(&pointcloud_.data[0 * pointcloud_.point_step + pointcloud_.fields[0].offset], &p_side_x_, sizeof(float));
    memcpy(&pointcloud_.data[0 * pointcloud_.point_step + pointcloud_.fields[1].offset], &p_side_y_, sizeof(float));
  }
  else
  {
    memcpy(&pointcloud_.data[0 * pointcloud_.point_step + pointcloud_.fields[0].offset], &P_INF_X, sizeof(float));
    memcpy(&pointcloud_.data[0 * pointcloud_.point_step + pointcloud_.fields[1].offset], &P_INF_Y, sizeof(float));
  }

  if ((msg->bumper & kobuki_msgs::SensorState::BUMPER_CENTRE) ||
      (msg->cliff  & kobuki_msgs::SensorState::CLIFF_CENTRE))  //中间传感器
  {
    memcpy(&pointcloud_.data[1 * pointcloud_.point_step + pointcloud_.fields[0].offset], &pc_radius_, sizeof(float));
  }
  else
  {
    memcpy(&pointcloud_.data[1 * pointcloud_.point_step + pointcloud_.fields[0].offset], &P_INF_X, sizeof(float));
  }

  if ((msg->bumper & kobuki_msgs::SensorState::BUMPER_RIGHT) ||
      (msg->cliff  & kobuki_msgs::SensorState::CLIFF_RIGHT))  //右侧传感器
  {
    memcpy(&pointcloud_.data[2 * pointcloud_.point_step + pointcloud_.fields[0].offset], &p_side_x_, sizeof(float));
    memcpy(&pointcloud_.data[2 * pointcloud_.point_step + pointcloud_.fields[1].offset], &n_side_y_, sizeof(float));
  }
  else
  {
    memcpy(&pointcloud_.data[2 * pointcloud_.point_step + pointcloud_.fields[0].offset], &P_INF_X, sizeof(float));
    memcpy(&pointcloud_.data[2 * pointcloud_.point_step + pointcloud_.fields[1].offset], &N_INF_Y, sizeof(float));
  }

  pointcloud_.header.stamp = msg->header.stamp;
  pointcloud_pub_.publish(pointcloud_);  //发布点云数据
}

在 kobuki_bumper2pc.hpp 中定义了 P_INF_X 、 P_INF_Y 、 N_INF_Y 的默认值:

Bumper2PcNodelet()
  : P_INF_X(+100*sin(0.34906585)),
    P_INF_Y(+100*cos(0.34906585)),
    N_INF_Y(-100*cos(0.34906585)),
    ZERO(0), prev_bumper(0), prev_cliff(0) { }
~Bumper2PcNodelet() { }

standalone.launch

standalone.launch 独立启动保险杆和悬崖 Nodelet 节点:

  • 启动 nodelet_manager Nodelet 管理器
  • 启动碰撞传感器保险杆和悬崖传感器点云 Nodelet
  • 设置 pointcloud_radius 参数,碰撞传感器、悬崖传感器点云到底座半径范围。大概就是机器人半径加上 costmap 识别区用于应对机器人的惯性。 这个参数不容易设置:设置太小 costmap 会忽略点云,导致机器人撞上障碍;设置太大,障碍占据地方太大,围绕他们做导航,就可能失败。
  • 重映射 pointcloud , core_sensors 两个话题
<!--
  Example standalone launcher for the bumper/cliff to pointcloud nodelet.
  Parameter pointcloud_radius gives the bumper/cliff pointcloud distance to base frame;
  should be something like the robot radius plus plus costmap resolution plus an extra to
  cope with robot inertia. This is a bit tricky parameter: if it's too low, costmap will
  ignore this pointcloud (the robot footprint runs over the hit obstacle), but if it's too
  big, hit obstacles will be mapped too far from the robot and the navigation around them
  will probably fail.
 -->
<launch>
  <node pkg="nodelet" type="nodelet" name="nodelet_manager" args="manager"/>
  <node pkg="nodelet" type="nodelet" name="bumper2pointcloud" args="load kobuki_bumper2pc/Bumper2PcNodelet nodelet_manager">
    <param name="pointcloud_radius" value="0.25"/>
    <remap from="bumper2pointcloud/pointcloud"   to="mobile_base/sensors/bumper_pointcloud"/>
    <remap from="bumper2pointcloud/core_sensors" to="mobile_base/sensors/core"/>
  </node>
</launch>
<think>好的,我现在需要帮用户解决在ROS中实现多传感器信息融合进行避障的问题。首先,我得回忆一下用户提供的引用内容,看看有没有相关的信息可以利用。用户提到了三个引用,分别是关于使用激光雷达、里程计、雷达和超声波传感器的内容。引用[1]提到ROS系统订阅激光雷达和里程计的消息,处理之后通过Twist消息控制机器人运动。引用[2]提到侧面安装雷达进行避障,虽然成本可能较高。引用[3]则是一个关于声纳层的配置示例,可能涉及传感器层配置。 接下来,我需要整理多传感器信息融合的基本步骤。通常来说,信息融合可以分为数据层、特征层和决策层。数据层融合需要处理不同传感器的原始数据,比如激光雷达的点云和超声波的测距数据,可能需要时间同步和坐标转换。特征层可能提取障碍物的特征,比如距离、方向、可信度,然后融合这些特征。决策层可能是各个传感器独立判断,然后投票或加权决定最终的避障策略。 然后,根据用户的引用,激光雷达(Rplidar A1)和里程计的数据已经被使用,所以可能需要将其他传感器如超声波或雷达的数据整合进来。例如,引用[3]中的sonar_layer可能是一个超声波传感器的配置,需要将其添加到costmap中。引用[2]中的雷达可能需要处理其数据,并与其他传感器的数据进行融合。 在ROS中,常用的工具包括传感器_msgs包来处理不同传感器的消息,tf2进行坐标变换,可能还需要用到robot_localization包或者卡尔曼滤波来融合数据。另外,costmap_2d可以用来整合多传感器数据到代价地图中,实现避障。用户提到的range_sensor_layer(引用[3])可能就是用来将超声波或雷达数据添加到costmap中的。 接下来要考虑时间同步问题,可能需要使用message_filters来同步不同传感器的话题。坐标转换也很重要,确保所有传感器数据在同一个坐标系下,比如base_link或odom。然后,数据融合的具体方法,比如贝叶斯估计、D-S证据理论或者机器学习方法,可能需要根据实际情况选择。 在实现方案中,硬件选择方面,用户已经用了激光雷达和里程计,可能需要添加超声波或雷达。软件架构方面,需要编写节点订阅各个传感器的话题,处理数据后进行融合,然后生成速度指令。具体步骤可能包括配置传感器层,处理数据同步,融合后更新costmap,最后路径规划。 最后,需要给出示例代码框架,可能包括订阅激光雷达、超声波的话题,使用message_filters进行同步,处理数据后发布融合后的障碍物信息,或者更新costmap。同时,参考文献中的信息,如引用[1]中的处理激光雷达和里程计数据,引用[3]中的传感器层配置,都需要整合进去。 用户可能的问题包括如何选择融合层次,如何处理传感器冲突,如何评估融合效果,以及是否需要机器学习。这些问题需要作为相关问题列出,帮助用户进一步探索。</think>### ROS传感器信息融合避障实现方案 在ROS中实现多传感器信息融合避障,需要结合激光雷达、超声波、深度相机等传感器的数据特性,主要分为以下步骤: --- #### 一、多传感器数据预处理 1. **时间同步** 使用`message_filters`包实现多传感器数据的时间对齐,确保$t\pm\Delta t$时间窗口内的数据具有同步性: ```python from message_filters import ApproximateTimeSynchronizer, Subscriber sync = ApproximateTimeScheduler([sub_laser, sub_sonar], queue_size=5, slop=0.1) ``` 2. **坐标统一** 通过`tf2`工具将不同坐标系(如激光雷达的`laser_link`、超声波的`sonar_link`)转换到机器人基坐标系`base_link`: $$ T_{base}^{sensor} = \begin{bmatrix} R & t \\ 0 & 1 \end{bmatrix} $$ 其中$R$为旋转矩阵,$t$为平移向量[^1]。 --- #### 二、多层级融合策略 | 融合层级 | 实现方法 | ROS工具 | |---------|--------|--------| | 数据层融合 | 直接合并原始数据 | `sensor_msgs/PointCloud2` | | 特征层融合 | 提取障碍物距离/方向特征 | `laser_geometry` | | 决策层融合 | 加权投票/贝叶斯估计 | `costmap_2d` | 1. **激光雷达+超声波融合示例** 在`costmap_common_params.yaml`中配置多层传感器: ```yaml obstacle_layers: - {name: laser_layer, type: "costmap_2d::ObstacleLayer"} - {name: sonar_layer, type: "range_sensor_layer::RangeSensorLayer"} # [^3] ``` --- #### 三、避障决策实现 1. **动态权重分配** 根据传感器置信度动态调整权重: $$ W_{total} = \alpha W_{laser} + \beta W_{sonar} + \gamma W_{camera} $$ 其中$\alpha+\beta+\gamma=1$,激光雷达通常赋予更高权重。 2. **速度控制逻辑** 在ROS节点中发布`Twist`消息: ```python def fusion_callback(data): if data.obstacle_distance < safe_threshold: twist.linear.x *= 0.5 # 减速 twist.angular.z = calc_avoidance_angle() # 计算转向角 cmd_vel_pub.publish(twist) ``` --- #### 四、完整实现框架 ```mermaid graph TD A[激光雷达] --> C[数据同步] B[超声波] --> C C --> D[坐标变换] D --> E[特征提取] E --> F[融合决策] F --> G[cmd_vel生成] ``` --- #### 五、优化建议 1. 使用`robot_localization`包融合里程计与IMU数据 2. 在Gazebo中构建包含多传感器的仿真环境 3. 采用D-S证据理论处理传感器冲突[^2]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值