【编程实践】点云曲率计算与可视化
基于Open3D和kNN的点云曲率计算与可视化,按下面指标定义的曲率
输入数据
- 输入数据
点云文件ply或pcd,加载为点云对象
指标定义
- 此处曲率定义:采用基于点云PCA协方差分析的局部曲率估计方法。对于每个点,其曲率定义为:
κ=λ1λ1+λ2+λ3\kappa = \frac{\lambda_{1}}{\lambda_{1}+\lambda_{2}+\lambda_{3}}κ=λ1+λ2+λ3λ1
其中λ1\lambda_{1}λ1是协方差矩阵最小特征值,反映法向方向上的变化程度。该指标不具备严格几何意义的主曲率属性,但在点云中广泛应用于反映局部几何尖锐程度和表面变化趋势。
数学处理过程
- 邻域搜索(K-Nearest Neighbour)
- 对每个点pip_{i}pi:
-
- 使用KD树查找其最近的k个临近点,点本身也被包含在邻域中;
-
- 得到临近点集N(pi)={pi1,pi2,...,pik}\mathscr{N}(p_i)=\{p_{i1},p_{i2},...,p_{ik}\}N(pi)={pi1,pi2,...,pik} ,对于某个点pip_{i}pi有k个邻接点。
-
协方差矩阵计算
对于每个邻域点集N(pi)\mathscr{N}(p_i)N(pi),计算其三维坐标协方差矩阵:
Ci=Cov(N(pi))∈R3×3C_i = Cov(\mathscr{N}(p_i)) \in \mathbb{R^{3 \times 3}} Ci=Cov(N(pi))∈R3×3
即:
Ci=1k∑j=1k(pij−p‾i)(pij−p‾i)TC_i = \frac{1}{k}\sum^{k}_{j=1}(p_{ij}-\overline{p}_{i})(p_{ij}-\overline{p}_{i})^{T}Ci=k1j=1∑k(pij−pi)(pij−pi)T
其中,p‾i\overline{p}_{i}pi是邻域点的均值向量。 -
特征值分级(Eigen Decomposition)
计算协方差矩阵的特征值λ1\lambda_{1}λ1、λ2\lambda_{2}λ2、λ3\lambda_{3}λ3并排序:
λ1≤λ2≤λ3\lambda_{1} \leq \lambda_{2} \leq \lambda_{3}λ1≤λ2≤λ3
特征值代表该邻域点云在主轴方向上的分布方差。
注:此处写的是数学上的标准形式1/k,属于有偏估计;实际适用了numpy的无偏估计1/(k-1). -
曲率估计
使用下面公式估算每个点的局部曲率:
κi=λ1∑(λi)+ϵ\kappa_{i} = \frac{\lambda_{1}}{\sum(\lambda_{i})+ \epsilon}κi=∑(λi)+ϵλ1
其中,λ1\lambda_{1}λ1为最小特征值,表示法向方向上的变化;分母为总变化量,归一化;ϵ\epsilonϵ为防止分母为0加的小常数。
曲率可视化(颜色映射)
- 对所有点的曲率值κi\kappa_{i}κi进行归一化:
κinorm=κi−min(κ)max(κ)−min(κ)+ϵ\kappa^{\text{norm}}_{i} = \frac{\kappa_{i}-\min(\kappa)}{\max{(\kappa)}-\min{(\kappa)}+\epsilon}κinorm=max(κ)−min(κ)+ϵκi−min(κ) - 将归一化值使用Matplotlib的 jet 颜色映射为RGB颜色:
colori=jet(κinorm)\text{color}_{i} = jet(\kappa^{\text{norm}}_{i})colori=jet(κinorm) - 将颜色赋值回Open3D点云对象用于可视化。
import open3d as o3d
import numpy as np
import matplotlib.pyplot as plt# 加载点云
pcd = o3d.io.read_point_cloud("Result.ply")
# 构建KD树
pcd_tree = o3d.geometry.KDTreeFlann(pcd)
# ---- 曲率计算函数(基于kNN) ----
def compute_curvature(pcd, knn=30):points = np.asarray(pcd.points)curvatures = []for i in range(len(points)):_, idx, _ = pcd_tree.search_knn_vector_3d(points[i], knn)neighbors = points[idx]covariance = np.cov(neighbors.T)eigvals, _ = np.linalg.eigh(covariance)eigvals = np.sort(eigvals)curvature = eigvals[0] / (np.sum(eigvals) + 1e-10) # 避免除0curvatures.append(curvature)return np.array(curvatures)
# ---- 计算曲率 ----
curvatures = compute_curvature(pcd, knn=20)
# ---- 映射为颜色(使用matplotlib colormap) ----
norm_curvatures = (curvatures - np.min(curvatures)) / (np.max(curvatures) - np.min(curvatures) + 1e-10)
colors = plt.cm.jet(norm_curvatures)[:, :3] # 只取 RGB,不要 alpha
pcd.colors = o3d.utility.Vector3dVector(colors)
# ---- 可视化 ----
o3d.visualization.draw_geometries([pcd])# ---- 可选:保存带颜色的点云 ----
# o3d.io.write_point_cloud("curvature_colored.ply", pcd)
计算结果可视化: