第二个实战项目:处理 3D 点云,做旋转和平移。这是计算机图形学、机器人、SLAM 的基础。本项目综合运用:vector、几何模块、刚体变换。
37.1 什么是点云
点云(point cloud)是一堆三维点的集合。激光雷达扫描、3D 扫描仪输出的就是点云。机器人、自动驾驶靠处理点云来"看"世界。
在 Eigen 里,一个点就是一个Vector3d,点云就是std::vector<Vector3d>。
37.2 项目目标
完成两个任务:
- 构造点云:生成一个立方体的 8 个顶点
- 变换点云:绕 Z 轴旋转 90°,再平移,观察点云的变化
- 统计点云:计算质心(平均点)
37.3 完整代码
#include<Eigen/Dense>#include<Eigen/Geometry>#include<iostream>#include<vector>intmain(){constdoublePI=3.14159265358979;// ---- 任务 1:构造立方体的 8 个顶点 ----std::vector<Eigen::Vector3d>cloud;for(intx=0;x<=1;x++)for(inty=0;y<=1;y++)for(intz=0;z<=1;z++)cloud.push_back(Eigen::Vector3d(x,y,z));std::cout<<"点云有 "<<cloud.size()<<" 个点"<<std::endl;// ---- 任务 2:变换(绕 Z 转 90° 再平移)----Eigen::Isometry3d T=Eigen::Isometry3d::Identity();T.rotate(Eigen::AngleAxisd(PI/2,Eigen::Vector3d::UnitZ()));T.pretranslate(Eigen::Vector3d(10,0,0));std::cout<<"变换前 -> 变换后:"<<std::endl;for(constauto&p:cloud){Eigen::Vector3d p2=T*p;std::cout<<" "<<p.transpose()<<" -> "<<p2.transpose()<<std::endl;}// ---- 任务 3:计算质心 ----Eigen::Vector3d centroid=Eigen::Vector3d::Zero();for(constauto&p:cloud){centroid+=p;}centroid/=cloud.size();std::cout<<"质心 = "<<centroid.transpose()<<std::endl;// (0.5, 0.5, 0.5)return0;}编译运行:存成ch37.cpp,按附录 C.3 的命令:
g++-std=c++17-O2-I"你的Eigen目录"ch37.cpp-och37.exe ./ch37.exe实际输出:
点云有 8 个点 变换前 -> 变换后: 0 0 0 -> 10 0 0 0 0 1 -> 10 0 1 0 1 0 -> 9 1.61554e-15 0 0 1 1 -> 9 1.61554e-15 1 1 0 0 -> 10 1 0 1 0 1 -> 10 1 1 1 1 0 -> 9 1 0 1 1 1 -> 9 1 1 质心 = 0.5 0.5 0.51.61554e-15是 0 的浮点残渣(第 21 章末尾讲过),不用管它。
37.4 运行结果解读
- 立方体 8 个顶点是 (0/1, 0/1, 0/1) 的所有组合
- 绕 Z 转 90°:X 轴方向转到 Y 轴((1,0,0) → (0,1,0))
- 平移 (10,0,0):所有点 X 坐标 +10
- 质心:8 个顶点的平均 = (0.5, 0.5, 0.5)
37.5 拓展练习
拓展 1:计算点云的包围盒
包围盒(bounding box)= 各方向上的最小值和最大值,用来快速判断点云占了多大空间。
接着第 37.3 节的程序做:把下面几行粘贴到ch37.cpp里打印质心那一行之后、return 0;之前(此处的cloud就是前面构造好的 8 个顶点):
// 包围盒 = 所有坐标的最小值和最大值Eigen::Vector3d minPt=cloud[0],maxPt=cloud[0];for(constauto&p:cloud){minPt=minPt.cwiseMin(p);// 逐元素取最小maxPt=maxPt.cwiseMax(p);// 逐元素取最大}std::cout<<"包围盒:"<<minPt.transpose()<<" 到 "<<maxPt.transpose()<<std::endl;重新编译运行,最后一行新增:
包围盒:0 0 0 到 1 1 1立方体的 8 个顶点正好落在 0 和 1 上,所以包围盒是 (0,0,0) 到 (1,1,1)。注意这段用的是原始点云(未变换),如果换成T * p的结果,包围盒会跟着平移走。
拓展 2:两点云配准(ICP 的思想)
机器人里常要做点云配准:找变换 T 使两个点云对齐。经典算法 ICP 的核心就是:
- 对每个点找最近对应点
- 用最小二乘求最优刚体变换
- 迭代直到收敛
这综合了本项目的刚体变换和第 36 章的最小二乘,是值得深入的方向。
37.6 学完这个项目你掌握了什么
- 用
vector<Eigen::Vector3d>表示点云 - 用
Isometry3d做刚体变换 - 用
cwiseMin/cwiseMax做逐元素运算 - 用循环 + 归约做统计(质心)
这些正是机器人、SLAM、3D 图形领域天天在用的操作。
37.7 小结
- 点云 =
vector<Vector3d>。 - 刚体变换 = 旋转 + 平移,用
Isometry3d。 - 质心、包围盒等统计都可用 Eigen 简洁实现。
- 点云配准(ICP)是更深入的实战方向。
练习题
- 生成一个 2×2×2 的网格点云(步长 0.5),打印点数。
- 对点云做一次刚体变换,观察变化。
- 计算点云的质心和包围盒。
- 用
cwiseMin/cwiseMax实现包围盒计算。 - 思考:怎么判断两个点云是否"对齐"?