专栏名称: 新机器视觉
最前沿的机器视觉与计算机视觉技术
目录
相关文章推荐
青年文摘  ·  多出去走走 ·  2 天前  
读书杂志  ·  中读年卡 | 中国纹样,美! ·  2 天前  
深夜书屋  ·  入山问樵,入水问渔 ·  3 天前  
51好读  ›  专栏  ›  新机器视觉

一文看懂点云包围盒计算原理+代码详细注释

新机器视觉  · 公众号  ·  · 2024-12-26 16:16

正文

来源:3D视觉工坊

任务定义

点云包围盒计算,是指在三维点云数据中,计算出一个最小的长方体(包围盒),这个长方体能够完全包含所有的点云数据。包围盒在计算机图形学、碰撞检测、机器人导航等领域具有广泛的应用。

目前主要有两种包围盒:

  1. AABB包围盒:包围盒与坐标轴方向对齐,适合瘦长型,显然留有很大空白,并且随着物体旋转,AAVV包围盒需要重新计算。

  2. OBB包围盒:沿着主方向的包围盒,空白较少,适合碰撞检测,并且物体发生旋转后,OBB包围盒可以随着一起旋转。

本文主要讨论的是 Oriented Bounding Box (OBB) 包围盒的计算,相比于 Axis-Aligned Bounding Box (AABB),OBB包围盒的方向可以自由旋转,从而更加紧密地包裹点云数据。

基本原理

OBB包围盒的计算主要分为以下几个步骤:

1、计算质心和协方差矩阵 :质心是点云的中心点,协方差矩阵描述了点云在三个方向上的分布情况。

2、主成分分析(PCA) :通过对协方差矩阵进行特征分解,得到特征值和特征向量。特征值反映了各方向上的方差大小,特征向量代表了主方向。

3、计算变换矩阵 :利用特征向量构成旋转矩阵,将点云旋转到主方向上。同时,结合质心计算平移矩阵。

4、计算变换点云的包围盒 :在旋转后的点云上计算包围盒,并通过逆变换矩阵将包围盒还原到原始点云的坐标系中。

代码实践

利用PCA主成分分析法,获取点云的三个主方向,获取质心。具体方法:求取协方差矩阵,并且计算它们的特征值和特征向量,特征向量即为主方向。

void calValueVector(PointCloud::Ptr& cloud_ptr, Eigen::Vector4f& center, Eigen::Vector3f &values, Eigen::Matrix3f &vectors) {
// 计算中心
pcl::compute3DCentroid(*cloud_ptr, center);
// 计算协方差
Eigen::Matrix3f covariance; // 协方差
pcl::computeCovarianceMatrixNormalized(*cloud_ptr, center, covariance);
// 计算特征值、特征向量
Eigen::SelfAdjointEigenSolver<:matrix3f> eigen_solver(covariance, Eigen::ComputeEigenvectors);
values = eigen_solver.eigenvalues(); // 特征值
vectors = eigen_solver.eigenvectors(); // 特征向量
// 矫正主方向间垂直
vectors.col(2) = vectors.col(0).cross(vectors.col(1));
vectors.col(0) = vectors.col(1).cross(vectors.col(2));
vectors.col(1) = vectors.col(2).cross(vectors.col(0));
// 打印结果
cout <"质心点(4x1):n" 

计算变换矩阵,并变换点云

Eigen::Matrix4f RT = Eigen::Matrix4f::Identity(); // 旋转+平移矩阵
Eigen::Matrix4f RT_inv = Eigen::Matrix4f::Identity(); // 逆向矩阵
RT.block<3, 3>(0, 0) = vectors.transpose(); // 旋转矩阵 R
RT.block<0, 3>(0, 3) = -1.0f * (vectors.transpose()) * (center.head<3>()); // -R * t
RT_inv = RT.inverse(); // 逆向变换矩阵
pcl::transformPointCloud(*cloud, *cloud_trans, RT); // 变换点云

计算变换点云的包围框。并且通过“变换矩阵”的逆变换,计算原始点云的包围框。

PointT min_p1, max_p1; // 1: 代表变换后的(已经完成了矫正)
pcl::getMinMax3D(*cloud_trans, min_p1, max_p1); // 每个维度的最小/最大值
Eigen::Vector3f c, c1; // 变换前后的点云中心
c1 = 0.5f * (min_p1.getVector3fMap() + max_p1.getVector3fMap()); // 计算变换后的中心
cout <"型心c1(3x1)n" Eigen::Affine3f RT_inv_aff(RT_inv); // 专门的RT矩阵,封装有很多操作
pcl::transformPoint(c1, c, RT_inv_aff); // 逆向变换型心
cout <"原始型心c(3x1)n" Eigen::Vector3f whd, whd1;
// 变换后的尺寸宽度、高度、尺寸
whd1 = max_p1.getVector3fMap() - min_p1.getVector3fMap();
whd = whd1; // 原始框的box尺寸同变换的
float sc1 = (whd(0) + whd(1) + whd(2)) / 3; // 平均尺寸,用于设置
cout <"width:" const Eigen::Quaternionf bboxQ1(Eigen::Quaternionf::Identity());
const Eigen::Vector3f bboxT1(c1); // 变换点云的中心
// 原始点云的Box
const Eigen::Quaternionf bboxQ(RT_inv.block<3, 3>(0, 0));
const Eigen::Vector3f bboxT(c); // 原始点云中心

变换后的坐标轴位置计算

PointT O_1; // 变换后的点云中心
O_1.x = 0.0; // X
O_1.y = 0.0; // Y
O_1.z = 0.0; // Z
// 三个角点
Eigen::Vector3f px, py, pz; // 原始点的坐标(坐标轴)
px = vectors.col(0); // 第一列
py = vectors.col(1); // 第二列
pz = vectors.col(2); // 第三列
// 将三个坐标点也变换到
Eigen::Vector3f px_1, py_1, pz_1; // 变换后的点
Eigen::Affine3f RT_aff(RT);
// 计算变换后的点
pcl::transformVector(px, px_1, RT_aff);
pcl::transformVector(py, py_1, RT_aff);
pcl::transformVector(pz, pz_1, RT_aff);
// 变换后点的坐标 x 尺度系数
PointT PX_1, PY_1, PZ_1;
PX_1.x = sc1 * px_1(0);
PX_1.y = sc1 * px_1(1);
PX_1.z = sc1 * px_1(2);
PY_1.x = sc1 * py_1(0);
PY_1.y = sc1 * py_1(1);
PY_1.z = sc1 * py_1(2);
PZ_1.x = sc1 * pz_1(0);
PZ_1.y = sc1 * pz_1(1);
PZ_1.z = sc1 * pz_1(2);
// 原始点云
PointT O;
O.x = center(0);
O.y = center(1);
O.z = center(2);
// 变换后坐标轴的位置
PointT PX, PY, PZ;
PX.x = sc1 * px(0) + O.x;
PX.y = sc1 * px(1) + O.y;
PX.z = sc1 * px(2) + O.z;
PY.x = sc1 * py(0) + O.x;
PY.y = sc1 * py(1) + O.y;
PY.z = sc1 * py(2) + O.z;
PZ.x = sc1 * pz(0) + O.x;
PZ.y = sc1 * pz(1) + O.y;
PZ.z = sc1 * pz(2) + O.z;

点云显示

/* ------ 06 可视化结果 ------ */
pcl::visualization::PCLVisualizer viewer;
viewer.setWindowName("PCA获取OBB点云框");
viewer.setBackgroundColor(255, 255, 255);
// 原始点云
int v1(0);
viewer.createViewPort(0.0, 0.0, 0.5, 1.0, v1);
// 颜色
pcl::visualization::PointCloudColorHandlerCustom color(cloud, 255, 0, 0); // 转换到原点的点云相关
viewer.addPointCloud(cloud, color, "color", v1);
// 增加包围盒
viewer.addCube(bboxT, bboxQ, whd(0), whd(1), whd(2), "bbox", v1);
// 设置透明属性等






请到「今天看啥」查看全文


推荐文章
青年文摘  ·  多出去走走
2 天前
读书杂志  ·  中读年卡 | 中国纹样,美!
2 天前
深夜书屋  ·  入山问樵,入水问渔
3 天前
天池大数据科研平台  ·  单身狗情人节如何防虐?
7 年前
每天发现一家店  ·  这就是你为什么找不到对象的原因?
7 年前