点云法向量及栅格下采样(附可能出现的问题)

点云数据处理初识

主成分分析

主成分分析(PCA)是一种常用于数据降维的技术,通过计算数据的主成分(即数据中方差最大的方向),可以找到数据的主要方向。它在点云处理中的应用主要是用于确定点云的主要方向和提取点云的几何特征。PCA 通过将数据中心化(即减去均值),然后计算协方差矩阵,再进行奇异值分解(SVD),从中提取出特征向量和特征值,特征向量表示数据的主方向,特征值表示各个主方向的方差大小。

具体步骤:

  1. 计算点云数据的均值:计算所有点在每个维度上的均值(例如,x, y, z 坐标的均值)。
  2. 数据中心化:将数据减去均值,使得数据的中心位于原点。
  3. 构造协方差矩阵:通过数据的协方差矩阵来捕捉点云中各个维度之间的关系。
  4. 进行奇异值分解(SVD):对协方差矩阵进行奇异值分解,提取出特征向量和特征值。特征向量指示数据的主方向,特征值表示这些方向的方差大小。
  5. 排序特征值:根据特征值的大小对特征向量进行排序,确保主方向在前。

核心代码

def PCA(data, correlation=False, sort=True):
    # 作业1
    # 计算均值
    data_mean = np.mean(data,axis=0)
    # 归一化
    normalize_data = data - data_mean
    # 构造协方差矩阵
    H = np.dot(normalize_data.T,normalize_data)
    # SVD分解
    eigenvectors, eigenvalues, eigenvectors_t = np.linalg.svd(H)


    if sort:
        sort = eigenvalues.argsort()[::-1]
        eigenvalues = eigenvalues[sort]
        eigenvectors = eigenvectors[:, sort]

    return eigenvalues, eigenvectors
v,u = PCA(points)
    # 对u特征值矩阵进行缩放
    scale = 100
    u = u * scale
    print('the main orientation of this pointcloud is:',u[:,0])
    point = [[0,0,0],u[:,0],u[:,1]]
    lines = [[0,1],[0,2]]
    colors = [[1,0,0],[0,1,0]]
    line_set = o3d.geometry.LineSet(
        points = o3d.utility.Vector3dVector(point),
        lines = o3d.utility.Vector2iVector(lines)
    )
    line_set.colors = o3d.utility.Vector3dVector(colors)
    o3d.visualization.draw_geometries([point_cloud_o3d,line_set])

结果展示:
请添加图片描述

不显示主成分问题

问题描述:排查主成分计算问题仍无法正常显示
解决:考虑主成分缩放

scale = 100
    u = u * scale

法向量

算法描述:

法向量是表示点云表面朝向的一个重要几何特征。通过计算点云中每个点的法向量,可以更好地理解点云的局部表面特征,进而用于表面重建、物体识别、碰撞检测等任务。法向量的计算通常基于邻域点的局部几何特征,常用的方法是基于 PCA(主成分分析)来估计法向量。

在该算法中,我们首先通过 KD 树来寻找每个点的邻域点,然后使用 PCA 对邻域点进行分析,提取主成分,法向量通常对应于最小的特征值方向(即点云表面的法向量)。

具体步骤:

  1. 构建 KD 树:通过 KD 树(K-Nearest Neighbor Tree)加速最近邻点的查询。
  2. 计算每个点的邻域:对于每个点,找到其最近的 K 个邻域点。
  3. PCA 计算法向量:对每个点的邻域点使用 PCA 计算主成分,法向量通常是对应于最小特征值的特征向量。
  4. 法向量归一化:得到法向量后,根据需要进行缩放或归一化。
  5. 可视化:将法向量绘制在点云上,帮助直观展示点云的表面方向。

核心代码

# 循环计算每个点的法向量
    pcd_tree = o3d.geometry.KDTreeFlann(point_cloud_o3d)
    normals = []
    for i in range(points.shape[0]):
        [_, idx, _] = pcd_tree.search_knn_vector_3d(point_cloud_o3d.points[i],15)
        k_nearest_point = np.asarray(point_cloud_o3d.points)[idx, :]
        v, u = PCA(k_nearest_point)
        normals.append(u[:,2])
    normals = np.array(normals, dtype=np.float64)
    normals_factor = 1
    normals = normals * normals_factor
    # TODO: 此处把法向量存放在了normals中
    point_cloud_o3d.normals = o3d.utility.Vector3dVector(normals)
    print(normals)
    o3d.visualization.draw_geometries([point_cloud_o3d, line_set],point_show_normal=True)
    # point_show_normal=True 绘制展示法向量

结果展示

在这里插入图片描述

法向量不显示问题

问题描述:排查法向量计算问题,无法正常显示
调用o3d.visualization.draw_geometries 函数时加上,point_show_normal=True(如代码最后一行)

法向量不垂直

请添加图片描述

问题描述:法向量显示发现与平面不垂直
解决:可能是像素点过少,1.可以增大选取的K临近点数量;2.选取像素点更多的点云数据(占用空间大的)

栅格下采样

算法描述:

栅格下采样(Voxel Downsampling)是一种常用的点云简化方法,通过将点云数据划分为小的体素(Voxel)单元,然后对每个体素内的点进行某种形式的聚合(如随机采样或均值采样)。这种方法能够有效减少点云的大小,同时保留其主要的几何特征,广泛应用于点云处理、物体识别等领域。

常见的体素采样方法包括:

  • 随机采样(Random Sampling):从每个体素内随机选取一个点。
  • 均值采样(Centroid Sampling):从每个体素内计算所有点的均值,并将均值作为该体素的代表点。

具体步骤:

  1. 计算最小值和最大值:首先,计算点云数据的最小值和最大值,用于确定点云数据的边界。
  2. 划分体素网格:根据设定的体素大小(leaf_size),将点云划分为多个体素单元,每个体素包含一组相邻的点。
  3. 对体素内的点进行采样:根据选择的采样模式(random 或 centroid),在每个体素内选择一个代表点。
    • random:随机选择一个点作为体素代表点。
    • centroid:计算体素内所有点的均值,作为该体素的代表点。
  4. 返回下采样后的点云:最终返回经过采样后的点云数据。

核心代码

# 功能:对点云进行voxel滤波
# 输入:
#     point_cloud:输入点云
#     leaf_size: voxel尺寸
def voxel_filter(point_cloud, leaf_size, mode):
    filtered_points = []
    # 作业3
    data = point_cloud.values  # Pandas DataFrame 转换为 numpy 数组
    # 求出xyz三轴各自的最小值最大值
    min_d = data.min(axis=0)
    max_d = data.max(axis=0)
    D = (max_d - min_d) / leaf_size
    # 求出所有点所在山歌序号h
    point_x, point_y, point_z = np.array(point_cloud.x), np.array(point_cloud.y), np.array(point_cloud.z)
    h_x, h_y, h_z = np.floor((point_x - min_d[0]) / leaf_size), \
        np.floor((point_y - min_d[1]) / leaf_size), \
        np.floor((point_z - min_d[2]) / leaf_size)
    h = np.array(np.floor(h_x + h_y * D[0] + h_z * D[0] * D[1]), dtype=int)
    # 根据h对点云进行排序
    data = np.c_[h, point_x, point_y, point_z]
    data = data[data[:, 0].argsort()]

    if mode == 'random':
        current_voxel = data[0][0]  # 初始体素分区
        voxel_points = []  # 当前体素内的所有点
        for i in range(data.shape[0]):
            # 判断是否相等
            if data[i][0] != current_voxel:
                # 如果遇到新体素分区,从当前体素分区内随机选一个点
                if len(voxel_points) > 0:
                    random_point = voxel_points[np.random.randint(len(voxel_points))]
                    filtered_points.append(random_point)
                # 清空当前体素内的点,开始新的体素分区
                current_voxel = data[i][0]
                voxel_points = []

            # 添加当前点到体素分区内
            voxel_points.append(data[i][1:])

        # 最后一个体素分区的随机点
        if len(voxel_points) > 0:
            random_point = voxel_points[np.random.randint(len(voxel_points))]
            filtered_points.append(random_point)

    # 均值采样
    if mode == 'centroid':
        filtered_points = []
        data_points = []
        for i in range(data.shape[0] - 1):
            # 判断是否相等
            if data[i][0] != data[i + 1][0]:
                if data_points:  # 确保data_points不为空
                    filtered_points.append(np.mean(data_points, axis=0))
                data_points = []  # 清空数据
            data_points.append(data[i][1:])
        # 处理最后一个分区
        if data_points:
            filtered_points.append(np.mean(data_points, axis=0))

    # 把点云格式改成array,并对外返回
    filtered_points = np.array(filtered_points, dtype=np.float64)
    return filtered_points
def main():
    # 指定点云路径
    cat_index = 2  # 物体编号,范围是0-39,即对应数据集中40个物体
    root_dir = os.path.join('E:', 'WY', 'Dataset', 'ModelNet', 'ply_data_points', 'ModelNet40')  # 数据集路径
    cat = os.listdir(root_dir)
    filename = os.path.join(root_dir, cat[cat_index], 'train', cat[cat_index] + '_0355.ply')  # 默认使用第一个点云

    # 加载点云文件
    point_cloud_pynt = PyntCloud.from_file(filename)

    # 转成open3d能识别的格式
    point_cloud_o3d = point_cloud_pynt.to_instance("open3d", mesh=False)
    o3d.visualization.draw_geometries([point_cloud_o3d])  # 显示原始点云

    # 调用voxel滤波函数,实现滤波
    filtered_cloud = voxel_filter(point_cloud_pynt.points, 2.0, 'random')
    point_cloud_o3d.points = o3d.utility.Vector3dVector(filtered_cloud)

    # 显示滤波后的点云
    o3d.visualization.draw_geometries([point_cloud_o3d])

结果展示:
原图:请添加图片描述
均值采样 :
半径为2

请添加图片描述 半径为4 请添加图片描述

随机采样:
半径为2
请添加图片描述

半径为4
请添加图片描述

可能出现的问题

numpy数组和pandas数据格式冲突可能会造成RuntimeError
解决方法: 统一转换成numpy数组进行数据处理

### 基于信息熵的点云简化方法 在计算机图形学和数据处理领域,基于信息熵的方法被广泛应用于点云简化。这些技术旨在通过减少冗余点来降低存储成本并提高计算效率,同时尽可能保持原始几何特征。 #### 信息熵理论基础 信息熵是一种度量不确定性的统计量,在此背景下用于评估点的重要性及其分布特性。对于给定的一组三维坐标集合P={p_1, p_2,...,p_n},可以定义局部邻域内的概率密度函数f(p),进而计算该区域的信息熵H(P)[^1]: \[ H(P)=-\sum_{i=1}^{n}{f(p_i)\log{f(p_i)}} \] 其中\( f(p_i)=\frac{\omega(d(i,j))}{Z}, Z=\sum_j{\omega(d(i,j))}\) 这里d表示两点间距离;ω是一个权重因子,通常取高斯核形式以强调近处样本的影响。 #### 点云简化策略 一种常见的做法是从初始模型中移除那些对整体形状贡献较小或者重复率较高的部分。具体来说,可以通过以下几种方式实现: - **基于体素栅格化**:将空间划分为固定大小的小立方体单元(voxel grids), 并保留每个网格中最能代表其内部结构特性的少数几个顶点。 - **曲面拟合与重采样**:利用多项式或其他数学表达式逼近表面形态,随后按照一定规则重新选取有限数量的关键位置作为新的节点集。 - **聚类分析**:采用K-means等无监督学习算法把相似属性的对象聚集在一起形成簇(cluster),再从中挑选最具代表性者构成精简后的版本。 上述过程均需考虑如何量化各候选对象的价值评判标准——即所谓的“重要性测度”。此时引入Shannon Entropy便显得尤为恰当,因为它不仅能够反映个体差异程度,还具备良好的可解释性和鲁棒性能。 ```python import numpy as np from scipy.spatial import KDTree def compute_entropy(points): tree = KDTree(points) distances, indices = tree.query(points, k=2) # Calculate local density using Gaussian kernel sigma = np.mean(distances[:, 1]) weights = np.exp(-distances[:, 1]**2 / (2 * sigma**2)) probs = weights / sum(weights) # Compute Shannon entropy entropy = -np.sum(probs * np.log(probs + 1e-9)) return entropy ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值