3.迭代最近点算法`ICP`及`SVD`求解


欢迎访问个人网络日志🌹🌹知行空间🌹🌹


ICP问题

迭代最近点算法Iterative Closet Points,ICP算法,是根据两组已知的三维空间点来估计采集两组点时传感器位姿发生的旋转和平移变化

在使用相机时,我们可以通过图像的特征匹配,找到两组三维点之间每个点的匹配关系。而在使用激光雷达时,由于点云是稀疏的,但是采集频率很高,通常认为两帧之间离的最近的点就是匹配点,因此称此算法为迭代最近点算法。

使用代数式描述为:

假设我们得到,

t 1 t_1 t1时刻传感器位置一采集到的一组三维空间点 P = { p 1 , . . . , p n } P=\{p_1,...,p_n\} P={p1,...,pn}

t 2 t_2 t2时刻传感器运动到位置二采集到的一组三维空间点 Q = { q 1 , . . . , q n } Q=\{q_1,...,q_n\} Q={q1,...,qn}

这里, P P P Q Q Q中的点是传感器坐标系中的表示,这两组点对应的是世界坐标系 { W } \{W\} {W}下相同的 n n n个点 { w 1 , . . . . , w 2 } \{w_1,....,w_2\} {w1,....,w2},且 p 1 , q 1 p_1,q_1 p1,q1对应 w 1 w_1 w1, p 2 , q 2 p_2,q_2 p2,q2对应 w 2 w_2 w2,依次类推。传感器坐标系是运动坐标系,从位置1到位置2姿态和位置发生的变化分别是 R , t R,t R,t,则可以得到如下关系,

p i = R q i + t , i = 1 , . . . , n p_i=Rq_i+t, i=1,...,n pi=Rqi+t,i=1,...,n

观察上式,我们有 n n n对匹配点就能得到 n n n个方程, R , t R,t R,t是待求量,一般情况下可以得到的匹配点对都会远远多于求解 R , t R,t R,t所需的量,所以得到的方程组都是超定的,只能求解其最小二乘解。

实际为了求解 R , t R,t R,t定义误差项,

对于第 i i i个点

e i = p i − ( R q i + t ) e_i=p_i-(Rq_i+t) ei=pi(Rqi+t)

对于所有 n n n个点,求解 R , t R,t R,t变成如下最优化问题,

min ⁡ R , t 1 2 ∑ i n ∣ ∣ p i − ( R q i + t ) ∣ ∣ 2 2 \min\limits_{R,t}\frac{1}{2}\sum\limits_{i}^n||p_i-(Rq_i+t)||_2^2 R,tmin21in∣∣pi(Rqi+t)22

要求解此最小二乘问题,有两种解法,一种使用SVD分解,一种使用非线性优化算法。

本文中,我们来一起学习SVD方法。

SVD分解来求解ICP

定义两组点的质心为 p c , q c p_c,q_c pc,qc

p c = 1 n ∑ i n p i p_c=\frac{1}{n}\sum\limits_i^np_i pc=n1inpi

q c = 1 n ∑ i n q i q_c=\frac{1}{n}\sum\limits_i^nq_i qc=n1inqi

将质心代入误差函数,

1 2 ∑ i n ∣ ∣ p i − ( R q i + t ) ∣ ∣ 2 2 = 1 2 ∑ i n ∣ ∣ p i − R q i − t − p c + R q c + p c − R q c ∣ ∣ 2 2 = 1 2 ∑ i n ∣ ∣ p i − p c − R ( q i − q c ) + ( p c − R q c − t ) ∣ ∣ 2 2 = 1 2 ∑ i n ( ∣ ∣ p i − p c − R ( q i − q c ) ∣ ∣ 2 2 + ∣ ∣ p c − R q c − t ∣ ∣ 2 2 + 2 ( p i − p − R ( q i − q ) ) T ( p c − R q c − t ) ) \begin{align}\frac{1}{2}\sum\limits_{i}^n||p_i-(Rq_i+t)||_2^2 = & \frac{1}{2}\sum\limits_{i}^n||p_i-Rq_i-t-p_c+Rq_c+p_c-Rq_c||_2^2\\ = & \frac{1}{2}\sum\limits_{i}^n||p_i-p_c-R(q_i-q_c)+(p_c-Rq_c-t)||_2^2\\ = &\frac{1}{2}\sum\limits_{i}^n(||p_i-p_c-R(q_i-q_c)||_2^2+||p_c-Rq_c-t||_2^2+2(p_i-p-R(q_i-q))^T(p_c-Rq_c-t))\end{align} 21in∣∣pi(Rqi+t)22===21in∣∣piRqitpc+Rqc+pcRqc2221in∣∣pipcR(qiqc)+(pcRqct)2221in(∣∣pipcR(qiqc)22+∣∣pcRqct22+2(pipR(qiq))T(pcRqct))

最后一项 p i − p − R ( q i − q ) p_i-p-R(q_i-q) pipR(qiq)求和后结果为零,因此:

min ⁡ R , t J = 1 2 ∑ i n ∣ ∣ p i − p c − R ( q i − q c ) ∣ ∣ 2 2 + ∣ ∣ p c − R q c − t ∣ ∣ 2 2 \min\limits_{R,t}J=\frac{1}{2}\sum\limits_{i}^n||p_i-p_c-R(q_i-q_c)||_2^2+||p_c-Rq_c-t||_2^2 R,tminJ=21in∣∣pipcR(qiqc)22+∣∣pcRqct22

观察变换后的优化问题形式,第一项只和 R R R有关,因此只要能求得 R R R,然后令第二项为零即可求得 t t t

如何求解旋转矩阵R呢?

取优化问题的第一项,

1 2 ∑ i n ∣ ∣ p i − p c − R ( q i − q c ) ∣ ∣ 2 2 \frac{1}{2}\sum\limits_{i}^n||p_i-p_c-R(q_i-q_c)||_2^2 21in∣∣pipcR(qiqc)22

令, u i = p i − p c v i = q i − q c \begin{matrix}u_i=p_i-p_c\\v_i=q_i-q_c\end{matrix} ui=pipcvi=qiqc

则,

1 2 ∑ i n ∣ ∣ p i − p c − R ( q i − q c ) ∣ ∣ 2 2 = 1 2 ∑ i n ∣ ∣ u i − R v i ∣ ∣ 2 2 \frac{1}{2}\sum\limits_{i}^n||p_i-p_c-R(q_i-q_c)||_2^2=\frac{1}{2}\sum\limits_{i}^n||u_i-Rv_i||^2_2 21in∣∣pipcR(qiqc)22=21in∣∣uiRvi22

根据二范数展开

1 2 ∑ i n ∣ ∣ u i − R v i ∣ ∣ 2 2 = 1 2 ∑ i n ( u i T u i + v i T R T R v i − 2 u i T R v i ) \frac{1}{2}\sum\limits_{i}^n||u_i-Rv_i||^2_2=\frac{1}{2}\sum\limits_{i}^n(u_i^Tu_i+v_i^TR^TRv_i-2u_i^TRv_i) 21in∣∣uiRvi22=21in(uiTui+viTRTRvi2uiTRvi)

展开后的三项中前两项都和 R R R无关,因此只需要关注最后一项,

− ∑ i n u i T R v i -\sum\limits_{i}^nu_i^TRv_i inuiTRvi

使其最小即可,上式中 u i u_i ui是三维空间中的点差,因此是 3 × 1 3\times 1 3×1的矩阵,根据矩阵迹的性质,

− ∑ i n u i T R v i = − ∑ i n t r ( R v i u i T ) = − t r ( R ∑ i n v i u i T ) -\sum\limits_{i}^nu_i^TRv_i=-\sum\limits_{i}^ntr(Rv_iu_i^T)=-tr(R\sum\limits_{i}^nv_iu_i^T) inuiTRvi=intr(RviuiT)=tr(RinviuiT)

引入一点数学知识,对于正定矩阵 A A T AA^T AAT,对于任何正交矩阵 B B B都有 t r ( A A T ) ≥ t r ( B A A T ) tr(AA^T)\ge tr(BAA^T) tr(AAT)tr(BAAT),关于此的证明可参考论文

W = ∑ i n v i u i T W=\sum\limits_{i}^nv_iu_i^T W=inviuiT

W W W是一个 3 × 3 3\times3 3×3的矩阵,对 W W W进行SVD分解,

W = U Σ V T W=U\Sigma V^T W=UΣVT

U / V U/V U/V 3 × 3 3\times 3 3×3的正交矩阵, Σ \Sigma Σ 3 × 3 3\times 3 3×3的元素非负的对角矩阵。

R = V U T R=VU^T R=VUT就是以上问题的解,可根据 R R R再求出 t t t

关于为什么 R = V U T R=VU^T R=VUT就是以上问题的解,可结合 t r ( A A T ) ≥ t r ( B A A T ) tr(AA^T)\ge tr(BAA^T) tr(AAT)tr(BAAT)来看,若 R = V U T R=VU^T R=VUT

R W = V U T U Σ V T = V Σ V T RW=VU^TU\Sigma V^T=V\Sigma V^T RW=VUTUΣVT=VΣVT

R W RW RW是正定对称矩阵,根据 t r ( A A T ) ≥ t r ( B A A T ) tr(AA^T)\ge tr(BAA^T) tr(AAT)tr(BAAT),对于任何正交矩阵 B B B,都有 t r ( R W ) ≥ t r ( B R W ) tr(RW)\ge tr(BRW) tr(RW)tr(BRW),而旋转矩阵一定是正交矩阵,因此 R = V U T R=VU^T R=VUT可以使 t r ( R W ) tr(RW) tr(RW)取最大值。若求得的 R R R的行列式为负,就取 − R -R R作为最后的旋转矩阵。关于此的详细说明可参考论文


SVD求解ICP实战

代码参考自视觉SLAM十四讲。

示例使用的RGB-D图像,先使用RGB图像找到两个位置图像中匹配点,再计算匹配点的三维坐标,然后利用SVD来求解Rt

关键的两个函数,一个是特征点匹配,

void ICPSolver::findMatchFeatures(cv::Mat &image1,
                                    cv::Mat &image2,
                                    std::vector<cv::KeyPoint> &kpt1,
                                    std::vector<cv::KeyPoint> &kpt2,
                                    std::vector<cv::DMatch> &matches)
{
    cv::Mat desc1, desc2;
    cv::Ptr<cv::FeatureDetector> detector = cv::ORB::create();
    detector->detectAndCompute(image1, cv::Mat(), kpt1, desc1);
    detector->detectAndCompute(image2, cv::Mat(), kpt2, desc2);

    cv::Ptr<cv::DescriptorMatcher> matcher = cv::DescriptorMatcher::create("BruteForce-Hamming");
    std::vector<cv::DMatch> match;
    matcher->match(desc1, desc2, match);

    double min_dist = 10000, max_dist = 0;
    for(auto &m : match) {
        auto d = m.distance;
        if(d < min_dist) min_dist = d;
        if(d > max_dist) max_dist = d;
    }
    std::cout << "Max Distance: " << max_dist << std::endl;
    std::cout << "Min Distance: " << min_dist << std::endl;

    for(auto &m : match) {
        if(m.distance < 2 * min_dist || m.distance < 30)
            continue;
        matches.push_back(m);
    }
}

SVD求解Rt的代码,

void ICPSolver::svdSolver(std::vector<cv::Point3f> &pts1,
                std::vector<cv::Point3f> &pts2,
                cv::Mat &R, cv::Mat &t)
{
    cv::Point3f pc1, pc2; // center point
    int N = pts1.size();
    for(int i = 0; i < N; i++) {
        pc1 += pts1[i];
        pc2 += pts2[i];
    }
    pc1 = cv::Point3f(cv::Vec3f(pc1) / N);
    pc2 = cv::Point3f(cv::Vec3f(pc2) / N);

    cv::Point3f u, v;
    Eigen::Matrix3d W = Eigen::Matrix3d::Zero();
    for(int i = 0; i < N; i++) {
        u = pts1[i] - pc1;
        v = pts2[i] - pc2;

        W += Eigen::Vector3d(u.x, u.y, u.z) * Eigen::Vector3d(v.x, v.y, v.z).transpose();
    }

    Eigen::JacobiSVD<Eigen::Matrix3d> svd(W, Eigen::ComputeFullU | Eigen::ComputeFullV);
    Eigen::Matrix3d U = svd.matrixU();
    Eigen::Matrix3d V = svd.matrixV();

    std::cout << "W=" << W << std::endl;
    std::cout << "U=" << U << std::endl;
    std::cout << "V=" << V << std::endl;

    Eigen::Matrix3d R_ = U * (V.transpose());
    std::cout <<"det(R_)=" << R_.determinant() << std::endl;
    if(R_.determinant() < 0) {
        R_ = -R_;
    }
    std::cout <<"R_R_^T=" << R_ * R_.transpose() << std::endl;

    Eigen::Vector3d t_ = Eigen::Vector3d(pc1.x, pc1.y, pc1.z) 
                            - R_ * Eigen::Vector3d(pc2.x, pc2.y, pc2.z);
    std::cout << "t_ = " << t_ << std::endl;

    R = (cv::Mat_<double>(3,  3) << R_(0, 0), R_(0, 1), R_(0, 2),
                                    R_(1, 0), R_(1, 1), R_(1, 2),
                                    R_(2, 0), R_(2, 1), R_(2, 2));
    t = (cv::Mat_<double>(3, 1) << t_(0, 0), t_(1, 0), t_(2, 0));
}

代码运行的结果可以发现 d e t ( R ) = 1 , R R T ≈ I det(R)=1,RR^T\approx I det(R)=1,RRTI

# det(R_)=1
# R_R_^T= [[1 -2.13805e-16    2.498e-16]
#          [-2.13805e-16            1  4.16334e-16],
#         [2.498e-16  4.22405e-16            1]]


完整可运行代码https://gitee.com/lx_r/basic_cplusplus_examples


欢迎访问个人网络日志🌹🌹知行空间🌹🌹


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SVD(奇异值分解)是一种常用的矩阵分解方法,它可以将一个矩阵分解为三个矩阵的乘积,其中一个矩阵是左奇异向量矩阵,一个矩阵是右奇异向量矩阵,还有一个矩阵是奇异值矩阵。在实际应用中,SVD经常被用于矩阵降维、数据压缩和信号处理等领域。 双边Jacobi求解SVD是一种常用的SVD求解方法,它通过迭代的方式不断逼近矩阵的SVD分解结果。该算法的具体步骤如下: 1. 初始化 对于一个$m \times n$的矩阵$A$,首先将其转化为一个对称矩阵$B=A^TA$。然后,设置初始值$U=V=I_n$,其中$I_n$是$n$阶单位矩阵。 2. 迭代计算 在每次迭代中,对矩阵$B$进行Jacobi旋转,得到$B_k$: $$B_k=J^TBJ$$ 其中$J$是一个$n \times n$的Jacobi旋转矩阵,它可以将矩阵$B_{k-1}$的某一对对角线元素旋转为$0$。具体而言,设$B_{k-1}$的第$i$行第$j$列和第$j$行第$i$列的元素为$b_{ij}$和$b_{ji}$,则$J$的第$i$行第$i$列和第$j$行第{j}列的元素为: $$\begin{cases} \cos{\theta} & i=j \\ \sin{\theta} & i<j \\ -\sin{\theta} & i>j \\ \end{cases}$$ 其中$\theta$是旋转角度,满足: $$\tan{2\theta}=\frac{2b_{ij}}{b_{ii}-b_{jj}}}$$ 旋转后的矩阵$B_k$的对角线元素就是$A$的奇异值的平方。 同时,我们也要更新$U$和$V$的值,具体而言,设$J=[j_{ij}]$,则: $$U_k=U_{k-1}J$$ $$V_k=V_{k-1}J$$ 3. 判断终止条件 在每次迭代中,计算矩阵$B_k$的对角线元素的变化量$|b_{ii}^{k-1}-b_{ii}^{k}|$。当变化量小于某个阈值时,停止迭代。 4. 计算奇异值和奇异向量 最终得到的矩阵$B_k$的对角线元素就是矩阵$A$的奇异值的平方,即$\sigma_i^2$。而矩阵$U_k$和$V_k$的每一列就是$A$的左奇异向量和右奇异向量。 下面是在Matlab中实现双边Jacobi求解SVD的代码: ```matlab function [U, S, V] = SVD(A) % 输入:矩阵A % 输出:矩阵A的左奇异向量矩阵U、奇异值矩阵S、右奇异向量矩阵V [m, n] = size(A); % 初始化 B = A' * A; U = eye(n); V = eye(n); % 迭代计算 while true % 判断终止条件 delta = abs(diag(B, -1)) - abs(diag(B, 1)); if all(abs(delta) < 1e-10) break; end % Jacobi旋转 for i = 1 : n-1 for j = i+1 : n if abs(B(i,j)) < 1e-10 continue; end delta = (B(j,j) - B(i,i)) / (2 * B(i,j)); t = sign(delta) / (abs(delta) + sqrt(1 + delta^2)); c = 1 / sqrt(1 + t^2); s = c * t; % 更新B temp1 = B(i,i); temp2 = B(j,j); B(i,i) = temp1 * c^2 + temp2 * s^2 - 2 * B(i,j) * c * s; B(j,j) = temp1 * s^2 + temp2 * c^2 + 2 * B(i,j) * c * s; B(i,j) = 0; B(j,i) = 0; % 更新U和V temp1 = U(:,i); temp2 = U(:,j); U(:,i) = temp1 * c + temp2 * s; U(:,j) = -temp1 * s + temp2 * c; temp1 = V(:,i); temp2 = V(:,j); V(:,i) = temp1 * c + temp2 * s; V(:,j) = -temp1 * s + temp2 * c; end end end % 计算奇异值和奇异向量 S = sqrt(abs(diag(B))); U = A * V ./ repmat(S', m, 1); end ``` 在实际使用中,我们可以将矩阵$A$进行中心化处理,即将每一列的均值减去该列所有元素的平均值,然后再进行SVD分解。这可以避免由于列之间的差异导致的不准确性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值