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

保留城市点云形态的网格孔洞修补与Watertight重建技术问询

城市点云转Watertight光线追踪网格解决方案

问题背景

现有城市局部区域PLY格式点云,目标转换为适合光线追踪的Watertight网格,当前遇到以下问题:

  • 使用Open3D的Ball Pivoting算法生成的网格存在大量孔洞,无法满足需求
  • 尝试Poisson重建时出现报错:
    [ERROR] /Users/yixing/repo/Open3D/build/poisson/src/ext_poisson/PoissonRecon/Src/FEMTree.IsoSurface.specialized.inl (Line 1463)
            operator()
            Failed to close loop [6: 87 64 18] | (113981): (2752,2560,2196)
    
  • pymeshfix因输入非单一闭合实体无法使用

以下是针对性的解决方案:


一、优化Ball Pivoting算法参数与后处理

1. 改进点云预处理与法线估计

Ball Pivoting对法线方向一致性要求高,先优化法线估计:

import open3d as o3d
import numpy as np
from scipy.spatial import cKDTree

input_file = "san.ply"
pcd = o3d.io.read_point_cloud(input_file)
point_cloud_in_numpy = np.asarray(pcd.points)
color = np.asarray(pcd.colors)

# 截取局部点云
kd = cKDTree(point_cloud_in_numpy)
near = kd.query_ball_point([0, 0, 0], 100)
items = point_cloud_in_numpy[near]
colors = color[near]

pcd2 = o3d.geometry.PointCloud()
pcd2.colors = o3d.utility.Vector3dVector(colors)
pcd2.points = o3d.utility.Vector3dVector(items)

# 统计滤波去噪,去除孤立点
cl, ind = pcd2.remove_statistical_outlier(nb_neighbors=20, std_ratio=2.0)
pcd2 = pcd2.select_by_index(ind)

# 优化法线估计,增大邻域并统一方向
distances = pcd2.compute_nearest_neighbor_distance()
avg_dist = np.mean(distances)
pcd2.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=avg_dist*5, max_nn=30))
# 强制法线朝向一致
pcd2.orient_normals_consistent_tangent_plane(100)

2. 多尺度半径Ball Pivoting重建

使用多尺度半径覆盖不同尺度的城市特征,减少孔洞:

# 生成多尺度半径数组,覆盖不同大小的几何特征
radii = o3d.utility.DoubleVector([avg_dist*1.5, avg_dist*2, avg_dist*3, avg_dist*4])
mesh = o3d.geometry.TriangleMesh.create_from_point_cloud_ball_pivoting(pcd2, radii)

3. 网格孔洞修补与清理

对生成的网格进行后处理,修补孔洞并优化拓扑:

# 修补孔洞,设置最大可修补孔洞的边长
mesh = mesh.fill_holes(maximum_length=avg_dist*10)
# 去除重复顶点与三角面
mesh.remove_duplicated_vertices()
mesh.remove_duplicated_triangles()
# 重新计算法线,保证一致性
mesh.compute_vertex_normals()
mesh.compute_triangle_normals()

# 检查是否Watertight
print(f"网格是否Watertight: {mesh.is_watertight()}")
o3d.visualization.draw_geometries([mesh])

二、解决Poisson重建报错的方案

Poisson报错通常源于点云法线不一致、噪声或边界不连续,按以下步骤处理:

1. 强化点云预处理

# 半径滤波,进一步去除噪声点
cl, ind = pcd2.remove_radius_outlier(nb_points=16, radius=avg_dist*2)
pcd2 = pcd2.select_by_index(ind)

# 提取并移除点云边界点(避免边界不连续导致Poisson无法闭合)
boundary_indices = pcd2.compute_boundary_points()
pcd2 = pcd2.select_by_index(np.setdiff1d(np.arange(len(pcd2.points)), boundary_indices))

2. 调整Poisson重建参数

降低重建深度,避免过拟合城市点云的复杂细节:

# 执行Poisson重建,深度设为8(城市点云无需过深,减少报错概率)
mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(pcd2, depth=8)

# 过滤低密度三角面,去除冗余的重建部分
vertices_to_remove = densities < np.quantile(densities, 0.01)
mesh.remove_vertices_by_mask(vertices_to_remove)

# 修补孔洞并清理网格
mesh.fill_holes(maximum_length=avg_dist*10)
mesh.remove_duplicated_vertices()
mesh.compute_vertex_normals()

print(f"网格是否Watertight: {mesh.is_watertight()}")

三、多实体分割处理方案

城市点云包含多个独立实体(建筑、道路等),先分割为单个实体再分别重建:

# DBSCAN聚类分割点云,eps为邻域半径,min_points为聚类最小点数
labels = np.array(pcd2.cluster_dbscan(eps=avg_dist*3, min_points=100))
max_label = labels.max()
print(f"分割为 {max_label + 1} 个独立实体")

meshes = []
for label in range(max_label + 1):
    # 提取单个聚类的点云
    cluster_pcd = pcd2.select_by_index(np.where(labels == label)[0])
    # 估计法线并统一方向
    cluster_pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=avg_dist*5, max_nn=30))
    cluster_pcd.orient_normals_consistent_tangent_plane(100)
    # Poisson重建单个实体
    cluster_mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(cluster_pcd, depth=8)
    # 过滤低密度面
    vertices_to_remove = densities < np.quantile(densities, 0.01)
    cluster_mesh.remove_vertices_by_mask(vertices_to_remove)
    # 修补孔洞
    cluster_mesh.fill_holes(maximum_length=avg_dist*10)
    # 添加到网格列表
    meshes.append(cluster_mesh)

# 合并所有实体网格
final_mesh = o3d.geometry.TriangleMesh()
for mesh in meshes:
    final_mesh += mesh

# 清理合并后的网格
final_mesh.remove_duplicated_vertices()
final_mesh.remove_duplicated_triangles()
final_mesh.compute_vertex_normals()

print(f"合并后网格是否Watertight: {final_mesh.is_watertight()}")

四、光线追踪适配优化

生成Watertight网格后,做以下适配处理:

  • 简化网格:减少三角面数量,提升光线追踪效率
    # 简化网格到目标三角面数量(根据需求调整)
    final_mesh = final_mesh.simplify_quadric_decimation(target_number_of_triangles=100000)
    
  • 传递顶点颜色:将点云颜色映射到网格顶点,保留视觉效果
    # 注意:如果是分割后合并的网格,需要单独为每个聚类传递颜色,此处为单网格示例
    final_mesh.vertex_colors = o3d.utility.Vector3dVector(np.asarray(pcd2.colors))
    
  • 导出格式:导出为OBJ或PLY格式,主流光线追踪引擎均支持

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 14:30:25