二维ICP配准算法-C++ 高斯牛顿实现

1 原理

1.1 2D的ICP算法的基本原理

算法的基本原理介绍见前文 二维ICP配准算法-C++ 直接法实现

1.2 目标函数

min ⁡ F ( R , T ) = 1 n ∑ i = 1 n ∣ ∣ f ( R , T ) ∣ ∣ 2 f ( R , T ) = P i − ( R ⋅ Q i + T ) \min F(R,T) = \frac{1}{n} \sum_{i=1}^n||f(R,T)||^2 \\ f(R,T) =P_i-(R\cdot Q_i+T) minF(R,T)=n1i=1n∣∣f(R,T)2f(R,T)=Pi(RQi+T)

1.3 Gauss-Newton 雅可比矩阵

高斯牛顿法采用f(R,T)的一阶泰勒展开式代替f(R,T)。因此注意此处的雅可比矩阵应该为f(R, T)的,而非F(R, T)的,如果为F(R, T)的话就变成牛顿法了。
根据如下SE(2)的定义
R = [ c o s ( θ ) − s i n ( θ ) t x s i n ( θ ) c o s ( θ ) t y 0 0 1 ] R=\left[ \begin{matrix} cos(\theta) & -sin(\theta) & t_x \\ sin(\theta) & cos(\theta) & t_y \\ 0 & 0 & 1 \end{matrix} \right] R= cos(θ)sin(θ)0sin(θ)cos(θ)0txty1
f(R,T)可以表达成如下形式
f ( R , T ) = f ( t x , t y , θ ) = [ P i x P i y 1 ] − [ c o s ( θ ) − s i n ( θ ) t x s i n ( θ ) c o s ( θ ) t y 0 0 1 ] [ Q i x Q i y 1 ] = [ P i x − c o s ( θ ) Q i x + s i n ( θ ) Q i y − t x P i y − s i n ( θ ) Q i x − c o s ( θ ) Q i y − t y 1 ] f(R,T)=f(t_x,t_y,\theta)= \left[ \begin{matrix} P_i^x \\ P_i^y \\ 1 \end{matrix} \right] - \left[ \begin{matrix} cos(\theta) & -sin(\theta) & t_x \\ sin(\theta) & cos(\theta) & t_y \\ 0 & 0 & 1 \end{matrix} \right] \left[ \begin{matrix} Q_i^x \\ Q_i^y \\ 1 \end{matrix} \right] \\ =\left[ \begin{matrix} P_i^x - cos(\theta) Q_i^x + sin(\theta)Q_i^y -t_x \\ P_i^y - sin(\theta) Q_i^x - cos(\theta)Q_i^y -t_y \\ 1 \end{matrix} \right] f(R,T)=f(tx,ty,θ)= PixPiy1 cos(θ)sin(θ)0sin(θ)cos(θ)0txty1 QixQiy1 = Pixcos(θ)Qix+sin(θ)QiytxPiysin(θ)Qixcos(θ)Qiyty1
对上述公式求导可得雅可比矩阵
J = [ − 1 0 0 − 1 s i n ( θ ) Q i x + c o s ( θ ) Q i y − c o s ( θ ) Q i x + s i n ( θ ) Q i y ] J=\left[ \begin{matrix} -1 & 0 \\ 0 & -1 \\ sin(\theta) Q_i^x + cos(\theta)Q_i^y & - cos(\theta) Q_i^x + sin(\theta)Q_i^y \end{matrix} \right] J= 10sin(θ)Qix+cos(θ)Qiy01cos(θ)Qix+sin(θ)Qiy

2 C++实现

// icp2d_gn.h
#ifndef ICP2D_GN_H
#define ICP2D_GN_H
#include <opencv2/opencv.hpp>

class ICP2DGN
{
public:
    ICP2DGN() = default;
    ~ICP2DGN() = default;

    double registration(const std::vector<cv::Point2f> &source,
                        const std::vector<cv::Point2f> &target,
                        const int &iter_num, const double &eps);

private:
    // 暴力搜索最近邻
    int bfnn_point_(const std::vector<cv::Point2f> &points,
                    const cv::Point2f &point);
};

#endif
// icp2d_gn.cc
#include "icp2d_gn.h"
#include <Eigen/Dense>

double ICP2DGN::registration(const std::vector<cv::Point2f> &source,
                             const std::vector<cv::Point2f> &target,
                             const int &iter_num, const double &eps)
{
    auto target_ = target;

    double theta = 0.f;
    cv::Point2f T(0.f, 0.f);

    for (int i = 0; i < iter_num; ++i)
    {
        Eigen::Matrix3d H = Eigen::Matrix3d::Zero();
        Eigen::Vector3d g = Eigen::Vector3d::Zero();

        for (int j = 0; j < target_.size(); ++j)
        {
            int nn_idx = bfnn_point_(source, target_[j]);

            Eigen::Matrix<double, 3, 2> J;
            J << -1, 0, 0, -1,
                std::sin(theta) * target_[j].x + std::cos(theta) * target_[j].y,
                std::sin(theta) * target_[j].y - std::cos(theta) * target_[j].x;

            H += J * J.transpose();

            Eigen::Vector2d e(source[nn_idx].x - target_[j].x,
                              source[nn_idx].y - target_[j].y);
            g += -J * e;
        }

        Eigen::Vector3d dx = H.ldlt().solve(g);

        T.x += dx[0];
        T.y += dx[1];
        theta += dx[2];

        // Update target points
        cv::Matx22f R_dx;
        R_dx << std::cos(dx[2]), -std::sin(dx[2]),
            std::sin(dx[2]), std::cos(dx[2]);
        for (int j = 0; j < target_.size(); ++j)
        {
            target_[j] = R_dx * target_[j] + cv::Point2f(dx[0], dx[1]);
        }

        if (std::abs(dx[2] / CV_PI * 180.f) < eps)
        {
            break;
        }

        std::cout << "iter R: " << dx[2] / CV_PI * 180.f << std::endl;
    }

    return theta / CV_PI * 180.f;
}

int ICP2DGN::bfnn_point_(const std::vector<cv::Point2f> &points,
                         const cv::Point2f &point)
{
    return std::min_element(points.begin(), points.end(),
                            [&point](const cv::Point2f &p1, const cv::Point2f &p2)
                            { return cv::norm(p1 - point) < cv::norm(p2 - point); }) -
           points.begin();
}

3 测试

#include <opencv2/opencv.hpp>
#include "icp2d_gn.h"

template <typename FuncT>
void evaluate_and_call(FuncT &&func, int run_num)
{
    double total_time = 0.f;
    for (int i = 0; i < run_num; ++i)
    {
        auto t1 = std::chrono::high_resolution_clock::now();
        func();
        auto t2 = std::chrono::high_resolution_clock::now();
        total_time +=  std::chrono::duration_cast<std::chrono::duration<double>>(t2 - t1).count() * 1000;;
    }

    std::cout << "run cost: " << total_time / 1000.f << std::endl;
}

void test_icp_gn()
{
    ICP2DGN icp;

    std::vector<cv::Point2f> points = {{100, 100}, {200, 200}, {300, 300}};
    cv::Matx22f R;
    double angle = 30.f;
    R << std::cos(angle / 180.f * CV_PI), -std::sin(angle / 180.f * CV_PI),
        std::sin(angle / 180.f * CV_PI), std::cos(angle / 180.f * CV_PI);
    cv::Point2f T(10, 20);

    std::vector<cv::Point2f> points_RT;
    for (int i = 0; i < points.size(); ++i)
    {
        points_RT.push_back(R * points[i] + T);
    }

    double angle_final = icp.registration(points, points_RT, 100, 0.0001);
    std::cout << "angle final: " << angle_final << std::endl;
}

int main()
{
    evaluate_and_call(test_icp_gn, 1);

    return 0;
}

/* Terminal print
iter R: -28.6479
iter R: -1.19412
iter R: -0.137148
iter R: -0.0180555
iter R: -0.00240684
iter R: -0.000322292
angle final: -30
run cost: 0.0013694
*/
  • 本文的收敛条件还是设置为 Δ \Delta ΔR为足够小
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值