点云的应用
点云(Point Cloud)就是一堆带 3D 坐标的点集合 ,本质是三维世界的数字化表达。
- 配准 → 把多帧点云拼成完整环境
- 分割 → 把环境分成不同东西
- 检测 → 找到感兴趣目标(车、人)
- 补全 → 让残缺物体变完整,方便识别 / 抓取
点云数据
- 无序性:只是点,排列顺序不影响
- 近密远疏:扫描与视角不同导致
- 非结构化数据,直接CNN有点难
- 要解决的任务就是如何对点云数据进行特征提取
open3D的IO与数据转换
open3D点云的读取和保存
import open3d as o3d
# 读取点云
pcd = o3d.io.read_point_cloud("./data/coalYard.pcd")
o3d.visualization.draw_geometries([pcd])
# 保存
o3d.io.write_point_cloud("./data/copy_coalYard.pcd", pcd)

open3D中rgbd的读取
colar_raw=o3d.io.read_image("./data/00000.jpg")
depth_raw=o3d.io.read_image("./data/00000.png")
rgbd_iamge=o3d.geometry.RGBDImage.create_from_color_and_depth(colar_raw,depth_raw)
plt.subplot(1,2,1)
plt.title("color")
plt.imshow(rgbd_iamge.color)
plt.subplot(1,2,2)
plt.title("depth")
plt.imshow(rgbd_iamge.depth)
plt.show()
pcd=o3d.geometry.PointCloud.create_from_rgbd_image(rgbd_iamge,o3d.camera.PinholeCameraIntrinsic(o3d.camera.PinholeCameraIntrinsicParameters.PrimeSenseDefault))
pcd.transform([[1,0,0,0],[0,-1,0,0],[0,0,-1,0],[0,0,0,-1]])
o3d.visualization.draw_geometries([pcd])


open3D中mesh的读取和保存
plymesh=o3d.io.read_triangle_mesh("./data/bunny10k.ply")
o3d.visualization.draw_geometries([plymesh])
o3d.io.write_triangle_mesh("./data/copy_bunny10k.ply",plymesh)

简单可视化
o3d.visualization.draw_geometries(geometry_list,window_name='Open3D',width=1920,height=1080,left=50,top=50,point_show_normal=False)
- geometry_list:带显示的所有实体组成的列表[1,1]
- window_name:待显示窗体的名字
- width:窗体的宽度
- height:窗体高度
- left:窗体左边缘的宽度
- top:窗体上边缘的宽度
- point_show_normal:是否显示法向量
paint_uniform_color([0,0,1]):设置点云颜色
ply=o3d.io.read_point_cloud("./data/tree.ply")
ply.paint_uniform_color([1,0,1])
o3d.visualization.draw_geometries([ply])

设置索引点颜色
ply=o3d.io.read_point_cloud("./data/tree.ply")
ply.paint_uniform_color([0,0,1])
#将点云颜色数据转化为numpy类型
color=np.array(ply.colors)
#获取需要修改颜色的索引
inlier=[i for i in range(0,color.shape[0]) if i%2==0]
color[inlier]=[1,0,0]
#再将颜色转回原来的类型
ply.colors=o3d.utility.Vector3dVector(color[:,:])
o3d.visualization.draw_geometries([ply])

可视化法线
ply=o3d.io.read_point_cloud("./data/tree.ply")
ply.paint_uniform_color([0,0,1])
#计算法线,搜索半径0.01m,只考虑领域内的30个点
ply.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.01,max_nn=30))
o3d.visualization.draw_geometries([ply],point_show_normal=True)

隐藏点移除
hidden_point_removal(camera_location,radius)
pcd=o3d.io.read_point_cloud("./data/building.ply")
# o3d.visualization.draw_geometries([pcd])
_,pt_map=pcd.hidden_point_removal([0,0,0.25],25)
pcd=pcd.select_by_index(pt_map)
o3d.visualization.draw_geometries([pcd])

pcd=o3d.io.read_point_cloud("./data/building.ply")
# o3d.visualization.draw_geometries([pcd])
_,pt_map=pcd.hidden_point_removal([0,0,0.25],25)
pcd=pcd.select_by_index(pt_map,invert=True)
o3d.visualization.draw_geometries([pcd])

其他(坐标系,点云大小)
pcd=o3d.io.read_point_cloud("./data/bunny.pcd")
#可视化句柄
viewer=o3d.visualization.Visualizer()
viewer.create_window(window_name="可视化",width=800,height=600)
#界面参数选项
opt=viewer.get_render_option()
#背景颜色
opt.background_color=np.asarray([0,0,0])
#点大小
opt.point_size=1
#添加坐标系
opt.show_coordinate_frame=True
#颜色
pcd.paint_uniform_color([1,0,0])
viewer.add_geometry(pcd)
#激活界面循环
viewer.run()

三维数据基本操作
欧拉角:用 3 个绕轴旋转的角度,描述物体在 3D 空间里的朝向。
万向锁(Gimbal Lock) 是使用欧拉角 表示三维旋转时,因旋转轴顺序嵌套而导致的固有数学奇异点 :当中间轴旋转至 ±90° 时,前后两个旋转轴会重合 ,导致丢失一个旋转自由度。
三维变换主要包括:平移,旋转,缩放
在open3D中,针对三维对象的变换主要有translate(平移),rotate(旋转),scale(缩放)和transform(变换矩阵)
pcd.translate([10, 0, 5]) # X+10, Y不动, Z+5
center = pcd.get_center() # 绕自身中心旋转(最常用)
import numpy as np
rot_mat = o3d.geometry.get_rotation_matrix_from_xyz([0, np.pi/2, 0])
pcd.rotate(rot_mat, center=pcd.get_center())
pcd.scale(2.0, center=pcd.get_center()) # 放大 2 倍
pcd.transform(4x4_matrix)
数据结构
八叉树
八叉树:一个非叶子结点包含8个子节点,每一个结点代表一个立方体区域,根节点表示整个三维图像
pcd=o3d.io.read_point_cloud("./data/people.ply")
pcd.paint_uniform_color([1,0,0])
#建立八叉树
octree=o3d.geometry.Octree(max_depth=10)#设置最大深度
octree.convert_from_point_cloud(pcd,size_expand=0.1)#size_expand表示叶子节点大小
#可视化
o3d.visualization.draw_geometries([octree],width=800,height=600)

K-D Tree
- 每次选一个轴(x→y→z 循环)
- 按这个轴的坐标把点分成左右两堆
- 递归分下去,直到叶子节点最终形成一棵二叉树,用来加速近邻搜索
点云的相交,求异
通过对比两个点云中每个点云之间的距离判断是否相同,如果小于阈值则视为相同
#读取原始点云和求异点云
pc1=o3d.io.read_point_cloud("./data/1.ply",remove_nan_points=True,remove_infinite_points=True)
pc2=o3d.io.read_point_cloud("./data/2.ply",remove_nan_points=True,remove_infinite_points=True)
#建立原始数据点云的k-d树结构
k_dtree=o3d.geometry.KDTreeFlann(pc1)
#ptsIDx是指灵片点云中相同部分的索引
ptsIDx=[]
#k是指k-nn搜索的参数,也就是说搜索另外一片点云中距离它最近点
k=1
#将距离阈值设置为0.1
dist_max=0.1
#得到点个数
points=np.array(pc2.points)
pointnum=points.shape[0]
#遍历点云
for i in range(0,pointnum):
#k返回个数
#idx返回点索引
#dist返回点距离
[k,idx,dist]=k_dtree.search_knn_vector_3d(pc2.points[i],k)
if dist[0]<dist_max:
ptsIDx.append(i)
same_part=pc2.select_by_index(ptsIDx)
diff_part=pc2.select_by_index(ptsIDx,invert=True)
same_part.paint_uniform_color([0,0,1])
diff_part.paint_uniform_color([1,0,0])
o3d.visualization.draw_geometries([same_part,diff_part])

常见点云处理方法
滤波
就是给点云 "美颜、去噪、去脏点":
- 去掉离群点(孤点、噪点)
- 去掉密度太低的散点
- 平滑点云表面
- 保留主体结构
统计滤波(去离群点,最常用)
计算每个点周围邻居的平均距离,距离明显偏大的点 → 判定为噪点 → 删除。
# 统计滤波
# nb_neighbors:每个点看多少个邻居
# std_ratio:几倍标准差之外算异常
pcd_filtered, ind = pcd.remove_statistical_outlier(
nb_neighbors=20,
std_ratio=2.0
)
半径滤波(去稀疏噪点)
在指定半径内,点数太少的点删掉。适合去掉漂浮的孤立点。
# 半径 0.1 范围内至少要有 5 个点,否则删掉
pcd_filtered, ind = pcd.remove_radius_outlier(
nb_points=5,
radius=0.1
)
双边滤波(Bilateral Filter)
核心思想
普通滤波(比如高斯滤波)只看空间距离 :离得近的点权重就大,结果是连边缘一起被磨平。
双边滤波多加了一个权重:
- 空间距离:越近权重越大(高斯核)
- 灰度 / 法向 / 深度差值:越像权重越大(相似核)
最终权重 = 空间权重 × 相似权重
结果:
- 平坦区域:点都差不多 → 被强力平滑
- 边缘区域:点差异大 → 权重变小 → 保留边缘,不模糊
引导滤波
在一个小的局部邻域内,滤波后的点(输出 q)与原始点(输入 p)存在线性关系。
qi=Ak⋅pi+bk
- qi: 滤波后的点坐标
- pi: 原始点坐标
- Ak: 3x3 线性变换矩阵(描述局部几何趋势)
- bk: 3x1 偏移向量
- k: 以点 i 为中心的局部邻域窗口
采样
点云数据量大,降低算法运行速度。在保留尽可能多特征的同时,压缩点云数据量。
随机采样
随机选择一些点保留,最简单的实现方法,当选择的随机数是均匀分布时,也可以理解成均匀采样。
import numpy as np
indices = np.random.choice(len(pcd.points), 1000, replace=False)
pcd_random = pcd.select_by_index(indices)
均匀采样
等间隔保留点,如每隔50个点索引保留一个
体素采样
空间八叉树的叶子结点里,选择一个点代表叶子里的所有点
基于曲率的采样
首先计算每一个点的曲率值,根据点云的曲率的值从大到小按照一定的比例来保留采样点。这种采样方法保留的点具有明显的特征,主要集中在点云场景的边缘、拐点等位置。
最远点采样
首先随机选择一个点,其次,在剩下的点中寻找最远的点,再去再剩下点中找到同时离这两个点最远的点,以此类推,直到满足采样点个数。
# 1. 体素
pcd1 = pcd.voxel_down_sample(0.05)
# 2. 均匀
pcd2 = pcd.uniform_down_sample(5)
# 3. 最远点采样
pcd3 = pcd.farthest_point_down_sample(2048)
法向降采样
在局部区域内,根据法线的方向,采样保留点法线方向较为分散的点,这样可以有效的保留点云的特征细节。

泊松采样
在计算机图形与点云处理中,通常特指 泊松圆盘采样(Poisson Disk Sampling, PDS)。
在随机分布的基础上,强制任意两点距离 ≥ 半径 r,避免聚集,实现均匀且自然的采样。
点云之间的距离
compute_point_cloud_distance()
dist=pcd.compute_point_cloud_distance(pcd1)
返回的是最近点云的距离
点云切片
已知三个点坐标可以通过公式Ax+By+Cz+D=0计算该平面,然后根据两个平行的平面切割出一段点云,遍历所有点云坐标,如果代入这两个平面坐标所得积小于则保留。
包围盒计算
简化几何形状,使用包围盒形状代替物体的真实形状以包围盒相交情况判断是否发生碰撞。
轴对齐包围盒 AABB(最简单、最快)
所有面都 平行于 X/Y/Z 轴不管点云怎么旋转,盒子永远贴坐标轴
pcd=o3d.io.read_point_cloud("./data/bunny10k.ply")
aabb=pcd.get_axis_aligned_bounding_box()
aabb.color=(1,0,0)
o3d.visualization.draw_geometries([pcd,aabb])

有向包围盒 OBB(最贴合、最实用)
自动旋转,紧紧包裹点云点云斜着、旋转,它也能贴合,不会留大片空白
obb=pcd.get_oriented_bounding_box()
obb.color=(0,0,1)
o3d.visualization.draw_geometries([pcd,obb])

质心计算
质心 = 所有点坐标的算术平均值 也就是点云的几何中心。质心 = 点云 X/Y/Z 分别求平均
把点云的质心平移到坐标原点 (0,0,0),就叫去重心化。
旋转时不 "乱跑"(最直观作用)
-
不去重心化:绕原点旋转 → 点云会甩飞、漂移、转圈飞走
-
去重心化:绕自身中心旋转 → 稳稳转动,位置不乱跑
直接计算质心
centroid = pcd.get_center()
import numpy as np#手动计算
points = np.asarray(pcd.points)
centroid = np.mean(points, axis=0)
点云重建
点云重建 = 从一堆离散 3D 点,恢复出连续、封闭、带表面的模型 最终结果一般是:网格(Mesh),也就是三角面片组成的 3D 模型。
显示建模方式
在已有的点上进行连线
Delaunay 三角剖分
核心思想
在平面 / 空间点集 中生成三角形,使得所有三角形的外接圆内部不包含任何其他点(空圆特性)。
原理
- 对一组离散点,生成三角网格
- 满足:任意三角形的外接圆内没有其他点
- 结果:
- 三角形尽量接近等边,避免狭长三角形
- 是最优、最均匀的三角剖分
Ball Pivoting Algorithm(球旋转 / 球滚动算法)
核心思想
用一个指定半径的小球 在点云表面 "滚动",每碰到3 个点就生成一个三角面。
原理
- 从一个种子三角形开始
- 让半径为 r 的球贴着已有的面滚动
- 球碰到新的 3 个点 ,且这些点都在球表面上
- 形成新的三角面片,并继续滚动
- 直到滚遍整个点云表面
Alpha Shapes
核心思想
用一个半径为 α 的圆 / 球 在点云外侧滚动,扫过的边界形成凹、凸都能表达的形状。
原理
- 给定参数 α(控制形状松紧)
- 用半径 α 的圆 / 球去 "包裹" 点云
- 两个点之间形成边,当存在一个过这两点的 α 球内部不含其他点
- 所有这样的边连接起来,就是 alpha shape
- α 很大 → 接近凸包(包得很松)
- α 变小 → 形状更贴合,能出现凹陷
- α 极小 → 完全贴合每个点
隐式建模方式
根据已有的点生成拟合函数,在函数中确定点
泊松重建
泊松重建 = 利用点云法向构建向量场 → 解泊松方程 → 生成光滑、封闭、水密的三角网格。
体素化
体素 = 三维空间里的小立方体,是 3D 版的 "像素"。
体素结构简单、速度快、适合降采样与 3D 卷积,但精度低、占内存、边缘锯齿明显,不适合高精度曲面表达。
体素化基本步骤
-
确定体素大小 voxel_size
-
计算点云的包围盒 AABB,确定空间范围
-
把每个点映射到对应的网格坐标:i=⌊(x−xmin)/voxel_size⌋j,k 同理
-
对每个体素内的点做处理:
- 保留 / 删除
- 降采样(取均值、重心)
- 标记占用(occupied /free)
体素降采样
pcd.voxel_down_sample(voxel_size=0.05)
生成体素网格
voxel_grid = o3d.geometry.VoxelGrid.create_from_point_cloud(pcd, voxel_size=0.05)
显示体素
o3d.visualization.draw_geometries([voxel_grid])
点云分割
聚类:将相似特征的点分为一类
(语义)分割:将点赋予语义的标签
传统方法
Hough(平面分割)
K-means
DBSCAN
DBSCAN 是基于密度的聚类算法,通过邻域半径 eps 和最小点数 min_samples,自动聚出任意形状簇并识别噪声,无需预设类别数。
- **核心点 (Core Point)**邻域内点数 ≥ min_samples
- **边界点 (Border Point)**邻域内点数不足,但在某个核心点邻域内
- **噪声点 (Noise)**既不是核心点,也不在任何核心点邻域内 → 标记为 -1
区域生长算法
RANSAC
随机选少量样本 → 建模型 → 验证多数 → 迭代出最优。
RANSAC 是一个迭代过程,不管解什么(点云配准、位姿估计、拟合直线),流程都一样:
-
**随机采样 (Random Sample)**从所有数据中,随机选最少的、能构建模型的样本点(例如直线选 2 点,位姿选 3 点)。
-
**模型拟合 (Fit Model)**用这组样本,计算出一个完整的模型参数(比如求直线方程 y=ax+b 或旋转平移矩阵 R,t)。
-
验证与投票 (Validate & Vote) 把这个模型放到所有数据点上测试。
- 内点 (Inliers):误差很小、符合模型的点
- 外点 (Outliers):误差很大、不符合模型的噪点统计有多少个内点。
-
**更新最优模型 (Update Best Model)**如果这一轮的内点数量是目前最多的,就把这个模型记为 "当前最优"。
-
迭代收敛 (Iterate) 重复 N 次上述过程。最终选出内点最多、误差最小的那个模型。
深度学习方法
PointNet
3DCNN
PointNet++
PointNet
PointNet = 共享 MLP + T-Net + Max Pooling 用对称函数解决无序,用变换对齐解决旋转,用全局 + 局部拼接做分割,是点云深度学习的基础骨架。
先将数据点通过多层感知机进行升维,然后利用max获取特征
PointNet++
PointNet++ 最核心的改进是引入了层次化(Hierarchical)的局部特征提取机制,解决了原始 PointNet 无法有效捕捉点云局部结构、对密度变化敏感、分割精度不高的三大痛点。
- 基于半径选择局部区域(类似于得到很多个簇)
- 针对得到的每个区域进行特征提取(卷积)
- 要解决的问题:如何选择区域(簇中心点选择)
最远点采样:选择离已采样点距离最远的点,让采样尽可能覆盖全局
采样:选择中心点,分组:在簇中选择16个样本,不够时,复制离中心点最近的样本进行不足,富余时,按距离排序,选择最近的16个样本
1.层次化特征提取(Set Abstraction, SA)
这是最根本的改进,模拟 CNN 的多层卷积,逐层扩大感受野。SA 模块 = 采样 (Sampling) → 分组 (Grouping) → 特征提取 (PointNet)
- 采样 (Sampling) :用 FPS (最远点采样) 选出覆盖全局的中心点,确保空间分布均匀。
- 分组 (Grouping) :用 Ball Query (球查询) 为每个中心点找邻域点,形成局部小簇。
- 特征提取 :在每个局部小簇上运行一个微型 PointNet ,提取局部特征。效果 :从点 → 局部特征 → 更大区域特征 → 全局特征,实现从细到粗的特征金字塔。
2. 多尺度 / 多分辨率特征(MSG & MRG)
专门解决点云密度不均匀问题。
- MSG (Multi-Scale Grouping) :同一层用多个不同半径提取特征(小半径抓细节,大半径看全局)。
- MRG (Multi-Resolution Grouping) :融合不同层级的特征(浅层细节 + 深层语义),效率更高。
3. 特征传播(Feature Propagation, FP)
专为点云分割任务设计。
- 插值上采样:将下采样后的高层特征,通过距离加权插值,还原到原始点云的每一个点。
- 跳跃连接 (Skip Connection) :将底层精细特征 与高层语义特征 拼接,精准恢复边缘和细节。效果:分割精度大幅提升,能清晰区分物体边界。
4. 密度自适应(Density-Aware)
根据点云局部密度动态调整邻域半径:
- 稠密区:用小半径,避免特征混淆。
- 稀疏区:用大半径,保证有足够点提取特征。
特征点提取
从一堆点里,找出少数稳定、有代表性、能用来匹配 / 配准 / 识别的点。
- 不是随便的点
- 是几何信息丰富的点:角点、边缘点、曲率大的点、法向突变点
- 特征点少但精,能大幅减少计算量,提升匹配鲁棒性
点云特征点提取常用方法
1. 基于曲率的提取
- 计算每个点的曲率
- 曲率越大,越可能是特征点
- 简单、快,但对噪声敏感
2. 基于法向变化
- 看点周围法向变化是否剧烈
- 变化大 = 特征明显
- 常用于边缘、棱角检测
3. ISS 特征点
Intrinsic Shape Signatures 内在形状特征
- 对点 p,找邻域
- 构造协方差矩阵,求三个特征值 λ₁≥λ₂≥λ₃
- 特征值比值越小,说明点越稳定、越 "棱角分明"
- 按阈值筛选出 ISS 关键点
优点:
- 对噪声鲁棒
- 均匀分布
- 配准常用
4. SIFT 3D / Harris 3D
- 把 2D SIFT、Harris 角点扩展到 3D
- 通过梯度、曲率找关键点
- 精度高,但计算慢
5. PFH
- 全连接点对 :对中心点
p₀,取半径r内的所有邻点。不仅计算中心点与邻点,还计算所有邻点之间的关系(全连接)。 - 4 个几何特征 :对每一对点及其法线,计算 α, φ, θ, d 四个值,描述它们的相对姿态。
- 统计直方图 :将成千上万对的特征值统计成直方图,作为该点的唯一特征签名。
- 特性 :旋转 / 平移不变、对噪声鲁棒、区分度极强。
点云配准
基于点特征的配准
ICP,color ICP,Trimed ICP
标准 ICP 步骤
-
对应点查找 对 P 中每个点,在 Q 中找最近点作为匹配对。
-
去除错误匹配用距离阈值、RANSAC 剔除离群点、误匹配。
-
位姿估计 利用匹配点对,用 SVD 分解 求解最优旋转平移矩阵 R,t,最小化误差:
-
迭代更新 用刚算出的 R,t 变换源点云,再回到第 1 步,直到误差不再减小,算法收敛。
点到点 ICP 最小化欧氏距离,简单但收敛慢;
点到面 ICP 最小化点到平面垂直距离,需法线,收敛更快、精度更高、鲁棒性更强。
#获取数据
source_cloud=o3d.io.read_point_cloud("./data/cloud_bin_0.pcd")
target_cloud=o3d.io.read_point_cloud("./data/cloud_bin_1.pcd")
source_cloud.paint_uniform_color([1,0.706,0])
target_cloud.paint_uniform_color([0,0.651,0.929])
threshold=0.02#RMSE残差阈值
#初始位姿
tran_init=np.asarray([[0.862,0.011,-0.507,0.5],
[-0.139,0.967,-0.215,0.7],
[0.487,0.255,0.835,-1.4],
[0.0,0.0,0.0,1.0]])
#显示未配准的点云
o3d.visualization.draw_geometries([source_cloud,target_cloud])
o3d.visualization.draw_geometries([source_cloud,target_cloud],
zoom=0.4459,
front=[0.9288,-0.2951,-0.2242],
lookat=[1.6784,2.0612,1.4451],
up=[-0.3402,-0.9189,-0.1996])
#点到面的ICP
result=o3d.pipelines.registration.registration_icp(source_cloud,target_cloud,threshold,tran_init,o3d.pipelines.registration.TransformationEstimationPointToPlane())
#显示配准结果
source_cloud.transform(result.transformation)
o3d.visualization.draw_geometries([source_cloud,target_cloud])


SAC-IA (Sample Consensus Initial Alignment)
- 预处理:下采样 + 计算特征
-
对源点云 P 和目标点云 Q 体素下采样,减少点数。
-
计算每个点的 FPFH 特征(33 维向量,对旋转平移不变)。
- 随机采样(生成假设)
-
从源点云 P 中随机选 3 个点 (最小集),且两两距离足够远。
-
对每个采样点,在目标点云 Q 中找 FPFH 特征最相似的 N 个候选点。
-
组合这些候选点,生成多组假设的匹配点对。
- 模型评估(选最优)
-
对每组假设匹配,用 SVD 分解 计算变换矩阵 (R,t)。
-
把源点云用该矩阵变换,统计内点数量(距离小于阈值的点)。
-
保留内点最多、误差最小的变换矩阵。
- 局部优化(精调)
- 用最优变换的内点集,通过 LM(Levenberg-Marquardt)算法 精调位姿。