目标函数, Q Q Q为target cloud, P P P为source cloud
F ( t ) = ∑ i = 1 n ∥ ( R P i + t ) − Q i ∥ 2 F(t)=\sum_{i=1}^{n}\left\|\left(R P_{i}+t\right)-Q_{i}\right\|^{2} F(t)=i=1∑n∥(RPi+t)−Qi∥2
首先对 t t t进行求导,
∂ F ( t ) ∂ t = 2 ∑ i = 1 n ( R P i + t ) − Q i = 0 ∂ F ( t ) ∂ t = 2 R ∑ i = 1 n P i + 2 t ∑ i = 1 n 1 − 2 ∑ i = 1 n Q i \begin{array}{c} \frac{\partial F(t)}{\partial t}=2 \sum_{i=1}^{n}\left(R P_{i}+t\right)-Q_{i}=0 \\ \frac{\partial F(t)}{\partial t}=2 R \sum_{i=1}^{n} P_{i}+2 t \sum_{i=1}^{n} 1-2 \sum_{i=1}^{n} Q_{i} \end{array} ∂t∂F(t)=2∑i=1n(RPi+t)−Qi=0∂t∂F(t)=2R∑i=1nPi+2t∑i=1n1−2∑i=1nQi
发现求出 R R R之后再去求解 t t t会非常简单,
t = 1 n ∑ i = 1 n Q i − R 1 n ∑ i = 1 n P i t=\frac{1}{n} \sum_{i=1}^{n} Q_{i}-R \frac{1}{n} \sum_{i=1}^{n} P_{i} t=n1i=1∑nQi−Rn1i=1∑nPi
t = Q ˉ − R P ˉ t=\bar{Q}-R \bar{P} t=Qˉ−RPˉ
将优化问题转化为只和 R R R有关
R = arg min R ∈ S O ( 3 ) ∥ ( R ( P i − P ˉ ) − ( Q i − Q ˉ ) ∥ 2 R=\underset{R \in S O(3)}{\arg \min }\left\|\left(R\left(P_{i}-\bar{P}\right)-\left(Q_{i}-\bar{Q}\right) \|^{2}\right.\right. R=R∈SO(3)argmin∥∥(R(Pi−Pˉ)−(Qi−Qˉ)∥2
X i = ( P i − P ˉ ) , Y i = ( Q i − Q ˉ ) X_i=\left(P_{i}-\bar{P}\right), Y_i=\left(Q_{i}-\bar{Q}\right) Xi=(Pi−Pˉ),Yi=(Qi−Qˉ)
对式子进行展开,发现
R = arg min R ∈ S O ( 3 ) ∑ ∥ R X i − Y i ∥ 2 = T r ( ∑ ∥ R X i − Y i ∥ 2 ) = ∑ T r ( ∥ R X i − Y i ∥ 2 ) = ∑ ( T r ( X i T R T R X i ) + T r ( Y i T Y i ) − 2 T r ( Y i T R X i ) ) R = \underset{R \in S O(3)}{\arg \min } \sum \| RX_i -Y_i\|^2 \\ = Tr(\sum \| RX_i -Y_i\|^2)\\ =\sum Tr(\| RX_i -Y_i\|^2)\\ =\sum (Tr(X_i^TR^TRX_i)+Tr(Y_i^TY_i)-2Tr(Y_i^TRX_i)) R=R∈SO(3)argmin∑∥RXi−Yi∥2=Tr(∑∥RXi−Yi∥2)=∑Tr(∥RXi−Yi∥2)=∑(Tr(XiTRTRXi)+Tr(YiTYi)−2Tr(YiTRXi))
发现前两项和 R R R没有关系
优化问题转化为
R = arg max R ∈ S O ( 3 ) ∑ T r ( Y i T R X i ) ) = ∑ T r ( R X i Y i T ) ) = T r ( R ∑ X i Y i T ) = T r ( R X 3 × n Y 3 × n T ) R = \underset{R \in S O(3)}{\arg \max } \sum Tr(Y_i^TRX_i))\\ =\sum Tr(RX_iY_i^T))\\ =Tr(R \sum X_iY_i^T)\\ =Tr(RX_{3×n}Y_{3×n}^T) R=R∈SO(3)argmax∑Tr(YiTRXi))=∑Tr(RXiYiT))=Tr(R∑XiYiT)=Tr(RX3×nY3×nT)
做svd分解
X Y T = U D V T XY^T=UDV^T XYT=UDVT
R = arg max R ∈ S O ( 3 ) T r ( R X Y T ) = T r ( R U D V T ) = T r ( V T R U D ) R = \underset{R \in S O(3)}{\arg \max }Tr(RXY^T)\\ =Tr(RUDV^T)\\ =Tr(V^TRUD) R=R∈SO(3)argmaxTr(RXYT)=Tr(RUDVT)=Tr(VTRUD)
let
M = V T R U M=V^TRU M=VTRU
有
T r ( M D ) = ∑ M i i d i < = ∑ d i Tr(MD)=\sum M_{ii}d_i<=\sum d_i Tr(MD)=∑Miidi<=∑di
此时 M = I = V T R U M=I=V^TRU M=I=VTRU
得到 R = V U T R=VU^T R=VUT
R R R应该满足正交矩阵的条件,并且 d e t ( R ) = 1 det(R)=1 det(R)=1
如果 d e t ( R ) < 0 det(R)<0 det(R)<0的时候选择第二大的值进行计算
T r ( M D ) = d 1 M 11 + d 2 M 22 + d 3 M 33 Tr(MD) = d_{1} M_{11}+d_{2} M_{22}+d_{3} M_{33} Tr(MD)=d1M11+d2M22+d3M33
中 M 11 = M 22 = 1 , M 33 = − 1 M_{11}=M_{22}=1, M_{33}=-1 M11=M22=1,M33=−1
此时 R = U C V T R=U C V^{T} R=UCVT
C = [ 1 0 0 0 1 0 0 0 sign ( det ( U V T ) ) ⋅ 1 ] C=\left[\begin{array}{cc} 1& 0 & 0 \\ 0& 1 & 0 \\ 0 & 0& \operatorname{sign}\left(\operatorname{det}\left(U V^{T}\right)\right) \cdot 1 \end{array}\right] C=⎣⎡10001000sign(det(UVT))⋅1⎦⎤
PCL中配准算法的标准流程,对于ICP来讲,输入是两团xyz点云,输出是source变换到target的刚体变换。其实核心求解过程都是上述公式,没啥可说的。在icp的实现里面默认是没有Reject bad correspondences这一步的
Iterative Closest Point Pipeline
关于上述推导公式的核心求解代码在pcl/registration/include/pcl/registration/impl/transformation_estimation_svd.hpp
文件下的estimateRigidTransformation
函数中,具体实现为
template <typename PointSource, typename PointTarget, typename Scalar> inline void
pcl::registration::TransformationEstimationSVD<PointSource, PointTarget, Scalar>::estimateRigidTransformation (
ConstCloudIterator<PointSource>& source_it,
ConstCloudIterator<PointTarget>& target_it,
Matrix4 &transformation_matrix) const
{
// Convert to Eigen format
const int npts = static_cast <int> (source_it.size ());
if (use_umeyama_)
{
Eigen::Matrix<Scalar, 3, Eigen::Dynamic> cloud_src (3, npts);
Eigen::Matrix<Scalar, 3, Eigen::Dynamic> cloud_tgt (3, npts);
for (int i = 0; i < npts; ++i)
{
cloud_src (0, i) = source_it->x;
cloud_src (1, i) = source_it->y;
cloud_src (2, i) = source_it->z;
++source_it;
cloud_tgt (0, i) = target_it->x;
cloud_tgt (1, i) = target_it->y;
cloud_tgt (2, i) = target_it->z;
++target_it;
}
// Call Umeyama directly from Eigen (PCL patched version until Eigen is released)
transformation_matrix = pcl::umeyama (cloud_src, cloud_tgt, false);
}
else
{
source_it.reset (); target_it.reset ();
// is the source dataset
transformation_matrix.setIdentity ();
Eigen::Matrix<Scalar, 4, 1> centroid_src, centroid_tgt;
// Estimate the centroids of source, target
compute3DCentroid (source_it, centroid_src);
compute3DCentroid (target_it, centroid_tgt);
source_it.reset (); target_it.reset ();
// Subtract the centroids from source, target
Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> cloud_src_demean, cloud_tgt_demean;
demeanPointCloud (source_it, centroid_src, cloud_src_demean);
demeanPointCloud (target_it, centroid_tgt, cloud_tgt_demean);
getTransformationFromCorrelation (cloud_src_demean, centroid_src, cloud_tgt_demean, centroid_tgt, transformation_matrix);
}
}
// 求解方式1:(默认求解方式)
//////////////////////////////////////////////////////////////////////////////////////////
template <typename Derived, typename OtherDerived>
typename Eigen::internal::umeyama_transform_matrix_type<Derived, OtherDerived>::type
pcl::umeyama (const Eigen::MatrixBase<Derived>& src, const Eigen::MatrixBase<OtherDerived>& dst, bool with_scaling)
{
typedef typename Eigen::internal::umeyama_transform_matrix_type<Derived, OtherDerived>::type TransformationMatrixType;
typedef typename Eigen::internal::traits<TransformationMatrixType>::Scalar Scalar;
typedef typename Eigen::NumTraits<Scalar>::Real RealScalar;
typedef typename Derived::Index Index;
EIGEN_STATIC_ASSERT (!Eigen::NumTraits<Scalar>::IsComplex, NUMERIC_TYPE_MUST_BE_REAL)
EIGEN_STATIC_ASSERT ((Eigen::internal::is_same<Scalar, typename Eigen::internal::traits<OtherDerived>::Scalar>::value),
YOU_MIXED_DIFFERENT_NUMERIC_TYPES__YOU_NEED_TO_USE_THE_CAST_METHOD_OF_MATRIXBASE_TO_CAST_NUMERIC_TYPES_EXPLICITLY)
enum { Dimension = PCL_EIGEN_SIZE_MIN_PREFER_DYNAMIC (Derived::RowsAtCompileTime, OtherDerived::RowsAtCompileTime) };
typedef Eigen::Matrix<Scalar, Dimension, 1> VectorType;
typedef Eigen::Matrix<Scalar, Dimension, Dimension> MatrixType;
typedef typename Eigen::internal::plain_matrix_type_row_major<Derived>::type RowMajorMatrixType;
const Index m = src.rows (); // dimension
const Index n = src.cols (); // number of measurements
// required for demeaning ...
const RealScalar one_over_n = 1 / static_cast<RealScalar> (n);
// computation of mean
const VectorType src_mean = src.rowwise ().sum () * one_over_n;
const VectorType dst_mean = dst.rowwise ().sum () * one_over_n;
// demeaning of src and dst points
const RowMajorMatrixType src_demean = src.colwise () - src_mean;
const RowMajorMatrixType dst_demean = dst.colwise () - dst_mean;
// Eq. (36)-(37)
const Scalar src_var = src_demean.rowwise ().squaredNorm ().sum () * one_over_n;
// Eq. (38)
const MatrixType sigma (one_over_n * dst_demean * src_demean.transpose ());
Eigen::JacobiSVD<MatrixType> svd (sigma, Eigen::ComputeFullU | Eigen::ComputeFullV);
// Initialize the resulting transformation with an identity matrix...
TransformationMatrixType Rt = TransformationMatrixType::Identity (m + 1, m + 1);
// Eq. (39)
VectorType S = VectorType::Ones (m);
if (sigma.determinant () < 0)
S (m - 1) = -1;
// Eq. (40) and (43)
const VectorType& d = svd.singularValues ();
Index rank = 0;
for (Index i = 0; i < m; ++i)
if (!Eigen::internal::isMuchSmallerThan (d.coeff (i), d.coeff (0)))
++rank;
if (rank == m - 1)
{
//如果三个值相差很大,判断UV行列式是否为+
if (svd.matrixU ().determinant () * svd.matrixV ().determinant () > 0)
Rt.block (0, 0, m, m).noalias () = svd.matrixU () * svd.matrixV ().transpose ();
else
{
const Scalar s = S (m - 1);
S (m - 1) = -1;
Rt.block (0, 0, m, m).noalias () = svd.matrixU () * S.asDiagonal () * svd.matrixV ().transpose ();
S (m - 1) = s;
}
}
else
{
Rt.block (0, 0, m, m).noalias () = svd.matrixU () * S.asDiagonal () * svd.matrixV ().transpose ();
}
// Eq. (42)
if (with_scaling)
{
// Eq. (42)
const Scalar c = 1 / src_var * svd.singularValues ().dot (S);
// Eq. (41)
Rt.col (m).head (m) = dst_mean;
Rt.col (m).head (m).noalias () -= c * Rt.topLeftCorner (m, m) * src_mean;
Rt.block (0, 0, m, m) *= c;
}
else
{
Rt.col (m).head (m) = dst_mean;
Rt.col (m).head (m).noalias () -= Rt.topLeftCorner (m, m) * src_mean;
}
return (Rt);
}
// 求解方式2
///////////////////////////////////////////////////////////////////////////////////////////
template <typename PointSource, typename PointTarget, typename Scalar> void
pcl::registration::TransformationEstimationSVD<PointSource, PointTarget, Scalar>::getTransformationFromCorrelation (
const Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> &cloud_src_demean,
const Eigen::Matrix<Scalar, 4, 1> ¢roid_src,
const Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> &cloud_tgt_demean,
const Eigen::Matrix<Scalar, 4, 1> ¢roid_tgt,
Matrix4 &transformation_matrix) const
{
transformation_matrix.setIdentity ();
// Assemble the correlation matrix H = source * target'
Eigen::Matrix<Scalar, 3, 3> H = (cloud_src_demean * cloud_tgt_demean.transpose ()).topLeftCorner (3, 3);
// Compute the Singular Value Decomposition
Eigen::JacobiSVD<Eigen::Matrix<Scalar, 3, 3> > svd (H, Eigen::ComputeFullU | Eigen::ComputeFullV);
Eigen::Matrix<Scalar, 3, 3> u = svd.matrixU ();
Eigen::Matrix<Scalar, 3, 3> v = svd.matrixV ();
// Compute R = V * U'
if (u.determinant () * v.determinant () < 0)
{
for (int x = 0; x < 3; ++x)
v (x, 2) *= -1;
}
Eigen::Matrix<Scalar, 3, 3> R = v * u.transpose ();
// Return the correct transformation
transformation_matrix.topLeftCorner (3, 3) = R;
const Eigen::Matrix<Scalar, 3, 1> Rc (R * centroid_src.head (3));
transformation_matrix.block (0, 3, 3, 1) = centroid_tgt.head (3) - Rc;
}