CGAL 从DSM到DTM holefilling & remeshing
上一节简单地删除被建筑物覆盖的大片区域中的顶点会导致大的Delaunay
三角面,从而得到了较差的DTM
,所以一个额外的步骤可以帮助产生更好的形状网格:删除大于阈值的面,并用孔洞填充算法进行三角化、细化和均匀孔洞。
孔洞填充和重新网格化
下面比较完整的代码,包含上一节的删除建筑物覆盖的大片区域,还包含了将结果复制到一个网格中,同时过滤掉过大的表面,然后识别出孔洞并填充它们,除了最大的一个(即外壳),最后重新网格化。
代码
#include<iostream>
#include <queue>
#include<CGAL/Surface_mesh.h>
#include<CGAL/Surface_mesh/IO/PLY.h>
#include <CGAL/draw_surface_mesh.h>
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Projection_traits_xy_3.h>
#include <CGAL/Delaunay_triangulation_2.h>
#include <CGAL/Triangulation_vertex_base_with_info_2.h>
#include <CGAL/Triangulation_face_base_with_info_2.h>
#include <CGAL/Polygon_mesh_processing/triangulate_hole.h>
#include <CGAL/Polygon_mesh_processing/border.h>
#include <CGAL/Polygon_mesh_processing/remesh.h>
#include <CGAL/boost/graph/graph_traits_Delaunay_triangulation_2.h>
#include <CGAL/boost/graph/copy_face_graph.h>
#include <CGAL/compute_average_spacing.h>
using Kernel = CGAL::Exact_predicates_inexact_constructions_kernel;
using Projection_traits = CGAL::Projection_traits_xy_3<Kernel>;
using Point_2 = Kernel::Point_2;
using Point_3 = Kernel::Point_3;
using Mesh = CGAL::Surface_mesh<Point_3>;
using Concurrency_tag = CGAL::Sequential_tag;
using TIN = CGAL::Delaunay_triangulation_2<Projection_traits>;
using Vbi = CGAL::Triangulation_vertex_base_with_info_2 <Mesh::Vertex_index, Projection_traits>;
using Fbi = CGAL::Triangulation_face_base_with_info_2<int, Projection_traits>;
using TDS = CGAL::Triangulation_data_structure_2<Vbi, Fbi>;
using TIN_with_info = CGAL::Delaunay_triangulation_2<Projection_traits, TDS>;
int main() {
Mesh mesh;
CGAL::IO::read_PLY("./data/dsm.ply", mesh);
auto idx_to_point_with_info
= [&](const Mesh::Vertex_index& idx) -> std::pair<Point_3, Mesh::Vertex_index>
{
return std::make_pair ( mesh.point(idx), idx);
};
TIN_with_info tin_with_info
(boost::make_transform_iterator (mesh.vertices().begin(), idx_to_point_with_info),
boost::make_transform_iterator (mesh.vertices().end(), idx_to_point_with_info));
double spacing = CGAL::compute_average_spacing<Concurrency_tag>(mesh.points(), 6);
spacing *= 2;
auto face_height
= [&](const TIN_with_info::Face_handle fh) -> double
{
double out = 0.;
for (int i = 0; i < 3; ++ i)
out = (std::max) (out, CGAL::abs(fh->vertex(i)->point().z() - fh->vertex((i+1)%3)->point().z()));
return out;
};
// Initialize faces info
for (TIN_with_info::Face_handle fh : tin_with_info.all_face_handles())
if (tin_with_info.is_infinite(fh) || face_height(fh) > spacing) // Filtered faces are given info() = -2
fh->info() = -2;
else // Pending faces are given info() = -1;
fh->info() = -1;
// Flooding algorithm
std::vector<int> component_size;
for (TIN_with_info::Face_handle fh : tin_with_info.finite_face_handles())
{
if (fh->info() != -1)
continue;
std::queue<TIN_with_info::Face_handle> todo;
todo.push(fh);
int size = 0;
while (!todo.empty())
{
TIN_with_info::Face_handle current = todo.front();
todo.pop();
if (current->info() != -1)
continue;
current->info() = int(component_size.size());
++ size;
for (int i = 0; i < 3; ++ i)
todo.push (current->neighbor(i));
}
component_size.push_back (size);
}
std::cerr << component_size.size() << " connected component(s) found" << std::endl;
///
//! [Filtering]
int min_size = int(mesh.vertices().size() / 2);
std::vector<TIN_with_info::Vertex_handle> to_remove;
for (TIN_with_info::Vertex_handle vh : tin_with_info.finite_vertex_handles())
{
TIN_with_info::Face_circulator circ = tin_with_info.incident_faces (vh),
start = circ;
// Remove a vertex if it's only adjacent to components smaller than threshold
bool keep = false;
do
{
if (circ->info() >= 0 && component_size[std::size_t(circ->info())] > min_size)
{
keep = true;
break;
}
}
while (++ circ != start);
if (!keep)
to_remove.push_back (vh);
}
std::cerr << to_remove.size() << " vertices(s) will be removed after filtering" << std::endl;
for (TIN_with_info::Vertex_handle vh : to_remove)
tin_with_info.remove (vh);
//! [Filtering]
///
///
//! [Hole filling]
// Copy and keep track of overly large faces 拷贝面的同时,标记孔洞的大面
Mesh dtm_mesh;
std::vector<Mesh::Face_index> face_selection;
Mesh::Property_map<Mesh::Face_index, bool> face_selection_map
= dtm_mesh.add_property_map<Mesh::Face_index, bool>("is_selected", false).first;
double limit = CGAL::square (5 * spacing);
CGAL::copy_face_graph (tin_with_info, dtm_mesh,
CGAL::parameters::face_to_face_output_iterator
(boost::make_function_output_iterator
([&](const std::pair<TIN_with_info::Face_handle, Mesh::Face_index>& ff)
{
double longest_edge = 0.;
bool border = false;
for (int i = 0; i < 3; ++ i)
{
longest_edge = (std::max)(longest_edge, CGAL::squared_distance
(ff.first->vertex((i+1)%3)->point(),
ff.first->vertex((i+2)%3)->point()));
TIN_with_info::Face_circulator circ
= tin_with_info.incident_faces (ff.first->vertex(i)),
start = circ;
do
{
if (tin_with_info.is_infinite (circ))
{
border = true;
break;
}
}
while (++ circ != start);
if (border)
break;
}
// Select if face is too big AND it's not
// on the border (to have closed holes)
if (!border && longest_edge > limit)
{
face_selection_map[ff.second] = true;
face_selection.push_back (ff.second);
}
})));
// Save original DTM 生成最初的DTM
std::ofstream dtm_ofile ("dtm.ply", std::ios_base::binary);
CGAL::IO::set_binary_mode (dtm_ofile);
CGAL::IO::write_PLY (dtm_ofile, dtm_mesh);
dtm_ofile.close();
std::cerr << face_selection.size() << " face(s) are selected for removal" << std::endl;
// Expand face selection to keep a well formed 2-manifold mesh after removal 重新扩展孔洞,保证好的网格形状
CGAL::expand_face_selection_for_removal (face_selection, dtm_mesh, face_selection_map);
face_selection.clear();
for (Mesh::Face_index fi : faces(dtm_mesh))
if (face_selection_map[fi])
face_selection.push_back(fi);
std::cerr << face_selection.size() << " face(s) are selected for removal after expansion" << std::endl;
for (Mesh::Face_index fi : face_selection)
CGAL::Euler::remove_face (halfedge(fi, dtm_mesh), dtm_mesh);
dtm_mesh.collect_garbage();
if (!dtm_mesh.is_valid())
std::cerr << "Invalid mesh!" << std::endl;
// Save filtered DTM 删除被建筑物覆盖的大片区域的带孔的DTM
std::ofstream dtm_holes_ofile ("dtm_with_holes.ply", std::ios_base::binary);
CGAL::IO::set_binary_mode (dtm_holes_ofile);
CGAL::IO::write_PLY (dtm_holes_ofile, dtm_mesh);
dtm_holes_ofile.close();
// Get all holes 获取孔洞面
std::vector<Mesh::Halfedge_index> holes;
CGAL::Polygon_mesh_processing::extract_boundary_cycles (dtm_mesh, std::back_inserter (holes));
std::cerr << holes.size() << " hole(s) identified" << std::endl;
// Identify outer hull (hole with maximum size) 获取最大的孔洞即外壳
double max_size = 0.;
Mesh::Halfedge_index outer_hull;
for (Mesh::Halfedge_index hi : holes)
{
CGAL::Bbox_3 hole_bbox;
for (Mesh::Halfedge_index haf : CGAL::halfedges_around_face(hi, dtm_mesh))
{
const Point_3& p = dtm_mesh.point(target(haf, dtm_mesh));
hole_bbox += p.bbox();
}
double size = CGAL::squared_distance (Point_2(hole_bbox.xmin(), hole_bbox.ymin()),
Point_2(hole_bbox.xmax(), hole_bbox.ymax()));
if (size > max_size)
{
max_size = size;
outer_hull = hi;
}
}
// Fill all holes except the bigest (which is the outer hull of the mesh)
// 重新填充孔洞,排除最大的孔洞外壳
for (Mesh::Halfedge_index hi : holes)
if (hi != outer_hull)
CGAL::Polygon_mesh_processing::triangulate_refine_and_fair_hole
(dtm_mesh, hi, CGAL::parameters::fairing_continuity(0));
// Save DTM with holes filled
// 保存填充的孔洞后的DTM
std::ofstream dtm_filled_ofile ("dtm_filled.ply", std::ios_base::binary);
CGAL::IO::set_binary_mode (dtm_filled_ofile);
CGAL::IO::write_PLY (dtm_filled_ofile, dtm_mesh);
dtm_filled_ofile.close();
//! [Hole filling]
///
///
//! [Remeshing]
// 重新网格均匀化
CGAL::Polygon_mesh_processing::isotropic_remeshing (faces(dtm_mesh), spacing, dtm_mesh);
std::ofstream dtm_remeshed_ofile ("dtm_remeshed.ply", std::ios_base::binary);
CGAL::IO::set_binary_mode (dtm_remeshed_ofile);
CGAL::IO::write_PLY (dtm_remeshed_ofile, dtm_mesh);
dtm_remeshed_ofile.close();
return 0;
}
如下图所示,最初DTM、带孔DTM、孔洞填充DTM及细化及均匀DTM效果对比图:
132 connected component(s) found
10805 vertices(s) will be removed after filtering
186 face(s) are selected for removal
186 face(s) are selected for removal after expansion
13 hole(s) identified
构建编译运行
cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=D:\vcpkg\scripts\buildsystems\vcpkg.cmake
cmake --build build --config Debug
.\build\Debug\HoleFilling_Remeshing.exe
参考
- https://doc.cgal.org/latest/Manual/tuto_gis.html