多种形式ICP问题的ceres实例应用

一家之言,仅作分享,如有不合理或需要改进的地方,欢迎各位讨论。
ICP方法主要解决空间点云3D-3D的运动估计问题,
已知: t 1 t_1 t1 t 2 t_2 t2时刻感知到的两个点云信息(基于传感器坐标系),和 t 1 t_1 t1时刻的传感器位于全局坐标系下的坐标 p 1 p_1 p1
求解:传感器 t 2 t_2 t2时刻相对 t 1 t_1 t1时刻的位姿变化。
通常,在点云配准过程中,将 t 2 t_2 t2时刻的感知点云称为source,将 t 1 t_1 t1时刻的感知点云称为target。
解决ICP问题的基本步骤如下:

  1. 确定 t 1 t_1 t1 t 2 t_2 t2时刻之间的传感器位姿变化初值 R 0 R_0 R0 t 0 t_0 t0,实车案例中取imu与轮速计融合计算的dr值。
  2. t 2 t_2 t2时刻的点云数据,即source点云,利用位姿变化初值坐标转换至 t 1 t_1 t1时刻target点云的坐标系下。
  3. 将target点云构建为KDtree,坐标转换后的source点云在KDtree t a r g e t _{target} target中找到最近点,完成数据关联。
  4. 构建代价函数,进行R、t优化。代价函数有多种形式,点对点的欧氏距离,点对线的垂直距离以及矢量的欧氏距离和夹角,最后一种形式在利用库位检测信息时将每个库位简化为一个矢量点进行优化。
  5. 迭代优化。将第4步优化后的R、t作为第1步的位姿变换值,重新第2、3、4步,直到前后两次迭代的R、t变化值满足要求。

利用KDtree解决数据关联问题,在另一个博客中已经介绍
KDtree相关库nanoflann的应用

以下代码为利用ceres解决优化问题的示例:

void ScanAlign::Align(LocalizedRangeScan* pSourceScan, LocalizedRangeScan* pTargetScan,
                      Pose2 &rBestpose, Eigen::Vector3d& rDelta)
{
    SetSource(pSourceScan);
    SetTarget(pTargetScan);
    // construct posetransform from  src to current.
    Eigen::Vector3d ypr_w_src(pSourceScan->GetPredictPose().GetYaw_Rad(),
                              0,
                              0);
    Eigen::Vector3d t_w_src(pSourceScan->GetPredictPose().GetX(),
                            pSourceScan->GetPredictPose().GetY(),
                            0);
    common::PoseTransformation pose_w_src(ypr_w_src, t_w_src);

    // construct posetransform from  target to current.
    Eigen::Vector3d ypr_w_target(pTargetScan->GetCorrectedPose().GetYaw_Rad(),
                                 0,
                                 0);
    Eigen::Vector3d t_w_target(pTargetScan->GetCorrectedPose().GetX(),
                               pTargetScan->GetCorrectedPose().GetY(),
                               0);
    common::PoseTransformation pose_w_target(ypr_w_target, t_w_target);

    // src frame relatived to target frame. raw state
    current_transform_ = pose_w_src.GetTransform(pose_w_target);
    last_transform_ = current_transform_;

    for (size_t opti_counter = 0; opti_counter < 10; ++opti_counter)
    {
        // 数据关联:统一在target坐标系下,确定source中每个点在target中的最近点
        Association();

        // Build The Problem
        ceres::Problem problem;
        ceres::Solver::Options options;
        options.linear_solver_type = ceres::SPARSE_NORMAL_CHOLESKY;
        options.minimizer_progress_to_stdout = false;
        //options.max_solver_time_in_seconds = SOLVER_TIME * 3;
        options.max_num_iterations = 100;
        ceres::Solver::Summary summary;
        ceres::LossFunction *loss_function = new ceres::HuberLoss(0.1);
        // 二维问题解决过参数化问题时,由于只使用 yaw角,故使用AngleLocalParameterization;
        // 三维问题使用四元数时,则使用 QuaternionParameterization
        ceres::LocalParameterization* angle_local_parameterization =  ceres::slam2d::AngleLocalParameterization::Create();

        //Add parameter blocks
        problem.AddParameterBlock(current_transform_.ypr_.data(), 1, angle_local_parameterization);
        problem.AddParameterBlock(current_transform_.t_.data(), 2);

        double *t_array     = current_transform_.t_.data();
        double *euler_array = current_transform_.ypr_.data();

        for(size_t src_index=0; src_index<m_vAssociation.size(); src_index++)
        {
            int target_index = m_vAssociation.at(src_index).first;
            if(target_index == -1)  //no match is found for this source point
                continue;

            CloudPoint src_pt    = m_stCloudPoints_src.at(src_index);
            CloudPoint target_pt = m_stCloudPoints_target.at(target_index);

            if(src_pt.type == 1)  // parkingsite
            {
                //point to point constraint
                Eigen::DiagonalMatrix<double, 2> cov(0.1, 0.1);
                Eigen::Matrix2d  sqrt_information = cov;
                sqrt_information = sqrt_information.inverse().llt().matrixL();
                double weight = 1.0;
                ceres::CostFunction* cost_function = ceres::slam2d::PP2dErrorTerm::Create(src_pt.x, src_pt.y, target_pt.x, target_pt.y,
                                                                                          sqrt_information, weight);
                problem.AddResidualBlock(cost_function, loss_function, euler_array, t_array);

            }
            else if(src_pt.type == 2)  // laneline
            {
                //point to line constraint
                Eigen::Matrix<double, 1, 1>  sqrt_information;
                sqrt_information(0, 0) = std::sqrt(1/0.1);
                double weight = 1.0;
                ceres::CostFunction* cost_function = ceres::slam2d::PL2dErrorTerm::Create(src_pt.x, src_pt.y,
                                                                                          target_pt.x, target_pt.y,
                                                                                          target_pt.norm_x, target_pt.norm_y,
                                                                                          sqrt_information, weight);
                problem.AddResidualBlock(cost_function, loss_function, euler_array, t_array);
            }

        } // end add ResidualBlock

        //Solve the problem
        ceres::Solve(options, &problem, &summary);

        //update current transformation, ypr and t are updated by optimizer
        current_transform_.UpdateFromEulerAngle_Translation();


        //check if we have converged
        common::PoseTransformation estimation_change = current_transform_.GetTransform(last_transform_);

        last_transform_ = current_transform_;
        if(estimation_change.t_.norm() < 1e-5 && estimation_change.ypr_.norm()< 1e-5){
            break;
        }
    } //end iteration (association and optimization cycles)

    //we need to update the poseinfo object as well
    pose_w_src = pose_w_target*current_transform_;
    rBestpose.SetX(pose_w_src.t_[0]);
    rBestpose.SetY(pose_w_src.t_[1]);
    rBestpose.SetYaw_Rad(pose_w_src.ypr_[0]);

    rDelta[0] = current_transform_.t_[0];   // delta x
    rDelta[1] = current_transform_.t_[1];   // delta y
    rDelta[2] = current_transform_.ypr_[0]; // delta yaw

    // cout icp result
//    std::cout<<"icp_target_src :  x = "<<current_transform_.t_[0]
//            <<" y = "<<current_transform_.t_[1]
//           <<" z = "<<current_transform_.t_[2]
//          <<" yaw = "<<current_transform_.ypr_[0]
//         <<" pitch = "<<current_transform_.ypr_[1]
//        <<" roll = "<<current_transform_.ypr_[2]<<std::endl<<std::endl;

}

针对不同的代价计算方式,我写了三种costfunction的仿函数。
第一种是点到点的欧式距离,也是最基本的icp形式

// 点到点
struct PP2dErrorTerm
{
public:

    PP2dErrorTerm(double obs_src_x, double obs_src_y, double obs_target_x, double obs_target_y,
                  Eigen::Matrix2d sqrt_information, double weight=1.0)
        :obs_src_x_(obs_src_x), obs_src_y_(obs_src_y), obs_target_x_(obs_target_x),obs_target_y_(obs_target_y),
          sqrt_information_(sqrt_information),weight_(weight){}

    template <typename T>
    bool operator()(const T* yaw, const T* t_param, T* residuals) const
    {
        const Eigen::Matrix<T, 2, 1> t(t_param[0], t_param[1]);
        const Eigen::Matrix<T, 2, 2> R = ceres::slam2d::RotationMatrix2D(*yaw);

        const Eigen::Vector2d obs_src_t(obs_src_x_, obs_src_y_);
        const Eigen::Vector2d obs_target_t(obs_target_x_, obs_target_y_);


        Eigen::Map<Eigen::Matrix<T, 2, 1> > residuals_map(residuals);

        residuals_map.template head<2>() = (R * obs_src_t + t - obs_target_t.cast<T>());

        //weight these constraints
        residuals_map = residuals_map * weight_;
        // Scale the residuals by the square root information matrix to account for
        // the measurement uncertainty.
        residuals_map = sqrt_information_.template cast<T>() * residuals_map;

        return true;
    }

    static ceres::CostFunction* Create(double obs_src_x, double obs_src_y, double obs_target_x, double obs_target_y,
                                       Eigen::Matrix2d sqrt_information, double weight=1.0)
    {
        return (new ceres::AutoDiffCostFunction<PP2dErrorTerm, 2, 1, 2>(
                    new PP2dErrorTerm(obs_src_x, obs_src_y, obs_target_x, obs_target_y, sqrt_information, weight)));
    }

    double obs_src_x_, obs_src_y_;
    double obs_target_x_, obs_target_y_;
    Eigen::Matrix2d  sqrt_information_;
    double weight_;
};

第二种是点到线的垂直距离,我的计算方式是先求出 target点云中每个点的法向量,然后数据关联后,利用法向量快速求解点到线的距离。其中利用了PCA主成分分析方法计算法向量,在后续博客会更新
PCA主成分分析求解二维点法向量

// 点到线
struct PL2dErrorTerm
{
public:

    PL2dErrorTerm(double obs_src_x, double obs_src_y, double obs_target_x, double obs_target_y,
                  double obs_target_norm_x, double obs_target_norm_y,
                  Eigen::Matrix<double, 1, 1> sqrt_information, double weight=1.0)
        :obs_src_x_(obs_src_x), obs_src_y_(obs_src_y), obs_target_x_(obs_target_x),obs_target_y_(obs_target_y),
          obs_target_norm_x_(obs_target_norm_x),obs_target_norm_y_(obs_target_norm_y),
          sqrt_information_(sqrt_information),weight_(weight){}

    template <typename T>
    bool operator()(const T* const yaw, const T* t_param, T* residuals) const
    {
        const Eigen::Matrix<T, 2, 1> t(t_param[0], t_param[1]);
        const Eigen::Matrix<T, 2, 2> R = ceres::slam2d::RotationMatrix2D(*yaw);

        const Eigen::Vector2d obs_src_t(obs_src_x_, obs_src_y_);
        const Eigen::Vector2d obs_target_t(obs_target_x_, obs_target_y_);
        const Eigen::Vector2d obs_target_norm(obs_target_norm_x_, obs_target_norm_y_);



        residuals[0] = (R * obs_src_t + t - obs_target_t ).dot(obs_target_norm);

        //weight these constraints
        residuals[0] = residuals[0] * weight_;
        // Scale the residuals by the square root information matrix to account for
        // the measurement uncertainty.
        residuals[0] = sqrt_information_(0, 0) * residuals[0];

        return true;
    }

    static ceres::CostFunction* Create(double obs_src_x, double obs_src_y, double obs_target_x, double obs_target_y,
                                       double obs_target_norm_x, double obs_target_norm_y,
                                       Eigen::Matrix<double, 1, 1> sqrt_information, double weight=1.0)
    {
        return (new ceres::AutoDiffCostFunction<
                PL2dErrorTerm, 1, 1, 2>(
                    new PL2dErrorTerm(obs_src_x, obs_src_y, obs_target_x, obs_target_y,
                                      obs_target_norm_x, obs_target_norm_y,
                                      sqrt_information, weight)));
    }

    double obs_src_x_, obs_src_y_;
    double obs_target_x_, obs_target_y_;
    double obs_target_norm_x_, obs_target_norm_y_;
    Eigen::Matrix<double, 1, 1>  sqrt_information_;
    double weight_;
};

第三种是矢量点的欧式距离与夹角,这是在实际应用中,将库位信息简化为一个带方向的二维点,以此进行更准确快速的库位关联

// 矢量形式的点到点
struct Vector2dErrorTerm
{
public:
    Vector2dErrorTerm(double obs_src_x, double obs_src_y, double obs_src_yaw,
                      double obs_target_x, double obs_target_y,double obs_target_yaw,
                      Eigen::Matrix3d sqrt_information, double weight=1.0)
        :obs_src_x_(obs_src_x), obs_src_y_(obs_src_y), obs_src_yaw_(obs_src_yaw),
          obs_target_x_(obs_target_x),obs_target_y_(obs_target_y),obs_target_yaw_(obs_target_yaw),
          sqrt_information_(sqrt_information),weight_(weight){}

    template <typename T>
    bool operator()(const T* const yaw, const T* t_param, T* residuals) const
    {
        const Eigen::Matrix<T, 2, 1> t(t_param[0], t_param[1]);
        const Eigen::Matrix<T, 2, 2> R = RotationMatrix2D(*yaw);

        const Eigen::Vector2d obs_src_t(obs_src_x_, obs_src_y_);
        const Eigen::Vector2d obs_target_t(obs_target_x_, obs_target_y_);
        const Eigen::Matrix<T, 2, 2> obs_src_R = RotationMatrix2D(static_cast<T>(obs_src_yaw_));


        Eigen::Map<Eigen::Matrix<T, 3, 1> > residuals_map(residuals);

        residuals_map.template head<2>() = (R * obs_src_t + t - obs_target_t.cast<T>());

        const Eigen::Matrix<T, 2, 2> expected_target_R = R * obs_src_R;
        residuals_map(2) = ceres::slam2d::NormalizeAngle(RotationMatrixToYaw(expected_target_R) - static_cast<T>(obs_target_yaw_));

        //weight these constraints
        residuals_map = residuals_map * weight_;
        // Scale the residuals by the square root information matrix to account for
        // the measurement uncertainty.
        residuals_map = sqrt_information_.template cast<T>() * residuals_map;

        return true;
    }

    static ceres::CostFunction* Create(double obs_src_x, double obs_src_y, double obs_src_yaw,
                                       double obs_target_x, double obs_target_y, double obs_target_yaw,
                                       Eigen::Matrix3d sqrt_information, double weight=1.0)
    {
        return (new ceres::AutoDiffCostFunction<Vector2dErrorTerm, 3, 1, 2>(
                    new Vector2dErrorTerm(obs_src_x, obs_src_y,  obs_src_yaw,
                                          obs_target_x, obs_target_y,obs_target_yaw,
                                          sqrt_information, weight)));
    }

    double obs_src_x_, obs_src_y_, obs_src_yaw_;
    double obs_target_x_, obs_target_y_, obs_target_yaw_;
    Eigen::Matrix3d  sqrt_information_;
    double weight_;
};
  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
下面是一个 Ceres 的完整代码使用实例,它演示了如何用 Ceres 拟合一个二次函数。 ```cpp #include <ceres/ceres.h> #include <iostream> #include <vector> // 定义二次函数残差的 CostFunction struct QuadraticCostFunctor { QuadraticCostFunctor(double x, double y) : x_(x), y_(y) {} template <typename T> bool operator()(const T* const abc, T* residual) const { residual[0] = T(y_) - (abc[0] * T(x_ * x_) + abc[1] * T(x_) + abc[2]); return true; } double x_; double y_; }; int main() { // 生成一些数据点 std::vector<double> x_values = {1, 2, 3, 4, 5}; std::vector<double> y_values = {10, 20, 30, 40, 50}; // 定义要优化的参数 double abc[3] = {1.0, 1.0, 1.0}; // 初始值 // 定义 Problem 对象 ceres::Problem problem; for (int i = 0; i < x_values.size(); ++i) { ceres::CostFunction* cost_function = new ceres::AutoDiffCostFunction<QuadraticCostFunctor, 1, 3>( new QuadraticCostFunctor(x_values[i], y_values[i])); problem.AddResidualBlock(cost_function, nullptr, abc); } // 定义 Solver 对象,并调用 Solve() 方法求解问题 ceres::Solver::Options options; options.max_num_iterations = 100; options.linear_solver_type = ceres::DENSE_QR; ceres::Solver::Summary summary; ceres::Solve(options, &problem, &summary); // 输出优化结果 std::cout << summary.FullReport() << std::endl; std::cout << "a = " << abc[0] << std::endl; std::cout << "b = " << abc[1] << std::endl; std::cout << "c = " << abc[2] << std::endl; return 0; } ``` 在上面的代码中,我们首先定义了一个二次函数残差的 CostFunction,并使用它创建了一个 Problem 对象。然后,我们定义了要优化的参数 $a$、$b$、$c$,并将它们作为参数传递给 AddResidualBlock() 方法,将每个数据点对应的 CostFunction 添加到 Problem 中。最后,我们定义了 Solver 对象,并调用 Solve() 方法求解问题。在 Solve() 方法返回之后,我们从 summary 中获取优化结果和求解的状态信息。 完整的代码可以在 Ceres 的 examples 目录中找到,它们可以帮助你更好地理解 Ceres 的使用方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值