论文
体素信息结构(VIS):
Name | mean | 符号 |
---|
position | 体素坐标 | pos |
occupancy | 占用概率 | occ |
ESDF | 到最近障碍物的欧几里得距离 | dis |
Closest Obstacle Coordinate | 最近障碍物的体素坐标 | coc |
observed | 是否曾经观察到这种体素 | obs |
prev, next, head | DLL用过 | prev, next, head |
Doubly Linked List(DLL) | 最近障碍物是该体素的所有体素 | dll |
Neighhoods | 该体素的所有观察到的Neighhood(如26连接) | nbrs |
索引数据结构
该数据结构用来将体素坐标映射到VIS的索引中,可以使用数组的方式或者哈希表的方式。
数组方式:当通过其坐标查询体素时,我们只需要计算其在该数组内的索引,然后返回VIS的相应指针即可。缺点:地图大占内存。
哈希表方式:使用哈希表将体素坐标转换为其相应的VIS指针。缺点:查找操作数量众多,性能差。
我们:将(块大小)3个体素,作为一块,哈希表仅用于管理块,在根据体素坐标计算块坐标之后,该哈希表将用于查找对应的块。该块中所有体素的VIS的所有指针都存储在相对于该块的数组中。
双向链表(DLLs)
使用DLLs可以在体素从占有变为空闲的时候,高效地更新ESDF地图。一个体素vox所对应的dll存放了所有最近障碍物是vox的体素。它是个双向链表,能够快速的遍历。
对于障碍物vox,DLL存储了所有最近障碍物是vox的体素。
过程
在此过程中,所有更改其占用状态的体素均被添加到分别名为insertQueue和deleteQueue的两个队列中。之后,将这两个队列合并为一个名为updateQueue的队列,并使用基于BFS的ESDF更新算法来更新ESDF可能更改的所有体素。
我们使用光线追踪法来将新的占有信息叠加到占有栅格地图中。所有占有状态发生变化的体素会被添加到insertQueue或deleteQueue中。
-
情况1:假设添加为“占据”的体素,对地图进行更新:
仅有insertQueue, deleteQueue 为空
对于每个被更新过的点cur,处理更新它的临近点nbr,如果临近点nbr之前的esdf 小于 其到cur的最近障碍物的距离,那esdf就更新为其到cur的最近障碍物的距离,把nbr也放到更新过的点里。
大致如图,当某个点占据情况发生变化时,搜索它的邻居,如果这个邻居离该点的距离 比之前的最近距离(esdf)近,就更新成本次距离,并把该邻居也作为生长点,搜索它的邻居进行比较。
-
情况2:既添加又删除
添加时,该点的
d
i
s
=
0
dis=0
dis=0,然后执行上一步的更新操作
删除时,该点的
d
i
s
=
∞
dis=∞
dis=∞,查找所有以被删除点cur为最近障碍物的点vox,查找vox的nbr的最近障碍物,最近的那个作为vox的最近障碍物。
-
情况3:补丁
bug:当观察到范围外的点,t0时观察到0,t1时观察到1~3。按步骤1的话,t0时刻不会更新体素1~3,因为他们属于为探测区域;t1时刻,体素3为占据,以它为中心更新它的邻居,第1个体素以为最近的是3。(因为0没发生变化,没再更新障碍0)
补丁:添加内容->当体素cur到其邻居的最近障碍物的距离,比他自身的近,就换成其邻居的最近障碍物
存在的误差:
假设A要被更新,因为
n
b
r
.
d
i
s
←
D
I
S
T
(
c
u
r
.
c
o
c
,
n
b
r
.
p
o
s
)
nbr.dis ← DIST(cur.coc, nbr.pos)
nbr.dis←DIST(cur.coc,nbr.pos),考虑AC, 其中A是nbr,c是cur。在红蓝之间,离A更近,但离C原,所以A的最近点也会被更新为B。
代码
test_fiesta.cpp:
从test_fiesta.cpp进入,就实例化了一个模板类fiesta,具体实现都在类的构造函数中。
这里可以修改类的输入类型,点云格式:点云还是深度图,位姿格式:path还是odom等。支持的类型回调中可以看到:
int main(int argc, char **argv)
fiesta::Fiesta<sensor_msgs::PointCloud2::ConstPtr, geometry_msgs::TransformStamped::ConstPtr> esdf_map(node);
Fiesta.h:
构造函数:
esdf_map_->SetParameters()
transform_sub_ = node.subscribe("transform", 10, &Fiesta::PoseCallback, this);\
depth_sub_ = node.subscribe("depth", 10, &Fiesta::DepthCallback, this);
pass
update_mesh_timer_ = node.createTimer(ros::Duration(time), &Fiesta::UpdateEsdfEvent, this);
1、PoseCallback:
if constexpr(std::is_same<PoseMsgType, geometry_msgs::PoseStamped::ConstPtr>::value)
pos = Eigen::Vector3d(msg->pose.position.x, msg->pose.position.y, msg->pose.position.z);
q = Eigen::Quaterniond(msg->pose.orientation.w, msg->pose.orientation.x, msg->pose.orientation.y, msg->pose.orientation.z);
transform_queue_.push(std::make_tuple(msg->header.stamp, pos, q));
2、DepthCallback:
depth_queue_.push(depth_map);
SynchronizationAndProcess();
SynchronizationAndProcess():时间同步、depth类型处理、位姿变换、Raycast
while (transform_queue_.size() > 1 && std::get<0>(transform_queue_.front()) <= depth_time + ros::Duration(time_delay))
sync_pos_ = std::get<1>(transform_queue_.front());
sync_q_ = std::get<2>(transform_queue_.front());
transform_queue_.pop();
transform_.block<3, 3>(0, 0) = sync_q_.toRotationMatrix();
transform_.block<3, 1>(0, 3) = sync_pos_;
transform_(3, 0) = transform_(3, 1) = transform_(3, 2) = 0;
transform_(3, 3) = 1;
transform_ = transform_*parameters_.T_D_B_*parameters_.T_B_C_;
raycast_origin_ = Eigen::Vector3d(transform_(0, 3), transform_(1, 3), transform_(2, 3))/transform_(3, 3);
if constexpr(std::is_same<DepthMsgType, sensor_msgs::Image::ConstPtr>::value) DepthConversion();
pcl::fromROSMsg(*tmp, cloud_);
滤波步骤,对深度图每个像素:
- 先判断是否在深度范围内
- 位姿变换:本次camera系 -> world系 -> 上次camera系 -> 上次图像系(u,v)
- 上次该点的depth和本次的depth差值小,就保留
RaycastMultithread()
RaycastMultithread():
RaycastProcess(0, cloud_.points.size(), tt);
for (size_t i = 0; i < parameters_.ray_cast_num_thread_; ++i)
integration_threads.emplace_back(&Fiesta::RaycastProcess, this, i, part, tt);
RaycastProcess():
for (int idx = part*i; idx < part*(i + 1); idx++)
pcl::PointXYZ pt = cloud_.points[idx];
Eigen::Vector4d tmp = transform_*Eigen::Vector4d(pt.x, pt.y, pt.z, 1);
Eigen::Vector3d point = Eigen::Vector3d(tmp(0), tmp(1), tmp(2))/tmp(3);
tmp_idx = esdf_map_->SetOccupancy((Eigen::Vector3d) point, 1)
if (set_occ_[tmp_idx]==tt)
continue;
else
set_occ_[tmp_idx] = tt;
Raycast(raycast_origin_/parameters_.resolution_,
point/parameters_.resolution_,
parameters_.l_cornor_/parameters_.resolution_,
parameters_.r_cornor_/parameters_.resolution_,
&output);
for (int i = output.size() - 2; i >= 0; i--)
tmp_idx = esdf_map_->SetOccupancy(tmp, 0);
if (set_free_[tmp_idx]==tt)
break;
else
set_free_[tmp_idx] = tt;
其中:
- SetOccupancy:只要被raycast到了,都放到occupancy_queue_,而是否占据通过num_miss和num_hit_判断:num_hit_ >= num_miss_- num_hit_ =>占据的次数>空闲的次数,则记为占据(体素可能包含好几个点云)
Pos2Vox(pos, vox);
idx = Vox2Idx(vox);
num_miss_[idx]++;
num_hit_[idx] += occ;
occupancy_queue_.push(QueueElement{vox, 0.0});
1、步进时x,y,z增加的方向( + or - )。
2、求最小的正t,使start.x()+t*ds为整数(即决定下一步 x+stepX or y+stepY or z+stepZ )。
3、求变化步长step
4、从相机原点开始,output->push_back(Eigen::Vector3d(x, y, z));
5、更新: x+stepX or y+stepY or z+stepZ ,返回 2
3、UpdateEsdfEvent 回调
esdf_map_->UpdateOccupancy(parameters_.global_update_);
esdf_map_->UpdateESDF();
Visualization(esdf_map_, parameters_.global_vis_, "");
UpdateOccupancy():
while (!occupancy_queue_.empty())
QueueElement xx = occupancy_queue_.front();
int idx = Vox2Idx(xx.point_);
int occupy = Exist(idx);
double log_odds_update = (num_hit_[idx] >= num_miss_[idx] - num_hit_[idx] ? prob_hit_log_ : prob_miss_log_);
if (!global_map && !VoxInRange(xx.point_, false)) {
occupancy_buffer_[idx] = 0;
distance_buffer_[idx] = infinity_;
}
occupancy_buffer_[idx] = std::min(std::max(occupancy_buffer_[idx] + log_odds_update, clamp_min_log_), clamp_max_log_);
if (Exist(idx) && !occupy)
insert_queue_.push(QueueElement{xx.point_, 0.0});
else if (!Exist(idx) && occupy)
delete_queue_.push(QueueElement{xx.point_, (double) infinity_});
UpdateESDF():这部分参阅论文就好了
论文中的几个变量
xx.point_:pos
Exist(idx):occ
distance_buffer_:dis
closest_obstacle_:coc
prev_\next_\head_:prev_\next_\head_
new_pos:nbr
idx:向量中的索引位置
QueueElement xx = insert_queue_.front();
insert_queue_.pop();
int idx = Vox2Idx(xx.point_);
DeleteFromList(Vox2Idx(closest_obstacle_[idx]), idx);
closest_obstacle_[idx] = xx.point_;
distance_buffer_[idx] = 0.0;
InsertIntoList(idx, idx);
update_queue_.push(xx);
QueueElement xx = delete_queue_.front();
delete_queue_.pop();
int idx = Vox2Idx(xx.point_);
int next_obs_idx;
for (int obs_idx = head_[idx]; obs_idx != undefined_; obs_idx = next_obs_idx)
closest_obstacle_[obs_idx] = Eigen::Vector3i(undefined_, undefined_, undefined_);
double distance = infinity_;
Eigen::Vector3i obs_vox = Idx2Vox(obs_idx);
for (const auto &dir : dirs_)
Eigen::Vector3i new_pos = obs_vox + dir;
int new_pos_idx = Vox2Idx(new_pos);
if (VoxInRange(new_pos) && closest_obstacle_[new_pos_idx](0) != undefined_ && Exist(Vox2Idx(closest_obstacle_[new_pos_idx])))
double tmp = Dist(obs_vox, closest_obstacle_[new_pos_idx]);
if (tmp < distance) {
distance = tmp;
closest_obstacle_[obs_idx] = closest_obstacle_[new_pos_idx];
break;
prev_[obs_idx] = undefined_;
next_obs_idx = next_[obs_idx];
next_[obs_idx] = undefined_;
update_queue_.push(QueueElement{obs_vox, distance});
- 更新
发生过删除和添加的点,都被放到了update_queue_里。这一步要更新这些点的邻居,即这个点变成了障碍物,他的邻居的最近障碍物很可能就需要改变。同时,使用广度优先搜索,邻居的邻居也很可能变化,把他们全部更新直到没有变化。
先执行第三步的补丁,然后:
int new_obs_idx = Vox2Idx(closest_obstacle_[idx]);
for (const auto &dir : dirs_)
Eigen::Vector3i new_pos = xx.point_ + dir;
int new_pos_id = Vox2Idx(new_pos);
double tmp = Dist(new_pos, closest_obstacle_[idx]);
if (distance_buffer_[new_pos_id] > tmp) {
closest_obstacle_[new_pos_id] = closest_obstacle_[idx];
distance_buffer_[new_pos_id] = tmp;
DeleteFromList(Vox2Idx(closest_obstacle_[new_pos_id]), new_pos_id);
InsertIntoList(new_obs_idx, new_pos_id);
update_queue_.push(QueueElement{new_pos, tmp});
4、发布
发布占据栅格地图
esdf_map->GetPointCloud(pc, parameters_.vis_lower_bound_, parameters_.vis_upper_bound_);
occupancy_pub_.publish(pc);
发布距离场
esdf_map->GetSliceMarker(slice_marker, parameters_.slice_vis_level_, 100,
Eigen::Vector4d(0, 1.0, 0, 1), parameters_.slice_vis_max_dist_);
slice_pub_.publish(slice_marker);
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)