Python查找过质心的3D点云或三维凸包长轴的方法咨询
问题描述
需要查找由三维布尔数组/标签数组描述的3D连通对象的长轴,要求轴线穿过(或紧邻)对象质心。
前期尝试的方案存在以下问题:
- 遍历对象内所有点对,取间距最大的点对连线作为长轴:对非凸/带突出结构的对象,该连线会远离对象中心,不符合要求
- 筛选与质心距离小于阈值的最长点对连线:需要手动调阈值,属于重复造轮子,且没有用到成熟的几何计算方法
- 检索到的最小二乘直线拟合方案大多仅适用于二维场景,无法直接用到3D数组上
实际处理的对象规模为数千个体素,具备两个特点:
- 对象内两点的最大间距连线可能远离质心
- 对象为单连通结构
以下代码可生成简单的测试3D形状:
import matplotlib.pyplot as plt import numpy as np # 构造简单3D测试形状 t = [[[0,0,0,0,0], [0,0,1,0,0], [0,0,0,0,0], [0,0,0,0,0], [0,0,0,0,0]], [[0,0,0,0,0], [0,0,1,0,0], [0,1,1,1,0], [0,0,0,0,0], [0,0,0,0,0]], [[0,0,0,0,0], [0,0,1,0,0], [0,1,1,1,0], [0,0,0,0,0], [0,0,0,0,0]], [[0,0,0,0,0], [0,0,1,0,0], [0,1,1,1,0], [0,0,0,0,0], [0,0,0,0,0]], [[0,0,0,0,0], [0,0,1,0,0], [0,0,0,0,0], [0,0,0,0,0], [0,0,0,0,0]]] t = np.array(t) # 计算对象质心 coords = np.where(t == 1) x = np.mean(coords[0]) y = np.mean(coords[1]) z = np.mean(coords[2]) # 多视角展示对象 fig, (ax1, ax2, ax3) = plt.subplots(1, 3) ax1.imshow(np.max(t, axis=0)) ax1.plot(x,y, 'ro') ax1.set_title('xy') ax2.imshow(np.max(t, axis=1)) ax2.plot(x,z, 'ro') ax2.set_title('xz') ax3.imshow(np.max(t, axis=2)) ax3.plot(y,z, 'ro') ax3.set_title('yz') plt.show()

解决方案
直接使用**3D主成分分析(PCA)**计算即可,这是该场景下的标准成熟方案,不需要手动遍历点对,也不需要调参。
核心原理
PCA计算得到的第一主成分方向,就是3D点云方差最大的延展方向,对应的拟合直线天然穿过点云质心,且是所有过质心直线中,点到直线垂直距离平方和最小的解,正好对应你要找的长轴,完全适配三维场景。
计算步骤非常简单:
- 提取对象所有体素的三维坐标,计算质心(现有代码已经完成这步)
- 将所有点坐标减去质心坐标做中心化处理
- 计算中心化后坐标的3×3协方差矩阵
- 对协方差矩阵做特征值分解,最大特征值对应的特征向量就是长轴的方向向量
- 过质心、沿该方向向量延伸的直线,就是目标长轴
实现代码
在你现有代码基础上,添加以下片段即可得到长轴方向:
# 整理为N行3列的点坐标数组 points = np.vstack(coords).T.astype(np.float64) centroid = np.array([x, y, z]) # 计算协方差矩阵 cov_mat = np.cov((points - centroid).T) # 特征值分解 eig_vals, eig_vecs = np.linalg.eig(cov_mat) # 取最大特征值对应的特征向量,即长轴方向 long_axis_direction = eig_vecs[:, np.argmax(eig_vals)]
如果需要可视化长轴,只需要从质心出发,沿long_axis_direction向两侧取合适长度的端点连线即可。
方案优势
- 时间复杂度为O(N),N为对象体素数,哪怕处理上万点的结构速度也极快,远优于遍历点对的O(N²)方案
- 不需要手动设置距离阈值,无额外调参成本
- 对给出的十字形测试样例,计算得到的长轴会沿竖直长杆方向,准确穿过质心,不会出现最远点对斜连对角、偏离中心的问题
- 额外可直接得到对象的另外两个正交主轴:第二、第三大特征值对应的特征向量分别对应中轴、短轴方向,可直接用于后续的形状朝向、尺寸测量等任务
内容的提问来源于stack exchange,提问作者Zac
相关产品推荐
相关产品推荐

