机器人路径规划梯形分解(Trapezoidal cell decomposition)算法

机器人路径规划梯形分解(Trapezoidal cell decomposition)算法

梯形分解(Trapezoidal Cell Decomposition)是一种用于机器人路径规划的技术。它适用于二维平面上的机器人导航,特别是在有障碍物的环境中。梯形分解的基本思想是将地图分解成多个不相交的梯形区域,然后构建一个连接这些梯形区域的导航图。机器人可以通过在导航图上搜索最短路径来确定从起点到终点的最佳路径。

梯形分解的步骤如下:

  1. 将地图中的障碍物多边形分解成线段。
  2. 将线段的端点向地图边界射出水平射线,将地图划分为若干个梯形区域。
  3. 通过将相邻梯形区域连接起来,构建导航图。
  4. 使用搜索算法(如A*算法)在导航图上找到从起点到终点的最短路径。

以下是一个使用C++实现的梯形分解示例程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#include <iostream>
#include <vector>
#include <algorithm>

// 定义点结构
struct Point {
double x, y;

Point(double x = 0, double y = 0) : x(x), y(y) {}
};

// 定义线段结构
struct Segment {
Point start, end;

Segment(Point start = Point(), Point end = Point()) : start(start), end(end) {}
};

// 定义梯形结构
struct Trapezoid {
Segment top, bottom;
Point leftp, rightp;
};

class TrapezoidalMap {
public:
TrapezoidalMap(const std::vector<Segment>& segments) {
buildTrapezoidalMap(segments);
}

void buildTrapezoidalMap(const std::vector<Segment>& segments) {
// 对线段的端点进行排序
std::vector<Point> endpoints(segments.size() * 2);
for (size_t i = 0; i < segments.size(); ++i) {
endpoints[i * 2] = segments[i].start;
endpoints[i * 2 + 1] = segments[i].end;
}
std::sort(endpoints.begin(), endpoints.end(), [](const Point& a, const Point& b) {
return a.x < b.x;
});

// 生成梯形分解
for (const Point& p : endpoints) {
// 查找当前端点左侧的梯形
Trapezoid* leftTrap = findTrapezoid(p);

// 如果找不到左侧梯形,说明当前点在地图边界之外,跳过处理
if (!leftTrap) continue;

// 更新梯形区域
if (p.x == leftTrap->rightp.x) {
leftTrap->rightp = p;
} else {
Trapezoid newTrap = *leftTrap;
newTrap.leftp = p;
trapezoids.push_back(newTrap);
leftTrap->rightp= p;
}
}
}
// 在梯形图中查找包含给定点的梯形
Trapezoid* findTrapezoid(const Point& p) {
for (Trapezoid& trap : trapezoids) {
if (p.x >= trap.leftp.x && p.x <= trap.rightp.x) {
double topY = getYatX(trap.top, p.x);
double bottomY = getYatX(trap.bottom, p.x);

if (p.y <= topY && p.y >= bottomY) {
return &trap;
}
}
}
return nullptr;
}

// 根据给定的x坐标和线段,计算线段上对应的y坐标
double getYatX(const Segment& segment, double x) {
double t = (x - segment.start.x) / (segment.end.x - segment.start.x);
return segment.start.y + t * (segment.end.y - segment.start.y);
}

private:
std::vector<Trapezoid> trapezoids;
};

int main() {
// 定义障碍物线段
std::vector<Segment> segments = {
Segment(Point(2, 2), Point(5, 6)),
Segment(Point(5, 6), Point(8, 2)),
Segment(Point(10, 3), Point(13, 6)),
Segment(Point(13, 6), Point(15, 3))
};
// 构建梯形图
TrapezoidalMap trapezoidalMap(segments);

// 寻找路径...

return 0;
}

上面的示例程序首先对障碍物线段进行排序,然后遍历每个端点,根据端点找到相应的梯形并更新梯形分解。为了简化问题,此示例仅处理了梯形分解的构建过程,而未包括实际的路径搜索部分。

为了在梯形分解的基础上进行路径规划,你可以使用一种图搜索算法(如A*或Dijkstra算法)来在导航图上搜索从起点到终点的最短路径。在实际应用中,你还需要考虑如何处理地图边界、障碍物多边形的凸性等问题。

在上面的示例程序中,我们完成了梯形分解的构建过程。接下来,我们将实现一个简化版的A*算法,用于在导航图上搜索从起点到终点的最短路径。

首先,我们需要定义一个函数来计算两点之间的距离,以作为启发式函数:

1
2
3
4
5
6
double distance(const Point& a, const Point& b) {
double dx = a.x - b.x;
double dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}


接下来,定义一个表示导航图节点的结构体,用于存储梯形区域和A算法所需的信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
struct NavNode {
Trapezoid* trapezoid;
NavNode* parent;
double cost;
double heuristic;
double total;

NavNode(Trapezoid* trapezoid, NavNode* parent, double cost, double heuristic)
: trapezoid(trapezoid), parent(parent), cost(cost), heuristic(heuristic) {
total = cost + heuristic;
}
};


现在我们可以开始实现A
算法。为简化问题,我们假设从一个梯形到另一个梯形的代价是两个梯形中心点之间的距离。实际应用中,你可能需要根据机器人的运动约束来计算代价。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
std::vector<Point> findPath(TrapezoidalMap& map, const Point& start, const Point& end) {
auto startTrap = map.findTrapezoid(start);
auto endTrap = map.findTrapezoid(end);

if (!startTrap || !endTrap) {
std::cerr << "Start or end point is not in the trapezoidal map." << std::endl;
return {};
}

std::vector<NavNode*> openList;
std::vector<NavNode*> closedList;

openList.push_back(new NavNode(startTrap, nullptr, 0, distance(start, end)));

while (!openList.empty()) {
// 从开放列表中选取代价最小的节点
auto minIt = std::min_element(openList.begin(), openList.end(), [](NavNode* a, NavNode* b) {
return a->total < b->total;
});
NavNode* current = *minIt;
openList.erase(minIt);

// 如果当前节点是目标梯形,回溯并构建路径
if (current->trapezoid == endTrap) {
std::vector<Point> path;
NavNode* node = current;
while (node) {
path.push_back(node->trapezoid->leftp);
node = node->parent;
}
std::reverse(path.begin(), path.end());

for (auto n : openList) delete n;
for (auto n : closedList) delete n;

return path;
}

// 将当前节点从开放列表移动到关闭列表
closedList.push_back(current);

// 访问当前节点的相邻梯形
std::vector<Trapezoid*> neighbors; // 获取当前梯形的相邻梯形
// 获取当前梯形的相邻梯形(在实际实现中,你需要根据梯形之间的连接关系来获取邻居梯形)
// 这里只是简化的演示代码
// ...

for (Trapezoid* neighbor : neighbors) {
// 如果邻居梯形已在关闭列表中,跳过
if (std::any_of(closedList.begin(), closedList.end(), [neighbor](NavNode* n) {
return n->trapezoid == neighbor;
})) {
continue;
}

// 计算从起点到邻居梯形的代价
double cost = current->cost + distance(current->trapezoid->leftp, neighbor->leftp);
double heuristic = distance(neighbor->leftp, end);
double total = cost + heuristic;

// 检查邻居梯形是否已在开放列表中
auto it = std::find_if(openList.begin(), openList.end(), [neighbor](NavNode* n) {
return n->trapezoid == neighbor;
});

if (it != openList.end()) {
// 如果邻居梯形已在开放列表中且新的代价更低,则更新节点信息
NavNode* existing = *it;
if (total < existing->total) {
existing->parent = current;
existing->cost = cost;
existing->heuristic = heuristic;
existing->total = total;
}
} else {
// 将邻居梯形添加到开放列表中
openList.push_back(new NavNode(neighbor, current, cost, heuristic));
}
}
}

std::cerr << "No path found." << std::endl;
return {};
}

现在,我们可以在`main`函数中使用`findPath`函数来搜索从起点到终点的最短路径:

```cpp
int main() {
// 定义障碍物线段
std::vector<Segment> segments = {
Segment(Point(2, 2), Point(5, 6)),
Segment(Point(5, 6), Point(8, 2)),
Segment(Point(10, 3), Point(13, 6)),
Segment(Point(13, 6), Point(15, 3))
};

// 构建梯形图
TrapezoidalMap trapezoidalMap(segments);

// 定义起点和终点
Point start(1, 4);
Point end(16, 4);

// 寻找路径
std::vector<Point> path = findPath(trapezoidalMap, start, end);

if (!path.empty()) {
std::cout << "Path found:" << std::endl;
for (const Point& p : path) {
std::cout << "(" << p.x << ", " << p.y << ")" << std::endl;
}
}

return 0;
}


这个示例程序展示了如何在梯形分解的基础上实现A*算法进行路径规划。请注意,这个示例仅用于演示目的,实际应用中可能需要对代码进行优化和改进。例如,你可以考虑以下方面的改进:

  1. 在梯形分解过程中,可以构建一个数据结构来存储梯形之间的连接关系。这将有助于在A*算法中获取邻居梯形。
  2. 考虑障碍物多边形的凸性。在实际应用中,障碍物可能是凹多边形,你需要对梯形分解算法进行相应的调整以适应这种情况。
  3. 根据机器人的运动约束来计算路径代价。本示例中,我们使用了两个梯形中心点之间的距离作为代价。实际应用中,你可能需要根据机器人的运动能力和环境特性来计算更为合适的代价。
  4. 处理地图边界。在实际应用中,你需要考虑地图边界对梯形分解和路径规划的影响。
  5. 使用更有效的数据结构和算法。示例中使用的数据结构和算法可能不是最高效的选择。实际应用中,你可能需要根据具体问题选择更适合的数据结构和算法来提高程序性能。

在实际项目中应用梯形分解和A*算法时,你可能还需要考虑其他因素,如机器人的尺寸、运动能力和环境特性。此外,还可以尝试使用其他路径规划算法,如Visibility Graph、Voronoi Diagram等,以满足不同应用场景的需求。

参考文献

  1. O’Rourke, J. (1998). Computational Geometry in C (2nd ed.). Cambridge University Press.
  • 本书详细介绍了计算几何领域的基本概念和算法,包括梯形分解、凸包、线段相交等。对于学习梯形分解算法的实现和应用非常有帮助。
  1. Lozano-Pérez, T., & Wesley, M. A. (1979). An algorithm for planning collision-free paths among polyhedral obstacles. Communications of the ACM, 22(10), 560-570.
  • 这篇经典论文首次提出了可视化图(Visibility Graph)算法,用于在多边形障碍物环境中规划无碰撞路径。该算法将机器人和障碍物看作点和多边形,并构建一个表示相互可见性的图来搜索最短路径。
  1. Choset, H., & Lynch, K. M. (2005). Robot motion planning and control. MIT OpenCourseWare.
  • 这是一门关于机器人运动规划和控制的课程,详细介绍了机器人运动学、动力学、运动规划和控制算法等方面的内容。课程中包含了多种路径规划算法,如梯形分解、可视化图、Voronoi图等,对于实现这些算法非常有帮助。
  1. Lavalle, S. M. (2006). Planning algorithms. Cambridge University Press.
  • 本书系统地介绍了规划算法的理论和实践,包括机器人运动规划、几何规划、决策理论等内容。书中提供了多种路径规划算法的详细解释和实现方法,对于学习路径规划算法非常有帮助。
  1. LaValle, S. M. (1999). Rapidly-exploring random trees: A new tool for path planning. Technical Report 98-11, Iowa State University.
  • 这篇论文首次提出了快速探索随机树(Rapidly-exploring Random Trees,RRT)算法,用于解决高维度和非完整约束的路径规划问题。RRT算法在机器人运动规划、避障、探索等领域有广泛应用。
  1. Kavraki, L. E., Svestka, P., Latombe, J. C., & Overmars, M. H. (1996). Probabilistic roadmaps for path planning in high-dimensional configuration spaces. IEEE Transactions on Robotics and Automation, 12(4), 566-580.
  • 这篇论文提出了一种基于概率的路径规划方法——概率路图(Probabilistic Roadmaps,PRM),用于解决高维度和复杂环境中的路径规划问题。PRM算法通过在配置空间中随机采样构建路图,并使用局部规划器连接可行点,最后搜索路图上的最短路径。
  1. Sánchez, G., & Latombe, J. C. (2002). A single-query bi-directional probabilistic roadmap planner with lazy collision checking. The International Journal of Robotics Research, 21(1), 5-18.
  • 本文介绍了一种单查询双向概率路图规划器(Bi-directional Probabilistic Roadmap Planner),并引入了惰性碰撞检测(Lazy Collision Checking)技术,以提高规划效率和性能。这种方法在机器人路径规划和避障领域有广泛应用。

机器人路径规划梯形分解(Trapezoidal cell decomposition)算法
https://qiangsun89.github.io/2023/04/24/机器人路径规划梯形分解(Trapezoidal-cell-decomposition)算法/
作者
Qiang Sun
发布于
2023年4月24日
许可协议