【CGAL_网格处理】平滑处理

CGAL网格处理中封装了针对网格平滑或形状平滑的算法来实现三角网格区域的平滑。

  • 形状平滑:CGAL::Polygon_mesh_processing::smooth_shape()通过使用平均曲率流来平滑网格的整体形状。沿着平均曲率流将顶点逐渐移向其邻居的加权重心。基于Desbrun等人的用于形状平滑的曲率流算法。
  • 网格平滑:CGAL::Polygon_mesh_processing::angle_and_area_smoothing()通过移动(非约束顶点)使三角形角度和面积分布尽可能均匀。

形状平滑

1
2
3
4
5
6
template<typename TriangleMesh , typename FaceRange , typename NamedParameters = parameters::Default_named_parameters>
void CGAL::Polygon_mesh_processing::smooth_shape(const FaceRange & faces,
TriangleMesh & tmesh,
const double time,
const NamedParameters & np = parameters::default_values()
)

#include <CGAL/Polygon_mesh_processing/smooth_shape.h>

参数名 解释
tmesh a polygon mesh with triangulated surface patches to be smoothed.
faces the range of triangular faces defining one or several surface patches to be smoothed.
time a time step that corresponds to the speed by which the surface is smoothed. A larger time step results in faster convergence but details may be distorted to a larger extent compared to more iterations with a smaller step. Typical values scale in the interval (1e-6, 1].
np an optional sequence of Named Parameters among the ones listed below

tmesh:三角化表面后的多边形网格。

faces:面片序列。

time:时间步长。越大收敛越快,平滑速度越快。但是细节上可能会导致丢失。取值(1e-6, 1]。

可选np:

  • number_of_iterations:平滑操作迭代次数。默认1次平滑操作。
  • vertex_is_constrained_map:一个包含了tmesh每个顶点在平滑过程中是否受约束的property map。如果某一顶点受约束,其在平滑操作中将不会被修改。

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/Polygon_mesh_processing/smooth_shape.h>
#include <CGAL/Polygon_mesh_processing/IO/polygon_mesh_io.h>
#include <CGAL/draw_surface_mesh.h>
#include <iostream>
#include <set>
#include <string>
typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef CGAL::Surface_mesh<K::Point_3> Mesh;
namespace PMP = CGAL::Polygon_mesh_processing;
int main()
{
const std::string filename = "data/remeshing_surface_layer_1.off";
Mesh mesh;
if (!PMP::IO::read_polygon_mesh(filename, mesh))
{
std::cerr << "Invalid input." << std::endl;
return 1;
}
CGAL::draw(mesh);
const unsigned int nb_iterations = 5;
const double time = 0.3;
std::set<Mesh::Vertex_index> constrained_vertices;
for (Mesh::Vertex_index v : vertices(mesh))
{
if (is_border(v, mesh))
constrained_vertices.insert(v);
}
std::cout << "Constraining: " << constrained_vertices.size() << " border vertices" << std::endl;
CGAL::Boolean_property_map<std::set<Mesh::Vertex_index> > vcmap(constrained_vertices);
std::cout << "Smoothing shape... (" << nb_iterations << " iterations)" << std::endl;
PMP::smooth_shape(mesh, time, CGAL::parameters::number_of_iterations(nb_iterations)
.vertex_is_constrained_map(vcmap));
CGAL::IO::write_polygon_mesh("mesh_shape_smoothed.off", mesh, CGAL::parameters::stream_precision(17));
std::cout << "Done!" << std::endl;
CGAL::draw(mesh);
return EXIT_SUCCESS;
}

报错如下:

1
1>D:\CGAL\CGAL-5.4.1\include\CGAL/Polygon_mesh_processing/smooth_shape.h(149,1): error C2338: static_assert failed: 'Eigen3 version 3.2 or later is required.'

原因很简单,Eigen3版本太低。所以我们只需重新下载安装最新版本的eigen即可。

参考下文进行Eigen3的安装和在VS中的配置:

VS2015中配置Eigen_Aria_J的博客-CSDN博客_vs2015配置eigen

测试结果

下面是pig.stl进行形状平滑前后的对照。

参数:time=0.5,number_of_iterations=10。

image-20220812132831145

image-20220812133438172

image-20220812134135238

image-20220812134117998

下图是默认vertex_is_constrained_map下,对有界网格进行形状平滑结果对照。

参数:time=0.3,number_of_iterations=5。

image-20220812134803235

image-20220812134837714

可见,边界点未约束将造成边界萎缩。

网格平滑

1
2
3
4
5
template<typename TriangleMesh , typename FaceRange , typename NamedParameters = parameters::Default_named_parameters>
void CGAL::Polygon_mesh_processing::angle_and_area_smoothing(const FaceRange & faces,
TriangleMesh & tmesh,
const NamedParameters & np = parameters::default_values()
)

#include <CGAL/Polygon_mesh_processing/angle_and_area_smoothing.h>

注意,如果CGAL版本是5.5以前,angle_and_area_smoothing应改为smooth_mesh,否则报错找不到头文件。

参数名 解释
tmesh a polygon mesh with triangulated surface patches to be smoothed.
faces the range of triangular faces defining one or several surface patches to be smoothed.
np an optional sequence of Named Parameters among the ones listed below

tmesh:同上smooth_shape()

faces:同上smooth_shape()

部分可选np:

  • number_of_iterations:平滑操作迭代次数。默认1次平滑操作。
  • using_angle_smoothingusing_area_smoothing:基于角度平滑和基于面积平滑的开关,默认为true。
  • vertex_is_constrained_mapedge_is_constrained_map:顶点和边的是否受约束映射关系。默认没有顶点和边受约束,即在平滑过程中都会被调整。
  • use_safety_constraints:布尔值。如果为true,会忽略那些使网格恶化的顶点移动行为。默认为false。
  • use_Delaunay_flips:布尔值。如果为true,则基于面积的平滑将通过基于 Delaunay 的边缘翻转来完成,以防止产生被拉长的三角面片。

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#define CGAL_PMP_USE_CERES_SOLVER
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/Polygon_mesh_processing/smooth_mesh.h>
#include <CGAL/Polygon_mesh_processing/detect_features.h>
#include <CGAL/Polygon_mesh_processing/IO/polygon_mesh_io.h>
#include <CGAL/draw_surface_mesh.h>
#include <iostream>
#include <string>
typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef CGAL::Surface_mesh<K::Point_3> Mesh;
typedef boost::graph_traits<Mesh>::edge_descriptor edge_descriptor;
namespace PMP = CGAL::Polygon_mesh_processing;

int main()
{
const std::string filename = "data/surface_layer_1.off";
Mesh mesh;
if (!PMP::IO::read_polygon_mesh(filename, mesh))
{
std::cerr << "Invalid input." << std::endl;
return 1;
}
CGAL::draw(mesh);
// Constrain edges with a dihedral angle over 60°
typedef boost::property_map<Mesh, CGAL::edge_is_feature_t>::type EIFMap;
EIFMap eif = get(CGAL::edge_is_feature, mesh);
PMP::detect_sharp_edges(mesh, 60, eif);
int sharp_counter = 0;
for (edge_descriptor e : edges(mesh))
if (get(eif, e))
++sharp_counter;
std::cout << sharp_counter << " sharp edges" << std::endl;
const unsigned int nb_iterations = 10;
std::cout << "Smoothing mesh... (" << nb_iterations << " iterations)" << std::endl;
// Smooth with both angle and area criteria + Delaunay flips
PMP::smooth_mesh(mesh, CGAL::parameters::number_of_iterations(nb_iterations)
//.use_angle_smoothing(false)
//.use_area_smoothing(false)
//.use_Delaunay_flips(false)
.use_safety_constraints(false) // authorize all moves
.edge_is_constrained_map(eif));
CGAL::IO::write_polygon_mesh("mesh_smoothed.off", mesh, CGAL::parameters::stream_precision(17));
std::cout << "Done!" << std::endl;
CGAL::draw(mesh);
return EXIT_SUCCESS;
}

运行上面代码,程序未报错,但是控制台输出如下内容:

1
2
Area-based smoothing requires the Ceres Library, which is not available.
No such smoothing will be performed!

查看CGAL5.4.2官方文档:

image-20220812163639546

果不其然,因此我们需要按照第三方库Ceres。

参见这篇文章:Win10 + VS2017 + Ceres配置_四片叶子的三叶草的博客-CSDN博客_ceres vs2017,安装并配置完成后,程序成功运行(运行时间比较长)。

注意:在程序最开始需加入如下一行:

1
#define CGAL_PMP_USE_CERES_SOLVER

测试结果

原网格:

image-20220812211240555

Smooth with angle,number_of_iterations=10.

image-20220812211316440

Smooth with area,number_of_iterations=10.

image-20220812211601771

Smooth with both angle and area criteria + Delaunay flips,number_of_iterations=10.

image-20220812211803547

问题与总结

进一步理解了可选命名参数vertex_is_constrained_map

  • Type: a class model of ReadWritePropertyMap with boost::graph_traits<TriangleMesh>::vertex_descriptor as key type and bool as value type. It must be default constructible.
  • Default: a default property map where no vertex is constrained
  • Extra: A constrained vertex cannot be modified at all during smoothing.

对于ReadWritePropertyMap的构造。

1
2
3
4
5
6
std::set<Mesh::Vertex_index> constrained_vertices;
for (Mesh::Vertex_index v : vertices(mesh))
{
if (is_border(v, mesh))
constrained_vertices.insert(v);
}

首先获得std::set<Mesh::Vertex_index>类型的变量constrained_vertices,其中元素类型为Mesh::Vertex_index,即顶点的索引值。这里通过is_border()判断mesh中的所有顶点,并将边界顶点放到constrained_vertices中。

这里的

接下来,使用constrained_vertices实例化CGAL::Boolean_property_map<std::set<Mesh::Vertex_index>>对象vcmap。

1
CGAL::Boolean_property_map<std::set<Mesh::Vertex_index> > vcmap(constrained_vertices);

这里我们转到Boolean_property_map的定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/// \ingroup PkgPropertyMapRef
/// Read-write property map turning a set (such a `std::set`,
/// `boost::unordered_set`, `std::unordered_set`) into a property map
/// associating a Boolean to the value type of the set. The function `get` will
/// return `true` if the key is inside the set and `false` otherwise. The `put`
/// function will insert an element in the set if `true` is passed and erase it
/// otherwise.
/// \cgalModels `ReadWritePropertyMap`
template<class Set>
struct Boolean_property_map
{
typedef typename Set::value_type key_type;
typedef bool value_type;
typedef bool reference;
typedef boost::read_write_property_map_tag category;

Set* set_ptr;
/// Constructor taking a copy of the set. Note that `set_` must be valid
/// while the property map is in use.
Boolean_property_map(Set& set_) : set_ptr(&set_) {}
Boolean_property_map() : set_ptr(nullptr) {}

friend bool get(const Boolean_property_map<Set>& pm, const key_type& k)
{
CGAL_assertion(pm.set_ptr!=nullptr);
return pm.set_ptr->count(k) != 0;
}

friend void put(Boolean_property_map<Set>& pm, const key_type& k, bool v)
{
CGAL_assertion(pm.set_ptr!=nullptr);
if (v)
pm.set_ptr->insert(k);
else
pm.set_ptr->erase(k);
}
};

以上分析证实了vertex_is_constrained_map的参数类型。

Type: a class model of ReadWritePropertyMap with boost::graph_traits<TriangleMesh>::vertex_descriptor as key type and bool as value type. It must be default constructible.

edge_is_constrained_map构造同理。