机器人导航算法——Costmap地图ROS源码解析

2023-11-30 04:36

本文主要是介绍机器人导航算法——Costmap地图ROS源码解析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 前言
  • 一、move_base:Costmap程序的执行入口
  • 二、costmap初始化和维护
  • 三、costmap地图
    • 1.costmap膨胀层InflationLayer
    • 2.costmap障碍物obstacleLayer
    • 3.costmap静态层staticLayer
  • 四、如何使用地图用于逻辑处理(如规划)
  • 总结


前言

本文将从move_base主程序开始,对costmap的原理进行详解,包括静态地图/障碍物地图/膨胀地图,本文主要侧重如何去理解costmap,由于作者水平有限,如有错误,谢谢指出。
costmap使用了一种分层地图的技术方案,即分别考虑障碍物层/膨胀层/静态障碍物层更新对应层的地图代价信息,然后再合并到主地图中,具体见我翻译的另一篇文章《costmap文献阅读—Layered Costmaps for Context-Sensitive Navigation》。

一、move_base:Costmap程序的执行入口

本部分,我们关心如何从move_base导航入口进入到costmap的运行程序,以及如何进行静态地图/障碍物层地图/膨胀层的处理。
代码路径:move_base/src/move_base.cpp
首先获取地图信息,内切圆半径inscribed_radius/外切圆形半径circumscribed_radius等信息。

 //we'll assume the radius of the robot to be consistent with what's specified for the costmapsprivate_nh.param("local_costmap/inscribed_radius", inscribed_radius_, 0.325);private_nh.param("local_costmap/circumscribed_radius", circumscribed_radius_, 0.46);private_nh.param("clearing_radius", clearing_radius_, circumscribed_radius_);private_nh.param("conservative_reset_dist", conservative_reset_dist_, 3.0);private_nh.param("shutdown_costmaps", shutdown_costmaps_, false);private_nh.param("clearing_rotation_allowed", clearing_rotation_allowed_, true);private_nh.param("recovery_behavior_enabled", recovery_behavior_enabled_, true);

下面通过模块costmap_2d构建了全局代价地图对象,并通过其成员函数pause()暂停运行,将在必要的功能模块初始化结束之后通过成员接口start()开启。

// 下面通过模块costmap_2d构建了全局代价地图对象,并通过其成员函数pause()暂停运行,将在必要的功能模块初始化结束之后通过成员接口start()开启。planner_costmap_ros_ = new costmap_2d::Costmap2DROS("global_costmap", tf_);planner_costmap_ros_->pause();

构建了必要的功能模块之后,就可以开启代价地图的计算了。


// Start actively updating costmaps based on sensor data  planner_costmap_ros_->start();

这是导航模块move_base初始化costmap和维护地图的方法,其中Costmap2DROS是初始化costmap,planner_costmap_ros_->start()开始维护代价地图。

二、costmap初始化和维护

代码路径:costmap_2d/src/costmap_2d_ros.cpp
按照上述的move_base分析,我们首先调用Costmap2DROS的构造函数:


Costmap2DROS::Costmap2DROS(std::string name, tf::TransformListener& tf) :layered_costmap_(NULL), name_(name), tf_(tf), stop_updates_(false), initialized_(true), stopped_(false),robot_stopped_(false), map_update_thread_(NULL), last_publish_(0),plugin_loader_("costmap_2d", "costmap_2d::Layer"), publisher_(NULL)

Costmap2DROS的构造函数进行了坐标变换和相关参数初始化。

 ros::NodeHandle private_nh("~/" + name);ros::NodeHandle g_nh;// get our tf prefix  //获取我们的tf前缀ros::NodeHandle prefix_nh;std::string tf_prefix = tf::getPrefixParam(prefix_nh);// get two framesprivate_nh.param("global_frame", global_frame_, std::string("/map"));private_nh.param("robot_base_frame", robot_base_frame_, std::string("base_link"));// make sure that we set the frames appropriately based on the tf_prefix  //确保我们根据tf_prefix适当地设置了框架global_frame_ = tf::resolve(tf_prefix, global_frame_);robot_base_frame_ = tf::resolve(tf_prefix, robot_base_frame_);ros::Time last_error = ros::Time::now();std::string tf_error;// we need to make sure that the transform between the robot base frame and the global frame is availablewhile (ros::ok()&& !tf_.waitForTransform(global_frame_, robot_base_frame_, ros::Time(), ros::Duration(0.1), ros::Duration(0.01),&tf_error)){ros::spinOnce();if (last_error + ros::Duration(5.0) < ros::Time::now()){ROS_WARN("Timed out waiting for transform from %s to %s to become available before running costmap, tf error: %s",robot_base_frame_.c_str(), global_frame_.c_str(), tf_error.c_str());last_error = ros::Time::now();}// The error string will accumulate and errors will typically be the same, so the last// will do for the warning above. Reset the string here to avoid accumulation.tf_error.clear();}// check if we want a rolling window version of the costmapbool rolling_window, track_unknown_space, always_send_full_costmap;private_nh.param("rolling_window", rolling_window, false);//private_nh.param(“参数名称”,被赋值变量,默认值),private_nh.param("track_unknown_space", track_unknown_space, false);private_nh.param("always_send_full_costmap", always_send_full_costmap, false);

大家都知道,代价地图是由不同层级的plugins逐层累加的,包括静态地图层、障碍层、膨胀层等等,那么通过LayeredCostmap的对象,创造了一个由指向plugin的共享指针组成的容器,通过配置文件,向里一一添加插件层。


layered_costmap_ = new LayeredCostmap(global_frame_, rolling_window, track_unknown_space);if (!private_nh.hasParam("plugins") )resetOldParameters(private_nh);if (private_nh.hasParam("plugins") )
{XmlRpc::XmlRpcValue my_list;private_nh.getParam("plugins", my_list);for (int32_t i = 0; i < my_list.size(); ++i){std::string pname = static_cast<std::string>(my_list[i]["name"]);std::string type = static_cast<std::string>(my_list[i]["type"]);ROS_INFO("Using plugin \"%s\"", pname.c_str()  );// plugin_loader_ 类型是 pluginlib::ClassLoader<Layer>,ROS插件机制boost::shared_ptr<Layer> plugin = plugin_loader_.createInstance(type);// 添加到容器 plugins_layered_costmap_->addPlugin(plugin);// 执行 Layer::initialize,它向各层地图通知主地图的存在,并调用// oninitialize方法(Layer类中的虚函数,被定义在各层地图中)plugin->initialize(layered_costmap_, name + "/" + pname, &tf_);}
}

Costmap2DROS调用的接口addPlugin,则是将一个指针放入容器中,仅此而已。

对车体尺寸进行建模,这个文件中均是对不同形状的车体进行外壳的位置计算,然后计算的内切圆半径和外切圆半径同步更新到各个地图layer。


footprint_sub_ = private_nh.subscribe(topic, 1, &Costmap2DROS::setUnpaddedRobotFootprintPolygon, this);void Costmap2DROS::setUnpaddedRobotFootprintPolygon(const geometry_msgs::Polygon& footprint)
{setUnpaddedRobotFootprint(toPointVector(footprint));
}void Costmap2DROS::setUnpaddedRobotFootprint(const std::vector<geometry_msgs::Point>& points)
{unpadded_footprint_ = points;padded_footprint_ = points;padFootprint(padded_footprint_, footprint_padding_);layered_costmap_->setFootprint(padded_footprint_);
}

这段代码是用于创建一个动态重新配置服务器,用于修改名为Costmap2DConfig的配置文件。动态重新配置服务器允许在运行时修改配置参数,而不需要重启机器人。

代码解释如下:

1.dsrv_=new dynamic_reconfigure::Server<Costmap2DConfig>(ros::NodeHandle("~/" + name));:创建一个动态重新配置服务器,用于处理Costmap2DConfig类型的配置文件。ros::NodeHandle用于创建一个节点,其中包含动态重新配置服务器的地址。name是配置文件的名称,包含在地址中。

2.dynamic_reconfigure::Server<Costmap2DConfig>::CallbackType cb = boost::bind(&Costmap2DROS::reconfigureCB, this, _1, _2);:定义一个回调函数类型,用于处理配置更新事件。boost::bind将回调函数绑定到reconfigureCB成员函数,并将传入的参数传递给它。_1_2是动态重新配置服务器传递的参数,分别对应配置文件的旧值和新值。

  1. dsrv_->setCallback(cb);:将回调函数设置为动态重新配置服务器的回调函数,以便在接收到新的配置更新时调用它。

总之,这段代码创建了一个动态重新配置服务器,用于处理Costmap2DConfig类型的配置文件。它还定义了一个回调函数,用于在接收到配置更新时修改配置文件。最后,将回调函数设置为动态重新配置服务器的回调函数。这在机器人导航、控制等领域非常有用,允许在不重启机器人的情况下实时修改配置。


dsrv_ = new dynamic_reconfigure::Server<Costmap2DConfig>(ros::NodeHandle("~/" + name));dynamic_reconfigure::Server<Costmap2DConfig>::CallbackType cb = boost::bind(&Costmap2DROS::reconfigureCB, this, _1,_2);dsrv_->setCallback(cb);

然后通过如下函数进行配置参数初始化并创建主线程&Costmap2DROS::mapUpdateLoop。


void Costmap2DROS::reconfigureCB(costmap_2d::Costmap2DConfig &config, uint32_t level)
 map_update_thread_ = new boost::thread(boost::bind(&Costmap2DROS::mapUpdateLoop, this, map_update_frequency));

mapUpdateLoop这个成员函数是用于不停刷新代价地图的,它在每个循环通过updateMap函数实现,而Costmap2DROS::updateMap函数是利用LayeredCostmap的updateMap函数进行更新。


void Costmap2DROS::mapUpdateLoop(double frequency)
{//mapUpdateLoop这个成员函数是用于不停刷新代价地图的,它在每个循环通过updateMap函数实现//,而Costmap2DROS::updateMap函数是利用LayeredCostmap的updateMap函数进行更新,在前面已经分析过。// the user might not want to run the loop every cycleupdateMap();//更新地图

更新地图信息,这个函数在Costmap2DROS的地图更新线程中被循环调用。它分为两步:第一步:更新bound,即确定地图更新的范围;第二步:更新cost,更新每层地图cell对应的cost值后整合到主地图上。

void Costmap2DROS::updateMap()
{if (!stop_updates_){// get global posetf::Stamped < tf::Pose > pose;if (getRobotPose (pose)){double x = pose.getOrigin().x(),y = pose.getOrigin().y(),yaw = tf::getYaw(pose.getRotation());//调用layered_costmap_的updateMap函数,参数是机器人位姿layered_costmap_->updateMap(x, y, yaw);geometry_msgs::PolygonStamped footprint;footprint.header.frame_id = global_frame_;footprint.header.stamp = ros::Time::now();transformFootprint(x, y, yaw, padded_footprint_, footprint);//make coordiates to global coordiatesfootprint_pub_.publish(footprint);initialized_ = true;}}
}

layered_costmap_->updateMap(x, y, yaw)来自于如下代码:

void LayeredCostmap::updateMap(double robot_x, double robot_y, double robot_yaw)
{// Lock for the remainder of this function, some plugins (e.g. VoxelLayer)// implement thread unsafe updateBounds() functions.boost::unique_lock<Costmap2D::mutex_t> lock(*(costmap_.getMutex()));// if we're using a rolling buffer costmap... we need to update the origin using the robot's positionif (rolling_window_){double new_origin_x = robot_x - costmap_.getSizeInMetersX() / 2;double new_origin_y = robot_y - costmap_.getSizeInMetersY() / 2;costmap_.updateOrigin(new_origin_x, new_origin_y);}if (plugins_.size() == 0)return;minx_ = miny_ = 1e30;maxx_ = maxy_ = -1e30;for (vector<boost::shared_ptr<Layer> >::iterator plugin = plugins_.begin(); plugin != plugins_.end();++plugin){double prev_minx = minx_;double prev_miny = miny_;double prev_maxx = maxx_;double prev_maxy = maxy_;(*plugin)->updateBounds(robot_x, robot_y, robot_yaw, &minx_, &miny_, &maxx_, &maxy_);if (minx_ > prev_minx || miny_ > prev_miny || maxx_ < prev_maxx || maxy_ < prev_maxy){ROS_WARN_THROTTLE(1.0, "Illegal bounds change, was [tl: (%f, %f), br: (%f, %f)], but ""is now [tl: (%f, %f), br: (%f, %f)]. The offending layer is %s",prev_minx, prev_miny, prev_maxx , prev_maxy,minx_, miny_, maxx_ , maxy_,(*plugin)->getName().c_str());}}int x0, xn, y0, yn;costmap_.worldToMapEnforceBounds(minx_, miny_, x0, y0);costmap_.worldToMapEnforceBounds(maxx_, maxy_, xn, yn);x0 = std::max(0, x0);xn = std::min(int(costmap_.getSizeInCellsX()), xn + 1);y0 = std::max(0, y0);yn = std::min(int(costmap_.getSizeInCellsY()), yn + 1);ROS_DEBUG("Updating area x: [%d, %d] y: [%d, %d]", x0, xn, y0, yn);if (xn < x0 || yn < y0)return;costmap_.resetMap(x0, y0, xn, yn);for (vector<boost::shared_ptr<Layer> >::iterator plugin = plugins_.begin(); plugin != plugins_.end();++plugin){(*plugin)->updateCosts(costmap_, x0, y0, xn, yn);}bx0_ = x0;bxn_ = xn;by0_ = y0;byn_ = yn;initialized_ = true;
}

rolling_window_默认为false,如果开启的话,地图是时刻跟随机器人中心移动的,这里需要根据机器人当前位置和地图大小计算出地图的新原点,设置给主地图。


// if we're using a rolling buffer costmap... we need to update the origin using the robot's position//rolling_window_默认为false,如果开启的话,地图是时刻跟随机器人中心移动的,//这里需要根据机器人当前位置和地图大小计算出地图的新原点,设置给主地图。if (rolling_window_){double new_origin_x = robot_x - costmap_.getSizeInMetersX() / 2;double new_origin_y = robot_y - costmap_.getSizeInMetersY() / 2;costmap_.updateOrigin(new_origin_x, new_origin_y);}

设置好minx_、miny_、maxx_、maxy_的初始值,然后对每一层的子地图调用其updateBounds函数,传入minx_、miny_、maxx_、maxy_,函数将新的bound填充进去。updateBounds函数在Layer类中声明,在各层地图中被重载,第二步使用到的updateCosts函数也是如此。


for (vector<boost::shared_ptr<Layer> >::iterator plugin = plugins_.begin(); plugin != plugins_.end();++plugin){double prev_minx = minx_;double prev_miny = miny_;double prev_maxx = maxx_;double prev_maxy = maxy_;(*plugin)->updateBounds(robot_x, robot_y, robot_yaw, &minx_, &miny_, &maxx_, &maxy_);if (minx_ > prev_minx || miny_ > prev_miny || maxx_ < prev_maxx || maxy_ < prev_maxy){ROS_WARN_THROTTLE(1.0, "Illegal bounds change, was [tl: (%f, %f), br: (%f, %f)], but ""is now [tl: (%f, %f), br: (%f, %f)]. The offending layer is %s",prev_minx, prev_miny, prev_maxx , prev_maxy,minx_, miny_, maxx_ , maxy_,(*plugin)->getName().c_str());}}

需要指出的是,(*plugin)->updateBounds(robot_x, robot_y, robot_yaw, &minx_, &miny_, &maxx_, &maxy_);成员函数updateBounds可能来自于三个类:(1)costmap_2d/plugins/inflation_layer.cpp (2)costmap_2d/plugins/obstacle_layer.cpp

(3) costmap_2d/plugins/static_layer.cpp

使用到了C++虚函数的多态!子类重写基类的方法,然后子类实例化指向基类即可使用。

接下来调用Costmap2D类的worldToMapEnforceBounds函数,将得到的bound转换到地图坐标系。这个函数可以防止转换后的坐标超出地图范围。

 //接下来调用Costmap2D类的worldToMapEnforceBounds函数,//将得到的bound转换到地图坐标系。这个函数可以防止转换后的坐标超出地图范围。int x0, xn, y0, yn;costmap_.worldToMapEnforceBounds(minx_, miny_, x0, y0);costmap_.worldToMapEnforceBounds(maxx_, maxy_, xn, yn);x0 = std::max(0, x0);xn = std::min(int(costmap_.getSizeInCellsX()), xn + 1);y0 = std::max(0, y0);yn = std::min(int(costmap_.getSizeInCellsY()), yn + 1);//范围更新ROS_DEBUG("Updating area x: [%d, %d] y: [%d, %d]", x0, xn, y0, yn);

接下来,调用resetMap,将主地图上bound范围内的cell的cost恢复为默认值(track_unknown:255 / 否则:0),再对每层子地图调用updateCosts函数。updateCosts也是来源于不同的文件的(

(1)costmap_2d/plugins/inflation_layer.cpp (2)costmap_2d/plugins/obstacle_layer.cpp

(3) costmap_2d/plugins/static_layer.cpp

),也是虚函数实现的多态。updateCosts实现了对costmap_主地图的合并。


//接下来,调用resetMap,将主地图上bound范围内的cell的cost恢复为默认值(track_unknown:255 / 否则:0),//再对每层子地图调用updateCosts函数。costmap_.resetMap(x0, y0, xn, yn);for (vector<boost::shared_ptr<Layer> >::iterator plugin = plugins_.begin(); plugin != plugins_.end();++plugin){(*plugin)->updateCosts(costmap_, x0, y0, xn, yn);}

其实上述完成了地图的初始化和循环更新地图的代码逻辑。通过上述的分析,我们知道有两个比较重要的接口:所有层都是继承layer类,这个类比较简单,是一个纯虚基函数,定定义了两个重要接口updateBounds、updateCosts,updateBounds用于计算栅格的权重,updateCosts用于将计算的地图合并到主地图上。具体类的继承关系可参见我的另一篇文章《机器人控制—ROS代价地图Costmap的层概述》。

三、costmap地图

1.costmap膨胀层InflationLayer

本文章主要是分析具体的膨胀层的算法具体是如何实现的。之前写过原理,可参见文章《机器人算法——costmap膨胀层InflationLayer》,总结下就是:
首先确认障碍物的栅格点,然后以障碍物栅格点位当前点,然后基于当前点的考察下一个点,即上下左右,当前障碍物会一直记录,后面用于距离比较。

如果在障碍物半径内,则继续循环到上述的“ unsigned char cost = costLookup(mx, my, sx, sy);为参考矩阵计算, master_array[index] = cost;为当前cell栅格赋值。” 如此循环即可,类似于A*算法的膨胀。

这些膨胀的操作都在 InflationLayer::updateCosts操作(costmap_2d/plugins/inflation_layer.cpp)并将将上述膨胀后的地图写入主地图中。

2.costmap障碍物obstacleLayer

障碍层地图通过订阅传感器话题,将传感器输出的障碍物信息存进buffer(剔除过高、过远的点),在本层地图上将观测到的点云标记为障碍物,将传感器到点云点连线上的点标记为FREE_SPACE。最后,在bound范围内,将本层地图合并到主地图上。

通过遍历传感器的数据,将符合要求的障碍点坐标从global系转换到map系,并在本层地图上标记致命障碍。

void ObstacleLayer::updateBounds(double robot_x, double robot_y, double robot_yaw, double* min_x,double* min_y, double* max_x, double* max_y)
{if (rolling_window_)updateOrigin(robot_x - getSizeInMetersX() / 2, robot_y - getSizeInMetersY() / 2);if (!enabled_)return;useExtraBounds(min_x, min_y, max_x, max_y);//在接收到传感器数据后,buffer将被更新,同样,marking_buffers_和//clearing_buffers_也更新(即buffer中的内容),将二者分别提取到observations和clearing_observations中存储。bool current = true;std::vector<Observation> observations, clearing_observations;// get the marking observationscurrent = current && getMarkingObservations(observations);// get the clearing observationscurrent = current && getClearingObservations(clearing_observations);// update the global current statuscurrent_ = current;// raytrace freespacefor (unsigned int i = 0; i < clearing_observations.size(); ++i){raytraceFreespace(clearing_observations[i], min_x, min_y, max_x, max_y);}
/***第二步是marking操作,即将点云中的点标记为障碍物。在标记时通过二重循环,外层迭代各观测轮次,内层迭代一次观测得到的点云点,
剔除本身太高(z坐标过大)、与传感器距离太远的点,将符合要求的障碍点坐标从global系转换到map系,并在本层地图上标记致命障碍。并调用touch函数,确保标记的障碍点包含在 bound内。作者:wanghuohuo0716
链接:https://www.jianshu.com/p/2984cdc99236
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。*/// place the new obstacles into a priority queue... each with a priority of zero to begin withfor (std::vector<Observation>::const_iterator it = observations.begin(); it != observations.end(); ++it){const Observation& obs = *it;const pcl::PointCloud<pcl::PointXYZ>& cloud = *(obs.cloud_);double sq_obstacle_range = obs.obstacle_range_ * obs.obstacle_range_;for (unsigned int i = 0; i < cloud.points.size(); ++i){double px = cloud.points[i].x, py = cloud.points[i].y, pz = cloud.points[i].z;// if the obstacle is too high or too far away from the robot we won't add itif (pz > max_obstacle_height_){ROS_DEBUG("The point is too high");continue;}//计算点与点之间的距离平方// compute the squared distance from the hitpoint to the pointcloud's origindouble sq_dist = (px - obs.origin_.x) * (px - obs.origin_.x) + (py - obs.origin_.y) * (py - obs.origin_.y)+ (pz - obs.origin_.z) * (pz - obs.origin_.z);// if the point is far enough away... we won't consider it  if (sq_dist >= sq_obstacle_range){ROS_DEBUG("The point is too far away");continue;}// now we need to compute the map coordinates for the observationunsigned int mx, my;if (!worldToMap(px, py, mx, my)){ROS_DEBUG("Computing map coords failed");continue;}//设置它为致命unsigned int index = getIndex(mx, my);costmap_[index] = LETHAL_OBSTACLE;touch(px, py, min_x, min_y, max_x, max_y);}}//在以上两步完成后,调用updateFootprint函数,//它的作用是基于机器人当前位置确定该位置下的足迹,并在内部调用touch函数保证足迹包含在bound范围内。updateFootprint(robot_x, robot_y, robot_yaw, min_x, min_y, max_x, max_y);
}

这个函数就是将机器人足迹范围内设置为FREE_SPACE,并且在bound范围内将本层障碍地图的内容合并到主地图上。


void ObstacleLayer::updateCosts(costmap_2d::Costmap2D& master_grid, int min_i, int min_j, int max_i, int max_j)
{//这个函数就是将机器人足迹范围内设置为FREE_SPACE,并且在bound范围内将本层障碍地图的内容合并到主地图上。if (!enabled_)return;if (footprint_clearing_enabled_){setConvexPolygonCost(transformed_footprint_, costmap_2d::FREE_SPACE);}switch (combination_method_){case 0:  // OverwriteupdateWithOverwrite(master_grid, min_i, min_j, max_i, max_j);break;case 1:  // MaximumupdateWithMax(master_grid, min_i, min_j, max_i, max_j);break;default:  // Nothingbreak;}
}

3.costmap静态层staticLayer

获取接收到的静态地图的尺寸,当地图不随机器人移动时,若接收到的静态地图和主地图的尺寸/分辨率/起点不同,以接收到的地图为准,调整主地图的参数。


void StaticLayer::incomingMap(const nav_msgs::OccupancyGridConstPtr& new_map)
{//获取接收到的静态地图的尺寸,当地图不随机器人移动时,//若接收到的静态地图和主地图的尺寸/分辨率/起点不同,以接收到的地图为准,调整主地图的参数。unsigned int size_x = new_map->info.width, size_y = new_map->info.height;ROS_DEBUG("Received a %d X %d map at %f m/pix", size_x, size_y, new_map->info.resolution);// resize costmap if size, resolution or origin do not match//如果master map的尺寸、分辨率或原点与获取到的地图不匹配,重新设置master mapCostmap2D* master = layered_costmap_->getCostmap();if (!layered_costmap_->isRolling() && (master->getSizeInCellsX() != size_x ||master->getSizeInCellsY() != size_y ||master->getResolution() != new_map->info.resolution ||master->getOriginX() != new_map->info.origin.position.x ||master->getOriginY() != new_map->info.origin.position.y ||!layered_costmap_->isSizeLocked())){// Update the size of the layered costmap (and all layers, including this one)ROS_INFO("Resizing costmap to %d X %d at %f m/pix", size_x, size_y, new_map->info.resolution);layered_costmap_->resizeMap(size_x, size_y, new_map->info.resolution, new_map->info.origin.position.x,new_map->info.origin.position.y, true);}else if (size_x_ != size_x || size_y_ != size_y ||resolution_ != new_map->info.resolution ||origin_x_ != new_map->info.origin.position.x ||origin_y_ != new_map->info.origin.position.y){ //若本层的数据和接收到的静态地图的参数不同时,继续以接收到的地图为准,调整本层参数。// only update the size of the costmap stored locally in this layerROS_INFO("Resizing static layer to %d X %d at %f m/pix", size_x, size_y, new_map->info.resolution);resizeMap(size_x, size_y, new_map->info.resolution,new_map->info.origin.position.x, new_map->info.origin.position.y);}

将接收到的静态地图数据复制到本层地图,复制过程中调用interpretValue函数,进行“翻译”,内容后述。并设置好地图坐标系中的原点x_、y_,以及地图尺寸和标志位。若first_map_only_开启,则在第一次接收到地图后,话题将不再接收消息。

 //将接收到的静态地图数据复制到本层地图,复制过程中调用interpretValue函数,进行“翻译”,内容后述。//并设置好地图坐标系中的原点x_、y_,以及地图尺寸和标志位。//若first_map_only_开启,则在第一次接收到地图后,话题将不再接收消息。// initialize the costmap with static datafor (unsigned int i = 0; i < size_y; ++i){for (unsigned int j = 0; j < size_x; ++j){unsigned char value = new_map->data[index];costmap_[index] = interpretValue(value);++index;}}map_frame_ = new_map->header.frame_id;更新updateboundsvoid StaticLayer::updateBounds(double robot_x, double robot_y, double robot_yaw, double* min_x, double* min_y,double* max_x, double* max_y)
{//若非rolling地图,在有地图数据更新时更新边界,否则,根据静态层更新的区域的边界更新传入的边界。if( !layered_costmap_->isRolling() ){if (!map_received_ || !(has_updated_data_ || has_extra_bounds_))return;}useExtraBounds(min_x, min_y, max_x, max_y);double wx, wy;
//将map系中的起点(x_, y_)与终点(x_ + width_,, y_ + height_)转换到世界系,
//确保传入的bound能包含整个map在世界系中的范围。mapToWorld(x_, y_, wx, wy);*min_x = std::min(wx, *min_x);*min_y = std::min(wy, *min_y);mapToWorld(x_ + width_, y_ + height_, wx, wy);*max_x = std::max(wx, *max_x);*max_y = std::max(wy, *max_y);has_updated_data_ = false;
}更新updateCosts  若为rolling地图,则找到静态地图和global系之间的坐标转换,通过主地图→global→静态地图的转换过程,找到主地图的cell在静态地图上对应的cost,赋值给主地图。void StaticLayer::updateCosts(costmap_2d::Costmap2D& master_grid, int min_i, int min_j, int max_i, int max_j)
{if (!map_received_)return;if (!layered_costmap_->isRolling()){// if not rolling, the layered costmap (master_grid) has same coordinates as this layerif (!use_maximum_)updateWithTrueOverwrite(master_grid, min_i, min_j, max_i, max_j);elseupdateWithMax(master_grid, min_i, min_j, max_i, max_j);}else{// If rolling window, the master_grid is unlikely to have same coordinates as this layerunsigned int mx, my;double wx, wy;// Might even be in a different frametf::StampedTransform transform;try{tf_->lookupTransform(map_frame_, global_frame_, ros::Time(0), transform);}catch (tf::TransformException ex){ROS_ERROR("%s", ex.what());return;}// Copy map data given proper transformationsfor (unsigned int i = min_i; i < max_i; ++i){for (unsigned int j = min_j; j < max_j; ++j){// Convert master_grid coordinates (i,j) into global_frame_(wx,wy) coordinateslayered_costmap_->getCostmap()->mapToWorld(i, j, wx, wy);// Transform from global_frame_ to map_frame_tf::Point p(wx, wy, 0);p = transform(p);// Set master_grid with cell from mapif (worldToMap(p.x(), p.y(), mx, my)){if (!use_maximum_)master_grid.setCost(i, j, getCost(mx, my));elsemaster_grid.setCost(i, j, std::max(getCost(mx, my), master_grid.getCost(i, j)));}}}}
}

四、如何使用地图用于逻辑处理(如规划)

上述介绍了如何创建和维护更新地图:

  // 下面通过模块costmap_2d构建了全局代价地图对象,并通过其成员函数pause()暂停运行,将在必要的功能模块初始化结束之后通过成员接口start()开启。planner_costmap_ros_ = new costmap_2d::Costmap2DROS("global_costmap", tf_);planner_costmap_ros_->pause();

使用地图要先运行下

 planner_costmap_ros_->start();

调用地图,获取lLayeredCostmap类中成员Costmap2D costmap_;

planner_costmap_ros_->getCostmap();

总结

以上就是costmap的介绍,后续会持续更新。。。。

Reference

  1. Costmap文献阅读——Layered Costmaps for Context-Sensitive Navigation
  2. 机器人算法——costmap膨胀层InflationLayer
  3. 机器人控制—代价地图Costmap的层概述
  4. 【ROS-Navigation】Costmap2D代价地图源码解读-障碍层ObstacleLayer
  5. 【ROS-Navigation】Costmap2D代价地图源码解读-膨胀层InflationLayer
  6. 【ROS-Navigation】Costmap2D代价地图源码解读-静态层StaticLayer
  7. 【ROS-Navigation】Costmap2D代价地图源码解读-1
  8. 【ROS-Navigation】Costmap2D代价地图源码解读-2

这篇关于机器人导航算法——Costmap地图ROS源码解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/435639

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

无人叉车3d激光slam多房间建图定位异常处理方案-墙体画线地图切分方案

墙体画线地图切分方案 针对问题:墙体两侧特征混淆误匹配,导致建图和定位偏差,表现为过门跳变、外月台走歪等 ·解决思路:预期的根治方案IGICP需要较长时间完成上线,先使用切分地图的工程化方案,即墙体两侧切分为不同地图,在某一侧只使用该侧地图进行定位 方案思路 切分原理:切分地图基于关键帧位置,而非点云。 理论基础:光照是直线的,一帧点云必定只能照射到墙的一侧,无法同时照到两侧实践考虑:关

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

poj 3974 and hdu 3068 最长回文串的O(n)解法(Manacher算法)

求一段字符串中的最长回文串。 因为数据量比较大,用原来的O(n^2)会爆。 小白上的O(n^2)解法代码:TLE啦~ #include<stdio.h>#include<string.h>const int Maxn = 1000000;char s[Maxn];int main(){char e[] = {"END"};while(scanf("%s", s) != EO