我在3D对象的表面上有一组随机采样点。我希望能够计算两个不同对象之间的相似性。为了做到这一点,我首先要确保我要比较的两个对象的样本点具有相同的旋转和比例。我想我可以通过沿x / y / z轴定向主分量轴,并进行缩放使得最长主成分具有单位长度来实现这一点。
我首先计算点集的质心,并翻译所有点,使原点成为新的质心。
我使用CGAL linear_least_squares_fitting_3函数进行主成分分析,该函数通过点给出最佳拟合平面。我通过取两个基矢量的叉积来计算这个平面的法线:
Plane plane;
linear_least_squares_fitting_3(points.begin(), points.end(),
plane, CGAL::Dimension_tag<0>());
auto dir1 = dir2vec(plane.base1().direction());
auto dir2 = dir2vec(plane.base2().direction());
auto normal = dir1 ^ dir2; // cross product
normal.normalize(); dir1.normalize(); dir2.normalize();
dir2vec
函数将CGAL::Direction_3
对象转换为等效的osg::Vec3d
对象(我正在使用OpenSceneGraph图形引擎)。最后,我使用以下代码将所有内容旋转到单位轴:
Matrixd r1, r2, r3;
r1.makeRotate(normal, Vec3d(1,0,0));
r2.makeRotate(dir1 * r1, Vec3d(0,1,0));
r3.makeRotate(dir2 * r1 * r2, Vec3d(0,0,1));
auto rotate = [&](Vec3d const &p) {
return p * r1 * r2 * r3;
};
transform(osgPoints.begin(), osgPoints.end(), osgPoints.begin(), rotate);
此处osgPoints
是vector<osg::Vec3d>
。出于测试目的,我将旋转点的质心转换回原始位置,因此两个点云都不会重叠。
Vec3d center = point2vec(centroid);
auto tocentroid = [&](Vec3d const &v) {
return v + center;
};
transform(osgPoints.begin(), osgPoints.end(), osgPoints.begin(), tocentroid);
为了测试它,我使用相同点集的两个副本,但是一个被转换(旋转和翻译)。上面的代码应该撤消旋转,但结果不是我所期望的:见this image。红线表示最佳拟合平面的基矢量及其法线。看起来两次调用linear_least_squares_fitting_3
的结果给出了稍微不同的答案,因为其中一个平面相对于另一个平面旋转了一点。
Here is another图像,其中两个对象的原点位于其原点。现在可以清楚地看到法线和基本向量一起出现,但这些点没有。
有人知道为什么会这样吗,我怎么能阻止它?
答案 0 :(得分:3)
将平面拟合到一组点会使一个自由度不受约束。飞机可以绕其法线自由旋转,拟合相等。我对CGAL一无所知,但我发现他们只是找到一个方便的平面(可能是距离空间原始轴最近的投影),我不会感到惊讶。
如果你在点云上做了真正的PCA,我认为你没有那个问题。或者,您可以沿着拟合算法发现的法线重新缩放(拉伸)数据,然后找到另一个拟合。如果您将数据充分拉伸,那么找到的第一个平面不应该像某个正交平面那样合适。
答案 1 :(得分:0)
正如JCooper建议的那样,CGAL似乎并没有计算所有主成分。我切换到ALGLIB库来做PCA,现在它可以正常工作。