机器人自主探索-DSVP:代码解析

DSVP(Dual-Stage Viewpoint Planner)是一种针对3D环境的快速探索算法,它改进了“Receding Horizon Next-Best-View”规划器。该算法通过动态扩展策略,有效地寻找新的观测点,以提高环境的覆盖率。代码实现包括一个drrtp_node.cpp文件,其中创建了一个drrtPlanner对象,该对象使用drrtp.cpp中的类来管理 octomap、占用栅格和全局图。在初始化过程中,DSVP订阅了传感器数据、状态估计和地形地图,根据接收到的数据更新地图、路径和边界信息。此外,它还定期检查并更新全局和局部边界点,以找到未探索区域。整个过程涉及多个订阅者、发布者和服务,以协调不同模块的交互,实现自主探索。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 写在前面

论文:DSVP: Dual-Stage Viewpoint Planner for Rapid Exploration by Dynamic Expansion

该文章对“Receding Horizon "Next-Best-View" Planner for 3D Exploration”进行改进

代码地址:GitHub - HongbiaoZ/dsv_planner: Dual-Stage Viewpoint Planner for Autonomous ExplorationDual-Stage Viewpoint Planner for Autonomous Exploration - GitHub - HongbiaoZ/dsv_planner: Dual-Stage Viewpoint Planner for Autonomous Explorationhttps://github.com/HongbiaoZ/dsv_planner

代码解析 :

首先查看launch文件:

<launch>

<arg name="enable_bag_record" default="false"/>

<arg name="bag_name" default="dsvp_garage"/>

<arg name="simulation" default="false"/>

<arg name="use_boundary" default="false"/>

<arg name="planner_param_file" default="$(find dsvp_launch)/config/exploration.yaml" />

<arg name="octomap_param_file" default="$(find dsvp_launch)/config/octomap.yaml" />

<rosparam command="load" file="$(arg planner_param_file)" />

<rosparam command="load" file="$(arg octomap_param_file)" />

<node name="dsvplanner" pkg="dsvplanner" type="dsvplanner" output="screen" />

<group if="$(arg use_boundary)">

<node pkg="dsvp_launch" type="navigationBoundary" name="navigationBoundary" output="screen" required="true">

<param name="boundary_file_dir" type="string" value="$(find dsvp_launch)/data/boundary.ply" />

<param name="sendBoundary" type="bool" value="true" />

<param name="sendBoundaryInterval" type="int" value="2" />

</node>

</group>

<node name="exploration" pkg="dsvp_launch" type="exploration" output="screen" >

<param name="simulation" type="bool" value="$(arg simulation)" />

</node>

<include file="$(find graph_planner)/launch/graph_planner.launch"/>

<group if="$(arg enable_bag_record)">

<include file="$(find dsvp_launch)/launch/rosbag_record.launch">

<arg name="bag_name" value="$(arg bag_name)" />

</include>

</group>

<node pkg="rviz" type="rviz" name="dsvp_rviz" args="-d $(find dsvp_launch)/default.rviz"/>

</launch>

从launch文件可以看出,主要执行两个node文件,首先看第一个dsvplanner,其对应的cpp文件为drrtp_node.cpp

drrtp_node.cpp

在该代码中,定义一drrtPlanner对象planner,该对象的具体实现在drrtp.cpp中

#include <eigen3/Eigen/Dense>
#include <ros/ros.h>
#include <dsvplanner/drrtp.h>

int main(int argc, char** argv)
{
  ros::init(argc, argv, "nbvPlanner");
  ros::NodeHandle nh;
  ros::NodeHandle nh_private("~");

  dsvplanner_ns::drrtPlanner planner(nh, nh_private);

  ros::spin();
  return 0;
}

drrtp.cpp

首先看该对象的构造函数

dsvplanner_ns::drrtPlanner::drrtPlanner(const ros::NodeHandle &nh,
                                        const ros::NodeHandle &nh_private)
    : nh_(nh), nh_private_(nh_private) {
  manager_ = new volumetric_mapping::OctomapManager(nh_, nh_private_);
  grid_ = new OccupancyGrid(nh_, nh_private_);
  dual_state_graph_ = new DualStateGraph(nh_, nh_private_, manager_, grid_);
  dual_state_frontier_ = new DualStateFrontier(nh_, nh_private_, manager_, grid_);
  drrt_ = new Drrt(manager_, dual_state_graph_, dual_state_frontier_, grid_);

  init();
  drrt_->setParams(params_);
  drrt_->init();

  ROS_INFO("Successfully launched DSVP node");
}

1. 构造函数中首先定义了4个对象:

1.1  manager_对象

manager_ = new volumetric_mapping::OctomapManager(nh_, nh_private_);
namespace volumetric_mapping {
OctomapManager::OctomapManager(const ros::NodeHandle &nh,
                               const ros::NodeHandle &nh_private)
    : nh_(nh), nh_private_(nh_private), world_frame_("map"),
      velodyne_cloud_topic_("/velodyne_cloud_topic"), robot_frame_("velodyne"),
      use_tf_transforms_(true), latch_topics_(true),
      timestamp_tolerance_ns_(10000000), Q_initialized_(false),
      Q_(Eigen::Matrix4d::Identity()), map_publish_frequency_(0.0) {
  setParametersFromROS();
  subscribe();
  advertiseServices();
  advertisePublishers();

  // After creating the manager, if the octomap_file parameter is set,
  // load the octomap at that path and publish it.
  std::string octomap_file;
  if (nh_private_.getParam("octomap_file", octomap_file)) {
    if (loadOctomapFromFile(octomap_file)) {
      ROS_INFO_STREAM(
          "Successfully loaded octomap from path: " << octomap_file);
      publishAll();
    } else {
      ROS_ERROR_STREAM("Could not load octomap from path: " << octomap_file);
    }
  }
}

subscribe():

void OctomapManager::subscribe() {
  pointcloud_sub_ = nh_.subscribe(velodyne_cloud_topic_, 1, &OctomapManager::insertPointcloudWithTf, this);
  octomap_sub_ = nh_.subscribe("input_octomap", 1, &OctomapManager::octomapCallback, this);
}

velodyne_cloud_topic_: /sensor_scan(lidar坐标系下的点云)

回调函数1:insertPointcloudWithTf(得到sensor坐标系到世界坐标系的变换,并将变换转换至相应格式)、光线追踪更新地图

void OctomapManager::insertPointcloudWithTf(
    const sensor_msgs::PointCloud2::ConstPtr &pointcloud) {
  // Look up transform from sensor frame to world frame.
  Transformation sensor_to_world;
  if (lookupTransform(pointcloud->header.frame_id, world_frame_,
                      pointcloud->header.stamp, &sensor_to_world)) {
    insertPointcloud(sensor_to_world, pointcloud);
  }
}

lookupTransformTf实现: 

1. pointcloud->header.frame_id为:/sensor_at_scan 在sensor_scan_generation功能包中定义;

2. world_frame_:/map

bool OctomapManager::lookupTransform(const std::string &from_frame,
                                     const std::string &to_frame,
                                     const ros::Time &timestamp,
                                     Transformation *transform) {
  if (use_tf_transforms_) {
    return lookupTransformTf(from_frame, to_frame, timestamp, transform);
  } else {
    return lookupTransformQueue(from_frame, to_frame, timestamp, transform);
  }
}
bool OctomapManager::lookupTransformTf(const std::string &from_frame,
                                       const std::string &to_frame,
                                       const ros::Time &timestamp,
                                       Transformation *transform) {
  tf::StampedTransform tf_transform;

  ros::Time time_to_lookup = timestamp;

  // If this transform isn't possible at the time, then try to just look up
  // the latest (this is to work with bag files and static transform publisher,
  // etc).
  if (!tf_listener_.canTransform(to_frame, from_frame, time_to_lookup)) {
    ros::Duration timestamp_age = ros::Time::now() - time_to_lookup;
    if (timestamp_age < tf_listener_.getCacheLength()) {
      time_to_lookup = ros::Time(0);
      // ROS_WARN("Using latest TF transform instead of timestamp match.");
    } else {
      // ROS_ERROR("Requested transform time older than cache limit.");
      return false;
    }
  }

  try {
    tf_listener_.lookupTransform(to_frame, from_frame, time_to_lookup,
                                 tf_transform);
  } catch (tf::TransformException &ex) {
    ROS_ERROR_STREAM(
        "Error getting TF transform from sensor data: " << ex.what());
    return false;
  }

  tf::transformTFToKindr(tf_transform, transform);
  return true;
}
void transformTFToKindr(const tf::Transform& tf_type,
                        kindr::minimal::QuatTransformation* kindr) {
  CHECK_NOTNULL(kindr);
  Eigen::Vector3d position;
  Eigen::Quaterniond rotation;

  quaternionTFToKindr(tf_type.getRotation(), &rotation);
  vectorTFToKindr(tf_type.getOrigin(), &position);

  // Enforce positive w.
  if (rotation.w() < 0) {
    rotation.coeffs() = -rotation.coeffs();
  }

  *kindr = kindr::minimal::QuatTransformation(rotation, position);
}

即进行类型转换(tf_transform--transform(得到rotation和position))

insertPointcloud(sensor_to_world, pointcloud):实现的是通过对当前帧点云中的每一个点光线追踪来更新free_cells和occupied_cells 

回调函数2: octomapCallback(这个暂时先不看它 )

1.2  grid_对象

grid_ = new OccupancyGrid(nh_, nh_private_);

 该对象的实现:

OccupancyGrid::OccupancyGrid(const ros::NodeHandle &nh,  const ros::NodeHandle &nh_private)
    : nh_(nh), nh_private_(nh_private) {
  initialize();
}
bool OccupancyGrid::initialize() {
  // Read in parameters
  if (!readParameters())
    return false;
  // Initialize subscriber
  odom_sub_.subscribe(nh_, sub_odom_topic_, 1);
  terrain_point_cloud_sub_.subscribe(nh_, sub_terrain_point_cloud_topic_, 1);
  sync_.reset(new Sync(syncPolicy(100), odom_sub_, terrain_point_cloud_sub_));
  sync_->registerCallback( boost::bind(&OccupancyGrid::terrainCloudAndOdomCallback, this, _1, _2));

  grid_cloud_pub_ = nh_.advertise<sensor_msgs::PointCloud2>(pub_grid_points_topic_, 1);

  map_half_width_grid_num_ = int(kMapWidth / 2 / kGridSize); //  40/2/0.1
  map_width_grid_num_ = map_half_width_grid_num_ * 2 + 1;

  clearGrid();

  ROS_INFO("Successfully launched OccupancyGrid node");

  return true;
}

初始化一些参数以及订阅/state_estimation和/terrain_map_ext,执行回调函数terrainCloudAndOdomCallback

void OccupancyGrid::terrainCloudAndOdomCallback(
    const nav_msgs::Odometry::ConstPtr &odom_msg,
    const sensor_msgs::PointCloud2::ConstPtr &terrain_msg) {
  terrain_time_ = terrain_msg->header.stamp;
  robot_position_[0] = odom_msg->pose.pose.position.x;
  robot_position_[1] = odom_msg->pose.pose.position.y;
  robot_position_[2] = odom_msg->pose.pose.position.z;
  terrain_cloud_->clear();
  terrain_cloud_ds->clear();
  terrain_cloud_traversable_->clear();
  terrain_cloud_obstacle_->clear();
  pcl::fromROSMsg(*terrain_msg, *terrain_cloud_);

  pcl::VoxelGrid<pcl::PointXYZI> point_ds;
  point_ds.setLeafSize(kDownsampleSize, kDownsampleSize, kDownsampleSize);
  point_ds.setInputCloud(terrain_cloud_);
  // 地形消息下采样
  point_ds.filter(*terrain_cloud_ds);

  pcl::PointXYZI point;
  int terrainCloudSize = terrain_cloud_ds->points.size();
  for (int i = 0; i < terrainCloudSize; i++) {
    point.x = terrain_cloud_ds->points[i].x;
    point.y = terrain_cloud_ds->points[i].y;
    point.z = terrain_cloud_ds->points[i].z;
    point.intensity = terrain_cloud_ds->points[i].intensity;
    // crop all ground points
    // 0.2m到1m之间的障碍物认为是obstacle,(kObstacleHeightThre默认0.2)
    if (point.intensity > kObstacleHeightThre && point.intensity < kFlyingObstacleHeightThre) {
      terrain_cloud_obstacle_->push_back(point);
    } else if (point.intensity <= kObstacleHeightThre) {
      terrain_cloud_traversable_->push_back(point);
    }
  }
  
  clearGrid(); // grid全部填充为unknown状态
  updateGrid(); 
  publishGridMap();
}
void OccupancyGrid::updateGrid() {
  pcl::PointXYZI point;
  for (int i = 0; i < terrain_cloud_obstacle_->points.size(); i++) {
    point = terrain_cloud_obstacle_->points[i];
    // 将障碍物点换算成索引
    // kGridSize = 0.1
    int indX = int((point.x - robot_position_[0] + kGridSize / 2) / kGridSize) + map_half_width_grid_num_;
    int indY = int((point.y - robot_position_[1] + kGridSize / 2) / kGridSize) + map_half_width_grid_num_;
    // 应对负数四舍五入情况
    if (point.x - robot_position_[0] + kGridSize / 2 < 0)
      indX--;
    if (point.y - robot_position_[1] + kGridSize / 2 < 0)
      indY--;
    if (indX < 0)
      indX = 0;
    if (indY < 0)
      indY = 0;
    if (indX > map_width_grid_num_ - 1)
      indX = map_width_grid_num_ - 1;
    if (indY > map_width_grid_num_ - 1)
      indY = map_width_grid_num_ - 1;

    if (indX >= 0 && indX < map_width_grid_num_ && indY >= 0 && indY < map_width_grid_num_) {
      gridStatus grid_state = occupied;
      gridState_[indX][indY] = grid_state;
    }
  }
  for (int i = 0; i < terrain_cloud_traversable_->points.size(); i++) {
    point = terrain_cloud_traversable_->points[i];
    int indX = int((point.x - robot_position_[0] + kGridSize / 2) / kGridSize) + map_half_width_grid_num_;
    int indY = int((point.y - robot_position_[1] + kGridSize / 2) / kGridSize) + map_half_width_grid_num_;
    if (point.x - robot_position_[0] + kGridSize / 2 < 0)
      indX--;
    if (point.y - robot_position_[1] + kGridSize / 2 < 0)
      indY--;
    if (indX < 0)
      indX = 0;
    if (indY < 0)
      indY = 0;
    if (indX > map_width_grid_num_ - 1)
      indX = map_width_grid_num_ - 1;
    if (indY > map_width_grid_num_ - 1)
      indY = map_width_grid_num_ - 1;
    if (indX >= 0 && indX < map_width_grid_num_ && indY >= 0 && indY < map_width_grid_num_) {
      if (gridState_[indX][indY] == 2) {
        continue;
      }
      if (updateFreeGridWithSurroundingGrids(indX, indY) == false) {
        gridStatus grid_state = free;
        gridState_[indX][indY] = grid_state;
      } else {
        gridStatus grid_state = near_occupied;
        gridState_[indX][indY] = grid_state;
      }
    }
  }
}

point.x() - robot_position_[0]为当前点到机器人原点的向量

kGridSize默认为0.1,该向量加上kGridSize/2再除以kGridSize再转为int变量,表示四舍五入;

即假设当前点距离机器人2m,栅格的分辨率为0.1,则距离20个格子,再加上map_half_width_grid_num_(200),即这个点再220处。

机器人是地图的中心,一个点在机器人右边20处,那么在整个地图里面,它应该是左边第220个格子。

void OccupancyGrid::publishGridMap() {
  grid_cloud_->clear();
  pcl::PointXYZI p1;
  geometry_msgs::Point p2;
  // typedef Vector2i GridIndex;
  GridIndex p3;
  for (int i = 0; i < map_width_grid_num_; i++) {
    for (int j = 0; j < map_width_grid_num_; j++) {
      p3[0] = i;
      p3[1] = j;
      // 返回点
      p2 = getPoint(p3);
      p1.x = p2.x;
      p1.y = p2.y;
      p1.z = p2.z;
      p1.intensity = gridState_[i][j];
      grid_cloud_->points.push_back(p1);
    }
  }
  sensor_msgs::PointCloud2 gridCloud2;
  pcl::toROSMsg(*grid_cloud_, gridCloud2);
  gridCloud2.header.stamp = terrain_time_;
  gridCloud2.header.frame_id = world_frame_id_;
  grid_cloud_pub_.publish(gridCloud2);
}

1.2 grid_对象总结 

grid_定义是仅执行初始化订阅者和发布者以及clearGrid()函数。

订阅/state_estimation以及/terrain_map_ext,执行回调函数,函数内执行以下:将点云中的点划分为四类:unknown , free , occupied , near_occupied ,并将每个点的该信息存储在intensity属性中,并将其以话题/occpancy_grid_map发布出去。

 1.3 定义全局图

dual_state_graph_ = new DualStateGraph(nh_, nh_private_, manager_, grid_);

 dual_state_graph_构造函数:

namespace dsvplanner_ns {
DualStateFrontier::DualStateFrontier(
    const ros::NodeHandle &nh, const ros::NodeHandle &nh_private,
    volumetric_mapping::OctomapManager *manager, OccupancyGrid *grid)
    : nh_(nh), nh_private_(nh_private) {
  manager_ = manager;
  grid_ = grid;
  initialize();
}

initialize()函数:

1. 初始化一些参数

2. 定义一些订阅者和发布者

// Initialize subscriber
  // sub_keypose_topic_: state_estimation_at_scan
  key_pose_sub_ = nh_.subscribe<nav_msgs::Odometry>(sub_keypose_topic_, 5, &DualStateGraph::keyposeCallback, this);
  // sub_path_topic_ : /graph_planner_path
  graph_planner_path_sub_ = nh_.subscribe<nav_msgs::Path>(sub_path_topic_, 1, &DualStateGraph::pathCallback, this);
  // sub_graph_planner_status_topic_ : /graph_planner_status
  graph_planner_status_sub_ = nh_.subscribe<graph_planner::GraphPlannerStatus>(sub_graph_planner_status_topic_, 1,
      &DualStateGraph::graphPlannerStatusCallback, this);

  // Initialize publishers
  // pub_local_graph_topic_: /local_graph
  local_graph_pub_ = nh_.advertise<graph_utils::TopologicalGraph>(pub_local_graph_topic_, 2);
  // pub_global_graph_topic_ : /global_graph
  global_graph_pub_ = nh_.advertise<graph_utils::TopologicalGraph>(pub_global_graph_topic_, 2);
  // pub_global_points_topic_ : /global_points
  graph_points_pub_ = nh_.advertise<sensor_msgs::PointCloud2>(pub_global_points_topic_, 2);

发布的/global_graph仅用于显示 。

 一. 订阅话题/state_estimation_at_scan(等同于/state_estimation),执行回调函数keyposeCallback 

void DualStateGraph::keyposeCallback(const nav_msgs::Odometry::ConstPtr &msg) {
  geometry_msgs::Pose keypose;
  keypose.position.x = msg->pose.pose.position.x;
  keypose.position.y = msg->pose.pose.position.y;
  keypose.position.z = msg->pose.pose.position.z;
  keypose.orientation.y = 0;
  addNewGlobalVertexWithKeypose(keypose);
}

即每到一个机器人当前位姿,就执行addNewGlobalVertexWithKeypose()回调函数 

void DualStateGraph::addNewGlobalVertexWithKeypose(geometry_msgs::Pose &vertex_msg) {
  // Same as addNewLocalVertex but only adds vertex if a similar vertex doesn't already exist

  // Extract the point
  geometry_msgs::Point new_vertex_location;
  new_vertex_location = vertex_msg.position;

  // Check if a similar vertex already exists
  bool already_exists = false;
  bool too_long = false;
  double distance = -1;
  int closest_vertex_idx = -1;
  if (!global_graph_.vertices.empty()) {
    closest_vertex_idx = graph_utils_ns::GetClosestVertexIdxToPoint(global_graph_, new_vertex_location);
    auto &closest_vertex_location = global_graph_.vertices[closest_vertex_idx].location;
    distance = misc_utils_ns::PointXYZDist(new_vertex_location, closest_vertex_location);
    if (distance < kMinVertexDist / 2) {
      already_exists = true;
    }
    if (distance > kMaxLongEdgeDist || fabs(new_vertex_location.z - closest_vertex_location.z) > kMaxVertexDiffAlongZ) {
      too_long = true;
    }
  }

}

1. 该回调函数接受机器人当前位姿,提取出机器人当前位置信息, 并提取出global_graph_中距离当前点位置最近的点的索引closest_vertex_idx,以及其位置信息closest_vertex_location

2. 判断当前点和最近的点的距离,如果距离太小或者太大,则忽略当前点。如果距离合适,则执行下面的函数:

  // If not, add a new one
  if (!already_exists && !too_long) {
    prev_track_vertex_idx_ = closest_vertex_idx;
    addNewGlobalVertex(vertex_msg);
    addGlobalEdgeWithoutCheck(prev_track_keypose_vertex_idx_, track_globalvertex_idx_, true);
    prev_track_keypose_vertex_idx_ = track_globalvertex_idx_;
  }

1. 将距离当前点最近的global_graph_中的点的索引closest_vertex_idx赋给prev_track_vertex_idx_

2. 执行addNewGlobalVertex(vertex_msg)函数:

        2.1 将当前点的position、vertex_id(global_graph_.vertices.size())和information_gain添加到global_graph_中, 得到当前vertex的索引track_globalvertex_idx_,以及指针vertex_new

        2.2 当global_graph_中vertex的数量大于1时,定义当前vertex的parrent_idx为prev_track_vertex_idx_,执行函数addGlobalEdgeWithoutCheck(prev_track_vertex_idx_, track_globalvertex_idx_, false)。

        2.3 addGlobalEdgeWithoutCheck()函数功能:(在两个vertex之间添加edge)

                2.3.1 首先从global_graph_中根据传参的索引获取prev_vertex以及当前vertex并分别定义为start_vertexend_vertex

                2.3.2 接着检查两个vertex之间是否有edge,如果没有的话,进行添加

                2.3.3 首先计算两个vertex之间的距离dist_diff,然后定义两个edge对象:graph_utils::Edge edge_to_start以及graph_utils::Edge edge_to_end;

                2.3.4 添加两个edge : edge_to_start.vertex_id_end = start_vertex_idx,edge_to_end.vertex_id_end = end_vertex_idx;其中start_vertex_idx为prev_track_vertex_idx_,end_vertex_idx为track_globalvertex_idx_

                2.3.5 计算traversal cost:edge_to_start.traversal_costs = dist_diff,edge_to_end.traversal_costs = dist_diff;

                2.3.6 edge_to_start.keypose_edge = trajectory_edge,edge_to_end.keypose_edge = trajectory_edge;此时trajectory_edge传参为false.

                2.3.7 将edge添加到vertex中:start_vertex.edges.push_back(edge_to_end),end_vertex.edges.push_back(edge_to_start).

        2.4 对当前vertex附近的vertex添加edge:计算global_graph_中每一个vertex距离当前vertex的路基,若小于一定阈值,则执行addGlobalEdge(graph_vertex.vertex_id, vertex_new.vertex_id)函数。

                2.4.1 函数声明:void DualStateGraph::addGlobalEdge(int start_vertex_idx, int end_vertex_idx),即start_vertex_idx为满足阈值条件的nearby_vertex以及当前vertex。

                2.4.2 计算两个vertex之间的距离dist_edge

                2.4.3 接着调用ShortestPathBtwVertex(path, global_graph_, start_vertex_idx, end_vertex_idx)函数:Function for getting the shortest path on a graph between two vertexes;Output a sequence of vertex ids as the path-->path

                2.4.4 然后计算path的长度dist_path

                2.4.5 进行论文中公式6的判断,若满足条件,同时进行碰撞检测,满足条件者,调用addGlobalEdgeWithoutCheck(),添加边。

        2.5  执行addGlobalEdgeWithoutCheck(prev_track_keypose_vertex_idx_, track_globalvertex_idx_, true);

        紧接着prev_track_keypose_vertex_idx_ = track_globalvertex_idx_;

void DualStateGraph::addNewGlobalVertex(geometry_msgs::Pose &vertex_msg) {
  // Extract the point
  geometry_msgs::Point new_vertex_location;
  new_vertex_location = vertex_msg.position;

  // Create a new vertex
  graph_utils::Vertex vertex;
  vertex.location = new_vertex_location;
  vertex.vertex_id = (int)global_graph_.vertices.size();
  vertex.information_gain = vertex_msg.orientation.y;

  // Add this vertex to the graph
  global_graph_.vertices.push_back(vertex);
  track_globalvertex_idx_ = (int)global_graph_.vertices.size() - 1;
  auto &vertex_new = global_graph_.vertices[track_globalvertex_idx_];
  // If there are already other vertices
  if (global_graph_.vertices.size() > 1) {
    // Add a parent backpointer to previous vertex
    vertex_new.parent_idx = prev_track_vertex_idx_;

    // Add an edge to previous vertex 
    addGlobalEdgeWithoutCheck(prev_track_vertex_idx_, track_globalvertex_idx_, false);

    // Also add edges to nearby vertices
    for (auto &graph_vertex : global_graph_.vertices) {
      // If within a distance
      float dist_diff = misc_utils_ns::PointXYZDist(graph_vertex.location, vertex_new.location);
      if (dist_diff < kConnectVertexDistMax) {
        // Add edge from this nearby vertex to current vertex
        addGlobalEdge(graph_vertex.vertex_id, vertex_new.vertex_id);
      }
    }
  } else {
    // This is the first vertex -- no edges
    vertex.parent_idx = -1;
  }
}
void DualStateGraph::addGlobalEdgeWithoutCheck(int start_vertex_idx, int end_vertex_idx, bool trajectory_edge) {
  // Add an edge in the graph from vertex start_vertex_idx to vertex end_vertex_idx

  // Get the two vertices
  auto &start_vertex = global_graph_.vertices[start_vertex_idx];
  auto &end_vertex = global_graph_.vertices[end_vertex_idx];

  // Check if edge already exists -- don't duplicate
  for (auto &edge : start_vertex.edges) {
    if (edge.vertex_id_end == end_vertex_idx) {
      // don't add duplicate edge
      edge.keypose_edge = trajectory_edge;
      for (auto &edge : end_vertex.edges) {
        if (edge.vertex_id_end == start_vertex_idx) {
          // don't add duplicate edge
            edge.keypose_edge = trajectory_edge;
            break;
        }
      }
      return;
    }
  }

  // Compute the distance between the two points
  float dist_diff = misc_utils_ns::PointXYZDist(start_vertex.location, end_vertex.location);

  // Create two edge objects
  graph_utils::Edge edge_to_start;
  graph_utils::Edge edge_to_end;

  // Join the two edges
  edge_to_start.vertex_id_end = start_vertex_idx;
  edge_to_end.vertex_id_end = end_vertex_idx;

  // Compute the traversal cost
  // For now, this is just Euclidean distance
  edge_to_start.traversal_costs = dist_diff;
  edge_to_end.traversal_costs = dist_diff;

  edge_to_start.keypose_edge = trajectory_edge;
  edge_to_end.keypose_edge = trajectory_edge;

  // Add these two edges to the vertices
  start_vertex.edges.push_back(edge_to_end);
  end_vertex.edges.push_back(edge_to_start);

  globalEdgeSize_++;
}
void DualStateGraph::addGlobalEdge(int start_vertex_idx, int end_vertex_idx) {
  if (start_vertex_idx == end_vertex_idx) {
    return;
  }

  // Get the edge distance
  float dist_edge = misc_utils_ns::PointXYZDist(
      global_graph_.vertices[start_vertex_idx].location,
      global_graph_.vertices[end_vertex_idx].location);

  if (dist_edge > kMaxLongEdgeDist) {
    // VERY long edge. Don't add it
  } else {
    // Get the path distance
    std::vector<int> path;
    graph_utils_ns::ShortestPathBtwVertex(path, global_graph_, start_vertex_idx, end_vertex_idx);
    bool path_exists = true;
    float dist_path = 0;

    // Check if there is an existing path
    if (path.empty()) {
      // No path exists
      path_exists = false;
    } else {
      // Compute path length
      dist_path = graph_utils_ns::PathLength(path, global_graph_);
      // ROS_WARN("path exists of edge of length %f, where path exists of length
      // %f", dist_edge, dist_path);
    }

    // Check if ratio is beyond threshold
    // bool collision = false;
    // 论文中公式6定义的约束检查
    if ((!path_exists || (path_exists && ((dist_path / dist_edge) >= kExistingPathRatioThresholdGlobal))) &&
        (!zCollisionCheck(start_vertex_idx, end_vertex_idx, global_graph_))) {
      Eigen::Vector3d origin;
      Eigen::Vector3d end;
      origin.x() = global_graph_.vertices[start_vertex_idx].location.x;
      origin.y() = global_graph_.vertices[start_vertex_idx].location.y;
      origin.z() = global_graph_.vertices[start_vertex_idx].location.z;
      end.x() = global_graph_.vertices[end_vertex_idx].location.x;
      end.y() = global_graph_.vertices[end_vertex_idx].location.y;
      end.z() = global_graph_.vertices[end_vertex_idx].location.z;
      // 确保连线没有碰撞(光线追踪进行碰撞检测)
      if (volumetric_mapping::OctomapManager::CellStatus::kFree == manager_->getLineStatusBoundingBox(origin, end, robot_bounding)) {
        addGlobalEdgeWithoutCheck(start_vertex_idx, end_vertex_idx, false);
      }
    }
  }
}

 二. 订阅/graph_planner_path以及/graph_planner_status,并执行回调函数。这两个话题是由graph_planner节点发布的,暂时先不看。

1.4 dual_state_frontier对象 

// 定义一个全局边界求解器

dual_state_frontier_ = new DualStateFrontier(nh_, nh_private_, manager_, grid_);

DualStateFrontier::DualStateFrontier(
    const ros::NodeHandle &nh, const ros::NodeHandle &nh_private,
    volumetric_mapping::OctomapManager *manager, OccupancyGrid *grid)
    : nh_(nh), nh_private_(nh_private) {
  manager_ = manager;
  grid_ = grid;
  initialize();
}

 构造函数只执行了initialize()函数

bool DualStateFrontier::initialize() {
  // Read in parameters
  if (!readParameters())
    return false;

  // Initialize subscriber
  // /global_points
  graph_points_sub_ = nh_.subscribe(sub_graph_points_topic_, 1, &DualStateFrontier::graphPointsCallback, this);
  // /state_estimation
  odom_sub_.subscribe(nh_, sub_odom_topic_, 1);
  // /terrain_map_ext
  terrain_point_cloud_sub_.subscribe(nh_, sub_terrain_point_cloud_topic_, 1);
  sync_.reset(new Sync(syncPolicy(10), odom_sub_, terrain_point_cloud_sub_));
  sync_->registerCallback(boost::bind(&DualStateFrontier::terrainCloudAndOdomCallback, this, _1, _2));

  // /octomap_unknown
  unknown_points_pub_ = nh_.advertise<sensor_msgs::PointCloud2>(pub_unknown_points_topic_, 1);
  // /global_frontier
  global_frontier_points_pub_ = nh_.advertise<sensor_msgs::PointCloud2>(pub_global_frontier_points_topic_, 1);
  // /local_frontier
  local_frontier_points_pub_ = nh_.advertise<sensor_msgs::PointCloud2>(pub_local_frontier_points_topic_, 1);
  terrain_elev_cloud_pub_ = nh_.advertise<sensor_msgs::PointCloud2>("/elevation_map", 1);

  if (kExecuteFrequency_ > 0.0) {
    executeTimer_ = nh_private_.createTimer(ros::Duration(1.0 / kExecuteFrequency_), &DualStateFrontier::execute, this);
  }
  for (int i = 0; i < kTerrainVoxelWidth * kTerrainVoxelWidth; i++) {
    terrain_voxel_elev_.push_back(robot_position_.z());
    terrain_voxel_points_num_.push_back(0);
    terrain_voxel_min_elev_.push_back(1000);
    terrain_voxel_max_elev_.push_back(-1000);
  }

  boundaryLoaded_ = false;

  ROS_INFO("Successfully launched DualStateFrontier node");

  return true;
}

 首先在一定频率下,不断执行execute()函数,而execute()函数则是调用getFrontiers()函数。

executeTimer_ = nh_private_.createTimer(ros::Duration(1.0 / kExecuteFrequency_), &DualStateFrontier::execute, this);
void DualStateFrontier::execute(const ros::TimerEvent &e) { getFrontiers(); }
void DualStateFrontier::getFrontiers() {
  // search_bounding[0] = 40
  // search_bounding[1] = 40
  // search_bounding[2] = 0.5
  // 给定一个范围,和位置中心,得到它周围的属于边界的点云,并将得到的边界点集合保存到local_frontier_中
  getUnknowPointcloudInBoundingBox(robot_position_, search_bounding);
  // 局部边界点的检查和更新
  localFrontierUpdate(robot_position_);
  // 全局边界点的检查和更新
  gloabalFrontierUpdate();
  // 对全局采样点的下采样
  globalFrontiersNeighbourCheck();
  // 对各个内容的发布,包括局部边界点,全局边界点,处于未知状态的点云
  publishFrontiers();
}

1. 首先调用getUnknowPointcloudInBoundingBox()函数:

        1.1 首先对中心位置进行调整,即调整center参数

        1.2 依次遍历给定范围内的x,y,z;调用octomap,根据点坐标得到索引值,并根据索引值得到具体的节点。

        1.3 如果octomap中没有这样的节点,就将该点加入到未知点集合unknown_points_中,如果该点在给定的范围之内,并且确实是一个边界点(如果该点确实是边界点,那么该点周围应该既有空闲也有未知),那么就加入到local_frontier_temp中

        1.4 对local_frontier_temp进行滤波得到local_frontier_

2. 接着调用localFrontierUpdate(robot_position_)函数:

        2.1  遍历local_frontier中的每一个点

        2.2 inSensorRangeofRobot()函数解析:只有其传参距离机器人当前位置在一定阈值之内,并且其向量的俯仰角小于15度,和机器人当前未知进行光线追踪的栅格单元都是free状态,则返回TRUE

        2.3 collisionCheckByTerrainWithVector(center, checkedPoint)函数:给定一个起点和终点,判断二者连线上有没有障碍物

        2.4 inSensorRangeofGraphPoints(checkedPoint):输入一个点,利用kd树,在全局中找到最近得到点的集合,只要有一个可以被形参位置正常观测,并且没有障碍物,则认为是true

        2.5 满足一定条件的边界点被加入到local_frontier_pcl_以及global_frontier_中,并对local_frontier_pcl_进行降采样点得到local_frontier_

3. 调用gloabalFrontierUpdate()函数:提取 global_frontier_中的每一个点,如果该点属于被清空过的边界点,则跳出本次循环,得到该点在octomap中的索引值,如果该点属于边界点则添加到global_frontier_pcl_中,并对其进行滤波得到global_frontier_

4. globalFrontiersNeighbourCheck();: 遍历 global_frontier_中每一个点,通过KD树搜索其附近的边界点,如果存在,则只保存一个边界点。相当于对全局边界点的一个下采样,并将结果保存到global_frontier_pcl_和global_frontier_中

5. 发布/global_frontier以及/local_frontier ,/global_frontier比/local_frontier多了一次检查是否属于被清空的边界点,然后进行了两次滤波

 execute()函数总结:遍历当前机器人位置一定范围内的点,并找出边界点,后续通过降采样得到边界点并发布出去

2. 执行init()函数(读取参数以及确定订阅者和发布者)

bool dsvplanner_ns::drrtPlanner::init() {
  if (!setParams()) {
    ROS_ERROR("Set parameters fail. Cannot start planning!");
  }

  odomSub_ = nh_.subscribe(odomSubTopic, 10, &dsvplanner_ns::drrtPlanner::odomCallback, this);
  boundarySub_ = nh_.subscribe(boundarySubTopic, 10, &dsvplanner_ns::drrtPlanner::boundaryCallback, this);

  params_.newTreePathPub_ = nh_.advertise<visualization_msgs::Marker>(newTreePathPubTopic, 1000);
  params_.remainingTreePathPub_ = nh_.advertise<visualization_msgs::Marker>(remainingTreePathPubTopic, 1000);
  params_.boundaryPub_ = nh_.advertise<visualization_msgs::Marker>(boundaryPubTopic, 1000);
  params_.globalSelectedFrontierPub_ = nh_.advertise<sensor_msgs::PointCloud2>(globalSelectedFrontierPubTopic, 1000);
  params_.localSelectedFrontierPub_ = nh_.advertise<sensor_msgs::PointCloud2>(localSelectedFrontierPubTopic, 1000);
  params_.randomSampledPointsPub_ = nh_.advertise<sensor_msgs::PointCloud2>(randomSampledPointsPubTopic, 1000);
  params_.plantimePub_ = nh_.advertise<std_msgs::Float32>(plantimePubTopic, 1000);
  params_.nextGoalPub_ = nh_.advertise<geometry_msgs::PointStamped>(nextGoalPubTopic, 1000);
  params_.shutdownSignalPub = nh_.advertise<std_msgs::Bool>(shutDownTopic, 1000);

  plannerService_ = nh_.advertiseService(plannerServiceName, &dsvplanner_ns::drrtPlanner::plannerServiceCallback,this);
  cleanFrontierService_ = nh_.advertiseService(cleanFrontierServiceName, &dsvplanner_ns::drrtPlanner::cleanFrontierServiceCallback, this);

  return true;
}

 2.1 订阅/state_estimation,执行odomCallback()回调函数(机器人当前位姿传递给drrt求解器)

void dsvplanner_ns::drrtPlanner::odomCallback(const nav_msgs::Odometry &pose) {
  drrt_->setRootWithOdom(pose);
  // Planner is now ready to plan.
  drrt_->plannerReady_ = true;
}
void dsvplanner_ns::Drrt::setRootWithOdom(const nav_msgs::Odometry &pose) {
  root_[0] = pose.pose.pose.position.x;
  root_[1] = pose.pose.pose.position.y;
  root_[2] = pose.pose.pose.position.z;
}

 2.3 定义服务: 

服务类型:dsvplanner_srv

Header header
-------
geometry_msgs/Point[] goal
std_msgs/Int32 mode
plannerService_ = nh_.advertiseService(plannerServiceName, &dsvplanner_ns::drrtPlanner::plannerServiceCallback,this);
cleanFrontierService_ = nh_.advertiseService(cleanFrontierServiceName, &dsvplanner_ns::drrtPlanner::cleanFrontierServiceCallback, this);

graph_planner_node.cpp

1. 定义一个GraphPlanner对象

2. 执行execute()函数

graph_planner_ns::GraphPlanner graph_planner(node_handle);
graph_planner.execute();

graph_planner.cpp

此函数为graph_planner的实现函数,首先看其构造函数

GraphPlanner::GraphPlanner(const ros::NodeHandle &nh) {
  nh_ = nh;
  initialize();
}

 1. initialize()

bool GraphPlanner::initialize() {
  if (!readParameters())
    return false;

  // Initialize target waypoint
  waypoint_ = geometry_msgs::PointStamped();
  waypoint_.point.x = -1.0;
  waypoint_.point.y = -1.0;
  waypoint_.point.z = 0.0;
  // world_frame_id_ : map
  waypoint_.header.frame_id = world_frame_id_;

  // Initialize subscribers
  // /local_graph
  graph_sub_ = nh_.subscribe(sub_graph_topic_, 1, &GraphPlanner::graphCallback, this);
  // /graph_planner_command
  graph_planner_command_sub_ = nh_.subscribe(graph_planner_command_topic_, 1, &GraphPlanner::commandCallback, this);
  // /state_estimation
  odometry_sub_ = nh_.subscribe(sub_odometry_topic_, 1, &GraphPlanner::odometryCallback, this);
  // /terrain_map_ext
  terrain_sub_ = nh_.subscribe(sub_terrain_topic_, 1, &GraphPlanner::terrainCallback, this);

  // Initialize publishers
  // /way_point
  waypoint_pub_ = nh_.advertise<geometry_msgs::PointStamped>(pub_waypoint_topic_, 2);
  // /graph_planner_status
  graph_planner_status_pub_ = nh_.advertise<graph_planner::GraphPlannerStatus>(graph_planner_status_topic_, 2);
  // /graph_planner_path
  graph_planner_path_pub_ = nh_.advertise<nav_msgs::Path>(pub_path_topic_, 10);

  ROS_INFO("Successfully launched graph_planner node");

  return true;
}

 首先看订阅函数/state_estimation

1. 订阅/state_estimation,执行odometryCallback()回调函数:

        将机器人当前(x,y,z)位置传递给robot_pos_,以及yaw信息传递给robot_yaw_

void GraphPlanner::odometryCallback(
    const nav_msgs::Odometry::ConstPtr &odometry_msg) {
  double roll, pitch, yaw;
  geometry_msgs::Quaternion geo_quat = odometry_msg->pose.pose.orientation;
  tf::Matrix3x3(tf::Quaternion(geo_quat.x, geo_quat.y, geo_quat.z, geo_quat.w))
      .getRPY(roll, pitch, yaw);

  robot_yaw_ = yaw;
  robot_pos_ = odometry_msg->pose.pose.position;
}

2. 订阅/terrain_map_ext,执行回调函数terrainCallback:

        接受地型点云消息,大于0.2,小于1.2的点云被判断为会对机器人行动造成障碍,并将该点云保存到terrain_point_crop_中,并对其进行体素滤波得到terrain_point_crop_。

void GraphPlanner::terrainCallback(
    const sensor_msgs::PointCloud2::ConstPtr &terrain_msg) {
  terrain_point_->clear();
  terrain_point_crop_->clear();
  pcl::fromROSMsg(*terrain_msg, *terrain_point_);

  pcl::PointXYZI point;
  int terrainCloudSize = terrain_point_->points.size();
  for (int i = 0; i < terrainCloudSize; i++) {
    point = terrain_point_->points[i];

    float pointX = point.x;
    float pointY = point.y;
    float pointZ = point.z;

    // kObstacleHeightThres : 0.20 # the obstacle height that will be considered when filtering the terrain cloud
    // kOverheadObstacleHeightThres : 1.20 # the overhead obstacle height that will be considered when filtering the terrain cloud
    // 高于0.2 低于1.2的障碍物点云判断为会对机器人行动造成障碍
    if (point.intensity > kObstacleHeightThres && point.intensity < kOverheadObstacleHeightThres) {
      point.x = pointX;
      point.y = pointY;
      point.z = pointZ;
      terrain_point_crop_->push_back(point);
    }
  }
  pcl::VoxelGrid<pcl::PointXYZI> point_ds;
  point_ds.setLeafSize(kDownsampleSize, kDownsampleSize, kDownsampleSize);
  point_ds.setInputCloud(terrain_point_crop_);
  point_ds.filter(*terrain_point_crop_);
}

 3. 接着订阅/local_graph以及/graph_planner_command,并执行回调函数。这里暂且不看

 2. execute()

 此部分函数和exeploration.cpp()函数有关

dsvp是一种双阶段视角规划器,用于通过动态扩展实现快速探索。这个概念可以应用于多个领域,如机器人导航、无人机探索和虚拟现实等。 双阶段视角规划器的核心思想是将视角规划分为两个阶段:扩展阶段和动态阶段。在扩展阶段,规划器通过探索周围环境的不同视角来获得尽可能广泛的信息。它可以快速生成多个视角,并评估它们的价值和可行性,以找到最好的选择。这个阶段的目标是尽可能涵盖整个环境,同时保证视角之间的差异性。 在动态阶段,规划器将利用从扩展阶段获得的信息来制定更具体的策略。它可以根据实时的环境变化,对之前选定的视角进行调整和优化,以适应新的情况。这个阶段的目标是实现高效的探索,尽量避免不必要的重复和盲目的行为。 通过结合扩展阶段的快速探索和动态阶段的实时调整,dsvp可以在限定的时间内快速发现新的信息并做出相应的决策。它具有高效性、灵活性和鲁棒性,可以适用于各种复杂的环境和任务。此外,dsvp还可以与其他算法和技术结合使用,以进一步提升探索和规划的能力。 总的来说,dsvp是一种基于双阶段视角规划的快速探索方法,可以在不同领域中应用并获得良好的效果。它为机器人和无人机等系统的导航和探索,以及虚拟现实中的场景展示和用户体验等方面提供了一种强大的规划工具。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一起抬水泥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值