CGAL 从DSM到DTM holefilling remeshing

news2024/9/20 11:02:59

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

holefilling

构建编译运行

cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=D:\vcpkg\scripts\buildsystems\vcpkg.cmake
cmake --build build --config Debug
.\build\Debug\HoleFilling_Remeshing.exe

参考

  1. https://doc.cgal.org/latest/Manual/tuto_gis.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2139783.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++速通LeetCode简单第17题-爬楼梯

思路要点&#xff1a;将问题转化为求斐波那契数列的第n项&#xff0c;然后迭代。 思路分析&#xff1a;最后一次爬的阶数不是1就是2&#xff0c;假设爬n阶的方法数是f(n)&#xff0c;假设最后一次爬1阶&#xff0c;那么爬前面的 n-1阶的方法数是f(n-1)&#xff1b;假设最后一次…

力扣题解2848

大家好&#xff0c;欢迎来到无限大的频道。 今日继续给大家带来力扣题解。 题目描述&#xff08;简单&#xff09;&#xff1a; 与车相交的点 给你一个下标从 0 开始的二维整数数组 nums 表示汽车停放在数轴上的坐标。对于任意下标 i&#xff0c;nums[i] [starti, endi] &…

熵权法详细讲解+Python代码实现

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

大数据新视界 --大数据大厂之 Cassandra 分布式数据库:高可用数据存储的新选择

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

基于单片机的超声波液位检测系统(论文+源码)

1总体设计 本课题为基于单片机的超声波液位检测系统的设计&#xff0c;系统的结构框图如图2.1所示。其中包括了按键模块&#xff0c;温度检测模块&#xff0c;超声波液位检测模块&#xff0c;显示模块&#xff0c;蜂鸣器等器件设备。其中&#xff0c;采用STC89C52单片机作为主…

2.5 Trigger源码分析 -- ant-design-vue系列

Trigger源码分析 – ant-design-vue系列 1 概述 源码地址&#xff1a; https://github.com/vueComponent/ant-design-vue/blob/main/components/vc-trigger/Trigger.tsx 在源码的实现中&#xff0c;Trigger组件主要有两个作用&#xff1a; 使用Portal组件&#xff0c;把Pop…

构建响应式 Web 应用:Vue.js 基础指南

构建响应式 Web 应用&#xff1a;Vue.js 基础指南 一 . Vue 的介绍1.1 介绍1.2 好处1.3 特点 二 . Vue 的快速入门2.1 案例 1 : 快速搭建 Vue 的运行环境 , 在 div 视图中获取 Vue 中的数据2.2 案例 2 : 点击按钮执行 vue 中的函数输出 vue 中 data 的数据2.3 小结 三 . Vue 常…

Leetcode3282. 到达数组末尾的最大得分

Every day a Leetcode 题目来源&#xff1a;3282. 到达数组末尾的最大得分 解法1&#xff1a;动态规划 代码&#xff1a; class Solution { public:long long findMaximumScore(vector<int>& nums) {if (nums.size() < 1) return 0LL;int n nums.size();vect…

JavaScript高级——循环遍历加监听

本文分享到这里&#xff0c;欢迎大家评论区相互讨论学习&#xff0c;下一篇继续分享JavaScript高级学习中的闭包的内容。

linux进程间通信——学习与应用命名管道, 日志程序的使用与实现

前言&#xff1a;本节主要讲解linux进程间通信里面的命名管道&#xff0c; 命名管道和我们学过的匿名管道是类似的。 博主将会带着友友们先看一下原理&#xff0c; 然后就会着手使用以下命名管道是怎么使用的。 最后我们还会试着引入日志系统&#xff0c; 我们从本节开始就会引…

npm 安装 与 切换 淘宝镜像

一、镜像源 npm默认镜像源是国外的&#xff0c;安装依赖速度较慢&#xff0c;使用国内的镜像源速度会快一些。 1、设置淘宝镜像源&#xff1a; #最新地址 淘宝 NPM 镜像站喊你切换新域名啦! npm config set registry https://registry.npm.taobao.org&#xff08;弃用了&…

网站采用H5+CSS3开发的优势和劣势

在现代网站开发中&#xff0c;HTML5和CSS3的结合使用已经成为一种趋势。以下是其优势和劣势的介绍&#xff1a; 优势 增强的多媒体支持&#xff1a;HTML5引入了新的标签&#xff0c;使开发者能够轻松嵌入音频、视频和图形&#xff0c;无需依赖第三方插件如Flash。这大大简化了…

【AI大模型】ChatGPT模型原理介绍(下)

目录 &#x1f354; GPT-3介绍 1.1 GPT-3模型架构 1.2 GPT-3训练核心思想 1.3 GPT-3数据集 1.4 GPT-3模型的特点 1.5 GPT-3模型总结 &#x1f354; ChatGPT介绍 2.1 ChatGPT原理 2.2 什么是强化学习 2.3 ChatGPT强化学习步骤 2.4 监督调优模型 2.5 训练奖励模型 2.…

基于单片机的风机故障检测装置的设计与实现(论文+源码)

1 系统总体设计方案 通过对风机故障检测装置的设计与实现的需求、可行性进行分析&#xff0c;本设计风机故障检测装置的设计与实现的系统总体架构设计如图2-1所示&#xff0c;系统风机故障检测装置采用STM32F103单片机作为控制器&#xff0c;并通过DS18B20温度传感器、ACS712电…

macOS使用brew安装并配置python环境

1.确认已安装brew环境,如没有安装,参考: macOS系统Homebrew工具安装及使用-CSDN博客 2.安装python python安装成功 3.添加pip路径到/etc/paths 4.查看python与pip默认安装版本

【leetcode】树形结构习题

二叉树的前序遍历 返回结果&#xff1a;[‘1’, ‘2’, ‘4’, ‘5’, ‘3’, ‘6’, ‘7’] 144.二叉树的前序遍历 - 迭代算法 给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,…

git 更换远程地址的方法

需要将正在开发的代码远程地址改成新的地址&#xff0c;通过查询发现有三个方法可以实现&#xff0c;特此记录。具体方法如下&#xff1a; &#xff08;1&#xff09;通过命令直接修改远程仓库地址 git remote 查看所有远程仓库git remote xxx 查看指定远程仓库地址git remote…

外卖会员卡是不是一个骗局?

大家好&#xff0c;我是鲸天科技千千&#xff0c;大家都知道我是做小程序开发的&#xff0c;平时会给大家分享一些互联网相关的创业项目&#xff0c;感兴趣的可以跟我关注一下。 首先就是要搭建一个自己的外卖会员卡系统小程序&#xff0c;我们自己的工作就是把这个小程序推广…

JDBC注册驱动及获取连接

文章目录 1. JDBC注册驱动1.1 导入驱动 Jar 包1.2 注册驱动1.2.1 API介绍1.2.2 使用步骤1.2.3 案例代码 2. 获取连接2.1 API介绍2.2 参数说明2.3 注意事项2.4 使用步骤3.5 案例代码 1. JDBC注册驱动 Connection表示Java程序与数据库之间的连接&#xff0c;只有拿到Connection才…

TCP/IP网络模型分层

应用层 应用层是最上层的&#xff0c;也就是我们能直接接触到的就是应用层(Application Layer),手机和电脑上的应用软件都是在应用层实现。当两个不同设备的应用需要通信的时候&#xff0c;应用就会把数据传输给下一层&#xff0c;也就是传输层 所以&#xff0c;应用层只需要…