当前位置:   article > 正文

CGAL 点云数据生成DSM、DTM、等高线和数据分类

CGAL 点云数据生成DSM、DTM、等高线和数据分类

原文链接 CGAL 点云数据生成DSM、DTM、等高线和数据分类 - 知乎

在GIS应用软件中使用的许多传感器(如激光雷达)都会产生密集的点云。这类应用软件通常利用更高级的数据结构:如:不规则三角格网 (TIN)是生成数字高程模型 (DEM) 的基础,也可以利用TIN生成数字地形模型 (DTM)。对点云数据进行分类,提取地面、植被和建筑点(或其他用户定义的标签)等分类数据,从而使得获取的信息更加丰富。

因空间数据源获取方式不同,数据结构的定义也不尽一致。CGAL中使用以下术语定义这些数据结构:

  • TIN:不规则三角格网,一种 2D 三角剖分结构,根据 3D 点在水平面上的投影关系连接它们,使用众多三角形构造地物表面。
  • DSM:数字表面模型,包括建筑和植被的整个扫描表面的模型。CGAL中使用一个TIN来存储DSM。
  • DTM:数字地形模型,一个没有建筑物或植被等物体的裸地面模型。CGAL中使用TIN和光栅来存储DTM。
  • DEM:数字高程模型,是一个更通用的术语,包括DSM和DTM。

1、不规则三角网(TIN)

提供多种三角测量数据结构和算法。可以通过结合二维Delaunay三角剖分和投影特征来生成TIN。三角剖分结构是利用点在选定平面(通常是xy平面)上的二维位置来计算的,而点的三维位置则保留来进行可视化和测量。

因此,TIN数据结构可以简单地定义如下:

  1. using Kernel = CGAL::Exact_predicates_inexact_constructions_kernel;
  2. using Projection_traits = CGAL::Projection_traits_xy_3<Kernel>;
  3. using Point_2 = Kernel::Point_2;
  4. using Point_3 = Kernel::Point_3;
  5. using Segment_3 = Kernel::Segment_3;
  6. // Triangulated Irregular Network
  7. using TIN = CGAL::Delaunay_triangulation_2<Projection_traits>;

2、数字表面模型(DSM)

许多格式的点云(XYZ, OFF, PLY, LAS)都可以使用流操作符轻松加载到CGAL::Point_set_3结构中,然后生成存DSM储在TIN中。DSM表示如下:

  1. // Read points
  2. std::ifstream ifile (fname, std::ios_base::binary);
  3. CGAL::Point_set_3<Point_3> points;
  4. ifile >> points;
  5. std::cerr << points.size() << " point(s) read" << std::endl;
  6. // Create DSM
  7. TIN dsm (points.points().begin(), points.points().end());

由于CGAL的Delaunay三角剖分是FaceGraph的一个模型,因此可以直接将生成的TIN转换为CGAL::Surface_mesh这样的网格结构,并保存为该结构支持的任何格式:

  1. using Mesh = CGAL::Surface_mesh<Point_3>;
  2. Mesh dsm_mesh;
  3. CGAL::copy_face_graph (dsm, dsm_mesh);
  4. std::ofstream dsm_ofile ("dsm.ply", std::ios_base::binary);
  5. CGAL::IO::set_binary_mode (dsm_ofile);
  6. CGAL::IO::write_PLY (dsm_ofile, dsm_mesh);
  7. dsm_ofile.close();

在旧金山数据集上以这种方式计算DSM的一个例子,如下图所示:

3、数字地形模型(DTM)

DSM 是 计算DTM的基础,将DSM上的非地形点进行数据清洗后,通过计算可以生成DTM,即另一个用TIN表示的DTM。作为一个例子,可以通过以下步骤计算得到一个 DTM :

  • 设置高度阈值消除高度的剧烈变化
  • 设置周长阈值聚类周边的地物作为连接的组件
  • 过滤所有小于定义阈值的组件

该算法依赖于 2 个参数:高度阈值对应于建筑物的最小高度,以及周长阈值对应于 2D 投影上建筑物的最大尺寸。

3.1 带有信息的TIN

CGAL Delaunay 提供了一组三角测量的 API,用于计算顶点、三角面的空间相互关系,可以获取比TIN本身更多的信息。在例子中,计算每个顶点跟踪的输入点云中对应点索引,并且每个面都被赋予其连接组件的索引。

  1. auto idx_to_point_with_info
  2. = [&](const Point_set::Index& idx) -> std::pair<Point_3, Point_set::Index>
  3. {
  4. return std::make_pair (points.point(idx), idx);
  5. };
  6. TIN_with_info tin_with_info
  7. (boost::make_transform_iterator (points.begin(), idx_to_point_with_info),
  8. boost::make_transform_iterator (points.end(), idx_to_point_with_info));

3.2 识别连接组件

使用泛洪算法计算不规则三角网构成的连接组件,以种子点进行泛洪,在未超过设定的阈值时,将附近的顶点坐标信息加入到同一个连接组件TIN中。

  1. double spacing = CGAL::compute_average_spacing<Concurrency_tag>(points, 6);
  2. spacing *= 2;
  3. auto face_height
  4. = [&](const TIN_with_info::Face_handle fh) -> double
  5. {
  6. double out = 0.;
  7. for (int i = 0; i < 3; ++ i)
  8. out = (std::max) (out, CGAL::abs(fh->vertex(i)->point().z() - fh->vertex((i+1)%3)->point().z()));
  9. return out;
  10. };
  11. // Initialize faces info
  12. for (TIN_with_info::Face_handle fh : tin_with_info.all_face_handles())
  13. if (tin_with_info.is_infinite(fh) || face_height(fh) > spacing) // Filtered faces are given info() = -2
  14. fh->info() = -2;
  15. else // Pending faces are given info() = -1;
  16. fh->info() = -1;
  17. // Flooding algorithm
  18. std::vector<int> component_size;
  19. for (TIN_with_info::Face_handle fh : tin_with_info.finite_face_handles())
  20. {
  21. if (fh->info() != -1)
  22. continue;
  23. std::queue<TIN_with_info::Face_handle> todo;
  24. todo.push(fh);
  25. int size = 0;
  26. while (!todo.empty())
  27. {
  28. TIN_with_info::Face_handle current = todo.front();
  29. todo.pop();
  30. if (current->info() != -1)
  31. continue;
  32. current->info() = int(component_size.size());
  33. ++ size;
  34. for (int i = 0; i < 3; ++ i)
  35. todo.push (current->neighbor(i));
  36. }
  37. component_size.push_back (size);
  38. }
  39. std::cerr << component_size.size() << " connected component(s) found" << std::endl;

这个包含了连接部件信息的TIN可以保存为彩色网格:

  1. Mesh tin_colored_mesh;
  2. Mesh::Property_map<Mesh::Face_index, CGAL::IO::Color>
  3. color_map = tin_colored_mesh.add_property_map<Mesh::Face_index, CGAL::IO::Color>("f:color").first;
  4. CGAL::copy_face_graph (tin_with_info, tin_colored_mesh,
  5. CGAL::parameters::face_to_face_output_iterator
  6. (boost::make_function_output_iterator
  7. ([&](const std::pair<TIN_with_info::Face_handle, Mesh::Face_index>& ff)
  8. {
  9. // Color unassigned faces gray
  10. if (ff.first->info() < 0)
  11. color_map[ff.second] = CGAL::IO::Color(128, 128, 128);
  12. else
  13. {
  14. // Random color seeded by the component ID
  15. CGAL::Random r (ff.first->info());
  16. color_map[ff.second] = CGAL::IO::Color (r.get_int(64, 192),
  17. r.get_int(64, 192),
  18. r.get_int(64, 192));
  19. }
  20. })));
  21. std::ofstream tin_colored_ofile ("colored_tin.ply", std::ios_base::binary);
  22. CGAL::IO::set_binary_mode (tin_colored_ofile);
  23. CGAL::IO::write_PLY (tin_colored_ofile, tin_colored_mesh);
  24. tin_colored_ofile.close();

下图给出了一个由连接组件着色的TIN示例。

3.3 数据清洗

比最大的建筑还小的组件可以这样移除:

  1. int min_size = int(points.size() / 2);
  2. std::vector<TIN_with_info::Vertex_handle> to_remove;
  3. for (TIN_with_info::Vertex_handle vh : tin_with_info.finite_vertex_handles())
  4. {
  5. TIN_with_info::Face_circulator circ = tin_with_info.incident_faces (vh),
  6. start = circ;
  7. // Remove a vertex if it's only adjacent to components smaller than threshold
  8. bool keep = false;
  9. do
  10. {
  11. if (circ->info() >= 0 && component_size[std::size_t(circ->info())] > min_size)
  12. {
  13. keep = true;
  14. break;
  15. }
  16. }
  17. while (++ circ != start);
  18. if (!keep)
  19. to_remove.push_back (vh);
  20. }
  21. std::cerr << to_remove.size() << " vertices(s) will be removed after filtering" << std::endl;
  22. for (TIN_with_info::Vertex_handle vh : to_remove)
  23. tin_with_info.remove (vh);

3.4 孔洞填充和网格重建

因为简单地移除被建筑物覆盖的大面积的顶点会导致生成大的Delaunay三角面片,从而造成DTM的质量较差,对于这些孔洞,使用孔填充算法填充对孔进行三角测量、细化和平整,以生成形状更好的网格数据模型。以下代码段将 TIN 复制到网格中,同时过滤掉过大的面,然后识别孔并填充所有孔,除了最大的孔(即外壳)。

  1. // Copy and keep track of overly large faces
  2. Mesh dtm_mesh;
  3. std::vector<Mesh::Face_index> face_selection;
  4. Mesh::Property_map<Mesh::Face_index, bool> face_selection_map
  5. = dtm_mesh.add_property_map<Mesh::Face_index, bool>("is_selected", false).first;
  6. double limit = CGAL::square (5 * spacing);
  7. CGAL::copy_face_graph (tin_with_info, dtm_mesh,
  8. CGAL::parameters::face_to_face_output_iterator
  9. (boost::make_function_output_iterator
  10. ([&](const std::pair<TIN_with_info::Face_handle, Mesh::Face_index>& ff)
  11. {
  12. double longest_edge = 0.;
  13. bool border = false;
  14. for (int i = 0; i < 3; ++ i)
  15. {
  16. longest_edge = (std::max)(longest_edge, CGAL::squared_distance
  17. (ff.first->vertex((i+1)%3)->point(),
  18. ff.first->vertex((i+2)%3)->point()));
  19. TIN_with_info::Face_circulator circ
  20. = tin_with_info.incident_faces (ff.first->vertex(i)),
  21. start = circ;
  22. do
  23. {
  24. if (tin_with_info.is_infinite (circ))
  25. {
  26. border = true;
  27. break;
  28. }
  29. }
  30. while (++ circ != start);
  31. if (border)
  32. break;
  33. }
  34. // Select if face is too big AND it's not
  35. // on the border (to have closed holes)
  36. if (!border && longest_edge > limit)
  37. {
  38. face_selection_map[ff.second] = true;
  39. face_selection.push_back (ff.second);
  40. }
  41. })));
  42. // Save original DTM
  43. std::ofstream dtm_ofile ("dtm.ply", std::ios_base::binary);
  44. CGAL::IO::set_binary_mode (dtm_ofile);
  45. CGAL::IO::write_PLY (dtm_ofile, dtm_mesh);
  46. dtm_ofile.close();
  47. std::cerr << face_selection.size() << " face(s) are selected for removal" << std::endl;
  48. // Expand face selection to keep a well formed 2-manifold mesh after removal
  49. CGAL::expand_face_selection_for_removal (face_selection, dtm_mesh, face_selection_map);
  50. face_selection.clear();
  51. for (Mesh::Face_index fi : faces(dtm_mesh))
  52. if (face_selection_map[fi])
  53. face_selection.push_back(fi);
  54. std::cerr << face_selection.size() << " face(s) are selected for removal after expansion" << std::endl;
  55. for (Mesh::Face_index fi : face_selection)
  56. CGAL::Euler::remove_face (halfedge(fi, dtm_mesh), dtm_mesh);
  57. dtm_mesh.collect_garbage();
  58. if (!dtm_mesh.is_valid())
  59. std::cerr << "Invalid mesh!" << std::endl;
  60. // Save filtered DTM
  61. std::ofstream dtm_holes_ofile ("dtm_with_holes.ply", std::ios_base::binary);
  62. CGAL::IO::set_binary_mode (dtm_holes_ofile);
  63. CGAL::IO::write_PLY (dtm_holes_ofile, dtm_mesh);
  64. dtm_holes_ofile.close();
  65. // Get all holes
  66. std::vector<Mesh::Halfedge_index> holes;
  67. CGAL::Polygon_mesh_processing::extract_boundary_cycles (dtm_mesh, std::back_inserter (holes));
  68. std::cerr << holes.size() << " hole(s) identified" << std::endl;
  69. // Identify outer hull (hole with maximum size)
  70. double max_size = 0.;
  71. Mesh::Halfedge_index outer_hull;
  72. for (Mesh::Halfedge_index hi : holes)
  73. {
  74. CGAL::Bbox_3 hole_bbox;
  75. for (Mesh::Halfedge_index haf : CGAL::halfedges_around_face(hi, dtm_mesh))
  76. {
  77. const Point_3& p = dtm_mesh.point(target(haf, dtm_mesh));
  78. hole_bbox += p.bbox();
  79. }
  80. double size = CGAL::squared_distance (Point_2(hole_bbox.xmin(), hole_bbox.ymin()),
  81. Point_2(hole_bbox.xmax(), hole_bbox.ymax()));
  82. if (size > max_size)
  83. {
  84. max_size = size;
  85. outer_hull = hi;
  86. }
  87. }
  88. // Fill all holes except the bigest (which is the outer hull of the mesh)
  89. for (Mesh::Halfedge_index hi : holes)
  90. if (hi != outer_hull)
  91. CGAL::Polygon_mesh_processing::triangulate_refine_and_fair_hole
  92. (dtm_mesh, hi, CGAL::Emptyset_iterator(), CGAL::Emptyset_iterator());
  93. // Save DTM with holes filled
  94. std::ofstream dtm_filled_ofile ("dtm_filled.ply", std::ios_base::binary);
  95. CGAL::IO::set_binary_mode (dtm_filled_ofile);
  96. CGAL::IO::write_PLY (dtm_filled_ofile, dtm_mesh);
  97. dtm_filled_ofile.close();

为了产生不受二维Delaunay面形状约束的更规则的网格,各向同性网格也可以作为最后一步进行。

  1. CGAL::Polygon_mesh_processing::isotropic_remeshing (faces(dtm_mesh), spacing, dtm_mesh);
  2. std::ofstream dtm_remeshed_ofile ("dtm_remeshed.ply", std::ios_base::binary);
  3. CGAL::IO::set_binary_mode (dtm_remeshed_ofile);
  4. CGAL::IO::write_PLY (dtm_remeshed_ofile, dtm_mesh);
  5. dtm_remeshed_ofile.close();

4 光栅化

TIN数据结构可以与重心坐标相结合,以便插值并栅格化任何分辨率的高度图,需要嵌入顶点的信息。由于最新的两个步骤(孔填充和网格划分)是在3D网格上进行的,因此DTM是2.5D表示的假设可能不再有效。因此,我们首先使用最后计算的各向同性DTM网格的顶点重建TIN。以下使用代码段使用简单位图格式(PPM)生成栅格DEM。

  1. CGAL::Bbox_3 bbox = CGAL::bbox_3 (points.points().begin(), points.points().end());
  2. // Generate raster image 1920-pixels large
  3. std::size_t width = 1920;
  4. std::size_t height = std::size_t((bbox.ymax() - bbox.ymin()) * 1920 / (bbox.xmax() - bbox.xmin()));
  5. std::cerr << "Rastering with resolution " << width << "x" << height << std::endl;
  6. // Use PPM format (Portable PixMap) for simplicity
  7. std::ofstream raster_ofile ("raster.ppm", std::ios_base::binary);
  8. // PPM header
  9. raster_ofile << "P6" << std::endl // magic number
  10. << width << " " << height << std::endl // dimensions of the image
  11. << 255 << std::endl; // maximum color value
  12. // Use rainbow color ramp output
  13. Color_ramp color_ramp;
  14. // Keeping track of location from one point to its neighbor allows
  15. // for fast locate in DT
  16. TIN::Face_handle location;
  17. // Query each pixel of the image
  18. for (std::size_t y = 0; y < height; ++ y)
  19. for (std::size_t x = 0; x < width; ++ x)
  20. {
  21. Point_3 query (bbox.xmin() + x * (bbox.xmax() - bbox.xmin()) / double(width),
  22. bbox.ymin() + (height-y) * (bbox.ymax() - bbox.ymin()) / double(height),
  23. 0); // not relevant for location in 2D
  24. location = dtm_clean.locate (query, location);
  25. // Points outside the convex hull will be colored black
  26. std::array<unsigned char, 3> colors { 0, 0, 0 };
  27. if (!dtm_clean.is_infinite(location))
  28. {
  29. std::array<double, 3> barycentric_coordinates
  30. = CGAL::Polygon_mesh_processing::barycentric_coordinates
  31. (Point_2 (location->vertex(0)->point().x(), location->vertex(0)->point().y()),
  32. Point_2 (location->vertex(1)->point().x(), location->vertex(1)->point().y()),
  33. Point_2 (location->vertex(2)->point().x(), location->vertex(2)->point().y()),
  34. Point_2 (query.x(), query.y()),
  35. Kernel());
  36. double height_at_query
  37. = (barycentric_coordinates[0] * location->vertex(0)->point().z()
  38. + barycentric_coordinates[1] * location->vertex(1)->point().z()
  39. + barycentric_coordinates[2] * location->vertex(2)->point().z());
  40. // Color ramp generates a color depending on a value from 0 to 1
  41. double height_ratio = (height_at_query - bbox.zmin()) / (bbox.zmax() - bbox.zmin());
  42. colors = color_ramp.get(height_ratio);
  43. }
  44. raster_ofile.write (reinterpret_cast<char*>(&colors), 3);
  45. }
  46. raster_ofile.close();

下图给出了一个用彩虹斜坡表示高度的光栅图像示例。

5 等高线生成

提取TIN上定义的函数的等值是可以用CGAL完成的另一个应用程序。我们在这里演示如何提取等高线来构建地形图。

5.1绘制等高线图

第一步是,从三角剖分的所有面中,以分段的形式提取每个等值面经过该面的截面。下面的函数允许测试一个等值值是否穿过一个面,然后提取它:

  1. bool face_has_isovalue (TIN::Face_handle fh, double isovalue)
  2. {
  3. bool above = false, below = false;
  4. for (int i = 0; i < 3; ++ i)
  5. {
  6. // Face has isovalue if one of its vertices is above and another
  7. // one below
  8. if (fh->vertex(i)->point().z() > isovalue)
  9. above = true;
  10. if (fh->vertex(i)->point().z() < isovalue)
  11. below = true;
  12. }
  13. return (above && below);
  14. }
  15. Segment_3 isocontour_in_face (TIN::Face_handle fh, double isovalue)
  16. {
  17. Point_3 source;
  18. Point_3 target;
  19. bool source_found = false;
  20. for (int i = 0; i < 3; ++ i)
  21. {
  22. Point_3 p0 = fh->vertex((i+1) % 3)->point();
  23. Point_3 p1 = fh->vertex((i+2) % 3)->point();
  24. // Check if the isovalue crosses segment (p0,p1)
  25. if ((p0.z() - isovalue) * (p1.z() - isovalue) > 0)
  26. continue;
  27. double zbottom = p0.z();
  28. double ztop = p1.z();
  29. if (zbottom > ztop)
  30. {
  31. std::swap (zbottom, ztop);
  32. std::swap (p0, p1);
  33. }
  34. // Compute position of segment vertex
  35. double ratio = (isovalue - zbottom) / (ztop - zbottom);
  36. Point_3 p = CGAL::barycenter (p0, (1 - ratio), p1,ratio);
  37. if (source_found)
  38. target = p;
  39. else
  40. {
  41. source = p;
  42. source_found = true;
  43. }
  44. }
  45. return Segment_3 (source, target);
  46. }

 通过这些函数,我们可以创建一个线段图,然后将其处理成一组折线。为此,我们使用`boost::adjacency_list`结构,并跟踪从端点到顶点的映射。下以下代码计算最小和最大高度差的50 个等值,并创建一个包含所有等值的图形:

  1. std::array<double, 50> isovalues; // Contour 50 isovalues
  2. for (std::size_t i = 0; i < isovalues.size(); ++ i)
  3. isovalues[i] = bbox.zmin() + ((i+1) * (bbox.zmax() - bbox.zmin()) / (isovalues.size() - 2));
  4. // First find on each face if they are crossed by some isovalues and
  5. // extract segments in a graph
  6. using Segment_graph = boost::adjacency_list<boost::listS, boost::vecS, boost::undirectedS, Point_3>;
  7. Segment_graph graph;
  8. using Map_p2v = std::map<Point_3, Segment_graph::vertex_descriptor>;
  9. Map_p2v map_p2v;
  10. for (TIN::Face_handle vh : dtm_clean.finite_face_handles())
  11. for (double iv : isovalues)
  12. if (face_has_isovalue (vh, iv))
  13. {
  14. Segment_3 segment = isocontour_in_face (vh, iv);
  15. for (const Point_3& p : { segment.source(), segment.target() })
  16. {
  17. // Only insert end points of segments once to get a well connected graph
  18. Map_p2v::iterator iter;
  19. bool inserted;
  20. std::tie (iter, inserted) = map_p2v.insert (std::make_pair (p, Segment_graph::vertex_descriptor()));
  21. if (inserted)
  22. {
  23. iter->second = boost::add_vertex (graph);
  24. graph[iter->second] = p;
  25. }
  26. }
  27. boost::add_edge (map_p2v[segment.source()], map_p2v[segment.target()], graph);
  28. }

5.2分割成折线

创建图形后,使用函数`CGAL::split_graph_into_polylines()`就可以很容易地将其分解为折线:

  1. // Split segments into polylines
  2. std::vector<std::vector<Point_3> > polylines;
  3. Polylines_visitor<Segment_graph> visitor (graph, polylines);
  4. CGAL::split_graph_into_polylines (graph, visitor);
  5. std::cerr << polylines.size() << " polylines computed, with "
  6. << map_p2v.size() << " vertices in total" << std::endl;
  7. // Output to WKT file
  8. std::ofstream contour_ofile ("contour.wkt");
  9. contour_ofile.precision(18);
  10. CGAL::IO::write_multi_linestring_WKT (contour_ofile, polylines);
  11. contour_ofile.close();

 该函数需要提供一个可供访问的对象,以便在开始折线、向其添加点以及结束它时调用。可以定义一个简单的类使用它。

  1. template <typename Graph>
  2. class Polylines_visitor
  3. {
  4. private:
  5. std::vector<std::vector<Point_3> >& polylines;
  6. Graph& graph;
  7. public:
  8. Polylines_visitor (Graph& graph, std::vector<std::vector<Point_3> >& polylines)
  9. : polylines (polylines), graph(graph) { }
  10. void start_new_polyline()
  11. {
  12. polylines.push_back (std::vector<Point_3>());
  13. }
  14. void add_node (typename Graph::vertex_descriptor vd)
  15. {
  16. polylines.back().push_back (graph[vd]);
  17. }
  18. void end_polyline()
  19. {
  20. // filter small polylines
  21. if (polylines.back().size() < 50)
  22. polylines.pop_back();
  23. }
  24. };

 5.3 等高线简化

由于输出的噪声很大,用户可能希望简化折线。CGAL提供了一种折线简化算法,该算法保证简化后两条折线不相交。该算法利用了`CGAL::Constrained_triangulation_plus_2`,它将折线嵌入为一组约束:

  1. namespace PS = CGAL::Polyline_simplification_2;
  2. using CDT_vertex_base = PS::Vertex_base_2<Projection_traits>;
  3. using CDT_face_base = CGAL::Constrained_triangulation_face_base_2<Projection_traits>;
  4. using CDT_TDS = CGAL::Triangulation_data_structure_2<CDT_vertex_base, CDT_face_base>;
  5. using CDT = CGAL::Constrained_Delaunay_triangulation_2<Projection_traits, CDT_TDS>;
  6. using CTP = CGAL::Constrained_triangulation_plus_2<CDT>;

下面的代码根据折线到原始折线的平方距离简化了折线集,当没有更多的顶点可以移除时停止,而不超过平均间距的4倍。

  1. // Construct constrained Delaunay triangulation with polylines as constraints
  2. CTP ctp;
  3. for (const std::vector<Point_3>& poly : polylines)
  4. ctp.insert_constraint (poly.begin(), poly.end());
  5. // Simplification algorithm with limit on distance
  6. PS::simplify (ctp, PS::Squared_distance_cost(), PS::Stop_above_cost_threshold (16 * spacing * spacing));
  7. polylines.clear();
  8. for (CTP::Constraint_id cid : ctp.constraints())
  9. {
  10. polylines.push_back (std::vector<Point_3>());
  11. polylines.back().reserve (ctp.vertices_in_constraint (cid).size());
  12. for (CTP::Vertex_handle vh : ctp.vertices_in_constraint(cid))
  13. polylines.back().push_back (vh->point());
  14. }
  15. std::size_t nb_vertices
  16. = std::accumulate (polylines.begin(), polylines.end(), std::size_t(0),
  17. [](std::size_t size, const std::vector<Point_3>& poly) -> std::size_t
  18. { return size + poly.size(); });
  19. std::cerr << nb_vertices
  20. << " vertices remaining after simplification ("
  21. << 100. * (nb_vertices / double(map_p2v.size())) << "%)" << std::endl;
  22. // Output to WKT file
  23. std::ofstream simplified_ofile ("simplified.wkt");
  24. simplified_ofile.precision(18);
  25. CGAL::IO::write_multi_linestring_WKT (simplified_ofile, polylines);
  26. simplified_ofile.close();

 图中给出了轮廓和简化的例子。等高线使用50等值均匀间隔。顶部:使用148k个顶点的原始轮廓和简化容差等于输入点云的平均间距(原始折线顶点的3.4%剩余)。下图:简化,公差为平均间距的4倍(剩余顶点的1.3%),公差为平均间距的10倍(剩余顶点的0.9%)。折线在所有情况下都是无交叉的。

6、点云分类

提供了一个包分类,可用于将点云分割为用户定义的标签集。目前CGAL中最先进的分类器是随机森林。由于它是一个监督分类器,因此需要一个训练集。下面的代码片段展示了如何使用一些手动选择的训练集来训练随机森林分类器,并计算由图切割算法正则化的分类:

  1. // Get training from input
  2. Point_set::Property_map<int> training_map;
  3. bool training_found;
  4. std::tie (training_map, training_found) = points.property_map<int>("training");
  5. if (training_found)
  6. {
  7. std::cerr << "Classifying ground/vegetation/building" << std::endl;
  8. // Create labels
  9. Classification::Label_set labels ({ "ground", "vegetation", "building" });
  10. // Generate features
  11. Classification::Feature_set features;
  12. Classification::Point_set_feature_generator<Kernel, Point_set, Point_set::Point_map>
  13. generator (points, points.point_map(), 5); // 5 scales
  14. #ifdef CGAL_LINKED_WITH_TBB
  15. // If TBB is used, features can be computed in parallel
  16. features.begin_parallel_additions();
  17. generator.generate_point_based_features (features);
  18. features.end_parallel_additions();
  19. #else
  20. generator.generate_point_based_features (features);
  21. #endif
  22. // Train a random forest classifier
  23. Classification::ETHZ::Random_forest_classifier classifier (labels, features);
  24. classifier.train (points.range(training_map));
  25. // Classify with graphcut regularization
  26. Point_set::Property_map<int> label_map = points.add_property_map<int>("labels").first;
  27. Classification::classify_with_graphcut<Concurrency_tag>
  28. (points, points.point_map(), labels, classifier,
  29. generator.neighborhood().k_neighbor_query(12), // regularize on 12-neighbors graph
  30. 0.5f, // graphcut weight
  31. 12, // Subdivide to speed-up process
  32. label_map);
  33. // Evaluate
  34. std::cerr << "Mean IoU on training data = "
  35. << Classification::Evaluation(labels,
  36. points.range(training_map),
  37. points.range(label_map)).mean_intersection_over_union() << std::endl;
  38. // Save the classified point set
  39. std::ofstream classified_ofile ("classified.ply");
  40. CGAL::IO::set_binary_mode (classified_ofile);
  41. classified_ofile << points;
  42. classified_ofile.close();
  43. }

 下图给出了训练集和分类结果的示例。顶部:用手分类的点云切片。下图:在3个人工分类切片上训练后,通过图切割规范化的分类。

7、完整代码示例

本教程中使用的所有代码片段都可以组装起来创建一个完整的GIS管道(如果使用了正确的include)。我们给出了一个完整的代码示例,它实现了本教程中描述的所有步骤。

  1. #include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
  2. #include <CGAL/Projection_traits_xy_3.h>
  3. #include <CGAL/Delaunay_triangulation_2.h>
  4. #include <CGAL/Triangulation_vertex_base_with_info_2.h>
  5. #include <CGAL/Triangulation_face_base_with_info_2.h>
  6. #include <CGAL/boost/graph/graph_traits_Delaunay_triangulation_2.h>
  7. #include <CGAL/boost/graph/copy_face_graph.h>
  8. #include <CGAL/Point_set_3.h>
  9. #include <CGAL/Point_set_3/IO.h>
  10. #include <CGAL/compute_average_spacing.h>
  11. #include <CGAL/Surface_mesh.h>
  12. #include <CGAL/Polygon_mesh_processing/locate.h>
  13. #include <CGAL/Polygon_mesh_processing/triangulate_hole.h>
  14. #include <CGAL/Polygon_mesh_processing/border.h>
  15. #include <CGAL/Polygon_mesh_processing/remesh.h>
  16. #include <boost/graph/adjacency_list.hpp>
  17. #include <CGAL/boost/graph/split_graph_into_polylines.h>
  18. #include <CGAL/IO/WKT.h>
  19. #include <CGAL/Constrained_Delaunay_triangulation_2.h>
  20. #include <CGAL/Constrained_triangulation_plus_2.h>
  21. #include <CGAL/Polyline_simplification_2/simplify.h>
  22. #include <CGAL/Polyline_simplification_2/Squared_distance_cost.h>
  23. #include <CGAL/Classification.h>
  24. #include <CGAL/Random.h>
  25. #include <fstream>
  26. #include <queue>
  27. #include "include/Color_ramp.h"
  28. using Kernel = CGAL::Exact_predicates_inexact_constructions_kernel;
  29. using Projection_traits = CGAL::Projection_traits_xy_3<Kernel>;
  30. using Point_2 = Kernel::Point_2;
  31. using Point_3 = Kernel::Point_3;
  32. using Segment_3 = Kernel::Segment_3;
  33. // Triangulated Irregular Network
  34. using TIN = CGAL::Delaunay_triangulation_2<Projection_traits>;
  35. // Triangulated Irregular Network (with info)
  36. using Point_set = CGAL::Point_set_3<Point_3>;
  37. using Vbi = CGAL::Triangulation_vertex_base_with_info_2 <Point_set::Index, Projection_traits>;
  38. using Fbi = CGAL::Triangulation_face_base_with_info_2<int, Projection_traits>;
  39. using TDS = CGAL::Triangulation_data_structure_2<Vbi, Fbi>;
  40. using TIN_with_info = CGAL::Delaunay_triangulation_2<Projection_traits, TDS>;
  41. namespace Classification = CGAL::Classification;
  42. #ifdef CGAL_LINKED_WITH_TBB
  43. using Concurrency_tag = CGAL::Parallel_tag;
  44. #else
  45. using Concurrency_tag = CGAL::Sequential_tag;
  46. #endif
  47. bool face_has_isovalue (TIN::Face_handle fh, double isovalue)
  48. {
  49. bool above = false, below = false;
  50. for (int i = 0; i < 3; ++ i)
  51. {
  52. // Face has isovalue if one of its vertices is above and another
  53. // one below
  54. if (fh->vertex(i)->point().z() > isovalue)
  55. above = true;
  56. if (fh->vertex(i)->point().z() < isovalue)
  57. below = true;
  58. }
  59. return (above && below);
  60. }
  61. Segment_3 isocontour_in_face (TIN::Face_handle fh, double isovalue)
  62. {
  63. Point_3 source;
  64. Point_3 target;
  65. bool source_found = false;
  66. for (int i = 0; i < 3; ++ i)
  67. {
  68. Point_3 p0 = fh->vertex((i+1) % 3)->point();
  69. Point_3 p1 = fh->vertex((i+2) % 3)->point();
  70. // Check if the isovalue crosses segment (p0,p1)
  71. if ((p0.z() - isovalue) * (p1.z() - isovalue) > 0)
  72. continue;
  73. double zbottom = p0.z();
  74. double ztop = p1.z();
  75. if (zbottom > ztop)
  76. {
  77. std::swap (zbottom, ztop);
  78. std::swap (p0, p1);
  79. }
  80. // Compute position of segment vertex
  81. double ratio = (isovalue - zbottom) / (ztop - zbottom);
  82. Point_3 p = CGAL::barycenter (p0, (1 - ratio), p1,ratio);
  83. if (source_found)
  84. target = p;
  85. else
  86. {
  87. source = p;
  88. source_found = true;
  89. }
  90. }
  91. return Segment_3 (source, target);
  92. }
  93. template <typename Graph>
  94. class Polylines_visitor
  95. {
  96. private:
  97. std::vector<std::vector<Point_3> >& polylines;
  98. Graph& graph;
  99. public:
  100. Polylines_visitor (Graph& graph, std::vector<std::vector<Point_3> >& polylines)
  101. : polylines (polylines), graph(graph) { }
  102. void start_new_polyline()
  103. {
  104. polylines.push_back (std::vector<Point_3>());
  105. }
  106. void add_node (typename Graph::vertex_descriptor vd)
  107. {
  108. polylines.back().push_back (graph[vd]);
  109. }
  110. void end_polyline()
  111. {
  112. // filter small polylines
  113. if (polylines.back().size() < 50)
  114. polylines.pop_back();
  115. }
  116. };
  117. namespace PS = CGAL::Polyline_simplification_2;
  118. using CDT_vertex_base = PS::Vertex_base_2<Projection_traits>;
  119. using CDT_face_base = CGAL::Constrained_triangulation_face_base_2<Projection_traits>;
  120. using CDT_TDS = CGAL::Triangulation_data_structure_2<CDT_vertex_base, CDT_face_base>;
  121. using CDT = CGAL::Constrained_Delaunay_triangulation_2<Projection_traits, CDT_TDS>;
  122. using CTP = CGAL::Constrained_triangulation_plus_2<CDT>;
  123. int main (int argc, char** argv)
  124. {
  125. const std::string fname = argc != 2 ? CGAL::data_file_path("points_3/b9_training.ply") : argv[1];
  126. if (argc != 2)
  127. {
  128. std::cerr << "Usage: " << argv[0] << " points.ply" << std::endl;
  129. std::cerr << "Running with default value " << fname << "\n";
  130. }
  131. // Read points
  132. std::ifstream ifile (fname, std::ios_base::binary);
  133. CGAL::Point_set_3<Point_3> points;
  134. ifile >> points;
  135. std::cerr << points.size() << " point(s) read" << std::endl;
  136. // Create DSM
  137. TIN dsm (points.points().begin(), points.points().end());
  138. using Mesh = CGAL::Surface_mesh<Point_3>;
  139. Mesh dsm_mesh;
  140. CGAL::copy_face_graph (dsm, dsm_mesh);
  141. std::ofstream dsm_ofile ("dsm.ply", std::ios_base::binary);
  142. CGAL::IO::set_binary_mode (dsm_ofile);
  143. CGAL::IO::write_PLY (dsm_ofile, dsm_mesh);
  144. dsm_ofile.close();
  145. auto idx_to_point_with_info
  146. = [&](const Point_set::Index& idx) -> std::pair<Point_3, Point_set::Index>
  147. {
  148. return std::make_pair (points.point(idx), idx);
  149. };
  150. TIN_with_info tin_with_info
  151. (boost::make_transform_iterator (points.begin(), idx_to_point_with_info),
  152. boost::make_transform_iterator (points.end(), idx_to_point_with_info));
  153. double spacing = CGAL::compute_average_spacing<Concurrency_tag>(points, 6);
  154. spacing *= 2;
  155. auto face_height
  156. = [&](const TIN_with_info::Face_handle fh) -> double
  157. {
  158. double out = 0.;
  159. for (int i = 0; i < 3; ++ i)
  160. out = (std::max) (out, CGAL::abs(fh->vertex(i)->point().z() - fh->vertex((i+1)%3)->point().z()));
  161. return out;
  162. };
  163. // Initialize faces info
  164. for (TIN_with_info::Face_handle fh : tin_with_info.all_face_handles())
  165. if (tin_with_info.is_infinite(fh) || face_height(fh) > spacing) // Filtered faces are given info() = -2
  166. fh->info() = -2;
  167. else // Pending faces are given info() = -1;
  168. fh->info() = -1;
  169. // Flooding algorithm
  170. std::vector<int> component_size;
  171. for (TIN_with_info::Face_handle fh : tin_with_info.finite_face_handles())
  172. {
  173. if (fh->info() != -1)
  174. continue;
  175. std::queue<TIN_with_info::Face_handle> todo;
  176. todo.push(fh);
  177. int size = 0;
  178. while (!todo.empty())
  179. {
  180. TIN_with_info::Face_handle current = todo.front();
  181. todo.pop();
  182. if (current->info() != -1)
  183. continue;
  184. current->info() = int(component_size.size());
  185. ++ size;
  186. for (int i = 0; i < 3; ++ i)
  187. todo.push (current->neighbor(i));
  188. }
  189. component_size.push_back (size);
  190. }
  191. std::cerr << component_size.size() << " connected component(s) found" << std::endl;
  192. Mesh tin_colored_mesh;
  193. Mesh::Property_map<Mesh::Face_index, CGAL::IO::Color>
  194. color_map = tin_colored_mesh.add_property_map<Mesh::Face_index, CGAL::IO::Color>("f:color").first;
  195. CGAL::copy_face_graph (tin_with_info, tin_colored_mesh,
  196. CGAL::parameters::face_to_face_output_iterator
  197. (boost::make_function_output_iterator
  198. ([&](const std::pair<TIN_with_info::Face_handle, Mesh::Face_index>& ff)
  199. {
  200. // Color unassigned faces gray
  201. if (ff.first->info() < 0)
  202. color_map[ff.second] = CGAL::IO::Color(128, 128, 128);
  203. else
  204. {
  205. // Random color seeded by the component ID
  206. CGAL::Random r (ff.first->info());
  207. color_map[ff.second] = CGAL::IO::Color (r.get_int(64, 192),
  208. r.get_int(64, 192),
  209. r.get_int(64, 192));
  210. }
  211. })));
  212. std::ofstream tin_colored_ofile ("colored_tin.ply", std::ios_base::binary);
  213. CGAL::IO::set_binary_mode (tin_colored_ofile);
  214. CGAL::IO::write_PLY (tin_colored_ofile, tin_colored_mesh);
  215. tin_colored_ofile.close();
  216. int min_size = int(points.size() / 2);
  217. std::vector<TIN_with_info::Vertex_handle> to_remove;
  218. for (TIN_with_info::Vertex_handle vh : tin_with_info.finite_vertex_handles())
  219. {
  220. TIN_with_info::Face_circulator circ = tin_with_info.incident_faces (vh),
  221. start = circ;
  222. // Remove a vertex if it's only adjacent to components smaller than threshold
  223. bool keep = false;
  224. do
  225. {
  226. if (circ->info() >= 0 && component_size[std::size_t(circ->info())] > min_size)
  227. {
  228. keep = true;
  229. break;
  230. }
  231. }
  232. while (++ circ != start);
  233. if (!keep)
  234. to_remove.push_back (vh);
  235. }
  236. std::cerr << to_remove.size() << " vertices(s) will be removed after filtering" << std::endl;
  237. for (TIN_with_info::Vertex_handle vh : to_remove)
  238. tin_with_info.remove (vh);
  239. // Copy and keep track of overly large faces
  240. Mesh dtm_mesh;
  241. std::vector<Mesh::Face_index> face_selection;
  242. Mesh::Property_map<Mesh::Face_index, bool> face_selection_map
  243. = dtm_mesh.add_property_map<Mesh::Face_index, bool>("is_selected", false).first;
  244. double limit = CGAL::square (5 * spacing);
  245. CGAL::copy_face_graph (tin_with_info, dtm_mesh,
  246. CGAL::parameters::face_to_face_output_iterator
  247. (boost::make_function_output_iterator
  248. ([&](const std::pair<TIN_with_info::Face_handle, Mesh::Face_index>& ff)
  249. {
  250. double longest_edge = 0.;
  251. bool border = false;
  252. for (int i = 0; i < 3; ++ i)
  253. {
  254. longest_edge = (std::max)(longest_edge, CGAL::squared_distance
  255. (ff.first->vertex((i+1)%3)->point(),
  256. ff.first->vertex((i+2)%3)->point()));
  257. TIN_with_info::Face_circulator circ
  258. = tin_with_info.incident_faces (ff.first->vertex(i)),
  259. start = circ;
  260. do
  261. {
  262. if (tin_with_info.is_infinite (circ))
  263. {
  264. border = true;
  265. break;
  266. }
  267. }
  268. while (++ circ != start);
  269. if (border)
  270. break;
  271. }
  272. // Select if face is too big AND it's not
  273. // on the border (to have closed holes)
  274. if (!border && longest_edge > limit)
  275. {
  276. face_selection_map[ff.second] = true;
  277. face_selection.push_back (ff.second);
  278. }
  279. })));
  280. // Save original DTM
  281. std::ofstream dtm_ofile ("dtm.ply", std::ios_base::binary);
  282. CGAL::IO::set_binary_mode (dtm_ofile);
  283. CGAL::IO::write_PLY (dtm_ofile, dtm_mesh);
  284. dtm_ofile.close();
  285. std::cerr << face_selection.size() << " face(s) are selected for removal" << std::endl;
  286. // Expand face selection to keep a well formed 2-manifold mesh after removal
  287. CGAL::expand_face_selection_for_removal (face_selection, dtm_mesh, face_selection_map);
  288. face_selection.clear();
  289. for (Mesh::Face_index fi : faces(dtm_mesh))
  290. if (face_selection_map[fi])
  291. face_selection.push_back(fi);
  292. std::cerr << face_selection.size() << " face(s) are selected for removal after expansion" << std::endl;
  293. for (Mesh::Face_index fi : face_selection)
  294. CGAL::Euler::remove_face (halfedge(fi, dtm_mesh), dtm_mesh);
  295. dtm_mesh.collect_garbage();
  296. if (!dtm_mesh.is_valid())
  297. std::cerr << "Invalid mesh!" << std::endl;
  298. // Save filtered DTM
  299. std::ofstream dtm_holes_ofile ("dtm_with_holes.ply", std::ios_base::binary);
  300. CGAL::IO::set_binary_mode (dtm_holes_ofile);
  301. CGAL::IO::write_PLY (dtm_holes_ofile, dtm_mesh);
  302. dtm_holes_ofile.close();
  303. // Get all holes
  304. std::vector<Mesh::Halfedge_index> holes;
  305. CGAL::Polygon_mesh_processing::extract_boundary_cycles (dtm_mesh, std::back_inserter (holes));
  306. std::cerr << holes.size() << " hole(s) identified" << std::endl;
  307. // Identify outer hull (hole with maximum size)
  308. double max_size = 0.;
  309. Mesh::Halfedge_index outer_hull;
  310. for (Mesh::Halfedge_index hi : holes)
  311. {
  312. CGAL::Bbox_3 hole_bbox;
  313. for (Mesh::Halfedge_index haf : CGAL::halfedges_around_face(hi, dtm_mesh))
  314. {
  315. const Point_3& p = dtm_mesh.point(target(haf, dtm_mesh));
  316. hole_bbox += p.bbox();
  317. }
  318. double size = CGAL::squared_distance (Point_2(hole_bbox.xmin(), hole_bbox.ymin()),
  319. Point_2(hole_bbox.xmax(), hole_bbox.ymax()));
  320. if (size > max_size)
  321. {
  322. max_size = size;
  323. outer_hull = hi;
  324. }
  325. }
  326. // Fill all holes except the bigest (which is the outer hull of the mesh)
  327. for (Mesh::Halfedge_index hi : holes)
  328. if (hi != outer_hull)
  329. CGAL::Polygon_mesh_processing::triangulate_refine_and_fair_hole
  330. (dtm_mesh, hi, CGAL::Emptyset_iterator(), CGAL::Emptyset_iterator());
  331. // Save DTM with holes filled
  332. std::ofstream dtm_filled_ofile ("dtm_filled.ply", std::ios_base::binary);
  333. CGAL::IO::set_binary_mode (dtm_filled_ofile);
  334. CGAL::IO::write_PLY (dtm_filled_ofile, dtm_mesh);
  335. dtm_filled_ofile.close();
  336. CGAL::Polygon_mesh_processing::isotropic_remeshing (faces(dtm_mesh), spacing, dtm_mesh);
  337. std::ofstream dtm_remeshed_ofile ("dtm_remeshed.ply", std::ios_base::binary);
  338. CGAL::IO::set_binary_mode (dtm_remeshed_ofile);
  339. CGAL::IO::write_PLY (dtm_remeshed_ofile, dtm_mesh);
  340. dtm_remeshed_ofile.close();
  341. TIN dtm_clean (dtm_mesh.points().begin(), dtm_mesh.points().end());
  342. CGAL::Bbox_3 bbox = CGAL::bbox_3 (points.points().begin(), points.points().end());
  343. // Generate raster image 1920-pixels large
  344. std::size_t width = 1920;
  345. std::size_t height = std::size_t((bbox.ymax() - bbox.ymin()) * 1920 / (bbox.xmax() - bbox.xmin()));
  346. std::cerr << "Rastering with resolution " << width << "x" << height << std::endl;
  347. // Use PPM format (Portable PixMap) for simplicity
  348. std::ofstream raster_ofile ("raster.ppm", std::ios_base::binary);
  349. // PPM header
  350. raster_ofile << "P6" << std::endl // magic number
  351. << width << " " << height << std::endl // dimensions of the image
  352. << 255 << std::endl; // maximum color value
  353. // Use rainbow color ramp output
  354. Color_ramp color_ramp;
  355. // Keeping track of location from one point to its neighbor allows
  356. // for fast locate in DT
  357. TIN::Face_handle location;
  358. // Query each pixel of the image
  359. for (std::size_t y = 0; y < height; ++ y)
  360. for (std::size_t x = 0; x < width; ++ x)
  361. {
  362. Point_3 query (bbox.xmin() + x * (bbox.xmax() - bbox.xmin()) / double(width),
  363. bbox.ymin() + (height-y) * (bbox.ymax() - bbox.ymin()) / double(height),
  364. 0); // not relevant for location in 2D
  365. location = dtm_clean.locate (query, location);
  366. // Points outside the convex hull will be colored black
  367. std::array<unsigned char, 3> colors { 0, 0, 0 };
  368. if (!dtm_clean.is_infinite(location))
  369. {
  370. std::array<double, 3> barycentric_coordinates
  371. = CGAL::Polygon_mesh_processing::barycentric_coordinates
  372. (Point_2 (location->vertex(0)->point().x(), location->vertex(0)->point().y()),
  373. Point_2 (location->vertex(1)->point().x(), location->vertex(1)->point().y()),
  374. Point_2 (location->vertex(2)->point().x(), location->vertex(2)->point().y()),
  375. Point_2 (query.x(), query.y()),
  376. Kernel());
  377. double height_at_query
  378. = (barycentric_coordinates[0] * location->vertex(0)->point().z()
  379. + barycentric_coordinates[1] * location->vertex(1)->point().z()
  380. + barycentric_coordinates[2] * location->vertex(2)->point().z());
  381. // Color ramp generates a color depending on a value from 0 to 1
  382. double height_ratio = (height_at_query - bbox.zmin()) / (bbox.zmax() - bbox.zmin());
  383. colors = color_ramp.get(height_ratio);
  384. }
  385. raster_ofile.write (reinterpret_cast<char*>(&colors), 3);
  386. }
  387. raster_ofile.close();
  388. // Smooth heights with 5 successive Gaussian filters
  389. double gaussian_variance = 4 * spacing * spacing;
  390. for (TIN::Vertex_handle vh : dtm_clean.finite_vertex_handles())
  391. {
  392. double z = vh->point().z();
  393. double total_weight = 1;
  394. TIN::Vertex_circulator circ = dtm_clean.incident_vertices (vh),
  395. start = circ;
  396. do
  397. {
  398. if (!dtm_clean.is_infinite(circ))
  399. {
  400. double sq_dist = CGAL::squared_distance (vh->point(), circ->point());
  401. double weight = std::exp(- sq_dist / gaussian_variance);
  402. z += weight * circ->point().z();
  403. total_weight += weight;
  404. }
  405. }
  406. while (++ circ != start);
  407. z /= total_weight;
  408. vh->point() = Point_3 (vh->point().x(), vh->point().y(), z);
  409. }
  410. std::array<double, 50> isovalues; // Contour 50 isovalues
  411. for (std::size_t i = 0; i < isovalues.size(); ++ i)
  412. isovalues[i] = bbox.zmin() + ((i+1) * (bbox.zmax() - bbox.zmin()) / (isovalues.size() - 2));
  413. // First find on each face if they are crossed by some isovalues and
  414. // extract segments in a graph
  415. using Segment_graph = boost::adjacency_list<boost::listS, boost::vecS, boost::undirectedS, Point_3>;
  416. Segment_graph graph;
  417. using Map_p2v = std::map<Point_3, Segment_graph::vertex_descriptor>;
  418. Map_p2v map_p2v;
  419. for (TIN::Face_handle vh : dtm_clean.finite_face_handles())
  420. for (double iv : isovalues)
  421. if (face_has_isovalue (vh, iv))
  422. {
  423. Segment_3 segment = isocontour_in_face (vh, iv);
  424. for (const Point_3& p : { segment.source(), segment.target() })
  425. {
  426. // Only insert end points of segments once to get a well connected graph
  427. Map_p2v::iterator iter;
  428. bool inserted;
  429. std::tie (iter, inserted) = map_p2v.insert (std::make_pair (p, Segment_graph::vertex_descriptor()));
  430. if (inserted)
  431. {
  432. iter->second = boost::add_vertex (graph);
  433. graph[iter->second] = p;
  434. }
  435. }
  436. boost::add_edge (map_p2v[segment.source()], map_p2v[segment.target()], graph);
  437. }
  438. // Split segments into polylines
  439. std::vector<std::vector<Point_3> > polylines;
  440. Polylines_visitor<Segment_graph> visitor (graph, polylines);
  441. CGAL::split_graph_into_polylines (graph, visitor);
  442. std::cerr << polylines.size() << " polylines computed, with "
  443. << map_p2v.size() << " vertices in total" << std::endl;
  444. // Output to WKT file
  445. std::ofstream contour_ofile ("contour.wkt");
  446. contour_ofile.precision(18);
  447. CGAL::IO::write_multi_linestring_WKT (contour_ofile, polylines);
  448. contour_ofile.close();
  449. // Construct constrained Delaunay triangulation with polylines as constraints
  450. CTP ctp;
  451. for (const std::vector<Point_3>& poly : polylines)
  452. ctp.insert_constraint (poly.begin(), poly.end());
  453. // Simplification algorithm with limit on distance
  454. PS::simplify (ctp, PS::Squared_distance_cost(), PS::Stop_above_cost_threshold (16 * spacing * spacing));
  455. polylines.clear();
  456. for (CTP::Constraint_id cid : ctp.constraints())
  457. {
  458. polylines.push_back (std::vector<Point_3>());
  459. polylines.back().reserve (ctp.vertices_in_constraint (cid).size());
  460. for (CTP::Vertex_handle vh : ctp.vertices_in_constraint(cid))
  461. polylines.back().push_back (vh->point());
  462. }
  463. std::size_t nb_vertices
  464. = std::accumulate (polylines.begin(), polylines.end(), std::size_t(0),
  465. [](std::size_t size, const std::vector<Point_3>& poly) -> std::size_t
  466. { return size + poly.size(); });
  467. std::cerr << nb_vertices
  468. << " vertices remaining after simplification ("
  469. << 100. * (nb_vertices / double(map_p2v.size())) << "%)" << std::endl;
  470. // Output to WKT file
  471. std::ofstream simplified_ofile ("simplified.wkt");
  472. simplified_ofile.precision(18);
  473. CGAL::IO::write_multi_linestring_WKT (simplified_ofile, polylines);
  474. simplified_ofile.close();
  475. // Get training from input
  476. Point_set::Property_map<int> training_map;
  477. bool training_found;
  478. std::tie (training_map, training_found) = points.property_map<int>("training");
  479. if (training_found)
  480. {
  481. std::cerr << "Classifying ground/vegetation/building" << std::endl;
  482. // Create labels
  483. Classification::Label_set labels ({ "ground", "vegetation", "building" });
  484. // Generate features
  485. Classification::Feature_set features;
  486. Classification::Point_set_feature_generator<Kernel, Point_set, Point_set::Point_map>
  487. generator (points, points.point_map(), 5); // 5 scales
  488. #ifdef CGAL_LINKED_WITH_TBB
  489. // If TBB is used, features can be computed in parallel
  490. features.begin_parallel_additions();
  491. generator.generate_point_based_features (features);
  492. features.end_parallel_additions();
  493. #else
  494. generator.generate_point_based_features (features);
  495. #endif
  496. // Train a random forest classifier
  497. Classification::ETHZ::Random_forest_classifier classifier (labels, features);
  498. classifier.train (points.range(training_map));
  499. // Classify with graphcut regularization
  500. Point_set::Property_map<int> label_map = points.add_property_map<int>("labels").first;
  501. Classification::classify_with_graphcut<Concurrency_tag>
  502. (points, points.point_map(), labels, classifier,
  503. generator.neighborhood().k_neighbor_query(12), // regularize on 12-neighbors graph
  504. 0.5f, // graphcut weight
  505. 12, // Subdivide to speed-up process
  506. label_map);
  507. // Evaluate
  508. std::cerr << "Mean IoU on training data = "
  509. << Classification::Evaluation(labels,
  510. points.range(training_map),
  511. points.range(label_map)).mean_intersection_over_union() << std::endl;
  512. // Save the classified point set
  513. std::ofstream classified_ofile ("classified.ply");
  514. CGAL::IO::set_binary_mode (classified_ofile);
  515. classified_ofile << points;
  516. classified_ofile.close();
  517. }
  518. return EXIT_SUCCESS;
  519. }
  520. ```
  521. # 8、附:Color_ramp.h
  522. ```cpp
  523. #ifndef COLOR_RAMP_H
  524. #define COLOR_RAMP_H
  525. class Color_ramp
  526. {
  527. typedef std::array<unsigned char, 3> Color;
  528. typedef std::pair<double, Color> Step;
  529. std::vector<Step> m_steps;
  530. public:
  531. Color_ramp()
  532. {
  533. m_steps.push_back (std::make_pair (0, Color{ 192, 192, 255}));
  534. m_steps.push_back (std::make_pair (0.2, Color{ 0, 0, 255}));
  535. m_steps.push_back (std::make_pair (0.4, Color{ 0, 255, 0}));
  536. m_steps.push_back (std::make_pair (0.6, Color{ 255, 255, 0}));
  537. m_steps.push_back (std::make_pair (0.8, Color{ 255, 0, 0}));
  538. m_steps.push_back (std::make_pair (1.0, Color{ 128, 0, 0}));
  539. }
  540. Color get (double value) const
  541. {
  542. std::size_t idx = 0;
  543. while (m_steps[idx+1].first < value)
  544. ++ idx;
  545. double v0 = m_steps[idx].first;
  546. double v1 = m_steps[idx+1].first;
  547. const Color& c0 = m_steps[idx].second;
  548. const Color& c1 = m_steps[idx+1].second;
  549. double ratio = (value - v0) / (v1 - v0);
  550. Color out;
  551. for (std::size_t i = 0; i < 3; ++ i)
  552. out[i] = static_cast<unsigned char>((1 - ratio) * c0[i] + ratio * c1[i]);
  553. return out;
  554. }
  555. };
  556. #endif // COLOR_RAMP_H

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/522390
推荐阅读
相关标签
  

闽ICP备14008679号