点击下方卡片,关注「3D视觉工坊」公众号
选择星标,干货第一时间送达

来源:3D视觉工坊

如果要深入学习点云方面的知识,可以关注我们工坊推出的课程:

PCL点云处理库-QT-VTK 高阶实践班

任务定义

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

e85d708dd0d77c6db1f573038288b288.png bf54287bf88441049afb50c172fcb2cf.png

目前主要有两种包围盒:

  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<Eigen::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" << center << endl;
cout << "特征值va(3x1):n" << values << endl;
cout << "特征向量ve(3x3):n" << vectors << endl;
}

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

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" << c1 << endl;
// 逆向变换矩阵
Eigen::Affine3f RT_inv_aff(RT_inv); // 专门的RT矩阵,封装有很多操作
pcl::transformPoint(c1, c, RT_inv_aff); // 逆向变换型心
cout << "原始型心c(3x1)n" << c << endl; // 打印结果
// 计算窗口大小
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:" << whd(0) << endl;
cout << "hieght:" << whd(1) << endl;
cout << "depth:" << whd(2) << endl;
cout << "scale:" << sc1 << endl;
// 变换后点云box框的的位姿: 因为是对齐坐标了,因此是单位矩阵
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<PointT> color(cloud, 255, 0, 0); // 转换到原点的点云相关
viewer.addPointCloud(cloud, color, "color", v1);
// 增加包围盒
viewer.addCube(bboxT, bboxQ, whd(0), whd(1), whd(2), "bbox", v1);
// 设置透明属性等
viewer.setShapeRenderingProperties(pcl::visualization::PCL_VISUALIZER_REPRESENTATION, pcl::visualization::PCL_VISUALIZER_REPRESENTATION_WIREFRAME, "bbox");
viewer.setShapeRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 1.0, 0.0, 0.0, "bbox");
viewer.addText("origin", 10, 10, "bbox_txt", v1);
// 坐标轴
viewer.addArrow(PX, O, 1.0, 0., 0., false, "arrow_x", v1);
viewer.addArrow(PY, O, 0., 1.0, 0., false, "arrow_y", v1);
viewer.addArrow(PZ, O, 0., 0., 1.0, false, "arrow_z", v1);
// 变换点云
int v2(0);
viewer.createViewPort(0.5, 0.0, 1.0, 1.0, v2);
// 颜色
pcl::visualization::PointCloudColorHandlerCustom<PointT> color_trans(cloud_trans, 0, 255, 0); // 转换到原点的点云相关
viewer.addPointCloud(cloud_trans, color_trans, "color_trans", v2);
// 增加包围盒
viewer.addCube(bboxT1, bboxQ1, whd1(0), whd1(1), whd1(2), "bbox1", v2);
viewer.setShapeRenderingProperties(pcl::visualization::PCL_VISUALIZER_REPRESENTATION, pcl::visualization::PCL_VISUALIZER_REPRESENTATION_WIREFRAME, "bbox1");
viewer.setShapeRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 0.0, 1.0, 0.0, "bbox1");
viewer.addText("transformed", 10, 10, "bbox1_txt", v2);
// 增加坐标轴
viewer.addArrow(PX_1, O_1, 1.0, 0., 0., false, "arrow_x1", v2);
viewer.addArrow(PY_1, O_1, 0., 1.0, 0., false, "arrow_y1", v2);
viewer.addArrow(PZ_1, O_1, 0., 0., 1.0, false, "arrow_z1", v2);
// 显示点云
while(!viewer.wasStopped()) {
viewer.spinOnce(100);
this_thread::sleep_for(100ms);
}
viewer.close();

结果

1671bd156566802d69f7c12c75af829d.png

本文仅做学术分享,如有侵权,请联系删文。

54b750cc9380f66547d1a5c0455951e9.jpeg

b35fa7a3e590fd7645ed08626e8cacbb.jpeg

3D视觉交流群,成立啦!

目前我们已经建立了3D视觉方向多个社群,包括2D计算机视觉、最前沿、工业3D视觉、SLAM、自动驾驶、三维重建、无人机等方向,细分群包括:

工业3D视觉:相机标定、立体匹配、三维点云、结构光、机械臂抓取、缺陷检测、6D位姿估计、相位偏折术、Halcon、摄影测量、阵列相机、光度立体视觉等。

SLAM:视觉SLAM、激光SLAM、语义SLAM、滤波算法、多传感器融合、多传感器标定、动态SLAM、MOT SLAM、NeRF SLAM、机器人导航等。

自动驾驶:深度估计、Transformer、毫米波|激光雷达|视觉摄像头传感器、多传感器标定、多传感器融合、3D目标检测、路径规划、轨迹预测、3D点云分割、模型部署、车道线检测、Occupancy、目标跟踪等。

三维重建:3DGS、NeRF、多视图几何、OpenMVS、MVSNet、colmap、纹理贴图等

无人机:四旋翼建模、无人机飞控等

2D计算机视觉:图像分类/分割、目标/检测、医学影像、GAN、OCR、2D缺陷检测、遥感测绘、超分辨率、人脸检测、行为识别、模型量化剪枝、迁移学习、人体姿态估计等

最前沿:具身智能、大模型、Mamba、扩散模型、图像/视频生成等

除了这些,还有求职硬件选型视觉产品落地、产品、行业新闻等交流群

添加小助理: cv3d001,备注:研究方向+学校/公司+昵称(如3D点云+清华+小草莓), 拉你入群。

1fcf9f61744b0f6a01f16e775b3874de.jpeg
▲长按扫码添加助理:cv3d001
3D视觉工坊知识星球

「3D视觉从入门到精通」知识星球(点开有惊喜),已沉淀6年,星球内资料包括:秘制视频课程近20门(包括结构光三维重建、相机标定、SLAM、深度估计、3D目标检测、3DGS顶会带读课程、三维点云等)、项目对接3D视觉学习路线总结最新顶会论文&代码3D视觉行业最新模组3D视觉优质源码汇总书籍推荐编程基础&学习工具实战项目&作业求职招聘&面经&面试题等等。欢迎加入3D视觉从入门到精通知识星球,一起学习进步。

6bd8e865c63e0a349d27a57dc3d6730a.jpeg

▲长按扫码加入星球
3D视觉工坊官网:www.3dcver.com

大模型、扩散模型、具身智能、3DGS、NeRF结构光、相位偏折术、机械臂抓取、点云实战、Open3D、缺陷检测、BEV感知、Occupancy、Transformer、模型部署、3D目标检测、深度估计、多传感器标定、规划与控制、无人机仿真C++、三维视觉python、dToF、相机标定、ROS2机器人控制规划、LeGo-LAOM、多模态融合SLAM、LOAM-SLAM、室内室外SLAM、VINS-Fusion、ORB-SLAM3、MVSNet三维重建、colmap、线面结构光、硬件结构光扫描仪等。

cad7f227114478cbc9348b01ecdc1394.jpeg
▲ 长按扫码学习3D视觉精品课程
3D视觉模组选型:www.3dcver.com

caaf54afed77f455a0d6cf92fb7766a2.png

—  —

点这里👇关注我,记得标星哦~

一键三连「分享」、「点赞」和「在看」

3D视觉科技前沿进展日日相见 ~ 

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐