Apollo项目导航模式下的坐标转换研究

严正声明:本文系作者davidhopper原创,未经许可,不得转载。

Apollo项目导航模式下,规划模块输出的轨迹点使用FLU车身坐标系(见我的另一篇博客《Apollo项目坐标系研究》),在进行当前帧规划前,需要将前一帧未行驶完轨迹点的车身坐标转换为当前帧的车身坐标,并在其中找到最为匹配的点,作为当前帧的规划起点;若在指定的误差范围内找不到匹配点,则以当前车辆位置作为新的规划起点。该过程涉及到两套FLU车身坐标系的变换。本文首先图解介绍坐标变换的公式,然后给出Apollo项目的具体变换代码。

一、坐标变换公式

1.1 问题描述

如下图所示, X O Y XOY XOY是ENU全局坐标系, X o l d O o l d Y o l d X_{old}O_{old}Y_{old} XoldOoldYold X n e w O n e w Y n e w X_{new}O_{new}Y_{new} XnewOnewYnew是FLU车身坐标系。已知坐标原点 O o l d O_{old} Oold 在坐标系 X O Y XOY XOY中的坐标为 ( x 01 , y 01 , θ 1 ) (x_{01}, y_{01}, \theta_{1}) (x01,y01,θ1) O n e w O_{ new} Onew 在坐标系 X O Y XOY XOY中的坐标为 ( x 02 , y 02 , θ 2 ) (x_{02}, y_{02}, \theta_{2}) (x02,y02,θ2) P P P点在前一帧车身坐标系 X o l d O o l d Y o l d X_{old}O_{old}Y_{old} XoldOoldYold中的坐标为 ( x o l d , y o l d , θ o l d ) (x_{old}, y_{old}, \theta_{old}) (xold,yold,θold),求解 P P P点在当前帧车身坐标系 X n e w O n e w Y n e w X_{ new}O_{new}Y_{new} XnewOnewYnew中的坐标 ( x n e w , y n e w , θ n e w ) (x_{new}, y_{new}, \theta_{new}) (xnew,ynew,θnew)
1

1.2 公式推导

如下图所示,当前帧坐标原点 O n e w O_{ new} Onew在前一帧车身坐标系 X o l d O o l d Y o l d X_{old}O_{old}Y_{old} XoldOoldYold中的坐标 ( x d , y d , θ d ) (x_d, y_d, \theta_d) (xd,yd,θd)可通过下述表达式计算:
x d = O o l d E + E F = O o l d E + D C = ( x 02 − x 01 ) c o s θ 1 + ( y 02 − y 01 ) s i n θ 1 ( 1 ) x_d=O_{old}E+EF=O_{old}E+DC=(x_{02}-x_{01})cos \theta_{1}+(y_{02}-y_{01})sin\theta_{1}\qquad(1) xd=OoldE+EF=OoldE+DC=(x02x01)cosθ1+(y02y01)sinθ1(1)
y d = O n e w C − F C = O n e w C − E D = ( y 02 − y 01 ) c o s θ 1 − ( x 02 − x 01 ) s i n θ 1 ( 2 ) y_d=O_{ new}C-FC=O_{new}C-ED=(y_{02}-y_{01})cos \theta_{1}-(x_{02}-x_{01})sin\theta_{1}\qquad(2) yd=OnewCFC=OnewCED=(y02y01)cosθ1(x02x01)sinθ1(2)
θ d = θ 2 − θ 1 ( 3 ) \theta_d=\theta_2-\theta_1\qquad(3) θd=θ2θ1(3)
2
如下图所示, P P P点在当前帧车身坐标系 X n e w O n e w Y n e w X_{ new}O_{new}Y_{new} XnewOnewYnew中的坐标 ( x n e w , y n e w , θ n e w ) (x_{new}, y_{new}, \theta_{new}) (xnew,ynew,θnew)可通过下述表达式计算:
x n e w = O n e w J + J K = O n e w J + I H = ( x o l d − x d ) c o s θ d + ( y o l d − y d ) s i n θ d ( 4 ) x_{new}=O_{new}J+JK=O_{new}J+IH=(x_{old}-x_{d})cos \theta_{d}+(y_{old}-y_{d})sin\theta_{d}\qquad(4) xnew=OnewJ+JK=OnewJ+IH=(xoldxd)cosθd+(yoldyd)sinθd(4)
y n e w = P H − K H = P H − J I = ( y o l d − y d ) c o s θ d − ( x o l d − x d ) s i n θ d ( 5 ) y_{new}=PH-KH=PH-JI=(y_{old}-y_{d})cos \theta_{d}-(x_{old}-x_{d})sin\theta_{d}\qquad(5) ynew=PHKH=PHJI=(yoldyd)cosθd(xoldxd)sinθd(5)
θ n e w = θ o l d − θ d ( 6 ) \theta_{new}=\theta_{old}-\theta_{d}\qquad(6) θnew=θoldθd(6)
3

二、坐标变换代码

坐标变换代码见modules/planning/navi_planning.cc中的NaviPlanning::RunOnce函数,具体代码如下:

void NaviPlanning::RunOnce(const LocalView& local_view,
                           ADCTrajectory* const trajectory_pb) {
  // ...
  auto vehicle_config =
      ComputeVehicleConfigFromLocalization(*local_view_.localization_estimate);

  if (last_vehicle_config_.is_valid_ && vehicle_config.is_valid_) {
    auto x_diff_map = vehicle_config.x_ - last_vehicle_config_.x_;
    auto y_diff_map = vehicle_config.y_ - last_vehicle_config_.y_;

    auto cos_map_veh = std::cos(last_vehicle_config_.theta_);
    auto sin_map_veh = std::sin(last_vehicle_config_.theta_);

    auto x_diff_veh = cos_map_veh * x_diff_map + sin_map_veh * y_diff_map;
    auto y_diff_veh = -sin_map_veh * x_diff_map + cos_map_veh * y_diff_map;

    auto theta_diff = vehicle_config.theta_ - last_vehicle_config_.theta_;

    TrajectoryStitcher::TransformLastPublishedTrajectory(
        x_diff_veh, y_diff_veh, theta_diff, last_publishable_trajectory_.get());
  }
  // ...
}

其中的NaviPlanning::ComputeVehicleConfigFromLocalization函数代码为:

NaviPlanning::VehicleConfig NaviPlanning::ComputeVehicleConfigFromLocalization(
    const localization::LocalizationEstimate& localization) const {
  NaviPlanning::VehicleConfig vehicle_config;

  if (!localization.pose().has_position()) {
    return vehicle_config;
  }

  vehicle_config.x_ = localization.pose().position().x();
  vehicle_config.y_ = localization.pose().position().y();

  const auto& orientation = localization.pose().orientation();

  if (localization.pose().has_heading()) {
    vehicle_config.theta_ = localization.pose().heading();
  } else {
    vehicle_config.theta_ = common::math::QuaternionToHeading(
        orientation.qw(), orientation.qx(), orientation.qy(), orientation.qz());
  }

  vehicle_config.is_valid_ = true;
  return vehicle_config;
}

TrajectoryStitcher::TransformLastPublishedTrajectory函数位于文件modules/planning/common/trajectory_stitcher.cc中,代码如下:

void TrajectoryStitcher::TransformLastPublishedTrajectory(
    const double x_diff, const double y_diff, const double theta_diff,
    PublishableTrajectory* prev_trajectory) {
  if (!prev_trajectory) {
    return;
  }

  // R^-1
  double cos_theta = std::cos(theta_diff);
  double sin_theta = -std::sin(theta_diff);

  // -R^-1 * t
  auto tx = -(cos_theta * x_diff - sin_theta * y_diff);
  auto ty = -(sin_theta * x_diff + cos_theta * y_diff);

  std::for_each(prev_trajectory->begin(), prev_trajectory->end(),
                [&cos_theta, &sin_theta, &tx, &ty,
                 &theta_diff](common::TrajectoryPoint& p) {
                  auto x = p.path_point().x();
                  auto y = p.path_point().y();
                  auto theta = p.path_point().theta();

                  auto x_new = cos_theta * x - sin_theta * y + tx;
                  auto y_new = sin_theta * x + cos_theta * y + ty;
                  auto theta_new =
                      common::math::NormalizeAngle(theta - theta_diff);

                  p.mutable_path_point()->set_x(x_new);
                  p.mutable_path_point()->set_y(y_new);
                  p.mutable_path_point()->set_theta(theta_new);
                });
}

分析代码可知,其中的坐标变换代码与第一部分推导的公式吻合。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值