技术标签: navigation ROS costmap_2d ROS Navigation
写在最前:
尊重原创,尊重他人劳动成果。原文地址:https://blog.csdn.net/u013158492/article/details/50485418
在上一篇文章中moveBase就有关于costmap_2d的使用: planner_costmap_ros_是用于全局导航的地图, controller_costmap_ros_是局部导航用的地图,地图类型为经过ROS封装的costmap_2d::Costmap2DROS*。
move_base.cpp中planner_costmap_ros_的代码如下:
//create the ros wrapper for the planner's costmap... and initializer a pointer we'll use with the underlying map
planner_costmap_ros_ = new costmap_2d::Costmap2DROS("global_costmap", tf_);
planner_costmap_ros_->pause();
move_base.cpp中controller_costmap_ros_代码如下:
//create the ros wrapper for the controller's costmap... and initializer a pointer we'll use with the underlying map
controller_costmap_ros_ = new costmap_2d::Costmap2DROS("local_costmap", tf_);
controller_costmap_ros_->pause();
以下是这个ROS类的UML:
这个类的成员变量:LayeredCostmap* layered_costmap_; pluginlib::ClassLoader<Layer> plugin_loader_; 这两个最重要的成员变量,而LayeredCostmap类又包含了Costmap2D costmap_; 这个数据成员。
下面是这些类之间的关系:
绿色的是核心代码,从ROS用户的角度,只需要调用Costmap2DROS这个类,因为这个类已经把所有关于地图的操作都封装好了。不过我这里是分析底层算法实现,就不得不写得很长很长。
所以还是先回到对Costmap2DROS这个类的分析,然后再进一步一层一层的分析其他的类。这些类完成了对机器人地图的表示和操作,因此其数据结构和算法都很有分析的价值。
首先是构造函数Costmap2DROS::Costmap2DROS(std::string name, tf::TransformListener& tf) :
因此必须提供一个tf参数。tf参数需要提供以下两个坐标系的关系:
// get global and robot base frame names
private_nh.param("global_frame", global_frame_, std::string("map"));
private_nh.param("robot_base_frame", robot_base_frame_, std::string("base_link"));
如果没有找到这两个坐标系的关系或者超时,则构造函数会一直阻塞在这里:
// we need to make sure that the transform between the robot base frame and the global frame is available
while (ros::ok()
&& !tf_.canTransform(global_frame_, robot_base_frame_, ros::Time(), ros::Duration(0.1), &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();
}
然后加入各个层次的地图:
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("%s: Using plugin \"%s\"", name_.c_str(), pname.c_str());
copyParentParameters(pname, type, private_nh);
boost::shared_ptr<Layer> plugin = plugin_loader_.createInstance(type);
layered_costmap_->addPlugin(plugin);
plugin->initialize(layered_costmap_, name + "/" + pname, &tf_);
}
}
boost::shared_ptr<Layer> plugin = plugin_loader_.createInstance(type);
这行会创建一个以 type为类类型的实例变量,然后让plugin这个指针指向这个实例。
layered_costmap_->addPlugin(plugin);
然后 layered_costmap_ 将这些类型的地图都加入,addPlugin
实现:
void addPlugin(boost::shared_ptr<Layer> plugin)
{
plugins_.push_back(plugin);
}
这里的关系是:Costmap2DROS 有一个layered_costmap_ 数据成员,然后layered_costmap_ 又有一个std::vector<boost::shared_ptr<Layer> > plugins_; 成员,因此可以将各个子类的实例化对象的指针交给父类Layer 指针plugins_ 管理。
plugin->initialize(layered_costmap_, name + "/" + pname, &tf_);
这行将会对实例初始化,实际执行是plugin调用的父类Layer的方法void Layer::initialize(LayeredCostmap* parent, std::string name, tf::TransformListener *tf) 。
实际上父类Layer有一个成员变量为LayeredCostmap* layered_costmap_; 的指针,因此通过LayeredCostmap* layered_costmap_;指针指向了具体的子类,比如ObstacleLayer StaticLayer InflationLayer 等。
然后设置footprint:footprint_sub_ = private_nh.subscribe(topic, 1, &Costmap2DROS::setUnpaddedRobotFootprintPolygon, this); ,回调函数setUnpaddedRobotFootprintPolygon 实际调用的是成员函数:
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_);
}
以下是在footprint.cpp中的定义:
void padFootprint(std::vector<geometry_msgs::Point>& footprint, double padding)
{
// pad footprint in place
for (unsigned int i = 0; i < footprint.size(); i++)
{
geometry_msgs::Point& pt = footprint[ i ];
pt.x += sign0(pt.x) * padding;
pt.y += sign0(pt.y) * padding;
}
}
然后声明了一个timer,定时检测机器人是否在移动:
// Create a time r to check if the robot is moving
robot_stopped_ = false;
timer_ = private_nh.createTimer(ros::Duration(.1), &Costmap2DROS::movementCB, this);
这里回调函数movementCB
实现,是通过比较前后两个pose的差,判断机器人是否在移动:
void Costmap2DROS::movementCB(const ros::TimerEvent &event)
{
// don't allow configuration to happen while this check occurs
// boost::recursive_mutex::scoped_lock mcl(configuration_mutex_);
geometry_msgs::PoseStamped new_pose;
if (!getRobotPose(new_pose))
{
ROS_WARN_THROTTLE(1.0, "Could not get robot pose, cancelling reconfiguration");
robot_stopped_ = false;
}
// make sure that the robot is not moving
else
{
old_pose_ = new_pose;
robot_stopped_ = (tf2::Vector3(old_pose_.pose.position.x, old_pose_.pose.position.y,
old_pose_.pose.position.z).distance(tf2::Vector3(new_pose.pose.position.x,
new_pose.pose.position.y, new_pose.pose.position.z)) < 1e-3) &&
(tf2::Quaternion(old_pose_.pose.orientation.x,
old_pose_.pose.orientation.y,
old_pose_.pose.orientation.z,
old_pose_.pose.orientation.w).angle(tf2::Quaternion(new_pose.pose.orientation.x,
new_pose.pose.orientation.y,
new_pose.pose.orientation.z,
new_pose.pose.orientation.w)) < 1e-3);
}
}
在构造函数末尾,开启参数动态配置:
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);
回调函数reconfigureCB 除了对一些类成员的配置值做赋值以外,还会开启一个更新map的线程
void Costmap2DROS::reconfigureCB(costmap_2d::Costmap2DConfig &config, uint32_t level)
{
transform_tolerance_ = config.transform_tolerance;
if (map_update_thread_ != NULL)
{
map_update_thread_shutdown_ = true;
map_update_thread_->join();
delete map_update_thread_;
}
map_update_thread_shutdown_ = false;
double map_update_frequency = config.update_frequency;
double map_publish_frequency = config.publish_frequency;
if (map_publish_frequency > 0)
publish_cycle = ros::Duration(1 / map_publish_frequency);
else
publish_cycle = ros::Duration(-1);
// find size parameters
double map_width_meters = config.width, map_height_meters = config.height, resolution = config.resolution, origin_x =
config.origin_x,
origin_y = config.origin_y;
if (!layered_costmap_->isSizeLocked())
{
layered_costmap_->resizeMap((unsigned int)(map_width_meters / resolution),
(unsigned int)(map_height_meters / resolution), resolution, origin_x, origin_y);
}
// If the padding has changed, call setUnpaddedRobotFootprint() to
// re-apply the padding.
if (footprint_padding_ != config.footprint_padding)
{
footprint_padding_ = config.footprint_padding;
setUnpaddedRobotFootprint(unpadded_footprint_);
}
readFootprintFromConfig(config, old_config_);
old_config_ = config;
map_update_thread_ = new boost::thread(boost::bind(&Costmap2DROS::mapUpdateLoop, this, map_update_frequency));
}
mapUpdateLoop() 的线程函数定义:
void Costmap2DROS::mapUpdateLoop(double frequency)
{
// the user might not want to run the loop every cycle
if (frequency == 0.0)
return;
ros::NodeHandle nh;
ros::Rate r(frequency);
while (nh.ok() && !map_update_thread_shutdown_)
{
struct timeval start, end;
double start_t, end_t, t_diff;
gettimeofday(&start, NULL);
updateMap();
gettimeofday(&end, NULL);
start_t = start.tv_sec + double(start.tv_usec) / 1e6;
end_t = end.tv_sec + double(end.tv_usec) / 1e6;
t_diff = end_t - start_t;
ROS_DEBUG("Map update time: %.9f", t_diff);
if (publish_cycle.toSec() > 0 && layered_costmap_->isInitialized())
{
unsigned int x0, y0, xn, yn;
layered_costmap_->getBounds(&x0, &xn, &y0, &yn);
publisher_->updateBounds(x0, xn, y0, yn);
ros::Time now = ros::Time::now();
if (last_publish_ + publish_cycle < now)
{
publisher_->publishCostmap();
last_publish_ = now;
}
}
r.sleep();
// make sure to sleep for the remainder of our cycle time
if (r.cycleTime() > ros::Duration(1 / frequency))
ROS_WARN("Map update loop missed its desired rate of %.4fHz... the loop actually took %.4f seconds", frequency,
r.cycleTime().toSec());
}
}
核心功能在于调用updateMap();
void Costmap2DROS::updateMap()
{
if (!stop_updates_)
{
// get global pose
geometry_msgs::PoseStamped pose;
if (getRobotPose (pose))
{
double x = pose.pose.position.x,
y = pose.pose.position.y,
yaw = tf2::getYaw(pose.pose.orientation);
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);
footprint_pub_.publish(footprint);
initialized_ = true;
}
}
}
函数layered_costmap_->updateMap(x, y, yaw);
定义
void LayeredCostmap::updateMap(double robot_x, double robot_y, double robot_yaw)
{
// if we're using a rolling buffer costmap... we need to update the origin using the robot's position
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);
}
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)
{
(*plugin)->updateBounds(robot_x, robot_y, robot_yaw, &minx_, &miny_, &maxx_, &maxy_);
}
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;
{
// Clear and update costmap under a single lock
boost::unique_lock<Costmap2D::mutex_t> lock(*(costmap_.getMutex()));
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;
}
updateMap
分为两个阶段,第一个阶段是UpdateBounds
:这个阶段会更新每个Layer的更新区域,这样在每个运行周期内减少了数据拷贝的操作时间。
(*plugin)->updateBounds(robot_x, robot_y, robot_yaw, &minx_, &miny_, &maxx_, &maxy_);
第二个阶段是 ` UpdateCosts :
for (vector<boost::shared_ptr<Layer> >::iterator plugin = plugins_.begin(); plugin != plugins_.end();
++plugin)
{
(*plugin)->updateCosts(costmap_, x0, y0, xn, yn);
}
这个阶段将逐一拷贝数据到Master Map,关于Master Map是如何得到的,见下图,图来源于David Lu的Paper《Layered Costmaps for Context-Sensitive Navigation》:
void Costmap2DROS::start(),
这里通过成员变量layered_costmap_
拿到类LayeredCostmap
的数据成员std::vector<boost::shared_ptr<Layer> > plugins_;
,然后调用没个Layer
的子类的方法(*plugin)->activate();
void Costmap2DROS::start()
{
std::vector < boost::shared_ptr<Layer> > *plugins = layered_costmap_->getPlugins();
// check if we're stopped or just paused
if (stopped_)
{
// if we're stopped we need to re-subscribe to topics
for (vector<boost::shared_ptr<Layer> >::iterator plugin = plugins->begin(); plugin != plugins->end();
++plugin)
{
(*plugin)->activate();
}
stopped_ = false;
}
stop_updates_ = false;
// block until the costmap is re-initialized.. meaning one update cycle has run
ros::Rate r(100.0);
while (ros::ok() && !initialized_)
r.sleep();
}
函数
void Costmap2DROS::stop()
void Costmap2DROS::stop()
{
stop_updates_ = true;
std::vector < boost::shared_ptr<Layer> > *plugins = layered_costmap_->getPlugins();
// unsubscribe from topics
for (vector<boost::shared_ptr<Layer> >::iterator plugin = plugins->begin(); plugin != plugins->end();
++plugin)
{
(*plugin)->deactivate();
}
initialized_ = false;
stopped_ = true;
}
函数 void Costmap2DROS::resetLayers()
void Costmap2DROS::resetLayers()
{
Costmap2D* top = layered_costmap_->getCostmap();
top->resetMap(0, 0, top->getSizeInCellsX(), top->getSizeInCellsY());
std::vector < boost::shared_ptr<Layer> > *plugins = layered_costmap_->getPlugins();
for (vector<boost::shared_ptr<Layer> >::iterator plugin = plugins->begin(); plugin != plugins->end();
++plugin)
{
(*plugin)->reset();
}
}
函数 bool Costmap2DROS::getRobotPose, 这里只需要指定global_pose
和 robot_pose
各自的frame_id_
就可以通过tf_.transformPose(global_frame_, robot_pose, global_pose);
获得机器人的 global_pose
。
bool Costmap2DROS::getRobotPose(geometry_msgs::PoseStamped& global_pose) const
{
tf2::toMsg(tf2::Transform::getIdentity(), global_pose.pose);
geometry_msgs::PoseStamped robot_pose;
tf2::toMsg(tf2::Transform::getIdentity(), robot_pose.pose);
robot_pose.header.frame_id = robot_base_frame_;
robot_pose.header.stamp = ros::Time();
ros::Time current_time = ros::Time::now(); // save time for checking tf delay later
// get the global pose of the robot
try
{
tf_.transform(robot_pose, global_pose, global_frame_);
}
catch (tf2::LookupException& ex)
{
ROS_ERROR_THROTTLE(1.0, "No Transform available Error looking up robot pose: %s\n", ex.what());
return false;
}
catch (tf2::ConnectivityException& ex)
{
ROS_ERROR_THROTTLE(1.0, "Connectivity Error looking up robot pose: %s\n", ex.what());
return false;
}
catch (tf2::ExtrapolationException& ex)
{
ROS_ERROR_THROTTLE(1.0, "Extrapolation Error looking up robot pose: %s\n", ex.what());
return false;
}
// check global_pose timeout
if (current_time.toSec() - global_pose.header.stamp.toSec() > transform_tolerance_)
{
ROS_WARN_THROTTLE(1.0,
"Costmap2DROS transform timeout. Current time: %.4f, global_pose stamp: %.4f, tolerance: %.4f",
current_time.toSec(), global_pose.header.stamp.toSec(), transform_tolerance_);
return false;
}
return true;
}
函数 void Costmap2DROS::getOrientedFootprint 完成将机器人坐标系下的机器人轮廓点的坐标转化为机器人在当前全局坐标系下的轮廓点的值。具体定义如下:
void Costmap2DROS::getOrientedFootprint(std::vector<geometry_msgs::Point>& oriented_footprint) const
{
geometry_msgs::PoseStamped global_pose;
if (!getRobotPose(global_pose))
return;
double yaw = tf2::getYaw(global_pose.pose.orientation);
transformFootprint(global_pose.pose.position.x, global_pose.pose.position.y, yaw,
padded_footprint_, oriented_footprint);
}
到此,基本上 Costmap2DROS
的定义就这么多了。不过其中类和类之间的调用关系依然还是很复杂,因此需要需要分析plugin原理,才能真正知道这些类的关系是如何实现的。
文章浏览阅读645次。这个肯定是末尾的IDAT了,因为IDAT必须要满了才会开始一下个IDAT,这个明显就是末尾的IDAT了。,对应下面的create_head()代码。,对应下面的create_tail()代码。不要考虑爆破,我已经试了一下,太多情况了。题目来源:UNCTF。_攻防世界困难模式攻略图文
文章浏览阅读2.9k次,点赞3次,收藏10次。偶尔会用到,记录、分享。1. 数据库导出1.1 切换到dmdba用户su - dmdba1.2 进入达梦数据库安装路径的bin目录,执行导库操作 导出语句:./dexp cwy_init/[email protected]:5236 file=cwy_init.dmp log=cwy_init_exp.log 注释: cwy_init/init_123..._达梦数据库导入导出
文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js
文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6
文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输
文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...
文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure
文章浏览阅读10w+次,点赞93次,收藏352次。1、用strtok函数进行字符串分割原型: char *strtok(char *str, const char *delim);功能:分解字符串为一组字符串。参数说明:str为要分解的字符串,delim为分隔符字符串。返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。其它:strtok函数线程不安全,可以使用strtok_r替代。示例://借助strtok实现split#include <string.h>#include <stdio.h&_c++ 字符串分割
文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答
文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。
文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入
文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf