最近在做点云中的OBB算法工作,给定一个物体的点云,快速估计出物体的有向包围框。算法的主要原理是PCA(Principal Component Analysis,主成分分析),是数据分析的一种重要技术。它可以将高维数据投影到低维空间,找到数据的主要结构。在点云场景下,PCA用来提取点云的主成分方向和法向量方向。
PCL库已经实现好了OBB相关算法(pcl::MomentOfInertiaEstimation),MomentOfInertiaEstimation 类可以计算点云的 惯性矩、重心、主方向,同时可以得到 AABB(轴对齐包围盒)和OBB(方向对齐包围盒)。它内部会用点云的质心和协方差矩阵来求解主方向,从而得到有向包围盒。
注意1:该接口为模板函数,因为没有预编译pcl::PointXYZRGB类型的版本,编译时会报错。建议使用pcl::PointXYZ类型
注意2:该接口计算出的OBB不一定是最小包围盒
使用说明
1 2 3 4 5 6 7 8 9 10 11 12
| #include <pcl/features/moment_of_inertia_estimation.h>
pcl::MomentOfInertiaEstimation<pcl::PointXYZ> feature_extractor; feature_extractor.setInputCloud(cloud_filtered); feature_extractor.compute();
pcl::PointXYZ min_point_OBB, max_point_OBB, position_OBB; Eigen::Matrix3f rotational_matrix_OBB; feature_extractor.getOBB(min_point_OBB, max_point_OBB, position_OBB, rotational_matrix_OBB);
|
计算结果:
min_point_OBB :在 OBB坐标系下 的最小点坐标(即包围盒的八个顶点之一)。
max_point_OBB:在 OBB坐标系下 的最大点坐标(即包围盒的八个顶点之一)。
position_OBB:OBB 的中心点(世界坐标系下)。同时也是OBB坐标系到世界坐标系的平移量。
rotational_matrix_OBB:OBB 的旋转矩阵,用于把 OBB坐标系 转换到 世界坐标系。
有了OBB坐标系到世界坐标系的旋转平移矩阵后,就可以把OBB变换到世界坐标系下去包围住点云,或者将点云逆变换到OBB坐标系 下填入包围盒内。在可视化窗口内绘制包围盒,可以使用pcl::visualization::PCLVisualizer类的addCube函数,addCube有三种重载形式。
addCube(x_min, x_max, y_min, y_max, z_min, z_max, r, g, b, id);
优点:简单,直接用坐标画。
缺点:无法指定旋转,立方体总是平行于世界坐标轴。
addCube(const pcl::ModelCoefficients &coefficients, id, viewport);
addCube(translation, rotation, width, height, depth, id, viewport);
PCL库OBB(体素下采样加速)完整代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/filters/voxel_grid.h> #include <pcl/features/moment_of_inertia_estimation.h> #include <pcl/visualization/pcl_visualizer.h> #include <Eigen/Dense> #include <iostream>
int main(int argc, char** argv) { pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_rgb(new pcl::PointCloud<pcl::PointXYZRGB>); pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_xyz(new pcl::PointCloud<pcl::PointXYZ>);
if (argc < 2) { std::cerr << "Usage: " << argv[0] << " cloud.pcd" << std::endl; return -1; }
if (pcl::io::loadPCDFile(argv[1], *cloud_rgb) == -1) { PCL_ERROR("Couldn't read file\n"); return -1; }
pcl::copyPointCloud(*cloud_rgb, *cloud_xyz);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>); pcl::VoxelGrid<pcl::PointXYZ> vg; vg.setInputCloud(cloud_xyz); vg.setLeafSize(0.01f, 0.01f, 0.01f); vg.filter(*cloud_filtered);
pcl::MomentOfInertiaEstimation<pcl::PointXYZ> feature_extractor; feature_extractor.setInputCloud(cloud_filtered); feature_extractor.compute();
pcl::PointXYZ min_point_OBB, max_point_OBB, position_OBB; Eigen::Matrix3f rotational_matrix_OBB; feature_extractor.getOBB(min_point_OBB, max_point_OBB, position_OBB, rotational_matrix_OBB);
float length = max_point_OBB.x - min_point_OBB.x; float width = max_point_OBB.y - min_point_OBB.y; float height = max_point_OBB.z - min_point_OBB.z;
Eigen::Vector3f position(position_OBB.x, position_OBB.y, position_OBB.z); Eigen::Matrix3f R = rotational_matrix_OBB;
std::cout << "OBB 尺寸: 长=" << length << " 宽=" << width << " 高=" << height << std::endl;
std::cout << "旋转矩阵 R:\n" << R << std::endl; std::cout << "平移向量 t: " << position.transpose() << std::endl;
pcl::visualization::PCLVisualizer::Ptr viewer(new pcl::visualization::PCLVisualizer("OBB Viewer")); viewer->setBackgroundColor(0, 0, 0); viewer->addPointCloud<pcl::PointXYZRGB>(cloud_rgb, "cloud");
Eigen::Vector3f pos = position; Eigen::Quaternionf q(R);
viewer->addCube(pos, q, length, width, height, "OBB"); viewer->setShapeRenderingProperties(pcl::visualization::PCL_VISUALIZER_REPRESENTATION, pcl::visualization::PCL_VISUALIZER_REPRESENTATION_WIREFRAME, "OBB"); viewer->setShapeRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 1.0, 0.0, 0.0, "OBB");
viewer->addCoordinateSystem(0.1);
while (!viewer->wasStopped()) { viewer->spinOnce(10); }
return 0; }
|
基于凸包的最小 OBB 计算(体素降采样加速版)示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
| #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/visualization/pcl_visualizer.h> #include <pcl/common/common.h> #include <pcl/common/transforms.h> #include <pcl/filters/voxel_grid.h> #include <pcl/surface/convex_hull.h> #include <Eigen/Dense> #include <iostream>
int main(int argc, char** argv) { if (argc < 2) { std::cerr << "Usage: " << argv[0] << " cloud.pcd" << std::endl; return -1; }
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>); if (pcl::io::loadPCDFile(argv[1], *cloud) == -1) { PCL_ERROR("Couldn't read file\n"); return -1; }
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>); pcl::VoxelGrid<pcl::PointXYZ> vg; vg.setInputCloud(cloud); vg.setLeafSize(0.005f, 0.005f, 0.005f); vg.filter(*cloud_filtered);
pcl::ConvexHull<pcl::PointXYZ> chull; chull.setInputCloud(cloud_filtered); pcl::PointCloud<pcl::PointXYZ>::Ptr hull(new pcl::PointCloud<pcl::PointXYZ>); chull.reconstruct(*hull);
Eigen::Vector3f best_dims; Eigen::Matrix3f best_rot = Eigen::Matrix3f::Identity(); Eigen::Vector3f best_pos; float min_volume = std::numeric_limits<float>::max();
for (size_t i = 0; i < hull->size(); ++i) { for (size_t j = i + 1; j < hull->size(); ++j) { for (size_t k = j + 1; k < hull->size(); ++k) { Eigen::Vector3f v1 = hull->points[j].getVector3fMap() - hull->points[i].getVector3fMap(); Eigen::Vector3f v2 = hull->points[k].getVector3fMap() - hull->points[i].getVector3fMap(); Eigen::Vector3f axis1 = v1.normalized(); Eigen::Vector3f axis2 = v2.normalized(); Eigen::Vector3f axis3 = axis1.cross(axis2).normalized(); if (axis3.hasNaN()) continue; axis2 = axis3.cross(axis1);
Eigen::Matrix3f R; R.col(0) = axis1; R.col(1) = axis2; R.col(2) = axis3;
Eigen::Matrix4f transform = Eigen::Matrix4f::Identity(); transform.block<3, 3>(0, 0) = R.transpose(); pcl::PointCloud<pcl::PointXYZ> rotated; pcl::transformPointCloud(*cloud_filtered, rotated, transform);
pcl::PointXYZ min_pt, max_pt; pcl::getMinMax3D(rotated, min_pt, max_pt); Eigen::Vector3f dims = (max_pt.getVector3fMap() - min_pt.getVector3fMap()); float volume = dims.prod();
if (volume < min_volume) { min_volume = volume; best_dims = dims; best_rot = R; best_pos = R * ((max_pt.getVector3fMap() + min_pt.getVector3fMap()) * 0.5f); } } } }
std::cout << "最小 OBB 尺寸: " << best_dims.transpose() << std::endl; std::cout << "旋转矩阵:\n" << best_rot << std::endl; std::cout << "中心位置: " << best_pos.transpose() << std::endl;
pcl::visualization::PCLVisualizer::Ptr viewer(new pcl::visualization::PCLVisualizer("Min OBB Viewer")); viewer->setBackgroundColor(0, 0, 0); viewer->addPointCloud<pcl::PointXYZ>(cloud, "cloud");
Eigen::Quaternionf q(best_rot); viewer->addCube(best_pos, q, best_dims.x(), best_dims.y(), best_dims.z(), "OBB"); viewer->setShapeRenderingProperties(pcl::visualization::PCL_VISUALIZER_REPRESENTATION, pcl::visualization::PCL_VISUALIZER_REPRESENTATION_WIREFRAME, "OBB"); viewer->setShapeRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 1.0, 0.0, 0.0, "OBB");
while (!viewer->wasStopped()) viewer->spinOnce(10);
return 0; }
|