之前介绍了四种构建位姿的方式:
节点 通过 GetLocalToGlobalTransform * constant_data->local_pose 进行global下位姿的计算
子图 通过对前一个子图到后一个子图的坐标变换进行累计, 得到子图在global坐标系下的位姿
子图内约束 local坐标系系下, 子图原点指向节点间的坐标变换
子图间约束 根据global坐标计算初值, 然后通过分支定界算法粗匹配与ceres的精匹配, 获取校准后的位姿, 最后计算local坐标系系下, 子图原点指向校准后的节点间的坐标变换
下面构建残差,通过构建2D优化问题
1 2 3 4 5 6 7 8 9 10 11 12 13 void OptimizationProblem2D::Solve ( const std::vector<Constraint>& constraints, const std::map<int , PoseGraphInterface::TrajectoryState>& trajectories_state, const std::map<std::string, LandmarkNode>& landmark_nodes) {}
调用处 于pose_graph_2d.cc
文件中的函数RunOptimization
1 2 3 4 5 6 7 8 optimization_problem_->Solve (data_.constraints, GetTrajectoryStates (), data_.landmark_nodes);
Solve具体实现 他需要传入 约束,轨迹状态和landmark,注意landmark是存在PoseGraph2D中的,而不是保存在优化问题类中的
冻结的轨迹就是被不会被优化的了,第一个子图也不会被改变。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void OptimizationProblem2D::Solve ( const std::vector<Constraint>& constraints, const std::map<int , PoseGraphInterface::TrajectoryState>& trajectories_state, const std::map<std::string, LandmarkNode>& landmark_nodes) { if (node_data_.empty ()) { return ; } std::set<int > frozen_trajectories; for (const auto & it : trajectories_state) { if (it.second == PoseGraphInterface::TrajectoryState::FROZEN) { frozen_trajectories.insert (it.first); } }
创建ceres 优化问题
1 2 3 ceres::Problem::Options problem_options; ceres::Problem problem (problem_options) ;
把需要优化的变量,即submap,node,landmark的数据放入以下变量中
注意landmark的数据结构是CeresPose, 主要是用于更新其姿态而建立
1 2 3 4 5 6 7 MapById<SubmapId, std::array<double , 3>> C_submaps; MapById<NodeId, std::array<double , 3>> C_nodes; std::map<std::string, CeresPose> C_landmarks; bool first_submap = true ;
将需要优化的子图位姿设置为优化参数 把子图的global_pose 放入C_submaps,这里插入的子图的位姿为(x, y, theta)
1 2 3 4 5 6 7 8 for (const auto & submap_id_data : submap_data_) { const bool frozen = frozen_trajectories.count (submap_id_data.id.trajectory_id) != 0 ; C_submaps.Insert (submap_id_data.id, FromPose (submap_id_data.data.global_pose));
添加需要优化的参数
1 2 3 problem.AddParameterBlock (C_submaps.at (submap_id_data.id).data (), 3 );
Step: 如果是第一幅子图, 或者是已经冻结的轨迹中的子图, 不优化这个子图位姿
1 2 3 4 5 6 7 if (first_submap || frozen) { first_submap = false ; problem.SetParameterBlockConstant (C_submaps.at (submap_id_data.id).data ()); }
将需要优化的节点位姿设置为优化参数 逻辑和需要优化的子图位姿一样
1 2 3 4 5 6 7 8 9 10 11 12 for (const auto & node_id_data : node_data_) { const bool frozen = frozen_trajectories.count (node_id_data.id.trajectory_id) != 0 ; C_nodes.Insert (node_id_data.id, FromPose (node_id_data.data.global_pose_2d)); problem.AddParameterBlock (C_nodes.at (node_id_data.id).data (), 3 ); if (frozen) { problem.SetParameterBlockConstant (C_nodes.at (node_id_data.id).data ()); } }
第一种残差 添加第一种残差块,参数
cost_function: CreateAutoDiffSpaCostFunction(constraint.pose),
核函数:
优化变量:submap
优化变量:node
1 2 3 4 5 6 7 8 9 10 11 12 13 14 for (const Constraint& constraint : constraints) { problem.AddResidualBlock ( CreateAutoDiffSpaCostFunction (constraint.pose), constraint.tag == Constraint::INTER_SUBMAP ? new ceres::HuberLoss (options_.huber_scale ()) : nullptr , C_submaps.at (constraint.submap_id).data (), C_nodes.at (constraint.node_id).data ()); }
残差函数 1 2 3 4 5 6 7 8 ceres::CostFunction* CreateAutoDiffSpaCostFunction ( const PoseGraphInterface::Constraint::Pose& observed_relative_pose) { return new ceres::AutoDiffCostFunction<SpaCostFunction2D, 3 , 3 , 3 >( new SpaCostFunction2D (observed_relative_pose)); }
返回的是SpaCostFunction2D的构造函数
SpaCostFunction2D 构造函数存储了observedrelative_pose 的值,在bool operator()
中计算了残差,并且给e
赋值。
observedrelative_pose 是constraint.pose。是所有子图内和子图间的约束
无论是子图间约束还是子图内约束,都是通过local坐标系进行计算的,如node在local坐标系下的位姿的逆乘以submap在local坐标系下的逆
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class SpaCostFunction2D { public : explicit SpaCostFunction2D ( const PoseGraphInterface::Constraint::Pose& observed_relative_pose) : observed_relative_pose_(observed_relative_pose) { } template <typename T> bool operator () (const T* const start_pose, const T* const end_pose, T* e) const { const std::array<T, 3> error = ScaleError (ComputeUnscaledError ( transform::Project2D (observed_relative_pose_.zbar_ij), start_pose, end_pose), observed_relative_pose_.translation_weight, observed_relative_pose_.rotation_weight); std::copy (std::begin (error), std::end (error), e); return true ; } private : const PoseGraphInterface::Constraint::Pose observed_relative_pose_; };
计算残差 通过start和end来计算两个实际点的相对偏移,因为relative_pose为 end在start上的位姿
所以求他们相对偏差$h(c_s,c_e)$的时候应该求该偏差在start上的位姿,其中 $c_s,c_e$分别为start和end在global上的位姿。
偏差为
最后直接相减
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 template <typename T>static std::array<T, 3> ComputeUnscaledError( const transform::Rigid2d& relative_pose, const T* const start, const T* const end) { const T cos_theta_i = cos (start[2 ]); const T sin_theta_i = sin (start[2 ]); const T delta_x = end[0 ] - start[0 ]; const T delta_y = end[1 ] - start[1 ]; const T h[3 ] = {cos_theta_i * delta_x + sin_theta_i * delta_y, -sin_theta_i * delta_x + cos_theta_i * delta_y, end[2 ] - start[2 ]}; return {{T (relative_pose.translation ().x ()) - h[0 ], T (relative_pose.translation ().y ()) - h[1 ], common::NormalizeAngleDifference ( T (relative_pose.rotation ().angle ()) - h[2 ])}}; }
将节点与子图原点在global坐标系下的相对位姿 与 约束 的差值作为残差项