Grid_map總結
Gridmap是由蘇黎世理工自動駕駛實驗室ASL發布的一款地圖,Grid_map也是一種高程圖,和Octomap等空間占有地圖有着明顯的區別。其底層的存儲和運算使用Eigen。
1 地圖的構造、存儲和索引
Grid_map是由多層layer組成的一種復合地圖,每一層地圖可以表達不同的信息,不同的layer由各自的屬性的cell組成,但是所有的cell對應的空間(2D)坐標是統一的。例如A層layer表達地圖每個cell在空間中的高度,B層layer表達地圖每個cell的顏色,C層layer表達每個cell的可遍歷性等等。其多層特性,可以用圖1來描述。
Grid_map不是Global map,其屬於一種局部的可以移動的地圖,在機器人運動的過程中,地圖會隨着機器人而發生整體移動,如地圖是以機器人為中心,長寬各為 \(L\)的局部地圖。如圖3所示。
2 Gridmap中的迭代器
為了方便地圖中cell單元的遍歷,作者在Grid_map中加入了7個迭代器,涉及到的內容主要是計算機圖形學方面的內容。
2.1 Grid map迭代器
Grid map迭代器是地圖中最常見的一個迭代器,其迭代原理和大多數二維地圖的迭代原理類似,比較簡單。其原理可以描述為:
設地圖大小(Cell個數)為 \(m*n\),其乘積也為所有的cell數目。設定一個起始的一維索引值 \(l\),在迭代的過程中 \(l\)不斷的變換,且在 \(0\leqslant l \leqslant m*n\) 范圍內有且對應一個cell單元。那么該cell單元在在索引坐標系下對應的二維索引坐標為 \((l/n,l \% n )\) Grid map迭代器的迭代效果,如圖4所示。
為了使算法適應不同直線,減少浮點運算,將算法的核心思想做進一步的改進之后,偽代碼如下。如需進一步了解該算法可以參考上面的wiki。
function line(x0, x1, y0, y1)
boolean steep := abs(y1 - y0) > abs(x1 - x0)
if steep then
swap(x0, y0)
swap(x1, y1)
if x0 > x1 then
swap(x0, x1)
swap(y0, y1)
int deltax := x1 - x0
int deltay := abs(y1 - y0)
int error := deltax / 2
int ystep
int y := y0
if y0 < y1 then ystep := 1 else ystep := -1
for x from x0 to x1
if steep then plot(y,x) else plot(x,y)
error := error - deltay
if error < 0 then
y := y + ystep
error := error + deltax
2.5 Polygon(多邊形) 迭代器
多邊形迭代器是Gridmap迭代器中最復雜的一個迭代器。多邊形迭代器在構造的過程中需要提前設定多邊形的各個頂點,其中頂點的坐標以Position坐標系表示。
下面主要對多邊形迭代器的迭代過程做總結。
2.5.1 獲取起始索引和索引區
對起始索引點和索引區的獲取還是老思路,即求取多邊形 \(n\) 個頂點中,\(x\) 方向和 \(y\) 方向的最大值和最小值,然后將兩個軸的最大值作為左上角點,即起始點,兩個軸向的最小值作為右下角點,即終止點(在Position坐標系下),相當於做了一個外接矩形。
2.5.2 判斷點是否在多邊形內
點與多邊形的關系有內部、外部和多邊形上,判斷方法也有很多種。
引射線法,即由點向多邊形的左側引射線,如果射線與多邊形的交點個數為奇數,則點在多邊形內或多邊形上,反之點在多邊形外。或者將其理解為光源照射后與梯形區域的關系,都是一樣的原理。如圖6所示。
面積法。若點在多邊形上或者點在多邊形內部,那么點與多邊形各個邊構成的三角形的面積之和是等於多邊形面積,反之在外部則不相等。此種方法會因為計算精度會帶來一定的誤差。關於多邊形面積求取,多邊形的面積等於由組成多邊形三角形的面積之和。可用式(2)取
關於點與多邊形關系判斷的其他方法可以參考博客。
2.5.3 求取多邊形的質心
多邊形質心在求取,可以參考wiki中多邊形部分。利用式(3)求取。
2.5.3 多邊形的縮放
如圖7所示,多邊形的縮放可以看做是將多邊形的頂點向內做一個偏移 \(s\) 而多邊形的形狀不發生變化,所以最直接的方法就是做多邊形任意兩條邊的平行線,平行線之之間的距離是 \(s\),兩條平行線的交點就是新的頂點。新的頂點求取方法也比較簡單,如圖7所示。
其中, $\theta $為兩條邊的夾角。
2.6 Ellipse(橢圓) 迭代器
橢圓迭代器也比較簡單,其構造需要提供橢圓的圓心,長短軸和橢圓的旋轉角度等變量。
EllipseIterator::EllipseIterator(const GridMap& gridMap, const Position& center, const Length& length, const double rotation)
橢圓迭代器的迭代過程。橢圓迭代器是Submap迭代器的繼承,所以僅需要找到起始和終止迭代點,然后按照子地圖的迭代方式迭代即可,只不過在迭代過程中需要判斷index對應的cell是否在地圖之內。具體的過程可以描述為:
1).求取旋轉之后的長短軸。
2).以圓形為中心,長短軸平方和的根作為長,求取橢圓的外接矩形,主要是求取外接矩形的左上角點和右下角點。
3).將矩形的左上角點作為起始點,右下角點作為終止點。
4).按子地圖迭代方式,迭代地圖,並判斷cell是否在橢圓內。判斷的方法也比較簡單,即 \(\frac{x^{2}}{a_{2}}+\frac{y^{2}}{b_{2}}\leq 1\)其中 \((x,y)\) 是cell單元相對於圓心的坐標。
2.7 Spiral(螺旋) 迭代器
&esmp; 由螺旋迭代器可以看出,螺旋迭代器是圓形子地圖除了線性迭代的另一種迭代方式。在迭代器初始化的時候需要迭代器所屬的父地圖、子地圖的圓形和半徑等變量。
SpiralIterator::SpiralIterator(const grid_map::GridMap& gridMap, const Eigen::Vector2d& center, const double radius)
螺旋迭代器的迭代過程。螺旋迭代器的迭代原理比較簡單,應該可以算是參加工作筆試編程題的難度吧!!!!具體過程可以描述為
- 以子地圖圓心為中心,\(r\) 為半徑,安順時針方向取cell單元存放到
vector
中。- 每迭代器一次,就從
vector
中彈一次,若vector
為空了,執行1)即可。
所以整個螺旋迭代器的原理還是比較簡單的,迭代的代碼如下:
/**
* @function [generateRing]
* @description [獲取距圓心距離為distance的環上的點
* 按照順時針的順序,繞圓心,以distance_為半徑,求取圓上的點存放到pointsRing_中]
*
*
*/
void SpiralIterator::generateRing()
{
distance_++;
Index point(distance_, 0);
Index pointInMap;
Index normal;
do
{
// 1.增加mappoint點
pointInMap.x() = point.x() + indexCenter_.x();
pointInMap.y() = point.y() + indexCenter_.y();
// 判斷增加了坐標值的點是否還在map內
if (checkIfIndexWithinRange(pointInMap, bufferSize_))
{
// 當距離值等於或者接近半徑時,要判斷該點是否還在圓內
if (distance_ == nRings_ || distance_ == nRings_ - 1)
{
if (isInside(pointInMap))
pointsRing_.push_back(pointInMap);
}
else
{
pointsRing_.push_back(pointInMap);
}
}
// 2. 螺旋式遞增算法
// 2.1 獲取下一個迭代點的方向:0,不動 1,向右或者向下 -1,向左或者向下
normal.x() = -signum(point.y());
normal.y() = signum(point.x());
// 2.1 在x軸上判斷,移動后的點是否滿足要求
if (normal.x() != 0
&& (int) Vector(point.x() + normal.x(), point.y()).norm() == distance_)
point.x() += normal.x();
// 2.2 在y軸上判斷,移動后的點是否滿足要求
else if (normal.y() != 0
&& (int) Vector(point.x(), point.y() + normal.y()).norm() == distance_)
point.y() += normal.y();
// 2.3 如果上面兩個條件都不滿足,則在兩個軸上同時移動
else
{
point.x() += normal.x();
point.y() += normal.y();
}
// 當且僅當point.x()等於當前距離且y的值等於0的時候跳出
} while (point.x() != distance_ || point.y() != 0);
}
const Eigen::Array2i& SpiralIterator::operator *() const
{
return pointsRing_.back();
}
/**
* @function [++]
* @description [迭代重載運算符]
* @return
****/
SpiralIterator& SpiralIterator::operator ++()
{
// 先把最后一個index給刪了,相當於return一次彈一次
pointsRing_.pop_back();
// 當這個環空的時候,再增加環
if (pointsRing_.empty() && !isPastEnd())
generateRing();
return * this;
}