用于存储网格(将具有负索引)的数据结构

Data structure to store a grid (that will have negative indices)

本文关键字:索引 数据结构 存储 网格 用于      更新时间:2023-10-16

我在大学学习机器人技术,我必须在自己的SLAM算法上实现。为此,我将使用ROS,凉亭和C++。

我怀疑我必须使用什么数据结构来存储地图(以及我将存储什么,但这是另一回事)。

我想将地图表示为2D网格,机器人的起始位置是(0,0)。但我不知道我必须绘制的世界上的机器人到底在哪里。它可能位于左上角,在世界的中间,或者在世界内的任何其他unknonw位置。

网格的每个像元均为 1x1 米。我将使用激光来知道障碍物在哪里。使用当前机器人的位置,我将在所有表示障碍物的单元格上设置为 1。例如,它激光检测机器人前方2米处的障碍物,I将(0,2)处的单元格设置为1。

使用向量或 2D 矩阵,这里有一个问题,因为向量和矩阵索引从 0 开始,机器人后面可能有更多的空间来映射。该房间将在 (-1,-3) 处有一个障碍物。

在这个数据结构上,我需要存储有障碍物的单元格和我知道它们是自由的单元格。

我必须使用哪种数据结构?

更新:

存储地图的过程如下:

  1. 机器人从 (0,0) 单元开始。它将检测障碍物并将其存储在地图中。
  2. 机器人移动到(1,0)单元。再次,检测并存储地图中的障碍物。
  3. 继续移动到自由细胞并存储它发现的障碍物。

机器人将检测其前方和侧面的障碍物,但绝不会检测其后面的障碍物。

当机器人检测到负细胞上的障碍物(如(0,-1))时,我的问题就来了。如果我以前只将障碍物存储在"阳性"单元格上,我不知道如何存储该障碍物。所以,也许是"偏移",这不是这里的解决方案(或者我错了)。

在这里,您可以编写一个class来帮助您:

class RoboArray 
{
constexpr int width_ = ...
constexpr int height_ = ...
Cell grid_[width_ * 2][height_ * 2];
...
public:
...
Cell get(int x, int y) // can make this use [x][y] notation with a helper class
{
return grid_[x + width_][y + height];
}
...
}

您拥有的选项:

  • 有一个偏移量。简单而肮脏。您的网格是 100x100,但存储 -50,-50 到 50x50。
  • 具有多个偏移网格。当您离开网格时,在它旁边分配一个新的,具有不同的偏移量。网格的列表或地图。
  • 结构稀疏。一组坐标或坐标地图。
  • 具有层次结构。您的整体(例如 50x50)网格是更高级别的网格中的一个单元格。用链表或其他东西实现它,这样当你移动时,你就会建立一个嵌套网格树。对于内存和计算时间非常有效,但实现起来要复杂得多。

可以通过创建position类使用std::set来表示网格布局。它包含一个xy变量,因此可用于直观地用于查找网格内的点。如果要在网格内存储有关特定位置的信息,也可以使用std::map

如果您不想在外部提供比较运算符,请不要忘记满足set/map(例如比较)C++命名要求。

例: 位置.h

/* this class is used to store the position of things
* it is made up by a horizontal and a vertical position.
*/
class position{
private:
int32_t horizontalPosition;
int32_t verticalPosition;
public:
position::position(const int hPos = 0,const int vPos = 0) : horizontalPosition{hPos}, verticalPosition{vPos}{}
position::position(position& inputPos) : position(inputPos.getHorPos(),inputPos.getVerPos()){}
position::position(const position& inputPos) : position((inputPos).getHorPos(),(inputPos).getVerPos()){}
//insertion operator, it enables the use of cout on this object: cout << position(0,0) << endl;
friend std::ostream& operator<<(std::ostream& os, const position& dt){
os << dt.getHorPos() << "," << dt.getVerPos();
return os;
}
//greater than operator
bool operator>(const position& rh) const noexcept{
uint64_t ans1 = static_cast<uint64_t>(getVerPos()) | static_cast<uint64_t>(getHorPos())<<32;
uint64_t ans2 = static_cast<uint64_t>(rh.getVerPos()) | static_cast<uint64_t>(rh.getHorPos())<<32;
return(ans1 < ans2);
}
//lesser than operator
bool operator<(const position& rh) const noexcept{
uint64_t ans1 = static_cast<uint64_t>(getVerPos()) | static_cast<uint64_t>(getHorPos())<<32;
uint64_t ans2 = static_cast<uint64_t>(rh.getVerPos()) | static_cast<uint64_t>(rh.getHorPos())<<32;
return(ans1 > ans2);
}
//equal comparison operator
bool operator==(const position& inputPos)const noexcept {
return((getHorPos() == inputPos.getHorPos()) && (getVerPos() == inputPos.getVerPos()));
}
//not equal comparison operator
bool operator!=(const position& inputPos)const noexcept {
return((getHorPos() != inputPos.getHorPos()) || (getVerPos() != inputPos.getVerPos()));
}
void movNorth(void) noexcept{
++verticalPosition;
}
void movEast(void) noexcept{
++horizontalPosition;
}
void movSouth(void) noexcept{
--verticalPosition;
}
void movWest(void) noexcept{
--horizontalPosition;
}
position getNorthPosition(void)const noexcept{
position aPosition(*this);
aPosition.movNorth();
return(aPosition);
}
position getEastPosition(void)const noexcept{
position aPosition(*this);
aPosition.movEast();
return(aPosition);
}
position getSouthPosition(void)const noexcept{
position aPosition(*this);
aPosition.movSouth();
return(aPosition);
}
position getWestPosition(void)const noexcept{
position aPosition(*this);
aPosition.movWest();
return(aPosition);
}
int32_t getVerPos(void) const noexcept {
return(verticalPosition);
}
int32_t getHorPos(void) const noexcept {
return(horizontalPosition);
}
};
std::set<position> gridNoData;
std::map<position, bool> gridWithData;
gridNoData.insert(point(1,1));
gridWithData.insert(point(1,1),true);
gridNoData.insert(point(0,0));
gridWithData.insert(point(0,0),true);
auto search = gridNoData.find(point(0,0));
if (search != gridNoData.end()) {
std::cout << "0,0 exists" << 'n';
} else {
std::cout << "0,0 doesn't existn";
}
auto search = gridWithData.find(point(0,0));
if (search != gridWithData.end()) {
std::cout << "0,0 exists with value" << search->second  << 'n';
} else {
std::cout << "0,0 doesn't existn";
}


我在类似的设置中使用了上面的类,我们使用了std::map定义为:

std::map<position,directionalState> exploredMap;

存储我们是否在某个位置找到了任何墙壁。
通过使用这种基于std::map的方法,您不必进行数学运算来了解 2D 数组(或类似的结构)中必须具有的偏移量。它还允许您自由移动,因为您不可能超出您在施工时设置的预定义边界。与2D阵列相比,这种结构也更节省空间,因为这种结构只保存机器人所在的区域。这也是一种C++的工作方式:依靠 STL 而不是使用 C 构造创建自己的 2D 地图。

使用偏移解(通过固定公式转换值(我们在数学课上称之为"映射函数"),就像对所有坐标执行"+50"一样,即 [-30,-29] 将变为 [+20,+21] 和 [0,0] 将变为 [+50,+50]),您仍然需要知道最大大小是多少。

如果你想像从 0 到某个 Nstd::vector<>(尽可能多地使用可用内存)一样动态,你可以创建更复杂的映射函数,例如 map(x) = x*2 当 (0 <= x) 和 x*(-2)-1 当 (x <0) ...这样,您可以使用标准std::vector,并通过达到新的最大坐标来使其根据需要增长。

使用 2D 网格与std::vector这有点复杂,因为从性能的角度来看,矢量的矢量有时不是最好的主意,但只要您的代码更喜欢简短和简单而不是性能,也许您可以使用相同的映射两个坐标并使用矢量的矢量(在所有坐标上使用reserve(..),并具有一些合理的默认值,以避免在常见用例中调整矢量的大小, 就像您知道 100m x 100m 面积通常是最大值一样,您可以最初将所有内容保留为 201 容量以避免在常见情况下调整矢量大小,但在不太常见的情况下,它仍然可以无限增长(直到堆内存耗尽)。

您还可以添加另一个映射函数,将 2D 坐标转换为 1D 并仅使用单个向量,如果您想要真正复杂的事情,例如您可以将这些 2D 映射到 0,1,2,...序列从中心周围的区域向外增长,以节省小区域的内存使用量...如果你对C++开发有点陌生,并且你不使用单元测试和TDD方法(即,一开始只是通过简单的向量向量,这一段是JFYI,如果你试图变得太聪明,事情会变得复杂:))。

class robotArray

{

Int* 左,右;

}

机器人阵列::机器人阵列 ()

{

Int* a=new int [50][50];

Int* b=new int[50][50];

左边是 -ve 空间,右边是正空间

删除了两个数组中的 0,0

个左=a+1;

右=b+1;

}

我想我明白你在这里追求什么:你不知道空间有多大,甚至不知道坐标可能是多少。

这是非常通用的,但我会创建一个使用向量(另一种选择 - 对向量或特征向量(库)向量)保存所有数据的类。 当您发现新区域时,您需要将坐标和占用信息添加到地图中(通过 AddObservation() 或类似内容)。

稍后,您可以确定最小和最大 x 和 y 坐标,并根据需要创建适当的网格。

class RoboMap{
public:
vector<int> map_x_coord;
vector<int> map_y_coord;
vector<bool> occupancy;
RoboMap();

void AddObservation(int x, int y, bool in_out){
map_x_coord.push_back(x);
map_y_coord.push_back(y);
occupancy.push_back(in_out);
}
};