Reeds-Shepp曲线公式推导及代码实现-第三部分(共三部分)

:由于优快云篇幅限制,文章只能拆分成三部分,完整的文章可见Reeds-Shepp曲线公式推导及代码实现

优快云剩余部分:
Reeds-Shepp曲线公式推导及代码实现-第一部分(共三部分)
Reeds-Shepp曲线公式推导及代码实现-第二部分(共三部分)
Reeds-Shepp曲线公式推导及代码实现-第三部分(共三部分)

3. 轨迹求解:

3.1 局部轨迹求解

在上一章节我们求解到 t t t u u u v v v后,我们需要根据字段类型( L L L R R R S S S)求解出局部坐标下路径点位姿。

其具体过程如下(建议结合后面的代码查看):

  1. 根据 t t t u u u v v v的值进行等步长插值,得到每段轨迹对应的生成一系列插值距离列表。

    计算插值距离列表 (calc_interpolate_dists_list)的过程如下

    • 输入:t_u_v(包含每个段落的长度),step_len(每一步的长度)。
    • 处理:对于每个段落长度,根据长度的正负确定步长d_dist。使用np.arange生成从0到length的序列,步长为d_dist,然后添加length本身,以确保终点包含在内。
    • 输出:interpolate_dists_list,包含每个段落的插值距离列表。
  2. 根据上一步生成的插值距离列表的值,计算其对应的局部位姿值。

    插值计算 (interpolate)的过程如下:

    • 输入:dist(当前插值距离),length(段落长度),mode(模式,"S"代表直线,"L"代表左转,"R"代表右转),max_cur(最大曲率),以及当前位置和朝向。
    • 处理:
      • 如果模式是"S"(直线),则使用直线方程计算新位置和朝向。
      • 如果模式是"L"或"R"(曲线),则计算曲线上对应距离的点的新位置和朝向。
    • 输出:新位置(x, y),新朝向yaw,以及方向(1表示前进,-1表示后退)。

3.2 全局轨迹求解

根据原始起点的坐标和航向,将上一小节计算出来的局部进行整体的平移和旋转(与归一化的步骤刚好相反),最终得到全局路径的轨迹。

3.3 求解最优路径

根据计算出来的路径,选择路径最短的轨迹(也可根据我们实际的需要定制选优规则),作为最终的轨迹输出。

4. 代码实现

reeds_shepp.py

import math
import matplotlib.pyplot as plt
import numpy as np
# import imageio.v2 as imageio
from scipy.spatial.transform import Rotation as Rot


def rot_mat_2d(angle):
    """
    平面内的旋转矩阵
    """
    return Rot.from_euler('z', angle).as_matrix()[0:2, 0:2]


def polar(x, y):
    r = math.hypot(x, y)
    theta = math.atan2(y, x)
    return r, theta


def mod2pi(x):
    v = np.mod(x, np.copysign(2.0 * math.pi, x))
    if v < -math.pi:
        v += 2.0 * math.pi
    else:
        if v > math.pi:
            v -= 2.0 * math.pi
    return v


def lsl(x, y, phi):
    """L+S+L+"""
    u, t = polar(x - math.sin(phi), y - 1.0 + math.cos(phi))
    if 0.0 <= t <= math.pi:
        v = mod2pi(phi - t)
        if 0.0 <= v <= math.pi:
            return True, [t, u, v], ['L', 'S', 'L']

    return False, [], []


def lsr(x, y, phi):
    """L+S+R+"""
    u1, t1 = polar(x + math.sin(phi), y - 1.0 - math.cos(phi))
    u1 = u1 ** 2
    if u1 >= 4.0:
        u = math.sqrt(u1 - 4.0)
        theta = math.atan2(2.0, u)
        t = mod2pi(t1 + theta)
        v = mod2pi(t - phi)

        if (t >= 0.0) and (v >= 0.0):
            return True, [t, u, v], ['L', 'S', 'R']

    return False, [], []


def lr_l(x, y, phi):
    """L+R-L+"""
    u1, theta = polar(x - math.sin(phi), y - 1 + math.cos(phi))

    if u1 < 4.0:
        beta = math.acos(0.25 * u1)
        t = mod2pi(beta + theta + math.pi / 2)
        u = mod2pi(math.pi - 2 * beta)
        v = mod2pi(phi - t - u)
        return True, [t, -u, v], ['L', 'R', 'L']

    return False, [], []


def lr_l_(x, y, phi):
    """L+R-L-"""
    u1, theta = polar(x - math.sin(phi), y - 1 + math.cos(phi))

    if u1 <= 4.0:
        beta = math.acos(0.25 * u1)
        t = mod2pi(beta + theta + math.pi / 2)
        u = mod2pi(math.pi - 2 * beta)
        v = mod2pi(-phi + t + u)
        return True, [t, -u, -v], ['L', 'R', 'L']

    return False, [], []


def lrl_(x, y, phi):
    """L+R+L-"""
    u1, theta = polar(x - math.sin(phi), y - 1 + math.cos(phi))

    if u1 <= 4.0:
        beta = math.acos(0.25 * u1)
        t = mod2pi(-beta + theta + math.pi / 2)
        u = mod2pi(math.pi - 2 * beta)
        v = mod2pi(t - u - phi)
        return True, [t, u, -v], ['L', 'R', 'L']

    return False, [], []


def lrl_r_(x, y, phi):
    """L+R+L-R-"""
    u1, theta = polar(x + math.sin(phi), y - 1 - math.cos(phi))

    # 在论文中,(2 < u1 <= 4)的解被认为是次优的
    # 当u1 > 4时,不存在解
    if u1 <= 2:
        u = mod2pi(math.acos((u1 + 2) * 0.25))
        t = mod2pi(theta + u + math.pi / 2)
        v = mod2pi(phi - t + 2 * u)
        if (t >= 0) and (u >= 0) and (v >= 0):
            return True, [t, u, -u, -v], ['L', 'R', 'L', 'R']

    return False, [], []


def lr_l_r(x, y, phi):
    """L+R-L-R+"""
    u1, theta = polar(x + math.sin(phi), y - 1 - math.cos(phi))
    u2 = (20 - u1 ** 2) / 16

    if 0 <= u2 <= 1:
        u = math.acos(u2)
        beta = math.asin(2 * math.sin(u) / u1)
        t = mod2pi(theta + beta + math.pi / 2)
        v = mod2pi(t - phi)
        if (t >= 0) and (v >= 0):
            return True, [t, -u, -u, v], ['L', 'R', 'L', 'R']

    return False, [], []


def lr_90s_l_(x, y, phi):
    """L+R-90S-L-"""
    u1, theta = polar(x - math.sin(phi), y - 1 + math.cos(phi))

    if u1 >= 2.0:
        u = math.sqrt(u1 ** 2 - 4) - 2
        beta = math.atan2(2, math.sqrt(u1 ** 2 - 4))
        t = mod2pi(theta + beta + math.pi / 2)
        v = mod2pi(t - phi + math.pi / 2)
        if (t >= 0) and (v >= 0):
            return True, [t, -math.pi / 2, -u, -v], ['L', 'R', 'S', 'L']

    return False, [], []


def lr_90s_r_(x, y, phi):
    """L+R-90S-R-"""
    u1, theta = polar(x + math.sin(phi), y - 1 - math.cos(phi))

    if u1 >= 2.0:
        t = mod2pi(theta + math.pi / 2)
        u = u1 - 2
        v = mod2pi(phi - t - math.pi / 2)
        if (t >= 0) and (v >= 0):
            return True, [t, -math.pi / 2, -u, -v], ['L', 'R', 'S', 'R']

    return False, [], []


def lsr90l_(x, y, phi):
    """L+S+R+90L-"""
    u1, theta = polar(x - math.sin(phi), y - 1 + math.cos(phi))

    if u1 >= 2.0:
        u = math.sqrt(u1 ** 2 - 4) - 2
        beta = math.atan2(math.sqrt(u1 ** 2 - 4), 2)
        t = mod2pi(theta - beta + math.pi / 2)
        v = mod2pi(t - phi - math.pi / 2)
        if (t >= 0) and (v >= 0):
            return True, [t, u, math.pi / 2, -v], ['L', 'S', 'R', 'L']

    return False, [], []


def lsl90r_(x, y, phi):
    """L+S+L+90R-"""
    u1, theta = polar(x + math.sin(phi), y - 1 - math.cos(phi))

    if u1 >= 2.0:
        t = mod2pi(theta)
        u = u1 - 2
        v = mod2pi(phi - t - math.pi / 2)
        if (t >= 0) and (v >= 0):
            return True, [t, u, math.pi / 2, -v], ['L', 'S', 'L', 'R']

    return False, [], []


def lr_90s_l_90r(x, y, phi):
    """L+R-90S-L-90R+"""
    zeta = x + math.sin(phi)
    eeta = y - 1 - math.cos(phi)
    u1, theta = polar(zeta, eeta)

    if u1 >= 4.0:
        u = math.sqrt(u1 ** 2 - 4) - 4
        beta = math.atan2(2, math.sqrt(u1 ** 2 - 4))
        t = mod2pi(theta + beta + math.pi / 2)
        v = mod2pi(t - phi)
        if (t >= 0) and (v >= 0):
            return True, [t, -math.pi / 2, -u, -math.pi / 2, v], ['L', 'R', 'S', 'L', 'R']

    return False, [], []


class Path:
    """
    路径数据容器
    """

    def __init__(self):
        # 路径段长度(负值表示反向段)
        self.t_u_v = []
        # 路径段类型字符("S": 直线,"L": 左转,"R": 右转)
        self.ctypes = []
        self.L = 0.0  # 路径的总长度
        self.x = []  # x坐标位置
        self.y = []  # y坐标位置
        self.yaw = []  # 方向角[弧度]
        self.directions = []  # 方向(1: 前进,-1: 后退)


def set_path(paths, t_u_v, ctypes, step_len):
    path = Path()
    path.ctypes = ctypes
    path.t_u_v = t_u_v
    path.L = sum(np.abs(t_u_v))

    # 检查是否存在相同路径
    for i_path in paths:
        type_is_same = (i_path.ctypes == path.ctypes)
        length_is_close = (sum(np.abs(i_path.t_u_v)) - path.L) <= step_len
        if type_is_same and length_is_close:
            return paths  # 存在相同路径,不需要插入

    # 检查路径的长度是否太短
    if path.L <= step_len:
        return paths  # 路径太短,不需要插入

    paths.append(path)
    return paths


def time_flip(t_u_v):
    return [-x for x in t_u_v]


def reflect(ctypes):
    def switch_dir(dirn):
        if dirn == 'L':
            return 'R'
        elif dirn == 'R':
            return 'L'
        else:
            return 'S'

    return [switch_dir(dirn) for dirn in ctypes]


def generate_rs_path(q0, q1, max_cur, step_len):
    dx = q1[0] - q0[0]
    dy = q1[1] - q0[1]
    dyaw = q1[2] - q0[2]

    c = math.cos(q0[2])
    s = math.sin(q0[2])
    x = (c * dx + s * dy) * max_cur
    y = (-s * dx + c * dy) * max_cur
    step_len *= max_cur  # 步长归一化

    paths = []
    rs_path_func = [lsl, lsr,  # CSC
                    lr_l, lr_l_, lrl_,  # CCC
                    lrl_r_, lr_l_r,  # CCCC
                    lr_90s_l_, lr_90s_r_,  # CCSC
                    lsr90l_, lsl90r_,  # CSCC
                    lr_90s_l_90r]  # CCSCC

    for path_func in rs_path_func:

        # 遍历筛选后的12种组合
        flag, t_u_v, ctypes = path_func(x, y, dyaw)
        if flag:
            for dis in t_u_v:
                if 0.1 * sum([abs(d) for d in t_u_v]) < abs(dis) < step_len:
                    print("Step len too large for Reeds-Shepp paths.")
                    return []
            paths = set_path(paths, t_u_v, ctypes, step_len)

        # 时间翻转
        flag, t_u_v, ctypes = path_func(-x, y, -dyaw)
        if flag:
            for dis in t_u_v:
                if 0.1 * sum([abs(d) for d in t_u_v]) < abs(dis) < step_len:
                    print("Step len too large for Reeds-Shepp paths.")
                    return []
            t_u_v = time_flip(t_u_v)
            paths = set_path(paths, t_u_v, ctypes, step_len)

        # 反射变换
        flag, t_u_v, ctypes = path_func(x, -y, -dyaw)
        if flag:
            for dis in t_u_v:
                if 0.1 * sum([abs(d) for d in t_u_v]) < abs(dis) < step_len:
                    print("Step len too large for Reeds-Shepp paths.")
                    return []
            ctypes = reflect(ctypes)
            paths = set_path(paths, t_u_v, ctypes, step_len)

        # 逆向变换
        flag, t_u_v, ctypes = path_func(-x, -y, dyaw)
        if flag:
            for dis in t_u_v:
                if 0.1 * sum([abs(d) for d in t_u_v]) < abs(dis) < step_len:
                    print("Step len too large for Reeds-Shepp paths.")
                    return []
            t_u_v = time_flip(t_u_v)
            ctypes = reflect(ctypes)
            paths = set_path(paths, t_u_v, ctypes, step_len)

    return paths


def calc_interpolate_dists_list(t_u_v, step_len):
    interpolate_dists_list = []
    for length in t_u_v:
        d_dist = step_len if length >= 0.0 else -step_len
        interp_dists = np.arange(0.0, length, d_dist)
        interp_dists = np.append(interp_dists, length)
        interpolate_dists_list.append(interp_dists)

    return interpolate_dists_list


def interpolate(dist, length, mode, max_cur, origin_x, origin_y, origin_yaw):
    if mode == "S":
        x = origin_x + dist / max_cur * math.cos(origin_yaw)
        y = origin_y + dist / max_cur * math.sin(origin_yaw)
        yaw = origin_yaw
    else:  # 曲线
        ldx = math.sin(dist) / max_cur
        ldy = 0.0
        yaw = None
        if mode == "L":  # left turn
            ldy = (1.0 - math.cos(dist)) / max_cur
            yaw = origin_yaw + dist
        elif mode == "R":  # right turn
            ldy = (1.0 - math.cos(dist)) / -max_cur
            yaw = origin_yaw - dist
        gdx = math.cos(-origin_yaw) * ldx + math.sin(-origin_yaw) * ldy
        gdy = -math.sin(-origin_yaw) * ldx + math.cos(-origin_yaw) * ldy
        x = origin_x + gdx
        y = origin_y + gdy

    return x, y, yaw, 1 if length > 0.0 else -1


def generate_local_course(t_u_v, ctypes, max_cur, step_len):
    interpolate_dists_list = calc_interpolate_dists_list(t_u_v, step_len * max_cur)

    origin_x, origin_y, origin_yaw = 0.0, 0.0, 0.0

    xs, ys, yaws, directions = [], [], [], []
    for (interp_dists, mode, length) in zip(interpolate_dists_list, ctypes,
                                            t_u_v):

        for dist in interp_dists:
            x, y, yaw, direction = interpolate(dist, length, mode,
                                               max_cur, origin_x,
                                               origin_y, origin_yaw)
            xs.append(x)
            ys.append(y)
            yaws.append(yaw)
            directions.append(direction)
        origin_x = xs[-1]
        origin_y = ys[-1]
        origin_yaw = yaws[-1]

    return xs, ys, yaws, directions


def pi_2_pi(angle):
    a = math.fmod(angle + np.pi, 2 * np.pi)
    if a < 0.0:
        a += (2.0 * np.pi)
    return a - np.pi


def calc_paths(sx, sy, syaw, ex, ey, eyaw, max_cur, step_len):
    q0 = [sx, sy, syaw]
    q1 = [ex, ey, eyaw]

    paths = generate_rs_path(q0, q1, max_cur, step_len)
    for path in paths:
        xs, ys, yaws, directions = generate_local_course(path.t_u_v,
                                                         path.ctypes, max_cur,
                                                         step_len)
        # 转换为全局坐标
        path.x = [math.cos(-q0[2]) * ix + math.sin(-q0[2]) * iy + q0[0] for
                  (ix, iy) in zip(xs, ys)]
        path.y = [-math.sin(-q0[2]) * ix + math.cos(-q0[2]) * iy + q0[1] for
                  (ix, iy) in zip(xs, ys)]
        path.yaw = [pi_2_pi(yaw + q0[2]) for yaw in yaws]
        path.directions = directions
        path.lengths = [length / max_cur for length in path.t_u_v]
        path.L = path.L / max_cur

    return paths


def reeds_shepp_planning(sx, sy, syaw, ex, ey, eyaw, max_cur, step_len=0.2):
    paths = calc_paths(sx, sy, syaw, ex, ey, eyaw, max_cur, step_len)

    if not paths:
        return None, None, None, None, None  # 不能生成任何轨迹

    # 查找路径最短的路径
    best_path_index = paths.index(min(paths, key=lambda p: abs(p.L)))
    b_path = paths[best_path_index]

    return b_path.x, b_path.y, b_path.yaw, b_path.ctypes, b_path.t_u_v


def plot_arrow(x, y, yaw, length=1.0, width=0.5, fc="r", ec="k", linewidth=3.0):
    if isinstance(x, list):
        for (ix, iy, iyaw) in zip(x, y, yaw):
            plot_arrow(ix, iy, iyaw)
    else:
        plt.arrow(x, y, length * math.cos(yaw), length * math.sin(yaw), fc=fc,
                  ec=ec, head_width=width, head_length=width)
        plt.plot([x, x + length * math.cos(yaw)],
                 [y, y + length * math.sin(yaw)],
                 color=ec, linewidth=linewidth)


def plot_car(x, y, yaw, color='-k'):
    car_color = color
    c, s = math.cos(yaw), math.sin(yaw)
    rot = rot_mat_2d(-yaw)
    car_outline_x, car_outline_y = [], []
    width, rear_2_front, rear_2_back = 2.0, 3.3, 1.0
    outline_xs = [rear_2_front, rear_2_front, -rear_2_back, -rear_2_back, rear_2_front]
    outline_ys = [width / 2, -width / 2, -width / 2, width / 2, width / 2]
    for rx, ry in zip(outline_xs, outline_ys):
        converted_xy = np.stack([rx, ry]).T @ rot
        car_outline_x.append(converted_xy[0] + x)
        car_outline_y.append(converted_xy[1] + y)

    plot_arrow(x, y, yaw)
    plt.plot(car_outline_x, car_outline_y, car_color)


def main():
    # 起点
    start_x = 5.0
    start_y = 5.0
    start_yaw = np.deg2rad(0.0)

    # 目标点
    end_x = 25.0
    end_y = 20.0
    end_yaw = np.deg2rad(-90.0)

    max_curvature = 0.1  # 最大曲率
    path_step_length = 0.05  # 路径步长

    xs, ys, yaws, modes, lengths = reeds_shepp_planning(start_x, start_y, start_yaw,
                                                        end_x, end_y, end_yaw,
                                                        max_curvature, path_step_length)

    if not xs:
        print("Rs path palnning failed.")

    else:
        # image_list = []  # 存储图片
        # i = 0
        for i_x, i_y, i_yaw in zip(xs, ys, yaws):
            plt.cla()
            plt.plot(xs, ys, label="final course " + str(modes))
            plot_car(i_x, i_y, i_yaw)
            plot_car(end_x, end_y, end_yaw, 'gray')

            plt.legend()
            plt.grid(True)
            plt.axis("equal")
            plt.pause(0.0001)
        #     plt.savefig("temp.png")
        #     i += 1
        #     if (i % 5) == 0:
        #         image_list.append(imageio.imread("temp.png"))
        #
        # imageio.mimsave("display.gif", image_list, duration=0.1)


if __name__ == '__main__':
    main()

运行结果:

在这里插入图片描述

参考文献

Reeds Shepp planning-Mathematical Description of Individual Path Types

数据集介绍:野生动物与家畜多目标检测数据集 数据集名称:野生动物与家畜多目标检测数据集 数据规模: - 训练集:1,540张图片 - 验证集:377张图片 - 测试集:316张图片 分类类别: Brown-bear(棕熊)、Chicken(鸡)、Fox(狐狸)、Hedgehog(刺猬)、Horse(马)、Mouse(老鼠)、Sheep(绵羊)、Snake(蛇)、Turtle(龟)、Rabbit(兔)及通用object(物体)11个类别 标注格式: YOLO格式标注,包含归一化坐标与类别索引,支持目标检测模型训练 数据特性: 涵盖航拍与地面视角,包含动物个体及群体场景,适用于复杂环境下的多目标识别 农业智能化管理: 通过检测家畜(鸡/马/绵羊等)数量及活动状态,辅助畜牧场自动化管理 生态监测系统: 支持野生动物(棕熊/狐狸/刺猬等)识别与追踪,用于自然保护区生物多样性研究 智能安防应用: 检测农场周边危险动物(蛇/狐狸),构建入侵预警系统 动物行为研究: 提供多物种存场景数据,支持动物群体交互行为分析 高实用性标注体系: - 精细标注包含动物完整轮廓的边界框 - 特别区分野生动物与家畜类别,支持跨场景迁移学习 多维度覆盖: - 包含昼间/复杂背景/遮挡场景 - 涵盖陆地常见中小型动物与禽类 - 提供通用object类别适配扩展需求 工程适配性强: - 原生YOLO格式适配主流检测框架(YOLOv5/v7/v8等) - 验证集与测试集比例科学,支持可靠模型评估 生态价值突出: - 同步覆盖濒危物种(龟类)与常见物种 - 支持生物多样性保护与农业生产的双重应用场景
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

艰默

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值