You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

LiDAR点云粗糙度计算提速问询:并行处理失效后的优化方案

LiDAR点云表面粗糙度计算提速方案

问题背景

需要计算LiDAR点云的表面粗糙度,方法为:对每个点的邻域点集拟合最佳拟合平面,再计算该点到平面的距离。但邻域半径增大时计算耗时剧增,尝试用concurrent.futures并行处理后速度反而比串行更慢,寻求可行的提速方法。

现有代码

import numpy as np
import laspy
from concurrent.futures import ProcessPoolExecutor
from scipy.spatial import cKDTree as KDTree
from skspatial.objects import Plane

def process_point(point_idx, point, lidar_data, neighborhood_radius, tree):
    neighbor_indices = tree.query_ball_point(point, neighborhood_radius)
    neighbors = lidar_data[neighbor_indices]
    
    if neighbors.shape[0] >= 3:
        plane = Plane.best_fit(neighbors)
        # Distance calculation remains the same
        # Return roughness value and index
        return point_idx, calculate_distance_to_plane(point, plane)
    else:
        return point_idx, np.nan

# Parallel processing wrapper function
def calculate_roughness_parallel(lidar_data, neighborhood_radius, tree):
    roughness_values = np.zeros(len(lidar_data))
    with ProcessPoolExecutor() as executor:
        futures = [executor.submit(process_point, point_idx, point, lidar_data, neighborhood_radius, tree)
                   for point_idx, point in enumerate(lidar_data)]
        for future in futures:
            point_idx, roughness = future.result()
            roughness_values[point_idx] = roughness
    return roughness_values


if __name__ == "__main__":
    las = laspy.read("las0.laz")
    in_point = np.vstack((las.x, las.y, las.z)).transpose()
    tree = KDTree(in_point)
    roughness_result = calculate_roughness_parallel(in_point, 1, tree)

可行提速方法

1. 替换并行方式:用线程池替代进程池

ProcessPoolExecutor的进程间通信开销极大,尤其是传递大量点云数据时。改用ThreadPoolExecutor(线程池),利用多线程处理IO密集型的KDTree查询,同时结合NumPy向量化运算减少GIL锁的影响:

from concurrent.futures import ThreadPoolExecutor

def calculate_roughness_threadpool(lidar_data, neighborhood_radius, tree):
    roughness_values = np.zeros(len(lidar_data))
    with ThreadPoolExecutor() as executor:
        futures = [executor.submit(process_point, point_idx, point, lidar_data, neighborhood_radius, tree)
                   for point_idx, point in enumerate(lidar_data)]
        for future in futures:
            point_idx, roughness = future.result()
            roughness_values[point_idx] = roughness
    return roughness_values

2. 重写平面拟合逻辑:用NumPy替代skspatial

skspatial.objects.Plane.best_fit的底层实现存在额外的类实例化开销,改用NumPy直接计算最佳拟合平面,大幅提升计算效率:

def calculate_best_fit_plane(neighbors):
    # 计算邻域点中心
    centroid = np.mean(neighbors, axis=0)
    # 去中心化处理
    centered = neighbors - centroid
    # 协方差矩阵SVD分解求法向量
    _, _, vh = np.linalg.svd(centered.T @ centered)
    normal = vh[-1]
    # 计算平面方程参数d
    d = -np.dot(normal, centroid)
    return normal, d

def calculate_distance_to_plane(point, normal, d):
    # 点到平面距离公式
    return abs(np.dot(normal, point) + d) / np.linalg.norm(normal)

# 修改process_point函数适配新逻辑
def process_point(point_idx, point, lidar_data, neighborhood_radius, tree):
    neighbor_indices = tree.query_ball_point(point, neighborhood_radius)
    neighbors = lidar_data[neighbor_indices]
    
    if neighbors.shape[0] >= 3:
        normal, d = calculate_best_fit_plane(neighbors)
        return point_idx, calculate_distance_to_plane(point, normal, d)
    else:
        return point_idx, np.nan

3. 批量邻域查询:减少循环调用开销

利用KDTree的query_ball_point支持批量输入的特性,一次性获取所有点的邻域索引,避免循环调用的额外开销:

def calculate_roughness_batch(lidar_data, neighborhood_radius, tree):
    roughness_values = np.full(len(lidar_data), np.nan)
    # 批量获取所有点的邻域索引
    all_neighbor_indices = tree.query_ball_point(lidar_data, neighborhood_radius)
    
    for point_idx, neighbor_indices in enumerate(all_neighbor_indices):
        neighbors = lidar_data[neighbor_indices]
        if len(neighbors) >=3:
            normal, d = calculate_best_fit_plane(neighbors)
            roughness_values[point_idx] = calculate_distance_to_plane(lidar_data[point_idx], normal, d)
    return roughness_values

4. 降采样预处理:减少计算量

若精度要求允许,先对原始点云进行体素降采样,在保留整体特征的前提下减少需要处理的点数量:

def voxel_downsample(points, voxel_size):
    # 计算每个点所属的体素索引
    voxel_indices = np.floor(points / voxel_size).astype(int)
    # 按体素分组,取均值作为降采样点
    unique_indices, indices = np.unique(voxel_indices, axis=0, return_inverse=True)
    downsampled = np.zeros((len(unique_indices), 3))
    np.add.at(downsampled, indices, points)
    counts = np.bincount(indices)
    downsampled /= counts[:, np.newaxis]
    return downsampled

# 主函数中使用降采样
if __name__ == "__main__":
    las = laspy.read("las0.laz")
    in_point = np.vstack((las.x, las.y, las.z)).transpose()
    # 体素大小可根据需求调整
    downsampled_point = voxel_downsample(in_point, voxel_size=0.5)
    tree = KDTree(downsampled_point)
    roughness_result = calculate_roughness_batch(downsampled_point, 1, tree)

5. Numba JIT编译:核心函数加速

用numba对平面拟合、距离计算等核心函数进行JIT编译,将Python代码转为机器码执行,大幅提升计算速度:

from numba import jit

@jit(nopython=True)
def calculate_best_fit_plane_numba(neighbors):
    centroid = np.mean(neighbors, axis=0)
    centered = neighbors - centroid
    cov_matrix = np.dot(centered.T, centered)
    _, _, vh = np.linalg.svd(cov_matrix)
    normal = vh[-1]
    d = -np.dot(normal, centroid)
    return normal, d

@jit(nopython=True)
def calculate_distance_to_plane_numba(point, normal, d):
    return abs(np.dot(normal, point) + d) / np.linalg.norm(normal)

内容的提问来源于stack exchange,提问作者Purple_Ad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 14:32:41