# 迭代最近点算法ICP
及SVD
求解
# ICP
问题
迭代最近点算法Iterative Closet Points,ICP
算法,是根据两组已知的三维空间点来估计采集两组点时传感器位姿发生的旋转和平移变化。
在使用相机时,我们可以通过图像的特征匹配,找到两组三维点之间每个点的匹配关系。而在使用激光雷达时,由于点云是稀疏的,但是采集频率很高,通常认为两帧之间离的最近的点就是匹配点,因此称此算法为迭代最近点算法。
使用代数式描述为:
假设我们得到,
这里,1
到位置2
姿态和位置发生的变化分别是
观察上式,我们有
实际为了求解
对于第
对于所有
要求解此最小二乘问题,有两种解法,一种使用SVD
分解,一种使用非线性优化算法。
本文中,我们来一起学习SVD
方法。
# SVD分解来求解ICP
定义两组点的质心为
将质心代入误差函数,
最后一项
观察变换后的优化问题形式,第一项只和
如何求解旋转矩阵R呢?
取优化问题的第一项,
令,
\begin{matrix}u_i=p_i-p_c\\v_i=q_i-q_c\end{matrix}
则,
根据二范数展开
展开后的三项中前两项都和
使其最小即可,上式中
引入一点数学知识,对于正定矩阵
令
SVD
分解,
# SVD
求解ICP
实战
代码参考自视觉SLAM十四讲。
示例使用的RGB-D
图像,先使用RGB
图像找到两个位置图像中匹配点,再计算匹配点的三维坐标,然后利用SVD
来求解R
和t
。
关键的两个函数,一个是特征点匹配,
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
求解R
和t
的代码,
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));
}
代码运行的结果可以发现
# 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 (opens new window)