路径规划基本概念

机械臂视觉操作流程 = 手眼标定 + 目标识别与定位 + 操作姿态分析 + 运动规划 = 手眼标定 + 深度学习 + 运动规划

手眼标定

作用: 获得机器人坐标系和相机坐标系的关系 效果: 目前可达到误差在土5mm

运动规划

作用: 到达算法指定位置和姿态 路径规划: 从当前位置到期望位置规划一条无碰撞轨迹

路径规划在机械臂领域所处的位置--框架设计

  • 视觉:相机数据--》点云生成--》姿态预测--》手眼标定
  • 规划:碰撞检测 路径规划
  • 控制:上位机通讯 执行轨迹
  • 区分路径规划、轨迹规划与运动规划

路径规划(path planning):规划的是路径点,即机器人从起始点运动到目标点的路线,它规划的结果不包含时间信息。

轨迹规划(trajectory planning):规划的是轨迹,即机器人何时、以何种速度到达某路径点,如机械臂各关节角度随时间变化的关系,它规划的结果是包含时间信息的。

运动规划(motion planning):概念更加宽泛,可以近似理解为等于路径规划+轨迹规划。

算法类型具体算法是否完备是否最优
基于搜索Dijkstra、A*概率完备是
基于采样PRM、RRT、RRT-Connect概率完备否
RRT*、RRT*-smart渐进最优
基于智能算法遗传算法、蚁群算法概率完备否
  • 完备性:是指如果在起始点和目标点间有路径解存在,那么一定可以得到解,如果得不到解那么一定说明没有解存在:
  • 概率完备性:是指如果在起始点和目标点间有路径解存在,只要规划或搜索的时间足够长,就一定能确保找到一条路径解;
  • 最优性:是指规划得到的路径在某个评价指标上是最优的;
  • 渐进最优性:是指经过有限次规划迭代后得到的路径是接近最优的次优路径,且每次迭代后都与最优路径更加接近,是一个逐渐收敛的过程;
  • (一)笛卡尔空间 (1)直线运动 (2)圆运动
  • (二)关节空间 (1)点到点 1.三次/五次多项式 2.T型曲线 3.S型曲线 (2)路径点 1.三次/五次样条 2.过路径点的T型曲线 3.贝塞尔曲线 4.B样条(贝塞尔曲线改进) 5.NURBS曲线 6.基于优化的算法如minimum jerk/snap

RRT算法精讲

基于采样的路径规划算法简介

  • PRM: 1.先随机采样n个点构成图 2.基于图进行搜索
  • RRT: 快速搜索随机树,非最优解; 查询near可用kdtree等数据结构,快速找到树中最近结点
  • RRT*: 作了两个改进,一是改变了新节点连接到树的规则,二是对搜索树进行“剪枝”操作,使之更接近于真实的最优路径
  • RRT-connect: 双向扩展的RRT,从start和goal同时扩展,搜索速度比较快,ROS-Moveit!默认使用此算法
import copy
import math
import random
import time
 
import matplotlib.pyplot as plt
from scipy.spatial.transform import Rotation as Rot
import numpy as np
 
show_animation = True
 
 
class RRT:
 
    # randArea采样范围[-2--18] obstacleList设置的障碍物 maxIter最大迭代次数 expandDis采样步长为2.0 goalSampleRate 以10%的概率将终点作为采样点
    def __init__(self, obstacleList, randArea,
                 expandDis=2.0, goalSampleRate=10, maxIter=200):
 
        self.start = None
        self.goal = None
        self.min_rand = randArea[0]
        self.max_rand = randArea[1]
        self.expand_dis = expandDis
        self.goal_sample_rate = goalSampleRate
        self.max_iter = maxIter
        self.obstacle_list = obstacleList
        # 存储RRT树
        self.node_list = None
 
    # start、goal 起点终点坐标
    def rrt_planning(self, start, goal, animation=True):
        start_time = time.time()
        self.start = Node(start[0], start[1])
        self.goal = Node(goal[0], goal[1])
        # 将起点加入node_list作为树的根结点
        self.node_list = [self.start]
        path = None
 
        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]
 
            # 将Xrandom和Xnear连线方向作为生长方向
            # math.atan2() 函数接受两个参数,分别是 y 坐标差值和 x 坐标差值。它返回的值是以弧度表示的角度,范围在 -π 到 π 之间。这个角度表示了从 nearestNode 指向 rnd 的方向。
            theta = math.atan2(rnd[1] - nearestNode.y, rnd[0] - nearestNode.x)
            # 生长 : 输入参数为角度、下标、nodelist中最近的节点  得到生长过后的节点
            newNode = self.get_new_node(theta, n_ind, nearestNode)
            # 检查是否有障碍物 传入参数为新生城路径的两个节点
            noCollision = self.check_segment_collision(newNode.x, newNode.y, nearestNode.x, nearestNode.y)
            if noCollision:
                # 没有碰撞把新节点加入到树里面
                self.node_list.append(newNode)
                if animation:
                    self.draw_graph(newNode, 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
                        # 找路径
                        path = self.get_final_course(lastIndex)
                        pathLen = self.get_path_len(path)
                        print("current path length: {}, It costs {} s".format(pathLen, time.time()-start_time))
 
                        if animation:
                            self.draw_graph(newNode, path)
                        return path
 
    def rrt_star_planning(self, start, goal, animation=True):
        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
            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 noCollision:
                nearInds = self.find_near_nodes(newNode)
                newNode = self.choose_parent(newNode, nearInds)
 
                self.node_list.append(newNode)
                self.rewire(newNode, nearInds)
 
                if animation:
                    self.draw_graph(newNode, 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))
 
        return path
 
    def informed_rrt_star_planning(self, start, goal, animation=True):
        start_time = time.time()
        self.start = Node(start[0], start[1])
        self.goal = Node(goal[0], goal[1])
        self.node_list = [self.start]
        # max length we expect to find in our 'informed' sample space,
        # starts as infinite
        cBest = float('inf')
        path = None
 
        # Computing the sampling space
        cMin = math.sqrt(pow(self.start.x - self.goal.x, 2)
                         + pow(self.start.y - self.goal.y, 2))
        xCenter = np.array([[(self.start.x + self.goal.x) / 2.0],
                            [(self.start.y + self.goal.y) / 2.0], [0]])
        a1 = np.array([[(self.goal.x - self.start.x) / cMin],
                       [(self.goal.y - self.start.y) / cMin], [0]])
 
        e_theta = math.atan2(a1[1], a1[0])
 
        # 论文方法求旋转矩阵(2选1)
        # first column of identity matrix transposed
        # id1_t = np.array([1.0, 0.0, 0.0]).reshape(1, 3)
        # M = a1 @ id1_t
        # U, S, Vh = np.linalg.svd(M, True, True)
        # C = np.dot(np.dot(U, np.diag(
        #     [1.0, 1.0, np.linalg.det(U) * np.linalg.det(np.transpose(Vh))])),
        #            Vh)
 
        # 直接用二维平面上的公式(2选1)
        C = np.array([[math.cos(e_theta), -math.sin(e_theta), 0],
                      [math.sin(e_theta), math.cos(e_theta),  0],
                      [0,                 0,                  1]])
 
        for i in range(self.max_iter):
            # Sample space is defined by cBest
            # cMin is the minimum distance between the start point and the goal
            # xCenter is the midpoint between the start and the goal
            # cBest changes when a new path is found
 
            rnd = self.informed_sample(cBest, cMin, xCenter, C)
            n_ind = self.get_nearest_list_index(self.node_list, rnd)
            nearestNode = self.node_list[n_ind]
 
            # steer
            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 noCollision:
                nearInds = self.find_near_nodes(newNode)
                newNode = self.choose_parent(newNode, nearInds)
 
                self.node_list.append(newNode)
                self.rewire(newNode, nearInds)
 
                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 tempPathLen < cBest:
                            path = tempPath
                            cBest = tempPathLen
                            print("current path length: {}, It costs {} s".format(tempPathLen, time.time()-start_time))
            if animation:
                self.draw_graph_informed_RRTStar(xCenter=xCenter,
                                                cBest=cBest, cMin=cMin,
                                                e_theta=e_theta, rnd=rnd, path=path)
 
        return path
 
    def sample(self):
        # 取得1-100的随机数,如果比10大的话(以10%的概率取到终点)
        if random.randint(0, 100) > self.goal_sample_rate:
            # 在空间里随机采样一个点
            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:
                theta = math.atan2(newNode.y - nearNode.y,
                                   newNode.x - nearNode.x)
                if self.check_collision(nearNode, theta, d):
                    nearNode.parent = n_node - 1
                    nearNode.cost = s_cost
 
    @staticmethod
    def distance_squared_point_to_segment(v, w, p):
        # Return minimum distance between line segment vw and point p
        if np.array_equal(v, w):
            return (p - v).dot(p - v)  # v == w case
        l2 = (w - v).dot(w - v)  # i.e. |w-v|^2 -  avoid a sqrt
        # Consider the line extending the segment,
        # parameterized as v + t (w - v).
        # We find projection of point p onto the line.
        # It falls where t = [(p-v) . (w-v)] / |w-v|^2
        # We clamp t from [0,1] to handle points outside the segment vw.
        t = max(0, min(1, (p - v).dot(w - v) / l2))
        projection = v + t * (w - v)  # Projection falls on the segment
        return (p - projection).dot(p - projection)
 
    def check_segment_collision(self, x1, y1, x2, y2):
        # 遍历所有的障碍物
        for (ox, oy, size) in self.obstacle_list:
            dd = self.distance_squared_point_to_segment(
                np.array([x1, y1]),
                np.array([x2, y2]),
                np.array([ox, oy]))
            if dd <= size ** 2:
                return False  # collision
        return True
 
    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 draw_graph(self, 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.x, rnd.y, "^k")
 
        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:
            # self.plot_circle(ox, oy, size)
            plt.plot(ox, oy, "ok", ms=30 * size)
 
        plt.plot(self.start.x, self.start.y, "xr")
        plt.plot(self.goal.x, self.goal.y, "xr")
 
        if path is not None:
            plt.plot([x for (x, y) in path], [y for (x, y) in path], '-r')
 
        plt.axis([-2, 18, -2, 15])
        plt.grid(True)
        plt.pause(0.01)
 
 
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 = [
    #     (3,  3,  1.5),
    #     (12, 2,  3),
    #     (3,  9,  2),
    #     (9,  11, 2),
    # ]
 
    # 设置障碍物 (圆点、半径)
    obstacleList = [(5, 5, 1), (3, 6, 2), (3, 8, 2), (3, 10, 2), (7, 5, 2),
                    (9, 5, 2), (8, 10, 1)]
 
    # Set params
    # 采样范围 设置的障碍物 最大迭代次数
    rrt = RRT(randArea=[-2, 18], obstacleList=obstacleList, maxIter=200)
    # 传入的是起点和终点
    path = rrt.rrt_planning(start=[0, 0], goal=[15, 12], animation=show_animation)
    # path = rrt.rrt_star_planning(start=[0, 0], goal=[15, 12], animation=show_animation)
    # path = rrt.informed_rrt_star_planning(start=[0, 0], goal=[15, 12], animation=show_animation)
    print("Done!!")
 
    if show_animation and path:
        plt.show()
 
 
if __name__ == '__main__':
    main()

RRT与RRTStar算法及其matlab实现

地图创建

RRT与RRTStar所用到的地图是相同的。

function [start,target,max_x,max_y,wall,resolution]=creat_map_ob()%创建障碍物地图
%创建地图
clear all;
resolution = 1;%每个格子的长度
 
%map的边界
max_x = 50;
max_y = 50;
 
%obstacle
obstacle(1,:) = [25,5,10,5];
obstacle(2,:) = [15,25,5,25];
obstacle(3,:) = [40,20,5,10];
obstacle(4,:) = [30,25,5,10];
obstacle(5,:) = [5,0,5,20];
figure;%创建新窗口
axis([0 max_x 0 max_y]);
set(gca,'XTick',0:resolution:max_x);%x轴的间隔为1
set(gca,'YTick',0:resolution:max_y);%y轴的间隔为1
grid on;
title('RRT');
xlabel('x');
ylabel('y');
hold on;
 
wall = [obstacle];
for i = 1:1:size(wall,1)
    fill([wall(i,1),wall(i,1) + wall(i,3),wall(i,1) + wall(i,3),wall(i,1)],...
        [wall(i,2),wall(i,2),wall(i,2) + wall(i,4),wall(i,2) + wall(i,4)],'k');
end
pause(1);%延时一秒
%初始点
h=msgbox('初始位置标记!');%弹出初始框提示标记初始位置
uiwait(h,3);%3s后关闭对话框
if ishandle(h) == 1%删除对话框
    delete(h);
end
xlabel('请设置初始点X轴! ','Color','black');%设置x轴
but = 0;
while(but ~= 1)%收到左键点击
    [xval,yval,but] = ginput(1);
    xval=floor(xval);
    yval=floor(yval);
end
start.x = xval;%初始位置
start.y = yval;
plot(xval,yval,'b^','MarkerFaceColor','b','MarkerSize',10*resolution);%绘制初始点
text(xval + 1,yval + 0.5,'Start');
pause(1);%延时一秒
%目标点
h=msgbox('目标位置标记!');%弹出目标提示标记目标位置
uiwait(h,3);%3s后关闭对话框
if ishandle(h) == 1%删除对话框
    delete(h);
end
xlabel('请设置目标点X轴! ','Color','black');%设置x轴
but1 = 0;
while(but1 ~= 1)%收到左键点击
    [xval,yval,but1] = ginput(1);
    xval=floor(xval);
    yval=floor(yval);
end
target.x = xval;%目标位置
target.y = yval;
plot(xval,yval,'m^','MarkerFaceColor','m','MarkerSize',10*resolution);%绘制目标点
text(xval + 1,yval + 0.5,'Target');
hold on;

RRT主代码

function RRT()
clc
clear all
close all;
[start,target,max_x,max_y,wall,resolution] = creat_map_ob();
figure;%创建新窗口
axis([0 max_x 0 max_y]);
set(gca,'XTick',0:resolution:max_x);%x轴的间隔为1
set(gca,'YTick',0:resolution:max_y);%y轴的间隔为1
grid on;
title('RRT');
xlabel('x');
ylabel('y');
hold on;
for i = 1:1:size(wall,1)
    fill([wall(i,1),wall(i,1) + wall(i,3),wall(i,1) + wall(i,3),wall(i,1)],...
        [wall(i,2),wall(i,2),wall(i,2) + wall(i,4),wall(i,2) + wall(i,4)],'k');
end
plot(start.x,start.y,'b^','MarkerFaceColor','b','MarkerSize',20*resolution);%绘制初始点
text(start.x + 1,start.y + 0.5,'Start');
plot(target.x,target.y,'m^','MarkerFaceColor','m','MarkerSize',20*resolution);%绘制目标点
text(target.x + 1,target.y + 0.5,'Target');
%初始化随机树
tree.child = [];%存储所有节点
tree.parent = [];%存储节点的父节点
tree.distance = [];%存储child到起点的距离
 
tree.child = start;
tree.parent = start;
tree.distance = 0;
 
%存储新衍生的节点,起点初始化
%target_distance用于存储new_node到终点的距离
new_node.x = start.x;
new_node.y = start.y;
 
target_distance = sqrt((target.x-new_node.x)^2+(target.y-new_node.y)^2);
grow_distance = 1;
target_radius = 1.5;
while target_distance > target_radius
    random_point.x = max_x*rand();
    random_point.y = max_y*rand();
 
    handle1 = plot(random_point.x,random_point.y,'p','MarkerEdgeColor',[0.9290 0.6940 0.1250],'MarkerFaceColor',[0.9290 0.6940 0.1250],'MarkerSize',10*resolution);
    [angle,min_idx] = find_closet_node(random_point.x,random_point.y,tree);
    pause(0.001);
    handle2 = plot([tree.child(min_idx).x,random_point.x],[tree.child(min_idx).y,random_point.y],'-','Color',[0.7 0.7 0.7],'LineWidth',0.8*resolution); 
    %根据步长选择新节点
    pause(0.001);
    new_node.x = tree.child(min_idx).x + grow_distance*cos(angle);
    new_node.y = tree.child(min_idx).y + grow_distance*sin(angle);
    handle3 = plot(new_node.x,new_node.y,'.r','MarkerFaceColor','r','MarkerSize',10*resolution);
    obflag = 1;
    step = 0.01;
    %判断路径是否位于障碍物之中
    if new_node.x < tree.child(min_idx).x
        step = -step;
    end
    for k = 1:1:size(wall,1)
        for i = tree.child(min_idx).x:step:new_node.x
            if angle>pi/2-5e-03 && angle<pi/2+5e-03
                j = tree.child(min_idx).y+i-tree.child(min_idx).x;
            elseif angle>-pi/2-5e-03 && angle<-pi/2+5e-03
                j = tree.child(min_idx).y-i+tree.child(min_idx).x;
            else
                j = tree.child(min_idx).y+(i-tree.child(min_idx).x)*tan(angle);
            end
            if i >= wall(k,1) - 0.005 && i <= (wall(k,1)+wall(k,3)) + 0.005
                if j >= wall(k,2) - 0.005 && j <= (wall(k,2) + wall(k,4)) + 0.005
                    obflag = 0;
                    break
                end
            end
        end
        if obflag == 0
            break
        end
    end
    %更新
    pause(0.001);
    if obflag == 1
        tree.child(end + 1) = new_node;
        tree.parent(end + 1) = tree.child(min_idx);
        tree.distance(end + 1) = 1 + tree.distance(min_idx);
        target_distance = sqrt((target.x-new_node.x)^2 + (target.y-new_node.y)^2);
        %删除红色点
        delete(handle3);
        plot(new_node.x,new_node.y,'.g','MarkerFaceColor','g','MarkerSize',10*resolution); % draw the new node
        %画新连接线
        plot([tree.child(min_idx).x,new_node.x],[tree.child(min_idx).y,new_node.y],'-k','LineWidth',0.8*resolution);
        
    end
    pause(0.001);
    %删除随机点
    delete(handle1);
    %删除最近点与随机点连线。
    delete(handle2);
    pause(0.001);
end
tree.child(end + 1) = target;
tree.parent(end + 1) = new_node;
tree.distance(end + 1) = 2 + tree.distance(min_idx);
final_distance = tree.distance(end);
title('RRT, distance:',num2str(final_distance));
current_index = length(tree.child);
while current_index ~= 1
     plot([tree.child(current_index).x,tree.parent(current_index).x],[tree.child(current_index).y,tree.parent(current_index).y],'-','LineWidth',3*resolution,'Color',[0.8500 0.3250 0.0980]);
     for i = 1:length(tree.child)
         if tree.child(i).x == tree.parent(current_index).x
             if tree.child(i).y == tree.parent(current_index).y
                current_index = i;
                break
            end
        end
     end
end

RRTStar主代码

function RRT()
clc
clear all
close all;
[start,target,max_x,max_y,wall,resolution] = creat_map_ob();
figure;%创建新窗口
axis([0 max_x 0 max_y]);
set(gca,'XTick',0:resolution:max_x);%x轴的间隔为1
set(gca,'YTick',0:resolution:max_y);%y轴的间隔为1
grid on;
title('RRT');
xlabel('x');
ylabel('y');
hold on;
for i = 1:1:size(wall,1)
    fill([wall(i,1),wall(i,1) + wall(i,3),wall(i,1) + wall(i,3),wall(i,1)],...
        [wall(i,2),wall(i,2),wall(i,2) + wall(i,4),wall(i,2) + wall(i,4)],'k');
end
plot(start.x,start.y,'b^','MarkerFaceColor','b','MarkerSize',20*resolution);%绘制初始点
text(start.x + 1,start.y + 0.5,'Start');
plot(target.x,target.y,'m^','MarkerFaceColor','m','MarkerSize',20*resolution);%绘制目标点
text(target.x + 1,target.y + 0.5,'Target');
%初始化随机树
tree.child = [];%存储所有节点
tree.parent = [];%存储节点的父节点
tree.distance = [];%存储child到起点的距离
 
tree.child = start;
tree.parent = start;
tree.distance = 0;
radius = 5;
%存储新衍生的节点,起点初始化
%target_distance用于存储new_node到终点的距离
new_node.x = start.x;
new_node.y = start.y;
 
target_distance = sqrt((target.x-new_node.x)^2+(target.y-new_node.y)^2);
grow_distance = 1;
target_radius = 1.5;
 
line1.l = [];
line1.id = [];
while target_distance > target_radius
    random_point.x = max_x*rand();
    random_point.y = max_y*rand();
 
    handle1 = plot(random_point.x,random_point.y,'p','MarkerEdgeColor',[0.9290 0.6940 0.1250],'MarkerFaceColor',[0.9290 0.6940 0.1250],'MarkerSize',10*resolution);
    [angle,min_idx] = find_closet_node(random_point.x,random_point.y,tree);
    %pause(0.01);
    handle2 = plot([tree.child(min_idx).x,random_point.x],[tree.child(min_idx).y,random_point.y],'-','Color',[0.7 0.7 0.7],'LineWidth',0.8*resolution); 
    %根据步长选择新节点
    %pause(0.01);
    new_node.x = tree.child(min_idx).x + grow_distance*cos(angle);
    new_node.y = tree.child(min_idx).y + grow_distance*sin(angle);
    handle3 = plot(new_node.x,new_node.y,'.r','MarkerFaceColor','r','MarkerSize',10*resolution);
    step = 0.005;
    %判断路径是否位于障碍物之中
    %更新
    pause(0.001);
    if isobstacle(wall,step,tree,new_node,min_idx,angle) == 1
        target_distance = sqrt((target.x-new_node.x)^2 + (target.y-new_node.y)^2);
        %删除红色点
        delete(handle3);
        plot(new_node.x,new_node.y,'.g','MarkerFaceColor','g','MarkerSize',10*resolution); % draw the new node
        %画新连接线
        handle4 = plot([tree.child(min_idx).x,new_node.x],[tree.child(min_idx).y,new_node.y],'-k','LineWidth',0.8*resolution);
       % pause(0.001);
        %删除随机点
        delete(handle1);
        %删除最近点与随机点连线。
        delete(handle2);
        %pause(0.001);
        theta = linspace(0, 2*pi, 100);
        handle6 = plot(new_node.x+radius*cos(theta),new_node.y+radius*sin(theta),'b--');
        [minid,min_dis_fs,newid] = chooseparent(wall,tree,radius,min_idx,new_node,step,resolution);
        tree.child(end + 1) = new_node;
        tree.parent(end + 1) = tree.child(minid);
        tree.distance(end + 1) = min_dis_fs;
        delete(handle4);
 
        line1.l(end+1) = plot([tree.child(minid).x,new_node.x],[tree.child(minid).y,new_node.y],'-k','LineWidth',0.8*resolution);
        line1.id(end+1) = length(tree.child);
        [deid] = rewire(wall,tree,minid,new_node,step,newid,min_dis_fs);
        if(deid ~= 0)
           for j = 1:1:length(line1.id)
               if(line1.id(j) == deid)
                   %pause(2);
                 delete (line1.l(j));
                 break
               end
           end
       % pause(2);
       line1.l(j) = plot([tree.child(deid).x,new_node.x],[tree.child(deid).y,new_node.y],'-k','LineWidth',0.8*resolution);
       %pause(2);
        end
        delete(handle6);
    else
        %删除随机点
    %pause(0.001);
    delete(handle3);
    delete(handle1);
    %删除最近点与随机点连线。
    delete(handle2);
    %pause(0.001);
    end
    
end
tree.child(end + 1) = target;
tree.parent(end + 1) = new_node;
tree.distance(end + 1) = 2 + tree.distance(min_idx);
final_distance = tree.distance(end);
title('RRT, distance:',num2str(final_distance));
current_index = length(tree.child);
while current_index ~= 1
     plot([tree.child(current_index).x,tree.parent(current_index).x],[tree.child(current_index).y,tree.parent(current_index).y],'-','LineWidth',3*resolution,'Color',[0.8500 0.3250 0.0980]);
     for i = 1:length(tree.child)
         if tree.child(i).x == tree.parent(current_index).x
             if tree.child(i).y == tree.parent(current_index).y
                current_index = i;
                break
            end
        end
     end
end

rewire代码

function [deid] = rewire(wall,tree,minid,new_node,step,newid,min_dis_fs)
deid = 0;
for i = 1:1:length(newid)
    if(newid(i) ~= minid)
        newcost = min_dis_fs + sqrt((tree.child(newid(i)).x-new_node.x)^2+(tree.child(newid(i)).y-new_node.y)^2);
        if(newcost < tree.distance(newid(i)))
            angle = atan2(new_node.y-tree.child(newid(i)).y,new_node.x-tree.child(newid(i)).x);
            if isobstacle(wall,step,tree,new_node,newid(i),angle) == 1
                tree.parent(newid(i)) = new_node;
                tree.distance(newid(i)) = newcost;
                deid = newid(i);
            end
        end
    end
end

RRT-connect算法精讲

import math
import random

import matplotlib.pyplot as plt
import numpy as np
from celluloid import Camera  # 保存动图时用,pip install celluloid
import operator
import copy


class RRT_Connect:
    """
    Class for RRT_Connect planning
    """

    class Node:
        """
        创建节点
        """

        def __init__(self, x, y):
            self.x = x  # 节点坐标
            self.y = y
            self.path_x = [] # 路径,作为画图的数据,也可以理解成保存的边集
            self.path_y = []
            self.parent = None #父节点

    class AreaBounds:
        """区域大小
        """
        def __init__(self, area):
            self.xmin = float(area[0])
            self.xmax = float(area[1])
            self.ymin = float(area[2])
            self.ymax = float(area[3])

    def __init__(self,
            start,
            goal,
            obstacle_list,
            rand_area,
            expand_dis=3.0,
            goal_sample_rate=5,
            max_iter=1000,
            play_area=None,
            robot_radius=0.0,
            ):
        """
        Setting Parameter

        start:起点 [x,y]
        goal:目标点 [x,y]
        obstacleList:障碍物位置列表 [[x,y,size],...]
        rand_area: 采样区域 x,y ∈ [min,max]
        play_area: 约束随机树的范围 [xmin,xmax,ymin,ymax]
        robot_radius: 机器人半径
        expand_dis: 扩展的步长
        goal_sample_rate: 采样目标点的概率,百分制.default: 5,即表示5%的概率直接采样目标点

        """
        self.start = self.Node(start[0], start[1]) # 根节点
        self.end = self.Node(goal[0], goal[1]) 
        self.min_rand = rand_area[0]
        self.max_rand = rand_area[1]
        if play_area is not None:
            self.play_area = self.AreaBounds(play_area)
        else:
            self.play_area = None
        self.expand_dis = expand_dis
        self.goal_sample_rate = goal_sample  _rate
        self.max_iter = max_iter
        self.obstacle_list = obstacle_list
        self.node_list_1 = []
        self.node_list_2 = []
        self.robot_radius = robot_radius

    def planning(self, animation=True,camara=None):
        """
        rrt path planning

        animation: flag for animation on or off

        camara: 是否保存动图
        """

        # 将起点作为根节点x_{init},加入到随机树的节点集合中。
        self.node_list_1 = [self.start]
        self.node_list_2 = [self.end]
        for i in range(self.max_iter):
            # 从可行区域内随机选取一个节点q_{rand}
            rnd_node = self.sample_free()  

            # 已生成的树中利用欧氏距离判断距离q_{rand}最近的点q_{near}。
            nearest_ind_1 = self.get_nearest_node_index(self.node_list_1, rnd_node)
            nearest_node_1 = self.node_list_1[nearest_ind_1]  
            # 从q_{near}与q_{rand}的连线方向上扩展固定步长u,得到新节点 q_{new}
            new_node_1 = self.steer(nearest_node_1, rnd_node, self.expand_dis)

            
            # 第一棵树,如果在可行区域内,且q_{near}与q_{new}之间无障碍物
            if self.is_inside_play_area(new_node_1, self.play_area) and self.obstacle_free(new_node_1, self.obstacle_list, self.robot_radius):
                self.node_list_1.append(new_node_1)
                # 扩展完第一棵树的新节点$x_{𝑛𝑒𝑤}$后,以这个新的目标点$x_{𝑛𝑒𝑤}$作为第二棵树扩展的方向。
                nearest_ind_2 = self.get_nearest_node_index(self.node_list_2, new_node_1)
                nearest_node_2 = self.node_list_2[nearest_ind_2]  
                new_node_2 = self.steer(nearest_node_2, new_node_1, self.expand_dis)
                # 第二棵树
                if self.is_inside_play_area(new_node_2, self.play_area) and self.obstacle_free(new_node_2, self.obstacle_list, self.robot_radius):
                    self.node_list_2.append(new_node_2)
                    while True:
                        new_node_2_ = self.steer(new_node_2, new_node_1, self.expand_dis)
                        if self.obstacle_free(new_node_2_, self.obstacle_list, self.robot_radius):
                            self.node_list_2.append(new_node_2_)
                            new_node_2 = new_node_2_
                        else:
                            break
                        # print([new_node_2.x,new_node_2.y], [new_node_1.x,new_node_1.y])
                        # 当$𝑞′_{𝑛𝑒𝑤}=𝑞_{𝑛𝑒𝑤}$时,表示与第一棵树相连,算法结束
                        if operator.eq([new_node_2.x,new_node_2.y], [new_node_1.x,new_node_1.y]):
                            return self.generate_final_path()
            
            # 考虑两棵树的平衡性,即两棵树的节点数的多少,交换次序选择“小”的那棵树进行扩展。
            # 不过不交换的情况下好像搜索速度还更快
            if len(self.node_list_1)>len(self.node_list_2):
                list_tmp = copy.deepcopy(self.node_list_1)
                self.node_list_1 = copy.deepcopy(self.node_list_2)
                self.node_list_2 = list_tmp


            if animation and i % 5 ==0:
                self.draw_graph(rnd_node,new_node_1, camara)

        return None  # cannot find path

    def steer(self, from_node, to_node, extend_length=float("inf")):
        """连线方向扩展固定步长查找x_new

        Args:
            from_node (_type_): x_near
            to_node (_type_): x_rand
            extend_length (_type_, optional): 扩展步长u. Defaults to float("inf").

        Returns:
            _type_: _description_
        """
        # 利用反正切计算角度, 然后利用角度和步长计算新坐标
        d, theta = self.calc_distance_and_angle(from_node, to_node)

        # 如果$q_{near}$与$q_{rand}$间的距离小于步长,则直接将$q_{rand}$作为新节点$q_{new}$
        if extend_length >= d:
            new_x = to_node.x
            new_y = to_node.y
        else:
            new_x = from_node.x+math.cos(theta)
            new_y = from_node.y+math.sin(theta)
        new_node_1 = self.Node(new_x,new_y)
        new_node_1.path_x = [from_node.x] # 边集
        new_node_1.path_y = [from_node.y]
        new_node_1.path_x.append(new_x)
        new_node_1.path_y.append(new_y)

        new_node_1.parent = from_node

        return new_node_1



    def generate_final_path(self):
        """生成路径
        Args:
        Returns:
            _type_: _description_
        """
        path_1 = []
        node = self.node_list_1[-1]
        while node.parent is not None:
            path_1.append([node.x, node.y])
            node = node.parent
        path_1.append([node.x, node.y])

        path_2 = []
        node = self.node_list_2[-1]
        while node.parent is not None:
            path_2.append([node.x, node.y])
            node = node.parent
        path_2.append([node.x, node.y])

        path=[]
        for i in range(len(path_1)-1,-1,-1):
            path.append(path_1[i])
        for i in range(len(path_2)):
            path.append(path_2[i])
        
        return path

    def calc_dist(self, x1, y1,x2,y2):
        """计算距离
        """
        dx = x1 - x2
        dy = y1 - y2
        return math.hypot(dx, dy)

    def sample_free(self):
        # 以(100-goal_sample_rate)%的概率随机生长,(goal_sample_rate)%的概率朝向目标点生长
        if random.randint(0, 100) > self.goal_sample_rate:
            rnd = self.Node(
                random.uniform(self.min_rand, self.max_rand),
                random.uniform(self.min_rand, self.max_rand))
        else:  # goal point sampling
            rnd = self.Node(self.end.x, self.end.y)
        return rnd

    def draw_graph(self, rnd=None,rnd_2=None, camera=None):
        if camera==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.x, rnd.y, "^k")
            if self.robot_radius > 0.0:
                self.plot_circle(rnd.x, rnd.y, self.robot_radius, '-r')
        if rnd_2 is not None:
            plt.plot(rnd_2.x, rnd_2.y, "^r")
            if self.robot_radius > 0.0:
                self.plot_circle(rnd_2.x, rnd_2.y, self.robot_radius, '-b')
        # 画已生成的树
        for node in self.node_list_1:
            if node.parent:
                plt.plot(node.path_x, node.path_y, "-g")
        for node in self.node_list_2:
            if node.parent:
                plt.plot(node.path_x, node.path_y, "-g")
        # 画障碍物
        for (ox, oy, size) in self.obstacle_list:
            self.plot_circle(ox, oy, size)

        # 如果约定了可行区域,则画出可行区域
        if self.play_area is not None:
            plt.plot([self.play_area.xmin, self.play_area.xmax,
                self.play_area.xmax, self.play_area.xmin,
                self.play_area.xmin],
                [self.play_area.ymin, self.play_area.ymin,
                self.play_area.ymax, self.play_area.ymax,
                self.play_area.ymin],
                "-k")

        # 画出起点和目标点
        plt.plot(self.start.x, self.start.y, "xr")
        plt.plot(self.end.x, self.end.y, "xr")
        plt.axis("equal")
        plt.axis([-2, 15, -2, 15])
        plt.grid(True)
        plt.pause(0.01)
        if camera!=None:
            camera.snap()
    # 静态方法无需实例化,也可以实例化后调用,静态方法内部不能调用self.的变量
    @staticmethod
    def plot_circle(x, y, size, color="-b"):  # pragma: no cover
        deg = list(range(0, 360, 5))
        deg.append(0)
        xl = [x + size * math.cos(np.deg2rad(d)) for d in deg]
        yl = [y + size * math.sin(np.deg2rad(d)) for d in deg]
        plt.plot(xl, yl, color)

    @staticmethod
    def get_nearest_node_index(node_list_1, rnd_node):
        dlist = [(node.x - rnd_node.x)**2 + (node.y - rnd_node.y)**2
                 for node in node_list_1]
        minind = dlist.index(min(dlist))

        return minind

    @staticmethod
    def is_inside_play_area(node, play_area):

        if play_area is None:
            return True  # no play_area was defined, every pos should be ok

        if node.x < play_area.xmin or node.x > play_area.xmax or \
           node.y < play_area.ymin or node.y > play_area.ymax:
            return False  # outside - bad
        else:
            return True  # inside - ok

    @staticmethod
    def obstacle_free(node, obstacleList, robot_radius):

        if node is None:
            return False

        for (ox, oy, size) in obstacleList:
            dx_list = [ox - x for x in node.path_x]
            dy_list = [oy - y for y in node.path_y]
            d_list = [dx * dx + dy * dy for (dx, dy) in zip(dx_list, dy_list)]

            if min(d_list) <= (size+robot_radius)**2:
                return False  # collision

        return True  # safe

    @staticmethod
    def calc_distance_and_angle(from_node, to_node):
        """计算两个节点间的距离和方位角

        Args:
            from_node (_type_): _description_
            to_node (_type_): _description_

        Returns:
            _type_: _description_
        """
        dx = to_node.x - from_node.x
        dy = to_node.y - from_node.y
        d = math.hypot(dx, dy)
        theta = math.atan2(dy, dx)
        return d, theta


def main(gx=6.0, gy=10.0):
    print("start " + __file__)
    fig = plt.figure(1)

    camera = Camera(fig) # 保存动图时使用
    camera = None # 不保存动图时,camara为None
    show_animation = True
    # ====Search Path with RRT_Connect====
    obstacleList = [(3, 6, 1), (3, 8, 2), (3, 10, 2), (7, 5, 1),
            (9, 5, 2), (8, 10, 1), (7, 5, 2), (10, 5, 2)]  # [x, y, radius]
    # Set Initial parameters
    rrt = RRT_Connect(
        start=[0, 0],
        goal=[gx, gy],
        rand_area=[-2, 15],
        obstacle_list=obstacleList,
        play_area=[0, 10, 0, 14],
        robot_radius=0.8
    )
    path = rrt.planning(animation=show_animation,camara=camera)
    if path is None:
        print("Cannot find path")
    else:
        path = np.array(path)
        print(path)
        print("found path!!")

        # Draw final path
        if show_animation:
            rrt.draw_graph(camera=camera)
            plt.grid(True)
            plt.pause(0.01)  
            plt.plot(path[:,0], path[:,1], '-r')
            if camera!=None:
                camera.snap()
                animation = camera.animate()
                animation.save('trajectory.gif')
            plt.figure(2)
            plt.axis("equal")
            plt.axis([-2, 15, -2, 15])
            plt.grid(True)
            plt.plot(path[:,0], path[:,1], '-r')
            plt.show()




if __name__ == '__main__':
    main()

OMPL简介

  1. 使用SimplSetup类我们就需要实现:StateValidityChecker与StateSpace
  2. 不适用SimpleSetup类我们就需要实现:StateValidityChecker、SpaceInformation、ProblemDefinition、planner、StateSpace
  3. 我看了后面教程才知道,使用OMPL的过程中还要按照要求去实现一些函数,因此下面的代码看起来可能有些难以理解,不过这是官方第一个教程,重点是理解上面图中的关系。
/*********************************************************************
* Software License Agreement (BSD License)
*
*  Copyright (c) 2010, Rice University
*  All rights reserved.
*
*  Redistribution and use in source and binary forms, with or without
*  modification, are permitted provided that the following conditions
*  are met:
*
*   * Redistributions of source code must retain the above copyright
*     notice, this list of conditions and the following disclaimer.
*   * Redistributions in binary form must reproduce the above
*     copyright notice, this list of conditions and the following
*     disclaimer in the documentation and/or other materials provided
*     with the distribution.
*   * Neither the name of the Rice University nor the names of its
*     contributors may be used to endorse or promote products derived
*     from this software without specific prior written permission.
*
*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
*  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
*  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
*  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
*  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
*  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
*  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
*  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
*  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
*  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
*  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
*  POSSIBILITY OF SUCH DAMAGE.
*********************************************************************/

/* Author: Ioan Sucan */

#include <ompl/base/SpaceInformation.h>
#include <ompl/base/spaces/SE3StateSpace.h>
#include <ompl/geometric/planners/rrt/RRTConnect.h>
#include <ompl/geometric/SimpleSetup.h>

#include <ompl/config.h>
#include <iostream>

namespace ob = ompl::base;
namespace og = ompl::geometric;

bool isStateValid(const ob::State *state)
{
    // cast the abstract state type to the type we expect
    // 这里将抽象基类装换成复合状态空间SE(3),这是看了后面教程才知道的
    const auto *se3state = state->as<ob::SE3StateSpace::StateType>();

    // extract the first component of the state and cast it to what we expect
    // 从复合状态空间SE(3)中提取R^3
    const auto *pos = se3state->as<ob::RealVectorStateSpace::StateType>(0);

    // extract the second component of the state and cast it to what we expect
    // 从复合状态空间SE(3)中提取SO(3)
    const auto *rot = se3state->as<ob::SO3StateSpace::StateType>(1);

    // check validity of state defined by pos & rot


    // return a value that is always true but uses the two variables we define, so we avoid compiler warnings
    return (const void*)rot != (const void*)pos;
}

void plan()
{
    // construct the state space we are planning in
    // 创建规划所在的状态空间
    auto space(std::make_shared<ob::SE3StateSpace>());

    // set the bounds for the R^3 part of SE(3)
    // 创建Rn的状态空间的边界
    ob::RealVectorBounds bounds(3);
    bounds.setLow(-1);
    bounds.setHigh(1);
    // 设置状态空间的边界
    space->setBounds(bounds);

    // construct an instance of  space information from this state space
    // 创建一个SpaceInformation类
    auto si(std::make_shared<ob::SpaceInformation>(space));

    // set state validity checking for this space
    // SpaceInformation对象进行状态点有效性检测,这里直接使用isStateValid,感觉是一样啊
    si->setStateValidityChecker(isStateValid);

    // create a random start state
    ob::ScopedState<> start(space);
    start.random();

    // create a random goal state
    ob::ScopedState<> goal(space);
    goal.random();

    // create a problem instance
    // 构建问题定义对象,需要SpaceInformation对象
    auto pdef(std::make_shared<ob::ProblemDefinition>(si));

    // set the start and goal states
    // 为问题定义对象设置起始点与终点
    pdef->setStartAndGoalStates(start, goal);

    // create a planner for the defined space
    // 创建规划器对象,需要SpaceInformation对象
    auto planner(std::make_shared<og::RRTConnect>(si));

    // set the problem we are trying to solve for the planner
    // 设置规划器解决的问题对象,需要ProblemDefinition
    planner->setProblemDefinition(pdef);

    // perform setup steps for the planner
    // 启动设置?
    planner->setup();

    // print the settings for this space
    si->printSettings(std::cout);

    // print the problem settings
    pdef->print(std::cout);

    // attempt to solve the problem within one second of planning time
    ob::PlannerStatus solved = planner->ob::Planner::solve(1.0);

    if (solved)
    {
        // get the goal representation from the problem definition (not the same as the goal state)
        // and inquire about the found path
        // 从问题对象中获取结果
        ob::PathPtr path = pdef->getSolutionPath();
        std::cout << "Found solution:" << std::endl;

        // print the path to screen
        path->print(std::cout);
    }
    else
        std::cout << "No solution found" << std::endl;
}

void planWithSimpleSetup()
{
    // construct the state space we are planning in
    // 创建状态空间对象
    auto space(std::make_shared<ob::SE3StateSpace>());

    // set the bounds for the R^3 part of SE(3)
    // 创建状态空间组件的边界,不理解,每个维度最小值为-1,最大1么
    ob::RealVectorBounds bounds(3);
    bounds.setLow(-1);
    bounds.setHigh(1);
    // 设置状态空间组件的边界
    space->setBounds(bounds);

    // define a simple setup class
    // 创建一个SimpleSetup对象,该对象自动实例化SpaceInformation,ProblemDefinition
    og::SimpleSetup ss(space);

    // set state validity checking for this space
    // 设置状态有效性检查类,本来是要传入一个类,此处传入的是一个函数,StateValidityCheckerFn中有说明
    ss.setStateValidityChecker([](const ob::State *state) { return isStateValid(state); });

    // create a random start state
    // 创建ScopedState(域内状态对象么。。。)
    ob::ScopedState<> start(space);
    // 设置这个状态为随机值
    start.random();

    // create a random goal state
    // 同上
    ob::ScopedState<> goal(space);
    goal.random();
    
    // set the start and goal states
    // 设置起始点与终点
    ss.setStartAndGoalStates(start, goal);

    // this call is optional, but we put it in to get more output information
    ss.setup();
    ss.print();

    // attempt to solve the problem within one second of planning time
    // 设置完毕开始求解
    ob::PlannerStatus solved = ss.solve(1.0);

    if (solved)
    {
        std::cout << "Found solution:" << std::endl;
        // print the path to screen
        // 简化求解的结果?
        ss.simplifySolution();
        // 输出结果
        ss.getSolutionPath().print(std::cout);
    }
    else
        std::cout << "No solution found" << std::endl;
}

int main(int /*argc*/, char ** /*argv*/)
{
    std::cout << "OMPL version: " << OMPL_VERSION << std::endl;

    plan();

    std::cout << std::endl << std::endl;

    planWithSimpleSetup();

    return 0;
}

上述代码流程的简单总结: 一、使用SimpleSetup

  • 创建SE3StateSpace对象
  • 创建RealVectorBounds
  • 设置SE3StateSpace的边界
  • 创建SimpleSetup对象,需要SE3StateSpace
  • 调用SimpleSetup的setStateValidityChecker,状态的有效性检测
  • 创建起始点与终点,ScopedState
  • 调用SimpleSetup的setStartAndGoalStates,设置起点终点
  • 调用SimpleSetup的solve,解算
  • 调用SimpleSetup的simplifySolution,简化结果?
  • 调用SimpleSetup的getSolutionPath,获取路径PathGeometric

二、不使用SimpleSetup

  • 创建SE3StateSpace对象
  • 创建RealVectorBounds,并设置边界
  • 设置SE3StateSpace的边界
  • 创建SpaceInformation对象,需要SE3StateSpace
  • 调用SpaceInformation的setStateValidityChecker,状态的有效性检测
  • 创建起始点与终点,ScopedState
  • 创建ProblemDefinition,需要SpaceInformation
  • 调用ProblemDefinition的setStartAndGoalStates,设置起点终点
  • 创建planner,需要SpaceInformation
  • 调用planner的setProblemDefinition,需要ProblemDefinition
  • 调用planner的solve,求解
  • 调用planner的getSolutionPath,获得路径