点云曲面重建输出网格的多余三角形裁剪方法(CGAL适用)
我有一个由点云表示的开放曲面,已按照Point Set Processing and Surface Reconstruction教程中的步骤完成处理。但使用教程中介绍的3种重建算法后,得到的都是带有大量多余多边形的封闭网格,尤其是在点云模型外围会形成类似“封闭穹顶”的冗余结构,可参考附图直观了解情况。
请问在CGAL中有没有准确、或者说符合规范的方法,能够裁剪掉与原始点云不相交的大小三角形?我之前尝试遍历输出网格的面片,移除满足以下两个条件之一的面片:(1)面片任意边的长度大于原始点云计算得到的average_spacing;(2)面片面积大于某个阈值。但使用这两种过滤条件后得到的PLY格式网格模型,在我试过的各类查看器中都无法正常显示。我参考编写的代码如下,但显然没能达到预期效果:
for (face_descriptor faced : output_mesh.faces()) { //std::cout << faced << std::endl; std::vector<double> lengths; // Get edges from face descriptor // - NOTE :> Assume three total half-edges because triangles. Not guaranteed, tho. for (halfedge_descriptor hed : CGAL::halfedges_around_face( output_mesh.halfedge(faced), output_mesh )) { vertex_descriptor target_vertex = output_mesh.target(hed); Point_3& target_point = output_mesh.point(target_vertex); halfedge_descriptor hed_next = output_mesh.next(hed); vertex_descriptor target_next = output_mesh.target(hed_next); Point_3& target_point_next = output_mesh.point(target_next); double length = CGAL::sqrt(CGAL::squared_distance(target_point, target_point_next)); lengths.push_back(length); simplex.push_back(target_point); } // if edge length greater than limit threshold based on average_spacing, // mark for removal? for (double length : lengths) { if (length > average_spacing) { output_mesh.remove_face(faced); //CGAL::Euler::remove_face( output_mesh.halfedge(faced), output_mesh ); std::cout << "Removed face " << faced << " with lengths: "; // List out the lengths found: for (double length : lengths) { std::cout << length << ", "; } std::cout << std::endl; continue; } } } // clear faces marked as removed output_mesh.collect_garbage(); //output_mesh.is_valid();


问题原因与解决方案
你现有代码出问题的核心原因是遍历网格的同时直接删除面,会破坏迭代器结构,导致后续遍历逻辑混乱,最终生成的网格存在无效拓扑结构,所以查看器无法正常加载。
下面是CGAL中处理这类问题的规范方案,按照实现优先级排序:
优先使用重建算法自带的裁剪能力
如果你用的是泊松重建,CGAL的泊松重建接口原生支持裁剪冗余结构:调用CGAL::poisson_surface_reconstruction_delaunay()时设置trim_threshold参数即可,这个参数就是专门用来过滤远离输入点云的低密度重建区域的,阈值一般设为2~5倍的点云平均间距,不需要后续手动处理。
如果你的场景是开放曲面重建,也可以直接换用CGAL::advancing_front_surface_reconstruction()算法,该算法天生适配开放点云,不会生成封闭的冗余穹顶结构。手动过滤的正确实现方式
如果你需要自定义过滤逻辑,按下面的步骤修改你的代码:- 不要边遍历边删面,先把所有符合删除条件的面存到单独的列表里,遍历完所有面之后再统一删除
- 过滤条件优先用面片到原始点云的距离,比边长、面积阈值更准确:计算每个三角面片的形心到原始点云的最近距离,大于设定阈值(一般是2~3倍average_spacing)就标记删除
- 删除完面之后要清理孤立顶点,避免无效数据
参考实现代码:
// 第一步:预处理点云的最近邻查询结构 CGAL::Kd_tree<Traits> kd_tree(points.begin(), points.end()); double threshold = 2.5 * average_spacing; std::vector<face_descriptor> faces_to_remove; // 第二步:遍历所有面,标记待删除的面 for (face_descriptor faced : output_mesh.faces()) { // 计算面的形心 Point_3 centroid = CGAL::centroid( output_mesh.point(output_mesh.halfedge(faced)->target()), output_mesh.point(output_mesh.halfedge(faced)->next()->target()), output_mesh.point(output_mesh.halfedge(faced)->next()->next()->target()) ); // 查询形心到点云的最近距离 Point_3 nearest = *kd_tree.nearest_neighbor_search(centroid).first; double dist = CGAL::sqrt(CGAL::squared_distance(centroid, nearest)); if (dist > threshold) { faces_to_remove.push_back(faced); } } // 第三步:统一删除标记的面 for (auto f : faces_to_remove) { output_mesh.remove_face(f); } // 清理无效数据 output_mesh.collect_garbage(); CGAL::Polygon_mesh_processing::remove_isolated_vertices(output_mesh);最后导出前调用
output_mesh.is_valid()检查网格有效性,确认没问题再导出PLY就可以正常打开了。
内容的提问来源于stack exchange,提问作者anachronicnomad

