使用 PCA 的 3D 面向对象边界框

3D Object Oriented Bounding Box using PCA

我正在尝试为一组点计算面向对象的边界框。我正在使用 C++ 和 Eigen 线性代数库。

我一直在使用两篇博文作为指南,但我的边界框仍然不正确(见图片)。

blog post 1 blog post 2

我希望我的评论代码清楚我的尝试,但该算法的要点是使用 PCA 找到面向对象坐标系的基向量。

然后将所有点投影到新框架中,找到定义框的最小和最大点,然后将这些点投影到原始坐标框架中并渲染它们。

我可以成功渲染一个框,但它不是边界框并且看起来与正常的 x、y、z 轴对齐。对于显示的两个对象中的每一个,这在第一张图片中都很清楚。

任何帮助将不胜感激。提前致谢。

// iglVertices is a X by 3 Eigen::::MatrixXf
// Covariance matrix and eigen decomposition
Eigen::MatrixXf centered = iglVertices.rowwise() - iglVertices.colwise().mean();
Eigen::MatrixXf cov = centered.adjoint() * centered;
Eigen::SelfAdjointEigenSolver<Eigen::MatrixXf> eig(cov);

//Setup homogenous tranformation to act as new basis functions for new coordinate frame
auto basis = Eigen::Matrix4f(eig.eigenvectors().colwise().homogeneous().rowwise().homogeneous());
basis.row(3) = Eigen::Vector4f::Zero();
basis.col(3) = Eigen::Vector4f::Zero();
basis(3,3) = 1.0f;

std::cout << "eig.eigenvectors() " << eig.eigenvectors() << std::endl;
std::cout << "Basis " << basis << std::endl;

//invert matrix and and transform points into new coordinate frame
auto invBasis = basis.inverse();
auto newVertices = invBasis * iglVertices.rowwise().homogeneous().transpose();

//Find max and min for all of the new axis
auto maxP = newVertices.rowwise().maxCoeff();
auto minP = newVertices.rowwise().minCoeff();

std::cout << "max " << maxP << std::endl;
std::cout << "min " << minP << std::endl;

//Find center and half extent in new coordinate frame
auto center = Eigen::Vector4f((maxP + minP) / 2.0);
auto half_extent = Eigen::Vector4f((maxP - minP) / 2.0);

auto t = Eigen::Vector4f((basis * center));
std::cout << "t " << t << std::endl;
//Update basis function with the translation between two coordinate origins
//I don't actually understand why I need this and have tried without it but still my bounding
//box is wrong
basis.col(3) = Eigen::Vector4f(t[0], t[1], t[2], t[3]);


std::cout << "Basis complete " << basis << std::endl;

std::cout << "center " << center << std::endl;
std::cout << "half_extent " << half_extent << std::endl;

//This is the same as the previous minP/maxP but thought i should try this as
// box is paramaterised with center and half-extent
auto max = center + half_extent;
auto min = center - half_extent;

//Transform back into the original coordinates
auto minNormalBasis = (basis * min).hnormalized();
auto maxNormalBasis = (basis * max).hnormalized();

std::cout << "min new coord" << min << std::endl;
std::cout << "max new coord"<< max << std::endl;
std::cout << "min old coord" << minNormalBasis << std::endl;
std::cout << "max old coord"<< maxNormalBasis << std::endl;

//Extract min and max
auto min_x = minNormalBasis[0];
auto min_y = minNormalBasis[1];
auto min_z = minNormalBasis[2];

auto max_x = maxNormalBasis[0];
auto max_y = maxNormalBasis[1];
auto max_z = maxNormalBasis[2];

bBox.clear();
//Build box for rendering
//Ordering specific to the faces I have manually generated
bBox.push_back(trimesh::point(min_x, min_y, min_z));
bBox.push_back(trimesh::point(min_x, max_y, min_z));

bBox.push_back(trimesh::point(min_x, min_y, max_z));
bBox.push_back(trimesh::point(min_x, max_y, max_z));

bBox.push_back(trimesh::point(max_x, min_y, max_z));
bBox.push_back(trimesh::point(max_x, max_y, max_z));

bBox.push_back(trimesh::point(max_x, min_y, min_z));
bBox.push_back(trimesh::point(max_x, max_y, min_z));

喷雾瓶示例的打印输出是

eig.eigenvectors()           0   -0.999992 -0.00411613
  -0.707107 -0.00291054    0.707101
   0.707107 -0.00291054    0.707101
Basis           0   -0.999992 -0.00411613           0
  -0.707107 -0.00291054    0.707101           0
   0.707107 -0.00291054    0.707101           0
          0           0           0           1
max 2.98023e-08
   0.216833
   0.582629
          1
min -2.98023e-08
      -0.215
   -0.832446
           1
t -0.000402254
  -0.0883253
  -0.0883253
           1
Basis complete            0    -0.999992  -0.00411613 -0.000402254
   -0.707107  -0.00291054     0.707101   -0.0883253
    0.707107  -0.00291054     0.707101   -0.0883253
           0            0            0            1
center           0
0.000916399
  -0.124908
          1
half_extent 2.98023e-08
   0.215916
   0.707537
          0
min new coord-2.98023e-08
      -0.215
   -0.832446
           1
max new coord2.98023e-08
   0.216833
   0.582629
          1
min old coord 0.218022
-0.676322
-0.676322
max old coord-0.219631
 0.323021
 0.323021

您必须计算 PCA 框架内轴对齐框的 8 个角,然后对它们应用旋转:

bBox.push_back(eig.eigenvectors() * Vector3f(minP.x(), minP.y(), minP.z()));
bBox.push_back(eig.eigenvectors() * Vector3f(minP.x(), maxP.y(), minP.z()));

bBox.push_back(eig.eigenvectors() * Vector3f(minP.x(), minP.y(), maxP.z()));
bBox.push_back(eig.eigenvectors() * Vector3f(minP.x(), maxP.y(), maxP.z()));

...

你也可以直接计算newVertices为:

Matrix<float,3,Dynamic> newVertices = eig.eigenvectors().transpose() * iglVertices.transpose();

进行这些更改后,您的代码将减少一半;)

更重要的是,除非您知道自己在做什么,否则请避免使用 auto 关键字。在您的示例中,它的大部分用法都是非常糟糕的做法,更不用说错误了。请阅读此 page.