一、自主探索建图
自主探索建图顶层逻辑
- 找到个最佳导航点goal。认为导航到goal,能最好探索地图。
- move_base操作机器人导航到goal。
- 如果认为地图建得差不多了,结束建图,否则回到步骤1。
建图增加了两个问题。一是如何找到到最佳导航点goal,二是如何判断地图建得差不多了。针对第二个问题,往往混合在了第一个。因为找goal时,通常用迭代法,如果向迭代次数设置个阈值,超过阈值都没找到,那就认为已建出地图。
如何找到最佳导航点goal。这里说下ros官方提供的rrt_exploration功能包的两种思路。
1.1 rrt_detector
RRT:快速扩展随机树,Rapidly exploring Random Tree。
bool tnode_wrapper::exploration_detect_rrt(const costmap_2d::Costmap2D& costmap, const tpose2d& robot_pose,
geometry_msgs::Point& unk)
{
......
(init_map_x, init_map_y)是全局代价地图尺寸,是map坐标系。
bool result = false;
int rest_try = 100;
while (rest_try >= 0) {
x_rand.x = (rand() % (init_map_x - 2)) + 1;
x_rand.y = (rand() % (init_map_y - 2)) + 1;
clip(x_rand, init_map_x, init_map_y);
// Nearest
x_nearest = Nearest(V, x_rand);
clip(x_nearest, init_map_x, init_map_y);
// Steer
x_new = Steer(x_nearest, x_rand, eta);
rest_try --;
// ObstacleFree 1:free -1:unkown (frontier region) 0:obstacle
char checking = ObstacleFree(costmap, x_nearest, x_new);x_rand是在[1, 1, init_map_x - 2, init_map_y - 2]这个矩形内产生的随机栅格。严格的话,应该要确保它不在树(V)中。要注意ros在进行全局规划时,要求goal必须位在{0, 0, size_x, size_y}矩形。
Nearset是遍历树,找到树中一个离x_rand最近结点,记为x_nearest。
Steer是此次要扩展出结点。如果x_rand离Nearset不到eta,则结果就是x_rand。否则,结果是从x_nearset到x_rand直线中的一点,距离x_nearset点eta米的位置。x_new存储着结果。
ObstacleFree。走出一条从x_nearest到xnew的直线。有三种结果。
- ObstacleFree_result_other。过程中没到遇到未知栅格或障碍栅格。
- ObstacleFree_result_obs。过程中遇到障碍栅格。
- ObstacleFree_result_unk。过程中遇到未知栅格。此时将中止走路,参数xnew会被改成未知栅格坐标。
if (checking == ObstacleFree_result_unk) {
costmap.mapToWorld(x_new.x, x_new.y, world_x, world_y);
unk.x = world_x;
unk.y = world_y;
unk.z = 0.0;
result = true;
break;过程中遇到未知栅格,xnew是那个未知栅格坐标,认为xnew就是最佳goal。找到了个goal,结束此次查找。
} else if (checking == ObstacleFree_result_other) {
V.push_back(x_new);
costmap.mapToWorld(x_new.x, x_new.y, world_x, world_y);
V_.push_back(SDL_FPoint{(float)world_x, (float)world_y});过程中没到遇到未知栅格或障碍栅格,把x_new加入到树中。
} // pub.publish(line); } return result; }
1.2 opencv_detector
这是将栅格地图进行一系列opencv操作,然后找到个最佳goal。
二、tright_exploration::updateMap逻辑
- master->updateOrigin(new_origin_x, new_origin_y)。根据此次栅格图position,更新主代价地图的map坐标系原点在world坐示系下的坐标(origin_x_, origin_y_)。
- dataptr_layer_->set_mat(info, data)。dataptr层会用此次栅格图更新私有代价地图。
- layered_costmap_->updateMap(robot_x, robot_y, robot_yaw)。更新主代价地图。
- footprint_pub_.publish(footprint)。发布此刻足迹topic。
- publisher_->publishCostmap()。发布此刻代价地图topic。
一、DataptrLayer
1.1 set_dataptr
set_dataptr作用是扣出栅格图中的3mx3m区域,“复制”到Dataptr层的私有代价地图。“复制”加引号,因为不是原则复制,要用interpretValue转换下。
void DataptrLayer::set_dataptr(const nav_msgs::MapMetaData& info, const int8_t* data)
{
resetMaps();重置整张私有价地图,方法是所有栅格赋值NO_INFORMATION(0xff)。
const Costmap2D* master = layered_costmap_->getCostmap(); int start_x; int start_y; master->worldToMapNoBounds(info.origin.position.x, info.origin.position.y, start_x, start_y);

将world坐标系下坐标info.origin.position,转换到map坐标系下坐标(start_x, start_y),这两坐标位置关系参考图1。因为得到start_x、start_y极可能不在代价地图内,须调用忽略范围限制的worldToMapNoBounds。
int end_size_x;
int end_size_y;
int costmap_start_x = 0;
int costmap_start_y = 0;
if (start_x <= 0) {
start_x *= -1;
end_size_x = size_x_;
if (start_x + end_size_x > (int)info.width) {
// overrun border: east
end_size_x = info.width - start_x;
}
} else {
// overrun border: west
costmap_start_x = start_x;
end_size_x = size_x_ - costmap_start_x;
start_x = 0;
}在x方向,计算后面for循环要用的start_x、end_size_x、costmap_start_x。参考图1所示,计算时要考虑三处情况。
if (start_y <= 0) {
start_y *= -1;
end_size_y = size_y_;
if (start_y + end_size_y > (int)info.height) {
// overrun border: north
end_size_y = info.height - start_y;
}
} else {
// overrun border: south
costmap_start_y = start_y;
end_size_y = size_y_ - costmap_start_y;
start_y = 0;
}类似x,在y方向,计算后面for循环要用的start_y、end_size_y、costmap_start_y。
int src_index = 0;
int dst_index = 0;
// initialize the costmap with static data
for (int y = 0; y < end_size_y; ++ y) {
const int src_y_line = (y + start_y) * info.width;
const int dst_y_line = (y + costmap_start_y) * size_x_;
for (int x = 0; x < end_size_x; ++ x) {
src_index = (x + start_x) + src_y_line;
dst_index = (x + costmap_start_x) + dst_y_line;
unsigned char value = data[src_index];
costmap_[dst_index] = interpretValue(value);
}
}
}set_dataptr要调用master->worldToMapNoBounds(...)得到start_x、start_y,worldToMapNoBounds要使用主代价地图的(orign_x_, origin_y_),所以在set_dataptr前须调用master->updateOrigin(...),先得到正确的(orign_x_, origin_y_)。
在整个过程中,私有copstmap尺寸总是1.5mx1.5m,它的origin_x_、origin_y_没有作用,保诗着初始值(0, 0)。