基于Frenet优化轨迹的无人车动作规划方法实例

原理参考:无人驾驶汽车系统入门(二十一)——基于Frenet优化轨迹的无人车动作规划方法
规划器的C++代码(出自CppRobotics):

/*************************************************************************
	> File Name: frenet_optimal_trajectory.cpp
	> Author: TAI Lei
	> Mail: ltai@ust.hk
	> Created Time: Wed Apr  3 09:52:17 2019
 ************************************************************************/

#include<iostream>
#include<limits>
#include<vector>
#include<opencv2/opencv.hpp>
#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<sys/time.h>
#include"cubic_spline.h"
#include"frenet_path.h"
#include"quintic_polynomial.h"
#include"quartic_polynomial.h"

#define SIM_LOOP 500
#define MAX_SPEED  50.0 / 3.6  // 最大速度 [m/s]
#define MAX_ACCEL  2.0  // 最大加速度 [m/ss]
#define MAX_CURVATURE  1.0  // 最大曲率 [1/m]
#define MAX_ROAD_WIDTH  7.0  // 最大道路宽度 [m]
#define D_ROAD_W  1.0  // 道路宽度采样间隔 [m]
#define DT  0.2  // Delta T [s]
#define MAXT  5.0  // 最大预测时间 [m]
#define MINT  4.0  // 最小预测时间 [m]
#define TARGET_SPEED  30.0 / 3.6  // 目标速度(即纵向的速度保持) [m/s]
#define D_T_S  5.0 / 3.6  // 目标速度采样间隔 [m/s]
#define N_S_SAMPLE  1  // 目标速度的采样数量
#define ROBOT_RADIUS  1.5  // 机器人底盘半径 [m]

#损失函数权重
#define KJ  0.1
#define KT  0.1
#define KD  1.0
#define KLAT  1.0
#define KLON  1.0

using namespace cpprobotics;


float sum_of_power(std::vector<float> value_list){
  float sum = 0;
  for(float item:value_list){
    sum += item*item;
  }
  return sum;
};

/**
 *使用基于Frenet坐标系的优化轨迹方法生成一系列横向和纵向的轨迹,并且计算每条轨迹对应的损失值
**/
Vec_Path calc_frenet_paths(
    float c_speed, float c_d, float c_d_d, float c_d_dd, float s0){
  std::vector<FrenetPath> fp_list;
  // 根据道路宽度进行采样
  for(float di=-1*MAX_ROAD_WIDTH; di<MAX_ROAD_WIDTH; di+=D_ROAD_W){
    // 横向动作规划
    for(float Ti=MINT; Ti<MAXT; Ti+=DT){
      FrenetPath fp;
      // 计算出关于目标配置di、Ti的横向多项式
      QuinticPolynomial lat_qp(c_d, c_d_d, c_d_dd, di, 0.0, 0.0, Ti);
      // Ti时间内得到每个点时刻位置、速度、加速度、加加速度
      for(float t=0; t<Ti; t+=DT){
        fp.t.push_back(t);
        // 获取采样时间内每一刻d的位置
        fp.d.push_back(lat_qp.calc_point(t));
        // 获取采样时间内每一刻d的速度
        fp.d_d.push_back(lat_qp.calc_first_derivative(t));
        // 获取采样时间内每一刻d的加速度
        fp.d_dd.push_back(lat_qp.calc_second_derivative(t));
        // 获取采样时间内每一刻d的加加速度
        fp.d_ddd.push_back(lat_qp.calc_third_derivative(t));
      }
      // 纵向速度规划(速度保持)
      for(float tv=TARGET_SPEED - D_T_S * N_S_SAMPLE;
          tv < TARGET_SPEED + D_T_S * N_S_SAMPLE;
          tv+=D_T_S){
        FrenetPath fp_bot = fp;
        // 计算出关于目标配置di、Ti的纵向多项式
        QuarticPolynomial lon_qp(s0, c_speed, 0.0, tv, 0.0, Ti);
        fp_bot.max_speed = std::numeric_limits<float>::min();
        fp_bot.max_accel = std::numeric_limits<float>::min();
        for(float t_:fp.t){
          // 获取采样时间内每一刻d的位置
          fp_bot.s.push_back(lon_qp.calc_point(t_));
          // 获取采样时间内每一刻s的速度
          fp_bot.s_d.push_back(lon_qp.calc_first_derivative(t_));
          // 获取采样时间内每一刻s的加速度
          fp_bot.s_dd.push_back(lon_qp.calc_second_derivative(t_));
          // 获取采样时间内每一刻s的加加速度
          fp_bot.s_ddd.push_back(lon_qp.calc_third_derivative(t_));
          if(fp_bot.s_d.back() > fp_bot.max_speed){
            fp_bot.max_speed = fp_bot.s_d.back();
          }
          if(fp_bot.s_dd.back() > fp_bot.max_accel){
            fp_bot.max_accel = fp_bot.s_dd.back();
          }
        }

        float Jp = sum_of_power(fp.d_ddd); // d方向加加速度的总和,舒适度越好,机器人速度越平滑
        float Js = sum_of_power(fp_bot.s_ddd); // s方向加加速度的总和,舒适度越好,机器人速度越平滑
        float ds = std::pow((TARGET_SPEED - fp_bot.s_d.back()),2);// 目标速度与数组内的最后一个速度差值的平方

        fp_bot.cd = KJ * Jp + KT * Ti + KD * std::pow(fp_bot.d.back(), 2);// 横向损失函数(原则上横向加加速度越小越好,速度变化时间越小越好,终点距离中心线越近越好)
        fp_bot.cv = KJ * Js + KT * Ti + KD * ds;// 纵向损失函数(原则上纵向加加速度越小越好,速度变化时间越小越好,终点距离中心线越近越好)
        fp_bot.cf = KLAT * fp_bot.cd + KLON * fp_bot.cv;// 总的损失函数为d和s方向的损失函数乘以对应的系数并相加

        fp_list.push_back(fp_bot);
      }
    }
  }
  return fp_list;
};

/************************计算全局路径************************/
void calc_global_paths(Vec_Path & path_list, Spline2D csp){
  for (Vec_Path::iterator path_p=path_list.begin(); path_p!=path_list.end();path_p++){
    for(unsigned int i=0; i<path_p->s.size(); i++){
      if (path_p->s[i] >= csp.s.back()){
        break;
      }
      std::array<float, 2> poi = csp.calc_postion(path_p->s[i]);
      float iyaw = csp.calc_yaw(path_p->s[i]);
      float di = path_p->d[i];
      float x = poi[0] + di * std::cos(iyaw + M_PI/2.0);
      float y = poi[1] + di * std::sin(iyaw + M_PI/2.0);
      path_p->x.push_back(x);
      path_p->y.push_back(y);
    }

    for(int i=0; i<path_p->x.size()-1; i++){
      float dx = path_p->x[i + 1] - path_p->x[i];
      float dy = path_p->y[i + 1] - path_p->y[i];
      path_p->yaw.push_back(std::atan2(dy, dx));
      path_p->ds.push_back(std::sqrt(dx * dx + dy * dy));
    }

    path_p->yaw.push_back(path_p->yaw.back());
    path_p->ds.push_back(path_p->ds.back());


    path_p->max_curvature = std::numeric_limits<float>::min();
    for(int i=0; i<path_p->x.size()-1; i++){
      path_p->c.push_back((path_p->yaw[i+1]-path_p->yaw[i])/path_p->ds[i]);
      if(path_p->c.back() > path_p->max_curvature){
        path_p->max_curvature = path_p->c.back();
      }
    }
  }
};

/************************碰撞检测************************/
bool check_collision(FrenetPath path, const Vec_Poi ob){
  for(auto point:ob){
    // 计算所有路径点到障碍物的距离来推测是否发生碰撞
    for(unsigned int i=0; i<path.x.size(); i++){
      float dist = std::pow((path.x[i] - point[0]), 2) + std::pow((path.y[i] - point[1]), 2);
      if (dist <= ROBOT_RADIUS * ROBOT_RADIUS){
        return false;
      }
    }
  }
  return true;
};

/************************优化轨迹检测函数************************/
Vec_Path check_paths(Vec_Path path_list, const Vec_Poi ob){
	Vec_Path output_fp_list;
  // 检查最大速度、加速度和曲率
  for(FrenetPath path:path_list){
    if (path.max_speed < MAX_SPEED && path.max_accel < MAX_ACCEL && path.max_curvature < MAX_CURVATURE && check_collision(path, ob)){
      output_fp_list.push_back(path);
    }
  }
  return output_fp_list;
};

FrenetPath frenet_optimal_planning(
    Spline2D csp, float s0, float c_speed,
    float c_d, float c_d_d, float c_d_dd, Vec_Poi ob){
  Vec_Path fp_list = calc_frenet_paths(c_speed, c_d, c_d_d, c_d_dd, s0);
  calc_global_paths(fp_list, csp);
  Vec_Path save_paths = check_paths(fp_list, ob);

  float min_cost = std::numeric_limits<float>::max();
  FrenetPath final_path;
  for(auto path:save_paths){
    if (min_cost >= path.cf){
      min_cost = path.cf;
      final_path = path;
    }
  }
  return final_path;
};

cv::Point2i cv_offset(
    float x, float y, int image_width=2000, int image_height=2000){
  cv::Point2i output;
  output.x = int(x * 100) + 300;
  output.y = image_height - int(y * 100) - image_height/3;
  return output;
};

int main(){
  Vec_f wx({0.0, 10.0, 20.5, 35.0, 70.5});
  Vec_f wy({0.0, -6.0, 5.0, 6.5, 0.0});
  std::vector<Poi_f> obstcles{
    {{20.0, 10.0}},
    {{30.0, 6.0}},
    {{30.0, 8.0}},
    {{35.0, 8.0}},
    {{50.0, 3.0}}
  };

  Spline2D csp_obj(wx, wy);
  Vec_f r_x;
  Vec_f r_y;
  Vec_f ryaw;
  Vec_f rcurvature;
  Vec_f rs;

  for(float i=0; i<csp_obj.s.back(); i+=0.1){
    std::array<float, 2> point_ = csp_obj.calc_postion(i);
    r_x.push_back(point_[0]);
    r_y.push_back(point_[1]);
    ryaw.push_back(csp_obj.calc_yaw(i));
    rcurvature.push_back(csp_obj.calc_curvature(i));
    rs.push_back(i);
  }

  float c_speed = 10.0/3.6;
  float c_d = 2.0;
  float c_d_d = 0.0;
  float c_d_dd = 0.0;
  float s0 = 0.0;

  float area = 20.0;

  cv::namedWindow("frenet", cv::WINDOW_NORMAL);
  int count = 0;

  for(int i=0; i<SIM_LOOP; i++){
    FrenetPath final_path = frenet_optimal_planning(
      csp_obj, s0, c_speed, c_d, c_d_d, c_d_dd, obstcles);
    s0 = final_path.s[1];
    c_d = final_path.d[1];
    c_d_d = final_path.d_d[1];
    c_d_dd = final_path.d_dd[1];
    c_speed = final_path.s_d[1];

    if (std::pow((final_path.x[1] - r_x.back()), 2) + std::pow((final_path.y[1]-r_y.back()), 2) <= 1.0){
        break;
    }

    // visualization
    cv::Mat bg(2000, 8000, CV_8UC3, cv::Scalar(255, 255, 255));
    for(unsigned int i=1; i<r_x.size(); i++){
      cv::line(
        bg,
        cv_offset(r_x[i-1], r_y[i-1], bg.cols, bg.rows),
        cv_offset(r_x[i], r_y[i], bg.cols, bg.rows),
        cv::Scalar(0, 0, 0),
        10);
    }
    for(unsigned int i=0; i<final_path.x.size(); i++){
      cv::circle(
        bg,
        cv_offset(final_path.x[i], final_path.y[i], bg.cols, bg.rows),
        40, cv::Scalar(255, 0, 0), -1);
    }

    cv::circle(
      bg,
      cv_offset(final_path.x.front(), final_path.y.front(), bg.cols, bg.rows),
      50, cv::Scalar(0, 255, 0), -1);

    for(unsigned int i=0; i<obstcles.size(); i++){
      cv::circle(
        bg,
        cv_offset(obstcles[i][0], obstcles[i][1], bg.cols, bg.rows),
        40, cv::Scalar(0, 0, 255), 5);
    }

    cv::putText(
      bg,
      "Speed: " + std::to_string(c_speed*3.6).substr(0, 4) + "km/h",
      cv::Point2i((int)bg.cols*0.5, (int)bg.rows*0.1),
      cv::FONT_HERSHEY_SIMPLEX,
      5,
      cv::Scalar(0, 0, 0),
      10);


    cv::imshow("frenet", bg);
    cv::waitKey(5);

    // save image in build/bin/pngs
    // struct timeval tp;
    // gettimeofday(&tp, NULL);
    // long int ms = tp.tv_sec * 1000 + tp.tv_usec / 1000;
    // std::string int_count = std::to_string(ms);
    // cv::imwrite("./pngs/"+int_count+".png", bg);
  }
  return 0;
};
  • 9
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
基于frenet优化轨迹无人车动作规划是一种先进的技术,通过使用Frenet坐标系来描述车辆在道路上的运动状态和路径规划。这种方法可以有效解决传统笛卡尔坐标系下的规划难题,例如避免路径交叉和处理曲率连续性等。 在这个实例中,我们考虑一个无人车需要在某个城市的道路网络中规划行驶路径。首先,采集并处理城市道路的地理数据,包括道路几何形状、交叉口信息和限速等。然后,将这些数据转换到Frenet坐标系下。 接下来,我们需要定义目标和约束条件。目标可能包括最短路径、最小车辆启停次数以及最大速度等。约束条件可以包括遵守交通规则、不与其他车辆碰撞、安全跟随前车等。 在进行路径规划时,我们需要考虑车辆的状态和环境信息。通过车辆传感器获取车辆当前位置、速度、加速度以及周围环境的感知信息。然后,算法会根据这些信息,结合车辆运动学模型,计算出车辆在接下来的时间段内的最优动作,例如加速度、转向角度等。 为了获得最优解,我们可以使用优化算法,例如动态规划、模型预测控制或强化学习等。这些算法会在考虑目标和约束条件的基础上,通过迭代求解来得到最优路径和动作。 在规划完成后,无人车便可以按照规划出的路径和动作进行行驶。同时,无人车还需不断更新环境感知信息,并根据实时的状态调整路径规划动作控制。 通过基于Frenet优化轨迹无人车动作规划,我们可以实现高效、安全和自主的无人车行驶。这种方法在城市交通管理、自动驾驶技术以及智能交通系统方面具有广阔应用前景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Travis.X

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

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

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

打赏作者

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

抵扣说明:

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

余额充值