Apollo 9.0 参考线生成器 -- ReferenceLineProvider

文章目录

  • 1. Planning 与 Routing交互
    • 1.1 路由请求RoutingRequest
    • 1.2 路由响应RoutingResponse
    • 1.3 换道过程
  • 2. 创建参考线线程
    • 2.1 创建参考线生成器
    • 2.2 启动参考线线程
  • 3. 参考线周期生成
    • 3.1 创建参考线
    • 3.2 更新参考线
  • 4. 参考线平滑
    • 4.1 设置中间点anchor points
    • 4.2 平滑算法平滑


在基于参考线的规划算法中,参考线是Planning一切工作的基础和前提,所以搞清楚参考线怎么来的就变得很重要。为此,首先需要了解Planning 与 Routing的交互,通过交互得到路径片段,这些路径片段就告诉我们该怎么走,再在此基础上生成参考线,最后使用不同的平滑算法对参考线进行平滑,得到一条光滑的参考线。

1. Planning 与 Routing交互

Routing模块,即路由,是进行全局路径规划的地方,是和Planning平级的模块。Routing模块接收RoutingRequest输入,通过A*算法搜索,得到从起点到终点的全局最优路径,最后通过RoutingResponse中将最优路径的信息反馈给请求模块Planning。需要说明的是,这是车道级别的路径
下面是Apollo收到一个导航请求时的处理流程,用户在Dreamview上通过鼠标点选和车辆当前的位置(实车应该是在车机中控大屏导航地图上输入起点和终点,以及途经点),得到起点和终点的位姿,经过在地图上映射,得到RoutingRequest信息,其中包含了起点和终点在最近车道上的对应点。
[图片]

1.1 路由请求RoutingRequest

file in apollo/modules/common_msgs/routing_msgs/routing.proto

message RoutingRequest {optional apollo.common.Header header = 1;// at least two points. The first is start point, the end is final point.// The routing must go through each point in waypoint.repeated LaneWaypoint waypoint = 2;  // 路由的起点、终点、途经点repeated LaneSegment blacklisted_lane = 3;repeated string blacklisted_road = 4;optional bool broadcast = 5 [default = true];optional apollo.routing.ParkingInfo parking_info = 6 [deprecated = true];// If the start pose is set as the first point of "way_point".optional bool is_start_pose_set = 7 [default = false];
}

file in apollo/modules/common_msgs/routing_msgs/geometry.proto

message LaneWaypoint {optional string id = 1;optional double s = 2;optional apollo.common.PointENU pose = 3;// When the developer selects a point on the dreamview route editing// the direction can be specified by dragging the mouse// dreamview calculates the heading based on this to support construct lane way point with headingoptional double heading = 4;
}message LaneSegment {optional string id = 1;optional double start_s = 2;optional double end_s = 3;
}

从上述的protobuf定义内容可以看到:

  1. RoutingRequest里面的waypoint(LaneWaypoint类型)是路径查询的核心,使用repeated形式表明这样的路由点不止1个;

例如:大雄要查询从家A到学校B的一条路径,那么waypoint就是两个;如果大雄要查询家A到学校B的一条路径,并且还要经过静香家C,那么最终的waypoint就是三个。

  1. LaneSegment和Prediction中的LaneSegment一样,定义了一条车道的 [start_s, end_s] 路段区间,使用repeated形式可以完整的离散化定义一条车道。
  2. LaneWaypoint可以定义车道上的任意一点,包括所在车道id,所在车道的累计距离s,以及世界坐标系下的坐标pose。

/// file in apollo/modules/common/proto/geometry.proto

message PointENU {optional double x = 1 [default = nan];  // East from the origin, in meters.optional double y = 2 [default = nan];  // North from the origin, in meters.optional double z = 3 [default = 0.0];  // Up from the WGS-84 ellipsoid, in meters.
}

1.2 路由响应RoutingResponse

file in apollo/modules/common_msgs/planning_msgs/planning_command.proto

message PlanningCommand {optional apollo.common.Header header = 1;// Unique identification for command.optional int64 command_id = 2 [default = -1];// Move along the lanes on map.optional apollo.routing.RoutingResponse lane_follow_command = 3;// Target speed in command.optional double target_speed = 4;// Indicate if the command is a motion command.optional bool is_motion_command = 5 [default = false];oneof command {// Move to the given pose with open space planning trajectory.ParkingCommand parking_command = 6;// Custom command defined by user for extensibility.google.protobuf.Any custom_command = 7;}
}

Planning 模块接收外部命令PlanningCommand,其中最重要的是路由响应RoutingResponse

file in apollo/modules/common_msgs/routing_msgs/routing.proto

message RoutingResponse {optional apollo.common.Header header = 1;repeated RoadSegment road = 2;                // 核心:路径optional Measurement measurement = 3;         // 行驶距离optional RoutingRequest routing_request = 4;  // 发出的查询// the map version which is used to build road graphoptional bytes map_version = 5;optional apollo.common.StatusPb status = 6;
}

file in apollo/modules/common_msgs/routing_msgs/geometry.proto

message Measurement { // 行驶距离optional double distance = 1;
}enum ChangeLaneType {FORWARD = 0;LEFT = 1;RIGHT = 2;
};message Passage {repeated LaneSegment segment = 1;  // 一条车道由多个LaneSegment组成optional bool can_exit = 2;optional ChangeLaneType change_lane_type = 3 [default = FORWARD];
}message RoadSegment {optional string id = 1;repeated Passage passage = 2;  // 多条车道
}

以上是路径查询的返回/响应结果RoutingResponse,其中routing_request是对应发出的查询,measurement是行驶距离,最核心的内容就是road(repeated RoadSegment),这是一条从起点家A到终点学校B,并且经过中间静香家C的完整路径,由一段段离散化的RoadSegment组成。

RoadSegment类型包含了repeated Passage,这意味着,一个RoadSegment中包含了多个通道,每个通道可以理解为一条车道,一个道路段RoadSegment可以有多条并行向前行驶的车道。而Passage中每条车道可以有多个LaneSegment组成,意味着进一步划分成小的区间段,便于精细化调度。

最终的可视化结果如下图所示
[图片]

RoutingResponse中最重要的数据是RoadSegment road,这个数据一共有三层:

  • RoadSegment:描述道路,一条道路可能包含了并行的几条通路(Passage)。
    • Passage 1:描述通路,通路是直连不含变道的可行驶区域。一个通路可能包含了前后连接的多个车道。
      • LaneSegment 1:描述车道,车道是道路中的一段,自动驾驶车辆会尽可能沿着车道的中心线行驶。
      • LaneSegment 2
      • LaneSegment x
    • Passage 2
    • Passage x
      另外需要注意的是Passage中的两个属性:
  • can_exit : 代表能否从当前通道去下一个通道。用于判断将来是否需要变道。
  • change_lane_type : 代表当前通道是直行(FORWARD),左转(LEFT)还是右转(RIGHT)。

下面使用cyber_monitor 命令查看 /apollo/planning/command 话题的结果,该结果对应下图仿真场景。该waypoint对应我在地图上选取的终点,该passage对应当前时刻ADC所处位置的passage,该通路要求右转。

在这里插入图片描述

如图6中,Routing搜索到从车辆起点到终点的结果用红线标出。

在这里插入图片描述

  • 从起点到终点一共有8条RoadSegment,除了Road4为4车道外,其他的RoadSegment都是3车道;
  • RoutingResponse中一共由4个Passage组成。
    • Passage1:lane2->lane5;
    • Passage2:lane6->lane10->lane14->lane15->lane16->lane19
    • Passage3:lane20->lane23
    • Passage4:lane21->lane24

注意:RoutingResponse中包含了从起点到终点的唯一最优路径方案,其他的车道如lane1,lane4等并不包含在RoutingResponse范围中。下文中所指的换道,也是在这个RoutingResponse范围中进行换道。如果因为障碍物等原因,车辆需要切换到RoutingResponse范围之外,称为借道,等借道完成绕开障碍物之后,车辆还需要回到原来的车道线上。

1.3 换道过程

Apollo planning模块接收到RoutingResponse之后,首先进行结果保存和转换,然后通过PncMap::GetRouteSegments函数,在当前车辆行驶范围内,进行短期路径查询,获取RouteSegments,转换成ReferenceLine后进行平滑,得到最终的ReferenceLine列表。
所以在Apollo规划中,并不是有些人想的那样,是可以通过主动请求来换道的,而是在规划过程中计算是否需要换道。
粗略理解为在车辆行驶前后一定范围内,每条Passage在这个范围内的线段对应一个ReferenceLine。

如下图所示,绿框是车辆当前位置,此时在短期范围内一共有两条ReferenceLine,第一条是在车辆当前所在的Passage1,因为与车辆所在车道相同,所以将其判断为“直行车道”;另外一条在Passage2,车辆需要右转才能切换到这条参考线上,因为判断为“换道车道”。
在这里插入图片描述

Planning会对这两条参考线都进行路径和速度的决策和优化,然后根据这两条路径的Cost选择最优并且路径最长的那条。

首先直行的Cost会比换道的Cost要低,所有优先选择直行。当车辆短期范围包含了直行车道的终点时,因为换道车道对应的参考线较长,所以会选择换道车道,这就完成了换道的决策。

apollo/modules/planning/planning_component.cc

Status OnLanePlanning::Plan(const double current_time_stamp,const std::vector<TrajectoryPoint>& stitching_trajectory,ADCTrajectory* trajectory_pb) {// 调用PublicRoadPlanner::Plan(),进行轨迹规划    planning_start_point = stitching_trajectory.back()auto status = planner_->Plan(stitching_trajectory.back(), frame_.get(), ptr_trajectory_pb);ExportReferenceLineDebug(ptr_debug);// 从可用参考线信息集获取最优的参考线信息 best_reference_line,也就是可能规划了多条路径,选择最优的路径const auto* best_ref_info = frame_->FindDriveReferenceLineInfo();if (!best_ref_info) {std::string msg("planner failed to make a driving plan");AERROR << msg;if (last_publishable_trajectory_) {last_publishable_trajectory_->Clear();}return Status(ErrorCode::PLANNING_ERROR, msg);}

apollo/modules/planning/planning_base/common/frame.cc

// 基于多条参考线规划了多条轨迹,选择其中一条轨迹
const ReferenceLineInfo *Frame::FindDriveReferenceLineInfo() {double min_cost = std::numeric_limits<double>::infinity();drive_reference_line_info_ = nullptr;for (const auto &reference_line_info : reference_line_info_) {if (reference_line_info.IsDrivable() &&reference_line_info.Cost() < min_cost) { // 寻找cost最小的参考线。drive_reference_line_info_ = &reference_line_info;min_cost = reference_line_info.Cost();}}return drive_reference_line_info_;
}

2. 创建参考线线程

Apollo 的Planning中有2个线程:规划线程和参考线提供线程。

规划线程:又称主线程,负责每周期具体的轨迹规划任务;
参考线提供线程:又称参考线线程,负责定期更新参考线,提供给主线程使用。
在这里插入图片描述

创建参考线线程在Planning第一步初始化中一次完成,主要包含两步:

  • 创建参考线生成器:ReferenceLineProvider;
  • 启动参考线线程:reference_line_provider_->Start()。

2.1 创建参考线生成器

首先是创建参考线生成器 – ReferenceLineProvider,这一步使用了智能指针直接调用构造函数。

配置文件中记录了pnc map的类名:“apollo::planning::LaneFollowMap”,这是ReferenceLineProvider构造函数中需要的参数。至于什么是pnc map,什么又是LaneFollowMap,它们和高精度地图HDmap又有什么关系,在Apollo 9.0 Planning 中重要的数据结构中有讲过。

在这里插入图片描述

在ReferenceLineProvider构造函数中:

  • 首先解析配置文件,将配置参数保存到smoother_config_中。FLAGS_smoother_config_filename为gflags定义的全局变量,如果没有在modules/planning/conf/planning.conf中定义 smoother_config_就会使用使用 modules/planning/common/planning_gflags.cc中的默认值。
ReferenceLineProvider::ReferenceLineProvider(const common::VehicleStateProvider *vehicle_state_provider,const ReferenceLineConfig *reference_line_config,const std::shared_ptr<relative_map::MapMsg> &relative_map): vehicle_state_provider_(vehicle_state_provider) {...   ACHECK(cyber::common::GetProtoFromFile(FLAGS_smoother_config_filename,&smoother_config_))<< "Failed to load smoother config file "<< FLAGS_smoother_config_filename;...   
}

modules/planning/conf/planning.conf中定义了使用:smoother_config_discrete_points_smoother_config.pb.txt,也就是离散点平滑器配置参数,下一篇参考线平滑算法(Apollo 9.0 参考线平滑算法 – Discrete Points ReferenceLine Smoother)中将主要以这种平滑算法为主来讲解。
在这里插入图片描述

  • 根据配置,创建参考线平滑器。这里使用了多态:父类指针指向子类对象。
ReferenceLineProvider::ReferenceLineProvider(const common::VehicleStateProvider *vehicle_state_provider,const ReferenceLineConfig *reference_line_config,const std::shared_ptr<relative_map::MapMsg> &relative_map): vehicle_state_provider_(vehicle_state_provider) {...   if (smoother_config_.has_qp_spline()) {smoother_.reset(new QpSplineReferenceLineSmoother(smoother_config_));       // 样条曲线} else if (smoother_config_.has_spiral()) {smoother_.reset(new SpiralReferenceLineSmoother(smoother_config_));         // 螺旋线} else if (smoother_config_.has_discrete_points()) {smoother_.reset(new DiscretePointsReferenceLineSmoother(smoother_config_)); // 离散点} else {ACHECK(false) << "unknown smoother config "<< smoother_config_.DebugString();}...   
}
  • 加载pnc map插件。根据传入的reference_line_config,创建插件对象。
ReferenceLineProvider::ReferenceLineProvider(const common::VehicleStateProvider *vehicle_state_provider,const ReferenceLineConfig *reference_line_config,const std::shared_ptr<relative_map::MapMsg> &relative_map): vehicle_state_provider_(vehicle_state_provider) {...   // Load pnc map plugins.pnc_map_list_.clear();// Set "apollo::planning::LaneFollowMap" as default if pnc_map_class is empty.if (nullptr == reference_line_config ||reference_line_config->pnc_map_class().empty()) {const auto &pnc_map = apollo::cyber::plugin_manager::PluginManager::Instance()->CreateInstance<planning::PncMapBase>("apollo::planning::LaneFollowMap");pnc_map_list_.emplace_back(pnc_map);} else {const auto &pnc_map_names = reference_line_config->pnc_map_class();for (const auto &map_name : pnc_map_names) {const auto &pnc_map = apollo::cyber::plugin_manager::PluginManager::Instance()->CreateInstance<planning::PncMapBase>(map_name);pnc_map_list_.emplace_back(pnc_map);}}...   
}

2.2 启动参考线线程

ReferenceLineProvider对象创建好后,会马上调用它的成员函数Start(),创建一个参考线线程(如果配置使用多线程的话)。参考线线程开启开关通过gflags定义FLAGS_enable_reference_line_provider_thread,在modules/planning/common/planning_gflags.cc中,默认开启。cyber::Async 允许异步执行一个函数,将任务放入后台线程执行。这样就会有一个单独的线程,调用ReferenceLineProvider::GenerateThread()生成参考线。这个线程我们可以称为参考线线程
在这里插入图片描述

3. 参考线周期生成

在《Apollo 9.0 Planning 初始化都做了什么?》中,我们了解到每一帧规划所需的参考线都是在OnLanePlanning::InitFrame()函数中更新得到。

在这里插入图片描述

于是,我们在ReferenceLineProvider::GetReferenceLines()打一个断点进入调试,可以看到函数调用栈如下,可以看到在多线程环境下,主线程只在这个函数下,就完成了参考线的更新,该函数也仅仅只是将计算好的参考线赋值一下,并没有实际的计算过程。

在这里插入图片描述

那么参考线又是怎么计算的呢?上面代码可以看到,如果不使用多线程,即FLAGS_enable_reference_line_provider_thread为false,可以调用CreateReferenceLine(reference_lines, segments)来创建参考线,接着调用UpdateReferenceLine(*reference_lines, *segments)来更新参考线,这样同样可以得到最新的参考线。
我们在ReferenceLineProvider::CreateReferenceLine()打一个断点进入调试,可以看到函数调用栈如下,可以看到在多线程环境下,参考线线程的调用情况。

在这里插入图片描述

接下来,我们看ReferenceLineProvider::GenerateThread()里面是如何周期性调用生成参考线的。
这里还是分为两步,与非多线程一样:

  • 调用CreateReferenceLine(reference_lines, segments)来创建参考线,
  • 接着调用UpdateReferenceLine(*reference_lines, *segments)来更新参考线。

在这里插入图片描述

3.1 创建参考线

  • 首先,是准备预处理。更新车辆状态vehicle_state和规划命令command,判断pnc map是否为空。
bool ReferenceLineProvider::CreateReferenceLine(std::list<ReferenceLine> *reference_lines,std::list<hdmap::RouteSegments> *segments) {...common::VehicleState vehicle_state;{std::lock_guard<std::mutex> lock(vehicle_state_mutex_);vehicle_state = vehicle_state_;}planning::PlanningCommand command;{std::lock_guard<std::mutex> lock(routing_mutex_);command = planning_command_;}if (nullptr == current_pnc_map_) {AERROR << "Current pnc map is null! " << command.DebugString();return false;}...
}
  • 然后获取当前全局的导航路径。全局导航路径段通过pnc map获取。
bool ReferenceLineProvider::CreateReferenceLine(std::list<ReferenceLine> *reference_lines,std::list<hdmap::RouteSegments> *segments) {...if (!CreateRouteSegments(vehicle_state, segments)) {AERROR << "Failed to create reference line from routing";return false;}...
}
bool ReferenceLineProvider::CreateRouteSegments(const common::VehicleState &vehicle_state,std::list<hdmap::RouteSegments> *segments) {{std::lock_guard<std::mutex> lock(pnc_map_mutex_);if (!current_pnc_map_->GetRouteSegments(vehicle_state, segments)) {AERROR << "Failed to extract segments from routing";return false;}}...
}
  • 最后,平滑生成参考线。

可以看到 ReferenceLine 是通过遍历segments 来进行生成的,所以segment 的数量以及位置,便决定了参考线ReferenceLine 的数量与位置。对于每个segments ,通过调用参考线平滑方法SmoothRouteSegment(*iter, &reference_lines->back()) 来生成参考线。ReferenceLineProvider::SmoothRouteSegment()的具体实现将在第4节详细讲解。

bool ReferenceLineProvider::CreateReferenceLine(std::list<ReferenceLine> *reference_lines,std::list<hdmap::RouteSegments> *segments) {...// 判断是否需要进行参考线拼接if (is_new_command_ || !FLAGS_enable_reference_line_stitching) { // 首次运行或者新路由,不拼接参考线for (auto iter = segments->begin(); iter != segments->end();) {reference_lines->emplace_back();// 参考线平滑// 平滑各路由片段列表segments,并将平滑后的路由片段存储到参考线列表reference_lines中,同时将不能平滑的路由片段从segments中删除;if (!SmoothRouteSegment(*iter, &reference_lines->back())) {AERROR << "Failed to create reference line from route segments";reference_lines->pop_back();iter = segments->erase(iter);} else {common::SLPoint sl;if (!reference_lines->back().XYToSL(vehicle_state.heading(), // 计算自车坐标在新参考线上的投影点,并转换成frenet坐标common::math::Vec2d(vehicle_state.x(), vehicle_state.y()), &sl)) {AWARN << "Failed to project point: {" << vehicle_state.x() << ","<< vehicle_state.y() << "} to stitched reference line";}Shrink(sl, &reference_lines->back(), &(*iter)); // 收缩参考线++iter;}}is_new_command_ = false;return true;} else {  // stitching reference linefor (auto iter = segments->begin(); iter != segments->end();) {reference_lines->emplace_back();// 合并不同参考线片段的重合部分,并将拼接后的路由片段保存到参考线列表reference_lines中,同时将不能拼接的路由片段从列表segments中删除。if (!ExtendReferenceLine(vehicle_state, &(*iter),&reference_lines->back())) {AERROR << "Failed to extend reference line";reference_lines->pop_back();iter = segments->erase(iter);} else {++iter;}}}...
}

这里仿真一个变道的场景,通过断点调试,segments是std::listhdmap::RouteSegments *类型,我们对其解引用然后调用.size()函数,可以看到是2,也就是有2段segments,也就是会生成两条参考线。
在这里插入图片描述

对于非变道场景,只有1段segments,也就是会生成一条参考线。
在这里插入图片描述

3.2 更新参考线

ReferenceLineProvider::UpdateReferenceLine()主要的功能很简单,其实就是将最新计算好的参考线拷贝给成员变量,主线程周期更新参考线的时候就是从成员变量取值。

void ReferenceLineProvider::UpdateReferenceLine(const std::list<ReferenceLine> &reference_lines,const std::list<hdmap::RouteSegments> &route_segments) {if (reference_lines.size() != route_segments.size()) {AERROR << "The calculated reference line size(" << reference_lines.size()<< ") and route_segments size(" << route_segments.size()<< ") are different";return;}if (reference_lines.empty()) {return;}std::lock_guard<std::mutex> lock(reference_lines_mutex_);// 将最新计算好的参考线拷贝给成员变量if (reference_lines_.size() != reference_lines.size()) {reference_lines_ = reference_lines;route_segments_ = route_segments;} else {auto segment_iter = route_segments.begin();auto internal_iter = reference_lines_.begin();auto internal_segment_iter = route_segments_.begin();for (auto iter = reference_lines.begin();iter != reference_lines.end() &&segment_iter != route_segments.end() &&internal_iter != reference_lines_.end() &&internal_segment_iter != route_segments_.end();++iter, ++segment_iter, ++internal_iter, ++internal_segment_iter) {if (iter->reference_points().empty()) {*internal_iter = *iter;            // 将 iter 所指向的数据复制到 internal_iter 所指向的位置*internal_segment_iter = *segment_iter;continue;}if (common::util::SamePointXY(iter->reference_points().front(),internal_iter->reference_points().front()) &&common::util::SamePointXY(iter->reference_points().back(),internal_iter->reference_points().back()) &&std::fabs(iter->Length() - internal_iter->Length()) <common::math::kMathEpsilon) {continue;}*internal_iter = *iter;*internal_segment_iter = *segment_iter;}}// update historyreference_line_history_.push(reference_lines_);route_segments_history_.push(route_segments_);static constexpr int kMaxHistoryNum = 3;if (reference_line_history_.size() > kMaxHistoryNum) {reference_line_history_.pop();route_segments_history_.pop();}
}

4. 参考线平滑

在Apollo的路径规划中,其采用的是基于Frenet坐标系下的规划算法,因此非常依赖于道路中心线的平滑性。而实际工程中,高精地图给出的道路中心线的平滑性往往都不能满足规划模块的需求,因此规划中是不能拿来直接用的,需要对其进行平滑操作。

上面我们提到了有多少段segments就会有多少条参考线,就会调用多次ReferenceLineProvider::SmoothRouteSegment()。实际上参考下平滑是由完成的。bool ReferenceLineProvider::SmoothReferenceLine()。主要包含两步:

  • 设置中间点anchor points
  • 平滑函数平滑
    在这里插入图片描述

4.1 设置中间点anchor points

  • 首先第一步就是根据原始的reference_line来选取中间点,其实这一步就是根据reference_line的s值进行采样,采样的间隔为0.25m。
void ReferenceLineProvider::GetAnchorPoints(const ReferenceLine &reference_line,std::vector<AnchorPoint> *anchor_points) const {...// interval为采样间隔,默认max_constraint_interval=0.25,即路径累积距离每0.25m采样一个点。const double interval = smoother_config_.max_constraint_interval();// 路径采样点数量计算int num_of_anchors = std::max(2, static_cast<int>(reference_line.Length() / interval + 0.5));// uniform_slice函数就是对[0, len]区间等间隔采样,每两个点之间距离为(length_-0.0)/(num_of_anchors - 1)std::vector<double> anchor_s;common::util::uniform_slice(0.0, reference_line.Length(), num_of_anchors - 1, &anchor_s);...
}

采样完毕后,就能得到一系列的anchor_s。说白了就是对s进行离散。
在这里插入图片描述

  • 然后通过等间距的s得到一系列的anchor_point。其中需要根据自车宽度,车道边界类型(是否为curb)等对横向裕度进行收缩。AnchorPoint结构中的enforced变量表示该点是否为强约束,在参考线平滑中只有首尾两点为强约束。
void ReferenceLineProvider::GetAnchorPoints(const ReferenceLine &reference_line,std::vector<AnchorPoint> *anchor_points) const {...// 计算采样点的坐标(x,y),并进行矫正for (const double s : anchor_s) {AnchorPoint anchor = GetAnchorPoint(reference_line, s);anchor_points->emplace_back(anchor);}...
}

每个anchor_point就包含了一个path_point以及横纵向的裕度。
在这里插入图片描述

横纵向裕度的配置参数为:
在这里插入图片描述

这里只对首尾两个点设置为强约束,也就是必须遵守横纵向裕度,其他点不设置。

裕度:采样时,这个点周围可以去平移的空间

void ReferenceLineProvider::GetAnchorPoints(const ReferenceLine &reference_line,std::vector<AnchorPoint> *anchor_points) const {...// 参考线首尾两点设置强约束anchor_points->front().longitudinal_bound = 1e-6;anchor_points->front().lateral_bound = 1e-6;anchor_points->front().enforced = true;anchor_points->back().longitudinal_bound = 1e-6;anchor_points->back().lateral_bound = 1e-6;anchor_points->back().enforced = true;...
}
  • 最后将计算完的anchor points赋值到smoother对象中
bool ReferenceLineProvider::SmoothReferenceLine(const ReferenceLine &raw_reference_line, ReferenceLine *reference_line) {...smoother_->SetAnchorPoints(anchor_points);...
}

4.2 平滑算法平滑

设置好中间点后,接下来就是根据配置文件配置的平滑算法对参考线进行平滑。
smoother_->Smooth(raw_reference_line, reference_line)
目前Apollo中共有三种参考线平滑算法,分别为:

  • QPSpline Smoother 五次多项式平滑
  • SpiralReferenceLine Smoother 五次螺旋曲线平滑
  • DiscretePoints Smoother 离散点平滑
    • CosThetaSmooth
    • FemPosSmooth
      • SqpWithOsqp 考虑参考线的曲率约束,其优化问题是非线性,进行线性化后使用osqp求解器
      • NlpWithIpopt 考虑参考线的曲率约束,其优化问题是非线性,ipopt非线性求解器求解(内点法)
      • QpWithOsqp 不考虑曲率约束(默认),直接用osqp求解二次规划问题

在这里插入图片描述

Apollo默认采用QpWithOsqp离散点平滑算法,受篇幅限制,详见下一篇参考线平滑算法(Apollo 9.0 参考线平滑算法 – Discrete Points ReferenceLine Smoother)

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

游戏引擎学习第103天

仓库:https://gitee.com/mrxiao_com/2d_game_2 回顾bug 接下来回顾一下这个bug的具体情况。当前是一个调试视图&#xff0c;我们并不是直接在调试视图下工作&#xff0c;而是在进行相关的调试。展示了地图&#xff0c;这里是环境贴图&#xff0c;上面是正在使用的环境贴图&am…

论文学习记录之《CLR-VMB》

目录 一、基本介绍 二、介绍 三、方法 3.1 FWI中的数据驱动方法 3.2 CLR-VMB理论 3.3 注意力块 四、网络结构 4.1 网络架构 4.2 损失函数 五、实验 5.1 数据准备 5.2 实验设置 5.3 训练和测试 5.4 定量分析 5.5 CLR方案的有效性 5.6 鲁棒性 5.7 泛化性 六、讨…

USART串口协议

USART串口协议 文章目录 USART串口协议1. 通信接口2.串口通信2.1硬件电路2.2电平标准2.3串口参数及时序&#xff08;软件部分&#xff09; 3.USART串口外设3.1串口外设3.2USART框图3.3USART基本结构3.4数据帧 4.输入电路4.1起始位侦测4.2数据采样 5.波特率发生器6.相关函数介绍…

【线性代数】1行列式

1. 行列式的概念 行列式的符号表示: 行列式的计算结果:一个数 计算模型1:二阶行列式 二阶行列式: 三阶行列式: n阶行列式: 🍎计算行列式 计算模型2:上三角形行列式 上三角形行列式特征:主对角线下皆为0。 上三角形行列式: 化上三角形通用方法:主对角线下,…

vite让每个scss文件自动导入某段内容

写了如下一个scss函数&#xff0c;希望自动导入到每个scss文件里面 vite.config.ts里面如下配置 import fs from fsconst filePath resolve(__dirname, ./src/assets/css/index.scss);const Minxcss fs.readFileSync(filePath, utf8); css: {preprocessorOptions: {scss: {…

【广州大学主办,发表有保障 | IEEE出版,稳定EI检索,往届见刊后快至1个月检索】第二届电气技术与自动化工程国际学术会议 (ETAE 2025)

第二届电气技术与自动化工程国际学术会议 (ETAE 2025) The 2nd International Conference on Electrical Technology and Automation Engineering 大会官网&#xff1a;http://www.icetae.com/【更多详情】 会议时间&#xff1a;2025年4月25-27日 会议地点&#xff1a…

Java面试第一山!《集合》!

一、引言 在 Java 编程的世界里&#xff0c;数据的存储和处理是非常重要的环节。Java 集合框架就像是一个功能强大的工具箱&#xff0c;为我们提供了各种各样的数据结构来高效地存储和操作数据。今天&#xff0c;跟随小编一起来深入了解 Java 集合框架&#xff0c;这不仅有助于…

APP端弱网模拟与网络测试:如何确保应用在各种网络环境下稳定运行

随着智能手机的普及&#xff0c;APP的网络性能成为用户体验的关键因素之一。尤其是在弱网环境下&#xff0c;应用的表现可能严重影响用户的满意度。因此&#xff0c;APP端的网络测试&#xff0c;尤其是弱网模拟&#xff0c;成为了提升产品质量和用户体验的重要环节。 当前APP网…

使用verilog 实现 cordic 算法 ----- 旋转模式

1-设计流程 ● 了解cordic 算法原理&#xff0c;公式&#xff0c;模式&#xff0c;伸缩因子&#xff0c;旋转方向等&#xff0c;推荐以下链接视频了解 cordic 算法。哔哩哔哩-cordic算法原理讲解 ● 用matlab 或者 c 实现一遍算法 ● 在FPGA中用 verilog 实现&#xff0c;注意…

【Linux】Socket编程—TCP

&#x1f525; 个人主页&#xff1a;大耳朵土土垚 &#x1f525; 所属专栏&#xff1a;Linux系统编程 这里将会不定期更新有关Linux的内容&#xff0c;欢迎大家点赞&#xff0c;收藏&#xff0c;评论&#x1f973;&#x1f973;&#x1f389;&#x1f389;&#x1f389; 文章目…

分布式技术

一、为什么需要分布式技术&#xff1f; 1. 科学技术的发展推动下 应用和系统架构的变迁&#xff1a;单机单一架构迈向多机分布式架构 2. 数据大爆炸&#xff0c;海量数据处理场景面临问题 二、分布式系统概述 三、分布式、集群 四、负载均衡、故障转移、伸缩性 负载均衡&a…

python后端调用Deep Seek API

python后端调用Deep Seek API 需要依次下载 ●Ollama ●Deepseek R1 LLM模型 ●嵌入模型nomic-embed-text / bge-m3 ●AnythingLLM 参考教程&#xff1a; Deepseek R1打造本地化RAG知识库:安装部署使用详细教程 手把手教你&#xff1a;deepseek R1基于 AnythingLLM API 调用本地…

优选驾考小程序

第2章 系统分析 2.1系统使用相关技术分析 2.1.1Java语言介绍 Java语言是一种分布式的简单的 开发语言&#xff0c;有很好的特征&#xff0c;在安全方面、性能方面等。非常适合在Internet环境中使用&#xff0c;也是目前企业级运用中最常用的一个编程语言&#xff0c;具有很大…

02、QLExpress从入门到放弃,相关API和文档

QLExpress从入门到放弃,相关API和文档 一、属性开关 public class ExpressRunner {private boolean isTrace;private boolean isShortCircuit;private boolean isPrecise; }/*** 是否需要高精度计算*/ private boolean isPrecise false;高精度计算在会计财务中非常重要&…

达梦:TPCC 压测

目录 造数1. 脚本启动2. 检查数据库信息3. 删除旧用户和表空间4. 创建新的表空间5. 创建用户和表6. 数据加载7. 创建索引8. 创建存储过程和序列9. 检查数据空间使用情况10. 启用表的快速访问池11. 数据加载完成总结 压测1. 脚本启动2. 检查数据表空间3. 设置表的快速池标志4. 检…

【ClickHouse】Ubuntu下离线安装ClickHouse数据库并使用DBeaver连接

目录 0. 安装前准备1 安装ClickHouse1.1 下载安装包1.2 离线安装1.3 配置密码1.4 启动ClickHouse服务 2 DBeaver连接配置2.1 下载ClickHouse驱动2.2 DBeaver配置2.2.1 配置主要参数2.2.2 配置驱动 2.3 常见问题处理2.3.1 修改远程登录配置2.3.2 更新驱动配置 0. 安装前准备 有…

CCF-GESP 等级考试 2024年9月认证C++二级真题解析

2024年9月真题 一、单选题&#xff08;每题2分&#xff0c;共30分&#xff09; 正确答案&#xff1a;A 考察知识点&#xff1a;计算机存储 解析&#xff1a;磁心存储元件是早期计算机中用于存储数据的部件&#xff0c;它和现代计算机中的内存功能类似&#xff0c;都是用于临时…

nuxt中引入element-ui组件控制台报错问题

在使用element-ui组件的外层加一层 <client-only placeholder"Loading..."><van-button type"primary">主要按钮</van-button> </client-only> 实际使用&#xff1a; <div class"tab"><client-only placehol…

京东 旋转验证码 分析

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 逆向分析 使用的第三方接码平台识别…

Git 查看修改记录 二

Git 查看修改记录 二 续接 Git 查看一个文件的修改记录 一 一、修改 A.txt 修改 A.txt number6执行命令 git add . git commit -a -m "修改 number6" # git commit -a -m "修改 number6" 执行 输出如下 # $ git commit -a -m "修改 number6"…