随笔 - 14  文章 - 0  评论 - 0  阅读 - 3978

12_视觉里程计1_ICP算法

前言

ICP的英文全称为Iterative Closest Point,即为迭代最近点。它在激光雷达应用频率很高,主要是在点云配准领域。ICP算法在是是视觉SLAM中应用也非常多,这个算法还是很重要。我们下面的讨论还是基于视觉SLAM,好了我们开始吧!

ICP算法流程

ICP算法顾名思义,就是找最近点。算法流程如下:

  • step1:预处理点云
  • step2:寻找对应点(最近点)
  • step3:根据对应点,计算R和t
  • step4:对点云进行转换,计算误差
  • step5:不断迭代,直至误差小于某一个值

3D-3D: ICP

假设我们有一组配对好的3D点如下:

P={p1,,pn},P={p1,,pn}

现在我们想要找到一个欧式变换,使得

i,pi=Rpi+t.

上述的这个问题就可以使用ICP算法求解。这里主要需要注意下,如果仅考虑3D点之间的变换,此时和相机并没有关系。在RGB-D SLAM中用ICP问题指代匹配好的两组点间的运动估计问题(跟激光SLAM有区别,激光SLAM通常是未知的情况)。ICP求解主要是两种方式,一种是SVD,另一种是非线性优化方式求解。

SVD方法

根据前面描述的ICP问题,令第i对点的误差为:

ei=pi(Rpi+t).

这里解释一下,我们用一个点 pi(第二幅图中数据)来估算另一个点pi时(在第一幅中数据,即为观测坐标),必然会产生误差。

我们构建最小二乘问题,求解使用平方和打到极小的Rt,则有

minR,t12i=1n(pi(Rpi+t))22.

定义图像1和图像2中两组点的之心为:

p=1ni=1n(pi),p=1ni=1n(pi).

在误差函数做如下处理(去质心处理):

12i=1npi(Rpi+t)2=12i=1npiRpitp+Rp+pRp2=12i=1n(pipR(pip))+(pRpt)2=12i=1n(pipR(pip)2+pRpt2+2(pipR(pip))T(pRpt))

最终优化目标函数简化为

minR,tJ=12i=1npipR(pip)2+pRpt2.

我们可以先求左边的第一项,我们记去质心坐标为:

qi=pip,qi=pip.

由于左边第一项只和R有关,因此我们先计算旋转矩阵R,则

R=argminR12i=1nqiRqi2

我们将R进行展开,则为

12i=1nqiRqi2=12i=1n(qiTqi+qiTRTRqi2qiTRqi)

由于RTR=I,继续化简优化目标函数变为

i=1nqiTRqi=i=1ntr(RqiqiT)=tr(Ri=1nqiqiT)

为了解R,定义矩阵:

W=i=1nqiqiT.

使用SVD分解,使得W=UΣVT,当W满秩时,则

R=UVT

解得R以后,t即为

t=pRp

非线性优化方法

李代数表达位姿,迭代方式找到最优值。目标函数为:

minξ=12i=1n(piexp(ξ)pi)22

实践:求解ICP

SVD方法

  • 原理和前面介绍的一致,代码如下:
void pose_estimation_3d3d(const vector<Point3f> &pts1, const vector<Point3f> &pts2, Mat &R, Mat &t) 
{
  Point3f p1, p2;     // center of mass
  int N = pts1.size();
  for (int i = 0; i < N; i++) {
    p1 += pts1[i];
    p2 += pts2[i];
  }
  p1 = Point3f(Vec3f(p1) / N);
  p2 = Point3f(Vec3f(p2) / N);
  vector<Point3f> q1(N), q2(N); // remove the center
  for (int i = 0; i < N; i++) {
    q1[i] = pts1[i] - p1;
    q2[i] = pts2[i] - p2;
  }

  // compute q1*q2^T
  Eigen::Matrix3d W = Eigen::Matrix3d::Zero();
  for (int i = 0; i < N; i++) {
    W += Eigen::Vector3d(q1[i].x, q1[i].y, q1[i].z) * Eigen::Vector3d(q2[i].x, q2[i].y, q2[i].z).transpose();
  }
  cout << "W=" << W << endl;

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

  cout << "U=" << U << endl;
  cout << "V=" << V << endl;

  Eigen::Matrix3d R_ = U * (V.transpose());
  if (R_.determinant() < 0) {
    R_ = -R_;
  }
  Eigen::Vector3d t_ = Eigen::Vector3d(p1.x, p1.y, p1.z) - R_ * Eigen::Vector3d(p2.x, p2.y, p2.z);

  // convert to cv::Mat
  R = (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 = (Mat_<double>(3, 1) << t_(0, 0), t_(1, 0), t_(2, 0));
}
posted on   LittleFishC  阅读(535)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
< 2025年3月 >
23 24 25 26 27 28 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 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示