改进RRT*的路径规划算法

news2024/9/19 19:38:38

一、RRT算法

RRT 算法是一种基于随机采样的快速搜索算法。该算法的主要思想是通过随机采样来创建一个快速探索的树,从而生长出一条从起点到终点的路径。如图为随机树的生长过程。

  1. 初始化。首先,初始化起始点和目标点位置,并将起点作为根节点,建立一棵只包含起点的树 T。
  2. 随机采样。从地图中随机生成一个点 x_rand 作为采样点,该点在自由空间中且不在障碍物中。
  3. 扩展树。在树 T 中找到最近邻节点 x_near。然后,计算从 x_near 到 x_rand 的一条路径是否在和障碍物发生碰撞。如果路径没有和障碍物发生碰撞,则将 x_new 作为路径上一个新的节点,并将该节点作为新的节点加入树 T。如果路径和障碍物发生碰撞,则此次生长作废,跳转到步骤 2。
  4. 反复迭代。重复执行步骤 2 和步骤 3,直到树 T 中包含目标点,或者超过了指定的次数,算法停止扩展。
  5. 返回路径。从终点回溯到起点,得到一条可行路径。

算法伪代码如下图所示

RRT 算法的优点是可以处理高维度的状态空间,计算速度快,适用于复杂的路径规划问题。但是,由于采用随机采样的方式生成树,该算法可能会产生不必要的路径, 需要使用启发式函数或其他优化方法进行改进。使用 RRT 算法进行路径规划的仿真结果如图所示,从图中可以看到 RRT 算法规划出的路径拐点较多,且可行性较低。

二、RRT*算法

RRT*算法在RRT的基础上引入了优化策略,在随机树扩张过程中引入了重选父节点和范围内重连策略,在生长的过程中不断对路径进行优化,保证生成路径为渐进最优的。

  1. 初始化。初始化起始点和目标点位置,并将起点作为根节点,建立一棵只包含起点的树 T。设置邻域半径 r(用于找到节点周围的候选节点进行优化)。
  2. 随机采样。从地图中随机生成一个点 x_rand 作为采样点,该点在自由空间中且不在障碍物中。
  3. 扩展树。在树 T 中找到最近邻节点 x_near。然后,计算从 x_nearx_rand 的路径是否在和障碍物发生碰撞。如果没有碰撞,则生成新节点 x_new,并将其连接到 x_near
  4. 重选父节点。找到 x_new 邻域内的所有节点(距离小于半径 r 的节点),并从这些节点中找到一条最短的、无碰撞的路径来连接 x_new。如果该路径的成本更低,则更新 x_new 的父节点为该邻域内的最优节点,并将新边添加到树中。
  5. 节点重连。在 x_new 被加入树之后,检查其邻域内的其他节点,判断是否通过 x_new 连接这些节点可以缩短它们的路径长度。如果可以,则更新这些节点的父节点为 x_new,并调整相应的路径。
  6. 反复迭代。重复执行步骤 2 和步骤 3,直到树 T 中包含目标点,或者超过了指定的次数,算法停止扩展。
  7. 返回路径。从终点回溯到起点,得到一条可行路径。

如图1为重选父节点示意图,其中橙色实线为更新的路径,蓝色虚线为舍去路径,在单位圆内存在某一节点,使得X_new选择这个节点为父节点的距离更近且无碰撞,即可更新。

图2为节点重连,在单位圆内,X_1选择X_new为父节点的距离比X_1选择X_2为父节点的距离更短且无碰撞,即可更新

图1  重选父节点                                                       图2  节点重连

通过重选父节点与节点重连,能让算法路径生成的路径拐点数量明显减少,路径长度更短。

三、改进的RRT*算法 

 1.地图评估复杂度策略

Bias-RRT算法通过目标偏置策略引入了目标点作为采样点的选取,设定的偏置概率P在简单地图中能够有效提升路径规划的收敛速度。

传统RRT算法使用固定步长进行随机树的生长,但同一步长难以适应不同类型的地图。使用固定步长在不同地图上是不够灵活的。

通过计算出地图复杂程度,来动态调整最优的偏置概率p和搜索步长s。

A_obstacle表示障碍物面积;A_map表示地图面积; D_obstacle表示障碍物分布情况。将地图的长宽各缩小为原来的十分之一后,将其划分为100个相等的网格,障碍物占据的网格数量与这100个网格的比例即为障碍物的分布情况。 

当地图复杂程度系数C接近1时,意味着地图的复杂程度较高,因此偏置概率P和步长S的值应相应减小。偏置概率P和步长S计算公式如下

其中α与β为所设置的常量,分别为0.3的7。α能保证偏置概率被控制在0-0.3之间。在地图较为简单时,即地图复杂度系数C接近0,偏置概率接近 0.3,从而确保算法仍具有较强的目标指向性;在地图复杂度较高时,即 C 接近1,偏置概率趋近于0,目标指向性减弱,适应复杂场景。步长S应与起点到终点的欧几里得距离相关,距离越远,初始步长应相应增加。同时,β的选取值需要在路径探索的准确性与效率之间取得平衡。经过实验验证,设定β=7可确保初始步长在大部分实际场景中足够灵活,既能够快速收敛于简单场景,又能在复杂场景中保持足够的探索能力。 

2.直连终点

为了加速路径生成过程,每当生成新的节点 Qnew 时,算法即刻朝向终点方向动态地生长,并对生成的路径进行实时碰撞检测。如果在此过程中检测到碰撞,算法将直接放弃当前路径并进入下一次迭代;若未检测到碰撞,则可以直接生成一条初始可行路径。

3.可变步长扩展策略

步长过大时,会增加Q_new与 Q_nearest之间的路径经过障碍物的概率,降低采样的成功率。

引入了可变步长的随机树扩展策略。在算法初始化时,设置最小步长Smin和基本步长S两个参数

n为距离因子,如图,可以扩展到Q_2的位置 

4.路径剪枝 

双向剪枝,减去多余的路径。从起点Qstart开始,依次检查Qstart与初始路径中各个节点Qi之间的连线是否存在障碍物。如果路径上不存在障碍物,则将Qi的父节点直接更新为Qstart,从而简化路径;如果存在障碍物,则继续以Qi的父节点作为新的起点进行检查,并重复这一过程,直到到达终点Qgoal。

 算法代码

import copy
import math
import random
import time

import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
from matplotlib.transforms import Affine2D
from scipy.spatial.transform import Rotation as Rot
import numpy as np

show_animation = True

C1 = 0
class RRT:
    def __init__(self, obstacleList, randArea,
                 expandDis=None, goalSampleRate=None, maxIter=200, n=4):

        self.start = None
        self.goal = None
        self.min_rand = randArea[0]
        self.max_rand = randArea[1]
        self.step = n

        # 计算地图面积
        rand_area_size = (self.max_rand - self.min_rand) ** 2

        # 计算障碍物的总面积
        obstacle_area_size = self.calculate_obstacle_area(obstacleList)

        # 将地图划分为400个均等格子
        grid_count = 20  # 20x20的网格,总共400个格子
        grid_size_x = (self.max_rand - self.min_rand) / grid_count
        grid_size_y = (self.max_rand - self.min_rand) / grid_count

        # 初始化20x20的网格,初始值为0
        grid = [[0 for _ in range(grid_count)] for _ in range(grid_count)]

        # 使用障碍物更新网格
        grid = self.update_grid_with_obstacles(grid, obstacleList, self.min_rand, self.max_rand, grid_count)

        # 计算障碍物所占格子数与总格子数400的比例
        total_cells = grid_count * grid_count  # 总共400个格子
        grid_cells = sum([sum(row) for row in grid])
        # print(grid_cells)
        filled_grid_ratio = grid_cells / total_cells  # 障碍物所占的比例

        # 计算复杂程度系数C1
        global C1
        C1 = 0.5 * (obstacle_area_size / rand_area_size) + 0.5 * filled_grid_ratio
        # print(f"障碍物空间分布: {filled_grid_ratio:f},C1: {C1:.2f}")
        # 自动生成goal_sample_rate
        self.goal_sample_rate = goalSampleRate if goalSampleRate is not None else ((1 - C1)*0.3)

        self.expand_dis = expandDis
        self.max_iter = maxIter
        self.obstacle_list = obstacleList
        self.node_list = None
    def rrt_star_planning(self, start, goal, animation=True):
        # 调整步长
        distance = math.sqrt((goal[0] - start[0]) ** 2 + (goal[1] - start[1]) ** 2)
        global C1
        if self.expand_dis is None:
            self.expand_dis = distance / 7 * (1 - C1)  # 起点到终点的欧几里得距离/7 *(1-C1)
        # print("Expand Dis:", self.expand_dis)
        # print("Goal Sample Rate:", self.goal_sample_rate)
        start_time = time.time()
        self.start = Node(start[0], start[1])
        self.goal = Node(goal[0], goal[1])
        self.node_list = [self.start]
        path = None
        lastPathLength = float('inf')

        for i in range(self.max_iter):
            rnd = self.sample()
            n_ind = self.get_nearest_list_index(self.node_list, rnd)
            nearestNode = self.node_list[n_ind]

            # steer  必须生长self.expand_dis 因此可能超过min_rand max_rand 可以酌情修改
            theta = math.atan2(rnd[1] - nearestNode.y, rnd[0] - nearestNode.x)
            newNode = self.get_new_node(theta, n_ind, nearestNode)

            noCollision = self.check_segment_collision(newNode.x, newNode.y, nearestNode.x, nearestNode.y)
            if not noCollision:
                adjusted = False
                for i in range(self.step - 1, 0, -1):  # 依次尝试减小
                    factor = self.step / i  # 动态计算因子
                    reduced_step = self.expand_dis / factor
                    x_i = nearestNode.x + reduced_step * math.cos(theta)
                    y_i = nearestNode.y + reduced_step * math.sin(theta)

                    if self.check_segment_collision(x_i, y_i, nearestNode.x, nearestNode.y):
                        newNode.x = x_i
                        newNode.y = y_i
                        newNode.cost += reduced_step - self.expand_dis  # 需要修正花费路径距离
                        adjusted = True
                        break

                if not adjusted:
                    # 如果没有找到合适的扩展节点,则丢弃该节点并重新采样
                    continue

            nearInds = self.find_near_nodes(newNode)
            newNode = self.choose_parent(newNode, nearInds)
            self.node_list.append(newNode)
            self.rewire(newNode, nearInds)

            # 从新生成的节点Xnew往终点生长
            while True:
                theta_to_goal = math.atan2(self.goal.y - newNode.y, self.goal.x - newNode.x)
                goalNode = self.get_new_node(theta_to_goal, len(self.node_list) - 1, newNode)

                noCollision = self.check_segment_collision(goalNode.x, goalNode.y, newNode.x, newNode.y)
                if not noCollision:
                    break  # 如果发生碰撞,退出循环

                self.node_list.append(goalNode)
                newNode = goalNode  # 更新newNode为goalNode,继续生长

                # 判断是否到达终点
                if self.is_near_goal(newNode):
                    lastIndex = len(self.node_list) - 1
                    tempPath = self.get_final_course(lastIndex)
                    tempPathLen = self.get_path_len(tempPath)
                    if lastPathLength > tempPathLen:
                        path = tempPath
                        lastPathLength = tempPathLen
                        print("current path length: {}, It costs {} s".format(tempPathLen, time.time() - start_time))
                    path, pruned_path_length = self.prune_path(path)
                    if animation:
                        self.draw_graph(path)
                    break

            if animation:
                self.draw_graph(path)

            if self.is_near_goal(newNode):
                if self.check_segment_collision(newNode.x, newNode.y, self.goal.x, self.goal.y):
                    lastIndex = len(self.node_list) - 1

                    tempPath = self.get_final_course(lastIndex)
                    tempPathLen = self.get_path_len(tempPath)
                    if lastPathLength > tempPathLen:
                        path = tempPath
                        lastPathLength = tempPathLen
                        print("current path length: {}, It costs {} s".format(tempPathLen, time.time() - start_time))
                        # print("剪枝前 path: {}",path)
                        if path:
                            path, pruned_path_length = self.prune_path(path)
                            if animation:
                                self.draw_graph(path)
                            # print(path)
                        break
        if path:
            path, pruned_path_length = self.prune_path(path)
            if animation:
                self.draw_graph(path)
            # print(path)
        return path

    def calculate_obstacle_area(self,obstacleList):
        total_area = 0
        for obstacle in obstacleList:
            if len(obstacle) == 3:  # 圆形障碍物 (x, y, r)
                r = obstacle[2]
                total_area += math.pi * (r ** 2)
            elif len(obstacle) == 4:  # 矩形障碍物 (x, y, width, height)
                width = obstacle[2]
                height = obstacle[3]
                total_area += width * height
        return total_area

    def update_grid_with_obstacles(self,grid, obstacleList, min_rand, max_rand, grid_count):
        grid_size_x = (max_rand - min_rand) / grid_count
        grid_size_y = (max_rand - min_rand) / grid_count

        for obstacle in obstacleList:
            if len(obstacle) == 3:  # 圆形障碍物 (x, y, r)
                x, y, r = obstacle
                min_x = max(int((x - r - min_rand) / grid_size_x), 0)
                max_x = min(int((x + r - min_rand) / grid_size_x), grid_count - 1)
                min_y = max(int((y - r - min_rand) / grid_size_y), 0)
                max_y = min(int((y + r - min_rand) / grid_size_y), grid_count - 1)

                for i in range(min_x, max_x + 1):
                    for j in range(min_y, max_y + 1):
                        grid[i][j] = 1

            elif len(obstacle) == 4:  # 矩形障碍物 (x, y, width, height)
                x, y, width, height = obstacle
                min_x = max(int((x - width / 2 - min_rand) / grid_size_x), 0)
                max_x = min(int((x + width / 2 - min_rand) / grid_size_x), grid_count - 1)
                min_y = max(int((y - height / 2 - min_rand) / grid_size_y), 0)
                max_y = min(int((y + height / 2 - min_rand) / grid_size_y), grid_count - 1)

                for i in range(min_x, max_x + 1):
                    for j in range(min_y, max_y + 1):
                        grid[i][j] = 1

        return grid

    def prune_path(self, original_path):
        pruned_path = [original_path[0]]  # 将起始节点添加到剪枝路径中
        current_node = original_path[0]

        # 从第二个节点开始遍历路径
        for i in range(1, len(original_path)):
            next_node = original_path[i]

            # 检查从 current_node 直接连接到 next_node 的路径是否无碰撞
            if self.is_collision_free_path([current_node, next_node]):
                # 如果无碰撞,则跳过中间节点
                continue

            # 如果存在碰撞或路径段长度较长,则将前一个节点添加到剪枝路径中,并更新当前节点
            pruned_path.append(original_path[i - 1])
            current_node = original_path[i - 1]

        # 最后添加终点节点到剪枝路径
        pruned_path.append(original_path[-1])

        # 计算剪枝后的路径长度
        pruned_path_length = self.calculate_path_length(pruned_path)
        original_path_length = self.calculate_path_length(original_path)

        # 如果剪枝后的路径没有缩短,则返回原始路径
        # if pruned_path_length > original_path_length:
        #     print("Pruned path is not shorter, returning original path")  # 调试信息
        #     return original_path, original_path_length

        return pruned_path, pruned_path_length

    def calculate_distance(self, point1, point2):
        return math.sqrt((point1[0] - point2[0]) ** 2 + (point1[1] - point2[1]) ** 2)

    def sample(self):
        if random.randint(0, 100) > self.goal_sample_rate*100:
            rnd = [random.uniform(self.min_rand, self.max_rand), random.uniform(self.min_rand, self.max_rand)]
        else:  # goal point sampling
            rnd = [self.goal.x, self.goal.y]
        return rnd

    def choose_parent(self, newNode, nearInds):
        if len(nearInds) == 0:
            return newNode

        dList = []
        for i in nearInds:
            dx = newNode.x - self.node_list[i].x
            dy = newNode.y - self.node_list[i].y
            d = math.hypot(dx, dy)
            theta = math.atan2(dy, dx)
            if self.check_collision(self.node_list[i], theta, d):
                dList.append(self.node_list[i].cost + d)
            else:
                dList.append(float('inf'))

        minCost = min(dList)
        minInd = nearInds[dList.index(minCost)]

        if minCost == float('inf'):
            print("min cost is inf")
            return newNode

        newNode.cost = minCost
        newNode.parent = minInd

        return newNode

    def find_near_nodes(self, newNode):
        n_node = len(self.node_list)
        r = 50.0 * math.sqrt((math.log(n_node) / n_node))
        d_list = [(node.x - newNode.x) ** 2 + (node.y - newNode.y) ** 2
                  for node in self.node_list]
        near_inds = [d_list.index(i) for i in d_list if i <= r ** 2]
        return near_inds

    def informed_sample(self, cMax, cMin, xCenter, C):
        if cMax < float('inf'):
            r = [cMax / 2.0,
                 math.sqrt(cMax ** 2 - cMin ** 2) / 2.0,
                 math.sqrt(cMax ** 2 - cMin ** 2) / 2.0]
            L = np.diag(r)
            xBall = self.sample_unit_ball()
            rnd = np.dot(np.dot(C, L), xBall) + xCenter
            rnd = [rnd[(0, 0)], rnd[(1, 0)]]
        else:
            rnd = self.sample()

        return rnd

    @staticmethod
    def sample_unit_ball():
        a = random.random()
        b = random.random()

        if b < a:
            a, b = b, a

        sample = (b * math.cos(2 * math.pi * a / b),
                  b * math.sin(2 * math.pi * a / b))
        return np.array([[sample[0]], [sample[1]], [0]])

    @staticmethod
    def get_path_len(path):
        pathLen = 0
        for i in range(1, len(path)):
            node1_x = path[i][0]
            node1_y = path[i][1]
            node2_x = path[i - 1][0]
            node2_y = path[i - 1][1]
            pathLen += math.sqrt((node1_x - node2_x)
                                 ** 2 + (node1_y - node2_y) ** 2)

        return pathLen

    @staticmethod
    def line_cost(node1, node2):
        return math.sqrt((node1.x - node2.x) ** 2 + (node1.y - node2.y) ** 2)

    @staticmethod
    def get_nearest_list_index(nodes, rnd):
        dList = [(node.x - rnd[0]) ** 2
                 + (node.y - rnd[1]) ** 2 for node in nodes]
        minIndex = dList.index(min(dList))
        return minIndex

    def get_new_node(self, theta, n_ind, nearestNode):
        newNode = copy.deepcopy(nearestNode)

        newNode.x += self.expand_dis * math.cos(theta)

        newNode.y += self.expand_dis * math.sin(theta)

        newNode.cost += self.expand_dis
        newNode.parent = n_ind
        return newNode

    def is_near_goal(self, node):
        d = self.line_cost(node, self.goal)
        if d < self.expand_dis:
            return True
        return False

    def rewire(self, newNode, nearInds):
        n_node = len(self.node_list)
        for i in nearInds:
            nearNode = self.node_list[i]

            d = math.sqrt((nearNode.x - newNode.x) ** 2
                          + (nearNode.y - newNode.y) ** 2)

            s_cost = newNode.cost + d

            if nearNode.cost > s_cost:
                if self.check_segment_collision(nearNode.x, nearNode.y, newNode.x, newNode.y):
                    nearNode.parent = n_node - 1
                    nearNode.cost = s_cost

    def update_node_parent(self, node, target_node, new_node_index, distance, new_cost):
        """
        检查并更新节点的父节点与路径成本。

        :param node: 需要更新的节点(nearNode或nearparentNode)
        :param target_node: 新节点(newNode)
        :param new_node_index: 新节点在节点列表中的索引
        :param distance: 两节点之间的距离
        :param new_cost: 新的路径成本
        :return: 是否成功更新了节点
        """
        theta = math.atan2(target_node.y - node.y, target_node.x - node.x)
        if self.check_collision(node, theta, distance):
            node.parent = new_node_index
            node.cost = new_cost
            return True
        return False
    def check_collision(self, nearNode, theta, d):
        tmpNode = copy.deepcopy(nearNode)
        end_x = tmpNode.x + math.cos(theta) * d
        end_y = tmpNode.y + math.sin(theta) * d
        return self.check_segment_collision(tmpNode.x, tmpNode.y, end_x, end_y)

    def get_final_course(self, lastIndex):
        path = [[self.goal.x, self.goal.y]]
        while self.node_list[lastIndex].parent is not None:
            node = self.node_list[lastIndex]
            path.append([node.x, node.y])
            lastIndex = node.parent
        path.append([self.start.x, self.start.y])
        return path

    def draw_graph_informed_RRTStar(self, xCenter=None, cBest=None, cMin=None, e_theta=None, rnd=None, path=None):
        plt.clf()
        # for stopping simulation with the esc key.
        plt.gcf().canvas.mpl_connect(
            'key_release_event',
            lambda event: [exit(0) if event.key == 'escape' else None])
        if rnd is not None:
            plt.plot(rnd[0], rnd[1], "^k")
            if cBest != float('inf'):
                self.plot_ellipse(xCenter, cBest, cMin, e_theta)

        for node in self.node_list:
            if node.parent is not None:
                if node.x or node.y is not None:
                    plt.plot([node.x, self.node_list[node.parent].x], [
                        node.y, self.node_list[node.parent].y], "-g")

        for (ox, oy, size) in self.obstacle_list:
            plt.plot(ox, oy, "ok", ms=30 * size)

        if path is not None:
            plt.plot([x for (x, y) in path], [y for (x, y) in path], '-r')

        plt.plot(self.start.x, self.start.y, "xr")
        plt.plot(self.goal.x, self.goal.y, "xr")
        plt.axis([-2, 18, -2, 15])
        plt.grid(True)
        plt.pause(0.01)

    @staticmethod
    def plot_ellipse(xCenter, cBest, cMin, e_theta):  # pragma: no cover

        a = math.sqrt(cBest ** 2 - cMin ** 2) / 2.0
        b = cBest / 2.0
        angle = math.pi / 2.0 - e_theta
        cx = xCenter[0]
        cy = xCenter[1]
        t = np.arange(0, 2 * math.pi + 0.1, 0.1)
        x = [a * math.cos(it) for it in t]
        y = [b * math.sin(it) for it in t]
        rot = Rot.from_euler('z', -angle).as_matrix()[0:2, 0:2]
        fx = rot @ np.array([x, y])
        px = np.array(fx[0, :] + cx).flatten()
        py = np.array(fx[1, :] + cy).flatten()
        plt.plot(cx, cy, "xc")
        plt.plot(px, py, "--c")

    def check_line_intersection(self, x1, y1, x2, y2, x3, y3, x4, y4):
        d1 = (x2 - x1, y2 - y1)
        d2 = (x4 - x3, y4 - y3)

        cross = d1[0] * d2[1] - d1[1] * d2[0]

        if cross == 0:
            return False

        t1 = ((x3 - x1) * d2[1] - (y3 - y1) * d2[0]) / cross
        t2 = ((x3 - x1) * d1[1] - (y3 - y1) * d1[0]) / cross

        if 0 <= t1 <= 1 and 0 <= t2 <= 1:
            return True

        return False

    def plot_pruned_path(self, path, obstacle_list, start, goal):
        try:
            # Example plotting code
            plt.figure()
            for (ox, oy, size) in obstacle_list:
                circle = plt.Circle((ox, oy), size, color='b', alpha=0.5)
                plt.gca().add_patch(circle)

            path_x, path_y = zip(*path)
            plt.plot(path_x, path_y, marker='o', color='b')

            plt.plot(start[0], start[1], 'go')  # Start point
            plt.plot(goal[0], goal[1], 'ro')  # Goal point

            plt.xlabel('X')
            plt.ylabel('Y')
            plt.title('Pruned Path')
            plt.grid(True)
            plt.show()
        except Exception as e:
            print(f"Error plotting path: {e}")
    def calculate_path_length(self, path):
        """
        Calculate the total length of the given path.
        """
        length = 0.0
        for i in range(len(path) - 1):
            x1, y1 = path[i]
            x2, y2 = path[i + 1]
            length += np.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
        return length

    def is_collision_free_path(self, path):
        """
        检查给定路径中的所有路径段是否都无碰撞。

        参数:
        path (list of lists): 需要检查的路径,每个路径点是一个坐标列表,例如[[x1, y1], [x2, y2], ...]

        返回:
        bool: 如果路径中的所有路径段都无碰撞,则返回 True;否则返回 False。
        """
        if len(path) < 2:
            return True  # 如果路径点少于两个,无法检测碰撞

        # 遍历路径中的每个路径段
        for i in range(len(path) - 1):
            segment_start = path[i]
            segment_end = path[i + 1]
            x1, y1 = segment_start
            x2, y2 = segment_end
            if not self.check_segment_collision(x1, y1, x2, y2):
                return False  # 如果发现碰撞,返回 False

        return True  # 如果所有路径段都无碰撞,返回 True

    def check_segment_collision(self, x1, y1, x2, y2):
        for obstacle in self.obstacle_list:
            if len(obstacle) == 3:  # 圆形障碍物
                ox, oy, size = obstacle
                segment_start = np.array([x1, y1])
                segment_end = np.array([x2, y2])
                obstacle_center = np.array([ox, oy])

                distance_squared = self.distance_squared_point_to_segment(segment_start, segment_end, obstacle_center)

                if distance_squared <= size ** 2:
                    return False

            elif len(obstacle) == 4:  # 矩形障碍物
                rx, ry, rw, rh = obstacle
                if self.check_segment_rectangle_collision(x1, y1, x2, y2, rx, ry, rw, rh):
                    return False

        return True

    def distance_squared_point_to_segment(self, v, w, p):
        if np.array_equal(v, w):
            return np.sum((p - v) ** 2)
        segment_length_squared = np.sum((w - v) ** 2)
        t = max(0, min(1, np.dot(p - v, w - v) / segment_length_squared))
        projection = v + t * (w - v)
        return np.sum((p - projection) ** 2)

    def check_segment_rectangle_collision(self, x1, y1, x2, y2, rx, ry, rw, rh):
        # 获取矩形的四个顶点
        left = rx - rw / 2
        right = rx + rw / 2
        bottom = ry - rh / 2
        top = ry + rh / 2

        # 定义矩形的四条边为线段
        rectangle_edges = [
            (left, bottom, left, top),  # 左边
            (left, top, right, top),  # 上边
            (right, top, right, bottom),  # 右边
            (right, bottom, left, bottom)  # 下边
        ]

        # 检查传入的线段是否与矩形的任何一条边相交
        for edge in rectangle_edges:
            if self.do_intersect(x1, y1, x2, y2, *edge):
                return True

        # 检查线段的一个端点是否在矩形内部
        if left < x1 < right and bottom < y1 < top:
            return True
        if left < x2 < right and bottom < y2 < top:
            return True

        return False

    def do_intersect(self, x1, y1, x2, y2, x3, y3, x4, y4):
        # 使用向量叉积来检查两个线段是否相交
        def ccw(A, B, C):
            return (C[1] - A[1]) * (B[0] - A[0]) > (B[1] - A[1]) * (C[0] - A[0])

        A = (x1, y1)
        B = (x2, y2)
        C = (x3, y3)
        D = (x4, y4)

        return ccw(A, C, D) != ccw(B, C, D) and ccw(A, B, C) != ccw(A, B, D)

    def draw_graph(self, path=None):
        plt.clf()
        plt.gcf().canvas.mpl_connect(
            'key_release_event',
            lambda event: [exit(0) if event.key == 'escape' else None])

        # 绘制节点和边(RRT生成的路径,绿色)
        for node in self.node_list:
            if node.parent is not None:
                plt.plot([node.x, self.node_list[node.parent].x], [
                    node.y, self.node_list[node.parent].y], "-g")

        # 绘制障碍物(黑色)
        for obstacle in self.obstacle_list:
            if len(obstacle) == 3:  # 圆形障碍物
                ox, oy, size = obstacle
                circle = plt.Circle((ox, oy), size, color="black", fill=True)
                plt.gca().add_patch(circle)
            elif len(obstacle) == 4:  # 矩形障碍物
                rx, ry, rw, rh = obstacle
                rectangle = plt.Rectangle(
                    (rx - rw / 2, ry - rh / 2), rw, rh,
                    color="black", fill=True  # 去掉alpha参数,确保为纯黑色
                )
                plt.gca().add_patch(rectangle)

        # 保持x和y轴的比例一致
        plt.axis('equal')

        # 绘制起点(绿色圆形)
        plt.plot(self.start.x, self.start.y, "ob", markersize=8)

        # 绘制终点(红色圆形)
        plt.plot(self.goal.x, self.goal.y, "or", markersize=8)

        # 绘制最终路径(红色)
        if path is not None and len(path) > 1:  # 确保路径至少有两个节点
            plt.plot([x for (x, y) in path], [y for (x, y) in path], '-r')

        # 为了增加可视范围,给坐标轴增加一定的余量
        x_margin = abs(self.start.x - self.goal.x) * 0.1
        y_margin = abs(self.start.y - self.goal.y) * 0.1

        x_min = min(self.start.x, self.goal.x) - x_margin
        x_max = max(self.start.x, self.goal.x) + x_margin
        y_min = min(self.start.y, self.goal.y) - y_margin
        y_max = max(self.start.y, self.goal.y) + y_margin

        plt.axis([x_min, x_max, y_min, y_max])
        plt.grid(False)
        plt.pause(0.01)

        # 使图形保持在屏幕上
        plt.show(block=False)


class Node:

    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.cost = 0.0
        self.parent = None


def main():
    # print("Start rrt planning")

    # create obstacles
    obstacleList = [
        (10, 9, 3),  # 圆形障碍物,远离起点
        (15, 15, 4),  # 圆形障碍物
        (30, 18, 4),  # 圆形障碍物
        (40, 10, 3),  # 圆形障碍物
        (25, 34, 4),  # 圆形障碍物
        (19, 21, 3),  # 圆形障碍物
        (12, 38, 2),  # 圆形障碍物
        (5, 38, 3),  # 圆形障碍物
        (47, 37, 3),  # 圆形障碍物
        (30, 10, 10, 6),  # 矩形障碍物
        (46, 18, 4, 6),  # 矩形障碍物
        (15, 30, 8, 8),  # 矩形障碍物
        (45, 25, 5, 5),  # 矩形障碍物,远离终点
        (20, 8, 6, 4),  # 矩形障碍物
        (37, 15, 6, 3),  # 矩形障碍物
        (32, 42, 5, 7),  # 矩形障碍物
        (8, 25, 4, 4),  # 矩形障碍物
        (39, 32, 7, 6),  # 矩形障碍物
        (36, 26, 6, 4),  # 矩形障碍物
        (25, 26, 3),  # 圆障碍物
        (25, 42, 3),  # 圆形障碍物,避免覆盖终点
    ]
    # obstacleList = [(5, 5, 1), (3, 6, 2), (3, 8, 2), (3, 10, 2), (7, 5, 2),
    #                 (9, 5, 2), (8, 10, 1)]

    # Set params
    randArea = [0, 50]

    rrt = RRT(obstacleList, randArea)

    path = rrt.rrt_star_planning(start=[5, 5], goal=[45, 45], animation=show_animation)

    print("Done!!")

    if show_animation and path:
        plt.show()



if __name__ == '__main__':
    main()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2139925.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

printf()函数的全面介绍及用法——简单易懂

printf&#xff08;&#xff09;函数介绍 目录 printf&#xff08;&#xff09;函数介绍 一&#xff1a;头文件 二&#xff1a;格式控制字符串 1.格式字符。 2.转义字符。 3.普通字符。 三&#xff1a;格式字符输出示例 1. %c-----------输出字符 2. %s-----------输…

Linux中断实操-概念

1、裸机中的中断处理方法&#xff1a; &#xff08;1&#xff09;使能中断、初始化相应寄存器 &#xff08;2&#xff09;注册中断服务函数&#xff0c;向irqTable数组的指定标号处写入中断服务函数 &#xff08;3&#xff09;中断发生后进入IRQ中断服务函数&#xff0c;执行对…

【0~1】实现一个精简版的Tomcat服务器

真正的勇气&#xff0c;是在知道生活的真相之后&#xff0c;依然热爱生活。 《To Kill a Mockingbird》 01 Tomcat 介绍 Tomcat 是一个开源的 Java 应用服务器&#xff0c;主要用来运行基于 Servlet 和 JSP 技术的 Web 应用。Tomcat 实现了 Servlet 规范和 JSP 规范&#xff0…

一次RPC调用过程是怎么样的?

注册中心 RPC&#xff08;Remote Procedure Call&#xff09;翻译成中文就是 {远程过程调用}。RPC 框架起到的作用就是为了实现&#xff0c;调用远程方法时&#xff0c;能够做到和调用本地方法一样&#xff0c;让开发人员更专注于业务开发&#xff0c;不用去考虑网络编程等细节…

【开源免费】基于SpringBoot+Vue.JS企业客户管理系统(JAVA毕业设计)

本文项目编号 T 036 &#xff0c;文末自助获取源码 \color{red}{T036&#xff0c;文末自助获取源码} T036&#xff0c;文末自助获取源码 目录 一、系统介绍1.1 管理员角色1.2 普通员工角色1.3 系统特点 二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内…

苹果手机备份照片怎么删除

在数字时代&#xff0c;备份照片是保护我们珍贵记忆不受意外丢失影响的一种重要方式。苹果手机用户通常利用iCloud或iTunes来备份他们的照片&#xff0c;确保数据的安全。然而&#xff0c;随着时间的推移&#xff0c;这些备份可能会积累大量不再需要的照片&#xff0c;占用宝贵…

鸿蒙开发之ArkTS 基础二

ArkTS常用的基础数据类型 1.字符串 关键字是string 2.数字 关键字是number 3.布尔 关键字是boolean 语法格式是:let 变量名:变量类型 变量值 其中let是关键表示变量&#xff0c;可以修改&#xff0c;可以改变一只对应的是const 修饰&#xff0c;常量不能修改&#xff0c;…

Python画笔案例-050 绘制天空之眼

1、绘制天空之眼 通过 python 的turtle 库绘制 天空之眼&#xff0c;如下图&#xff1a; 2、实现代码 绘制 天空之眼&#xff0c;以下为实现代码&#xff1a; """天空之眼.py """ import math import turtledef draw_square(length,level):if l…

idea同时装了两个版本,每次打开低版本都需要重新激活破解

问题描述&#xff1a; idea同时装了两个版本&#xff0c;每次打开低版本都需要重新激活破解。低版本是2021.1&#xff0c;高版本是2023.1 解决方案&#xff1a; 找到idea的配置路径&#xff0c;比如我的是&#xff1a;C:\Users\Administrator\AppData\Roaming\JetBrains 2021…

【我要成为配环境高手】Nodejs安装与配置

文章目录 1.nodejs安装2.配置npm的全局安装路径3.切换npm的淘宝镜像4.安装vue-cli 1.nodejs安装 从官网下载安装LTS版本的nodejs nodejs会自动安装环境变量&#xff0c;因此安装完成后直接在cmd中查看node版本 node -v2.配置npm的全局安装路径 以管理员身份运行cmd&#xff…

office 2021安装教程

软件介绍 Microsoft Office是微软公司开发的一套基于 Windows 操作系统的办公软件套装。常用组件有 Word、Excel、Powerpoint等。该软件最初出现于九十年代早期&#xff0c;最初是一个推广名称&#xff0c;指一些以前曾单独发售的软件的合集。当时主要的推广重点是购买合集比单…

matlab边缘点提取函数

1、边缘提取 matlab自带点云边缘提取函数,用于搜索点云边界,其核心是alpha shapes算法。alpha shapes提取边缘点,主要是依据滚动圆绕点云进行旋转,实现边缘检测,原理如下图所示。具体原理及效果,可以参考之前我写的博客:基于alpha shapes的边缘点提取(matlab)-CSDN博客…

实习项目|苍穹外卖|day10

Spring Task cron 表达式 入门案例 订单状态定时处理 通知用户支付&#xff01;通知商家完成订单&#xff01; Scheduled(cron "0 0/1 * * * ? ")public void processTimeoutOrder(){log.info("定时处理超时订单: {}", LocalDateTime.now());//答案是…

黑马程序员Java笔记整理(day01)

1.windowsR进入运行&#xff0c;输入cmd 2.环境变量 3.编写java第一步 4.使用idea 5.注释 6.字面量 7.变量 8.二进制 9.数据类型 10.关键词与标识符

仿真软件PROTEUS DESIGN SUITE遇到的一些问题

仿真软件PROTEUS DESIGN SUITE遇到的一些问题 软件网上有很多下载地址自己找哈! 首先如果遇到仿真 没有库 ,需要在网上下载库文件替换到DATA目录下 如果不是默认安装到C盘需要手动修改这些地址,不然会报错!! 当遇到点击仿真出现报错 : 检查这个设置地址是否正确: 随便在库文…

Unity3D 小案例 像素贪吃蛇 02 蛇的觅食

Unity3D 小案例 像素贪吃蛇 第二期 蛇的觅食 像素贪吃蛇 食物生成 在场景中创建一个 2D 正方形&#xff0c;调整颜色&#xff0c;添加 Tag 并修改为 Food。 然后拖拽到 Assets 文件夹中变成预制体。 创建食物管理器 FoodManager.cs&#xff0c;添加单例&#xff0c;可以设置…

周期冲激函数

指数函数的求和----真周期冲击 指数函数有限积分----假单个冲击 指数函数无限积分----真单个冲击

职业院校数据科学与大数据技术专业人工智能实训室建设方案

一、引言 随着人工智能&#xff08;AI&#xff09;技术的迅猛发展&#xff0c;其在全球范围内的应用日益广泛&#xff0c;从智能交通、环境保护到公共安全、智能家居等多个领域均展现出巨大的潜力。然而&#xff0c;我国在人工智能领域的人才储备仍显不足&#xff0c;这已成为…

8. 尝试微调LLM大型语言模型,让它会写唐诗

这篇文章与3. 进阶指南&#xff1a;自定义 Prompt 提升大模型解题能力一样&#xff0c;本质上是专注于“用”而非“写”&#xff0c;你可以像之前一样&#xff0c;对整体的流程有了一个了解&#xff0c;尝试调整超参数部分来查看对微调的影响。 这里同样是生成式人工智能导论&a…

华为HarmonyOS地图服务 -- 三种地图类型 -- HarmonyOS9

一. 场景介绍 Map Kit支持以下地图类型&#xff1a; STANDARD&#xff1a;标准地图&#xff0c;展示道路、建筑物以及河流等重要的自然特征。NONE&#xff1a;空地图&#xff0c;没有加载任何数据的地图。TERRAIN&#xff1a;地形图。 1 标准地图&#xff1a; …