13.3 推进前沿曲面重建

推进前沿重建是 曲面重建 的重要方法,它直接从无向点云重建三角网格,无需法向信息。

0. 算法动机:为什么需要推进前沿重建?

现实问题引入

想象你是一位地质学家,在野外采集了岩石表面的采样点:

  • 没有法向信息,只有点的位置
  • 采样不均匀,某些区域密集,某些区域稀疏
  • 需要重建岩石的真实表面,保留尖锐特征

或者你是一位游戏开发者:

  • 从摄影测量获得了场景的点云
  • 点云有噪声,有缺失
  • 需要快速生成可用的网格模型

传统方法的局限

泊松重建

  • 需要法向信息
  • 输出是隐式曲面,不经过原始点
  • 可能过度平滑尖锐特征

Delaunay 方法

  • 对噪声敏感
  • 计算复杂度高
  • 需要额外的过滤步骤

推进前沿的优势

插值原始点:输出网格顶点就是输入点 无需法向:直接从点云重建 保留特征:能够重建尖锐边缘 开放表面:支持带边界的表面

如果不使用推进前沿

  • 顶点偏移:重建结果不经过原始测量点
  • 法向依赖:必须先估计法向,引入额外误差
  • 特征丢失:尖锐边缘被圆滑
  • 封闭强制:开放表面被错误封闭

1. 直观理解:吹气球与结冰

1.1 “球体生长就像吹气球”

想象你在点云上吹气球:

初始状态

  • 每个点是一个微小的气球
  • 气球开始缓慢充气
   •       •       •
      •       •
   •       •       •
   
(微小气球,即将开始生长)

生长过程

  • 气球逐渐扩大
  • 当三个气球刚好接触时,形成一个三角形
   ●───●
  / \\   \\
 ●   ●───●
  \\ /   /
   ●───●
   
(气球扩大,形成三角形网格)

停止条件

  • 气球碰到其他点停止
  • 太大的空隙保持开放(边界)

1.2 “前沿推进就像结冰”

另一种类比:水面结冰

种子点

  • 某些点开始结冰(种子三角形)
  • 冰面从这些点向外扩展

前沿扩展

  • 冰面边缘不断推进
  • 遇到新点时连接形成新冰面
  • 在尖锐处停止,保留特征

边界形成

  • 冰面前沿无法继续推进时形成边界
  • 边界对应于数据缺失区域

1.3 “优先级队列就像智能队列”

算法使用优先级队列管理前沿:

优先级规则

  • 平坦区域优先(形成好三角形)
  • 狭窄缝隙延后(避免错误连接)
  • 尖锐特征特殊处理
前沿队列(按优先级排序):
┌─────────────────────────────────┐
│ 1. 平坦边 (优先级: 0.2)         │ ← 先处理
├─────────────────────────────────┤
│ 2. 中等边 (优先级: 0.5)         │
├─────────────────────────────────┤
│ 3. 困难边 (优先级: 0.8)         │
├─────────────────────────────────┤
│ 4. 极难边 (优先级: 1.2)         │ ← 后处理(可能放弃)
└─────────────────────────────────┘

2. 数学基础

2.1 球体生长算法

AFSR 的核心思想是球体生长(ball-pivoting)的变体。算法想象一个球在点云上滚动,当球与三个点同时接触时,这三个点构成一个三角形。

形式化地,给定半径为 的球,对于三个点 ,如果存在球同时与这三点相切,且球内不包含其他点,则这三点构成一个有效三角形。

2.2 Delaunay 三角剖分与 Voronoi 对偶

AFSR 基于三维 Delaunay 三角剖分,利用其对偶性质:

  • Delaunay 边:连接两点的边,存在空球(不含其他点)通过这两点
  • Delaunay 面:三角形面,存在空球通过三个顶点
  • Voronoi 边:到两个点等距的点的轨迹
  • Voronoi 面:到三个点等距的点的轨迹

AFSR 利用 Voronoi 边的长度作为面片优先级:较短的 Voronoi 边对应更”扁平”的三角形,更可能是表面的一部分。

2.3 优先级函数

算法使用优先级队列管理候选面片。默认优先级基于Delaunay 球的最小半径

其中 是共享面 的两个 Delaunay 四面体。


3. 算法流程详解

3.1 初始化阶段

构建输入点云的 Delaunay 三角剖分,识别初始种子面片:

对于每个 Delaunay 面 f:
    计算优先级 priority(f)
    如果 priority(f) 最小:
        选择 f 作为种子面
        将 f 的三条边加入前沿队列

可视化

点云:                    Delaunay三角剖分:           种子选择:
  •       •                  /\\      /|               /\\      /|
    •   •        →          /  \\    / |    →         /  \\    / |
  •       •                /____\\  /  |             /____\\  /  |
    •   •                  \\    /  \\ /              \\ 1  /  \\ /
                            \\  /    \/                \\ /  2  \/
                             \\/     /                  \\/     /

3.2 前沿推进阶段

维护一个前沿(frontier)边集,迭代地扩展表面:

while 前沿队列非空:
    取出优先级最高的边 e
    找到 e 周围的最佳候选面 f
    
    如果 f 满足以下条件:
        - 不与其他前沿边冲突
        - 不创建非流形边
        - 不创建退化三角形
    then:
        将 f 加入表面
        更新前沿队列

逐步过程可视化

步骤1: 种子三角形          步骤2: 扩展一条边
    •                      •───•
   /\\                     │  /|
  /  \\        →            │ / |
 •────•                    •/  •
                           
步骤3: 继续扩展            步骤4: 形成边界
•───•───•                 •───•───•
│  /│  /                  │  /│  /
│ / │ /        →          │ / │ /
•/  •/                    •/  •/
                          (开放边界)

3.3 边界处理

当算法无法继续推进时,可能形成边界。CGAL 的实现包括后处理步骤来优化边界。

3.4 多连通分量处理

算法可以处理多个不连通的表面分量,依次重建每个连通分量。


4. CGAL 架构分析

4.1 核心类层次

Advancing_front_surface_reconstruction<Dt, P>
    |
    +-- Delaunay_triangulation_3<Kernel, Tds> (用户传入)
            |
            +-- Advancing_front_surface_reconstruction_vertex_base_3<Kernel>
            |       +-- Triangulation_vertex_base_3<Kernel>
            |
            +-- Advancing_front_surface_reconstruction_cell_base_3<Kernel>
                    +-- Triangulation_cell_base_3<Kernel>

4.2 关键文件位置

文件路径功能
Advancing_front_surface_reconstruction.hAdvancing_front_surface_reconstruction/include/CGAL/核心重建类
Advancing_front_surface_reconstruction_vertex_base_3.hAdvancing_front_surface_reconstruction/include/CGAL/顶点基类
Advancing_front_surface_reconstruction_cell_base_3.hAdvancing_front_surface_reconstruction/include/CGAL/单元基类

4.3 顶点状态管理

每个顶点维护以下状态信息:

class Advancing_front_surface_reconstruction_vertex_base_3 : public Vb
{
    // 边界边信息
    Intern_successors_type* m_incident_border;
    
    // 内部边列表
    std::list<Vertex_handle>::iterator m_ie_first, m_ie_last;
    
    // 入射请求列表
    std::list<Incidence_request_elt>::iterator m_ir_first, m_ir_last;
    
    // 标记信息
    int m_mark;           // 当前前沿标记
    int m_post_mark;      // 后处理标记
    
public:
    // 状态查询
    bool is_on_border() const;      // 是否在边界上
    bool is_exterior() const;       // 是否在外部
    bool not_interior() const;      // 是否非内部
    bool is_interior() const;       // 是否在内部
    
    // 标记操作
    void inc_mark();                // 增加标记计数
    void dec_mark();                // 减少标记计数
    int number_of_incident_border() const;
};

4.4 核心类定义

template <class Dt = Default, class P = Default>
class Advancing_front_surface_reconstruction
{
public:
    // 类型定义
    typedef typename Default::Get<Dt, ...>::type Triangulation_3;
    typedef typename Default::Get<P, AFSR::Default_priority>::type Priority;
    
    typedef typename Triangulation_3::Geom_traits Kernel;
    typedef typename Kernel::FT FT;
    typedef typename Kernel::Point_3 Point;
    typedef typename Kernel::Vector_3 Vector;
    
    typedef typename Triangulation_3::Vertex_handle Vertex_handle;
    typedef typename Triangulation_3::Cell_handle Cell_handle;
    typedef typename Triangulation_3::Facet Facet;
    typedef typename Triangulation_3::Edge Edge;
    
    // 2D 表面三角剖分
    typedef TDS_2 Triangulation_data_structure_2;
    
    // 构造函数
    Advancing_front_surface_reconstruction(Triangulation_3& dt, 
                                           Priority priority = Priority());
    
    // 运行重建
    void run(double radius_ratio_bound = 5, double beta = 0.52);
    
    // 结果访问
    const Triangulation_data_structure_2& triangulation_data_structure_2() const;
    Triangulation_3& triangulation_3() const;
    
    // 统计信息
    int number_of_facets() const;
    int number_of_vertices() const;
    int number_of_connected_components() const;
    bool has_boundaries() const;
    
    // 边界遍历
    Boundary_iterator boundaries_begin() const;
    Boundary_iterator boundaries_end() const;
    
    // 离群点访问
    const Outlier_range& outliers() const;
};

5. 实现细节

5.1 前沿边表示

前沿边使用复杂的数据结构维护:

typedef std::pair<Vertex_handle, Vertex_handle> Edge_like;
 
typedef std::pair<criteria, std::pair<IO_edge_type, int>> Border_elt;
// criteria: 优先级值
// IO_edge_type: 输入/输出边信息
// int: 连通分量编号
 
typedef std::set<Radius_ptr_type> Ordered_border_type;
// 按优先级排序的前沿边集合

5.2 候选面计算

对于每条前沿边,算法计算最佳候选面:

Radius_edge_type compute_value(const Edge_incident_facet& e)
{
    Cell_handle c = e.first.first;
    int i = e.second;
    int i1 = e.first.second, i2 = e.first.third;
    int i3 = 6 - e.second - i1 - i2;
    
    // 遍历边周围的所有面
    Edge_incident_facet e_it = e;
    coord_type min_valueA = infinity();
    Facet min_facetA;
    
    do {
        Cell_handle neigh = e_it.first.first;
        Facet facet_it(neigh, e_it.second);
        
        if (\!T.is_infinite(facet_it)) {
            // 计算优先级
            coord_type tmp = priority(*this, neigh, e_it.second);
            
            // 检查有效性
            if (tmp \!= infinity() && neigh->vertex(n_i3)->not_interior()
                && \!is_interior_edge(el1) && \!is_interior_edge(el2))
            {
                // 检查三角形质量(避免狭长三角形)
                Vector v1 = construct_cross_product(construct_vector(p2, pc), 
                                                     construct_vector(p2, p1));
                Vector v2 = construct_cross_product(construct_vector(p2, p1), 
                                                     construct_vector(p2, pn));
                FT norm = sqrt(norm1 * compute_scalar_product(v2, v2));
                FT pscal = v1 * v2;
                bool sliver_facet = (pscal <= COS_ALPHA_SLIVER * norm);
                
                if (\!sliver_facet && tmp < min_valueA) {
                    min_facetA = facet_it;
                    min_valueA = tmp;
                }
            }
        }
        e_it = next(e_it);
    } while (e_it.first.first \!= c);
    
    // 返回结果
    return Radius_edge_type(value, IO_edge_type(e, Edge_incident_facet(...)));
}

5.3 验证与合并

当选择候选面时,需要验证多种情况:

enum Validation_case { 
    NOT_VALID,              // 无效候选
    NOT_VALID_CONNECTING_CASE,  // 连接情况无效
    FINAL_CASE,             // 最终情况(形成封闭环)
    EAR_CASE,               // 耳朵情况(单边合并)
    EXTERIOR_CASE,          // 外部扩展
    CONNECTING_CASE         // 连接情况
};
 
Validation_case validate(const Edge_incident_facet& edge_Efacet,
                         const criteria& value)
{
    // 获取相关顶点和边
    Vertex_handle v1 = c->vertex(edge_Efacet.first.second);
    Vertex_handle v2 = c->vertex(edge_Efacet.first.third);
    Vertex_handle v3 = c->vertex(i);
    
    Edge_like ordered_el1(v3, v1);
    Edge_like ordered_el2(v3, v2);
    Edge_like ordered_key(v1, v2);
    
    // 检查边界状态
    bool is_border_el1 = is_border_elt(ordered_el1, result1);
    bool is_border_el2 = is_border_elt(ordered_el2, result2);
    bool is_border_key = is_border_elt(ordered_key, result12);
    
    // 情况 1: 两个相邻边都在边界上(形成三角形)
    if (is_border_el1 && is_border_el2) {
        // 移除三条边界边,添加面片
        remove_border_elt(ordered_key);
        force_merge(ordered_el1, result1);
        force_merge(ordered_el2, result2);
        select_facet(c, edge_Efacet.second);
        return FINAL_CASE;
    }
    
    // 情况 2: 只有一条相邻边在边界上(耳朵裁剪)
    if (is_border_el1) {
        merge_ear(ordered_el1, result1, ordered_key, v1, v2, ...);
        return EAR_CASE;
    }
    if (is_border_el2) {
        merge_ear(ordered_el2, result2, ordered_key, v2, v1, ...);
        return EAR_CASE;
    }
    
    // 情况 3: 外部扩展
    if (v3->is_exterior()) {
        border_extend(ordered_key, result12, v1, v2, v3, ...);
        return EXTERIOR_CASE;
    }
    
    // 情况 4: 连接情况(处理边界合并)
    // ...
    
    return NOT_VALID;
}

5.4 优先级计算

默认优先级基于 Delaunay 球半径:

FT smallest_radius_delaunay_sphere(const Cell_handle& c, const int& index) const
{
    Cell_handle n = c->neighbor(index);
    
    // 检查缓存
    coord_type value = c->smallest_radius(index);
    if (value >= 0 && n->smallest_radius(n->index(c)) == value)
        return value;
    
    // 计算 Voronoi 边长度
    const Point& cp0 = c->vertex(index)->point();
    const Point& cp1 = c->vertex((index+1) & 3)->point();
    const Point& cp2 = c->vertex((index+2) & 3)->point();
    const Point& cp3 = c->vertex((index+3) & 3)->point();
    
    // 处理退化情况
    if (c_is_plane || n_is_plane || my_collinear(cp1, cp2, cp3))
        value = infinity();
    else {
        // 计算外心距离
        Point cc = circumcenter(cp0, cp1, cp2, cp3);
        Point cn = circumcenter(np0, np1, np2, np3);
        
        // 计算到 Voronoi 边的距离
        Vector V = construct_vector(cn, cc);
        Vector Vc = construct_vector(cp1, cc);
        FT ac = compute_scalar_product(V, Vc);
        FT an = compute_scalar_product(V, construct_vector(cn, cp1));
        FT norm_V = compute_scalar_product(V, V);
        
        if (ac > 0 && an > 0)
            value = compute_scalar_product(Vc, Vc) - ac*ac/norm_V;
        else if (ac <= 0)
            value = compute_squared_distance(cc, cp1);
        else
            value = compute_squared_distance(cn, cp1);
    }
    
    // 缓存结果
    c->set_smallest_radius(index, value);
    n->set_smallest_radius(n->index(c), value);
    
    return value;
}

6. 使用示例

6.1 完整示例代码

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Advancing_front_surface_reconstruction.h>
#include <CGAL/Triangulation_data_structure_3.h>
#include <CGAL/Delaunay_triangulation_3.h>
#include <CGAL/IO/read_points.h>
 
#include <vector>
#include <fstream>
#include <iostream>
 
// 类型定义
typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel;
typedef Kernel::Point_3 Point;
typedef CGAL::Advancing_front_surface_reconstruction_vertex_base_3<Kernel> LVb;
typedef CGAL::Advancing_front_surface_reconstruction_cell_base_3<Kernel> LCb;
typedef CGAL::Triangulation_data_structure_3<LVb, LCb> Tds;
typedef CGAL::Delaunay_triangulation_3<Kernel, Tds> Triangulation_3;
typedef CGAL::Advancing_front_surface_reconstruction<Triangulation_3> Reconstruction;
 
int main(int argc, char* argv[])
{
    // 1. 读取点云
    std::vector<Point> points;
    const char* input_file = (argc > 1) ? argv[1] : "input.xyz";
    
    if (\!CGAL::IO::read_points(input_file, std::back_inserter(points)))
    {
        std::cerr << "Error: cannot read file " << input_file << std::endl;
        return EXIT_FAILURE;
    }
    
    std::cout << "Loaded " << points.size() << " points" << std::endl;
    
    // 2. 构建 Delaunay 三角剖分
    Triangulation_3 dt(points.begin(), points.end());
    std::cout << "Delaunay triangulation: " << dt.number_of_vertices() << " vertices"
              << std::endl;
    
    // 3. 创建并运行重建
    Reconstruction reconstruction(dt);
    
    // 参数设置
    double radius_ratio_bound = 5.0;  // 半径比上界
    double beta = 0.52;               // 角度参数(约 30 度)
    
    reconstruction.run(radius_ratio_bound, beta);
    
    // 4. 输出统计信息
    std::cout << "Reconstruction completed:" << std::endl;
    std::cout << "  Facets: " << reconstruction.number_of_facets() << std::endl;
    std::cout << "  Vertices: " << reconstruction.number_of_vertices() << std::endl;
    std::cout << "  Components: " << reconstruction.number_of_connected_components() << std::endl;
    std::cout << "  Has boundaries: " << (reconstruction.has_boundaries() ? "yes" : "no") << std::endl;
    std::cout << "  Outliers: " << reconstruction.number_of_outliers() << std::endl;
    
    // 5. 提取结果并保存
    std::ofstream out("output.off");
    out << "OFF\n";
    out << points.size() << " " << reconstruction.number_of_facets() << " 0\n";
    
    // 输出顶点
    for (const auto& p : points)
        out << p << "\n";
    
    // 输出面片(需要访问 TDS_2)
    const auto& tds2 = reconstruction.triangulation_data_structure_2();
    for (auto fit = tds2.faces_begin(); fit \!= tds2.faces_end(); ++fit)
    {
        if (fit->is_on_surface())
        {
            auto v0 = fit->vertex(0)->vertex_3();
            auto v1 = fit->vertex(1)->vertex_3();
            auto v2 = fit->vertex(2)->vertex_3();
            
            out << "3 " << v0->index() << " " 
                << v1->index() << " " 
                << v2->index() << "\n";
        }
    }
    out.close();
    
    return EXIT_SUCCESS;
}

6.2 使用自定义优先级

用户可以定义自己的优先级函数:

// 自定义优先级:基于周长约束
struct Perimeter {
    double bound;
    
    Perimeter(double bound) : bound(bound) {}
    
    template <typename AdvancingFront, typename Cell_handle>
    double operator()(const AdvancingFront& adv, Cell_handle& c, 
                      const int& index) const
    {
        // 如果周长约束为 0,使用默认优先级
        if (bound == 0) {
            return adv.smallest_radius_delaunay_sphere(c, index);
        }
        
        // 计算三角形周长
        double d = sqrt(squared_distance(c->vertex((index+1)%4)->point(),
                                         c->vertex((index+2)%4)->point()));
        if (d > bound) return adv.infinity();
        
        d += sqrt(squared_distance(c->vertex((index+2)%4)->point(),
                                   c->vertex((index+3)%4)->point()));
        if (d > bound) return adv.infinity();
        
        d += sqrt(squared_distance(c->vertex((index+1)%4)->point(),
                                   c->vertex((index+3)%4)->point()));
        if (d > bound) return adv.infinity();
        
        // 周长满足约束,使用默认优先级
        return adv.smallest_radius_delaunay_sphere(c, index);
    }
};
 
// 使用自定义优先级
Perimeter perimeter(0.5);  // 周长上界为 0.5
Triangulation_3 dt(points.begin(), points.end());
Reconstruction<Triangulation_3, Perimeter> reconstruction(dt, perimeter);
reconstruction.run(5.0, 0.52);

6.3 便捷函数接口

CGAL 提供了更简单的函数接口:

#include <CGAL/Advancing_front_surface_reconstruction.h>
 
// 定义输出类型
typedef std::array<std::size_t, 3> Facet;
 
// 使用便捷函数
std::vector<Facet> facets;
CGAL::advancing_front_surface_reconstruction(
    points.begin(), points.end(),
    std::back_inserter(facets),
    radius_ratio_bound,  // 默认 5.0
    beta                 // 默认 0.52
);
 
// 输出结果
std::cout << "OFF\n";
std::cout << points.size() << " " << facets.size() << " 0\n";
for (const auto& p : points)
    std::cout << p << "\n";
for (const auto& f : facets)
    std::cout << "3 " << f[0] << " " << f[1] << " " << f[2] << "\n";

6.4 实际应用:快速原型制作

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Advancing_front_surface_reconstruction.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/IO/read_points.h>
#include <CGAL/IO/OBJ.h>
#include <CGAL/grid_simplify_point_set.h>
#include <CGAL/jet_estimate_normals.h>
#include <CGAL/mst_orient_normals.h>
#include <iostream>
#include <vector>
 
typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel;
typedef Kernel::Point_3 Point;
typedef CGAL::Surface_mesh<Point> Mesh;
 
// 快速原型制作流程
void rapid_prototyping_pipeline(const std::string& input_file,
                                const std::string& output_file)
{
    // 1. 读取点云
    std::vector<Point> points;
    if (\!CGAL::IO::read_points(input_file, std::back_inserter(points))) {
        std::cerr << "Failed to read input" << std::endl;
        return;
    }
    
    std::cout << "Loaded " << points.size() << " points" << std::endl;
    
    // 2. 预处理:简化(如果点云过于密集)
    if (points.size() > 100000) {
        double cell_size = 0.01;  // 1cm 网格
        auto iterator_to_first_to_remove = CGAL::grid_simplify_point_set(
            points, cell_size);
        points.erase(iterator_to_first_to_remove, points.end());
        std::cout << "Simplified to " << points.size() << " points" << std::endl;
    }
    
    // 3. 构建 Delaunay 三角剖分
    typedef CGAL::Advancing_front_surface_reconstruction_vertex_base_3<Kernel> LVb;
    typedef CGAL::Advancing_front_surface_reconstruction_cell_base_3<Kernel> LCb;
    typedef CGAL::Triangulation_data_structure_3<LVb, LCb> Tds;
    typedef CGAL::Delaunay_triangulation_3<Kernel, Tds> Dt;
    
    Dt dt(points.begin(), points.end());
    std::cout << "Delaunay triangulation created" << std::endl;
    
    // 4. 推进前沿重建
    typedef CGAL::Advancing_front_surface_reconstruction<Dt> Reconstruction;
    Reconstruction reconstruction(dt);
    
    // 根据数据质量调整参数
    double radius_ratio_bound = 5.0;  // 标准值
    double beta = 0.52;               // 约30度
    
    // 如果数据噪声较大,可以放宽参数
    if (points.size() < 5000) {
        radius_ratio_bound = 6.0;  // 更宽松
        beta = 0.45;               // 更小角度
    }
    
    reconstruction.run(radius_ratio_bound, beta);
    
    std::cout << "Reconstruction completed:" << std::endl;
    std::cout << "  Facets: " << reconstruction.number_of_facets() << std::endl;
    std::cout << "  Components: " << reconstruction.number_of_connected_components() << std::endl;
    std::cout << "  Boundaries: " << (reconstruction.has_boundaries() ? "yes" : "no") << std::endl;
    
    // 5. 转换为 Surface_mesh
    Mesh mesh;
    // 添加顶点
    for (const auto& p : points) {
        mesh.add_vertex(p);
    }
    
    // 添加面
    const auto& tds2 = reconstruction.triangulation_data_structure_2();
    for (auto fit = tds2.faces_begin(); fit \!= tds2.faces_end(); ++fit) {
        if (fit->is_on_surface()) {
            auto v0 = fit->vertex(0)->vertex_3();
            auto v1 = fit->vertex(1)->vertex_3();
            auto v2 = fit->vertex(2)->vertex_3();
            
            mesh.add_face(
                Mesh::Vertex_index(v0->index()),
                Mesh::Vertex_index(v1->index()),
                Mesh::Vertex_index(v2->index())
            );
        }
    }
    
    // 6. 保存结果
    CGAL::IO::write_OBJ(output_file, mesh);
    std::cout << "Saved to " << output_file << std::endl;
}
 
int main(int argc, char* argv[])
{
    std::string input = (argc > 1) ? argv[1] : "scan.xyz";
    std::string output = (argc > 2) ? argv[2] : "model.obj";
    
    rapid_prototyping_pipeline(input, output);
    
    return 0;
}

7. 复杂度分析

7.1 时间复杂度

步骤复杂度说明
Delaunay 三角剖分 为输入点数
初始化寻找种子面
前沿推进 为输出面片数,优先队列操作
后处理边界优化

总体复杂度为

7.2 空间复杂度

  • Delaunay 三角剖分
  • 前沿队列(前沿边数)
  • 表面存储

总体空间复杂度为

7.3 参数影响

  • radius_ratio_bound:控制候选面片的半径比阈值。较大的值允许更多面片,但可能包含噪声。
  • beta:控制楔形区域的角度。较大的值更严格,可能产生更多边界。

8. 应用建议

8.1 适用场景

AFSR 最适合以下场景:

  • 无向点云(没有法向信息)
  • 开放表面重建(允许边界)
  • 需要插值输入点的应用
  • 点云密度较均匀的情况

8.2 预处理建议

  1. 离群点移除:AFSR 对离群点敏感,建议先进行统计过滤
  2. 采样均匀化:如果点云密度变化大,可考虑重采样
  3. 边界检测:对于已知开放边界的扫描数据,可利用边界信息

8.3 常见问题

问题 1:重建结果有过多孔洞

  • 原因:beta 参数过大或点云密度不足
  • 解决:减小 beta 值,或增加 radius_ratio_bound

问题 2:重建结果包含噪声面片

  • 原因:radius_ratio_bound 过大或输入有离群点
  • 解决:减小 radius_ratio_bound,或预处理移除离群点

问题 3:多个连通分量被合并

  • 原因:组件间距过小
  • 解决:使用自定义优先级添加距离约束

9. 参数调优指南

9.1 参数选择流程图

开始
  │
  ▼
数据质量评估
  │
  ├─ 高质量扫描(噪声低,密度均匀)
  │     └─ radius_ratio_bound = 5.0, beta = 0.52
  │
  ├─ 中等质量(有噪声,密度变化)
  │     └─ radius_ratio_bound = 6.0, beta = 0.45
  │
  └─ 低质量(噪声大,缺失多)
        └─ radius_ratio_bound = 7.0, beta = 0.40
  │
  ▼
检查重建结果
  │
  ├─ 孔洞过多?→ 增大 radius_ratio_bound 或减小 beta
  │
  ├─ 噪声面片?→ 减小 radius_ratio_bound
  │
  └─ 组件合并?→ 使用自定义优先级
  │
  ▼
结束

9.2 自定义优先级示例

// 距离约束优先级(防止远距离连接)
struct DistanceConstraint {
    double max_distance;
    
    DistanceConstraint(double d) : max_distance(d) {}
    
    template <typename AdvancingFront, typename Cell_handle>
    double operator()(const AdvancingFront& adv, Cell_handle& c, 
                      const int& index) const {
        // 计算三角形最大边长
        const Point& p1 = c->vertex((index+1)%4)->point();
        const Point& p2 = c->vertex((index+2)%4)->point();
        const Point& p3 = c->vertex((index+3)%4)->point();
        
        double d12 = sqrt(squared_distance(p1, p2));
        double d23 = sqrt(squared_distance(p2, p3));
        double d31 = sqrt(squared_distance(p3, p1));
        
        double max_d = std::max({d12, d23, d31});
        
        if (max_d > max_distance)
            return adv.infinity();  // 拒绝这个大三角形
        
        return adv.smallest_radius_delaunay_sphere(c, index);
    }
};

10. 实际应用案例

10.1 案例一:考古文物数字化

场景:从摄影测量获得破碎陶片的点云。

挑战

  • 点云密度不均匀
  • 有缺失区域(边界)
  • 需要保留尖锐边缘

解决方案

// 使用较小的 beta 保留特征
reconstruction.run(5.0, 0.45);  // 更严格的角度控制
 
// 后处理:检测并标记边界
if (reconstruction.has_boundaries()) {
    std::cout << "Detected boundaries:" << std::endl;
    for (auto bit = reconstruction.boundaries_begin();
         bit \!= reconstruction.boundaries_end(); ++bit) {
        std::cout << "  Boundary with " << bit->size() << " edges" << std::endl;
    }
}

10.2 案例二:工业零件检测

场景:检测机械零件的表面缺陷。

方法

  1. 使用 AFSR 重建标称表面
  2. 将重建结果与CAD模型对比
  3. 偏差区域即为缺陷

10.3 案例三:地形建模

场景:从无人机摄影测量获得地形点云。

优势

  • 自然处理开放边界(地形边缘)
  • 保留地形特征(山脊、沟壑)
  • 无需法向估计

参考文献

  1. Boissonnat, J. D., & Oudot, S. (2005). Provably good sampling and meshing of surfaces. Graphical Models, 67(5), 405-451.

  2. Bernardini, F., Mittleman, J., Rushmeier, H., Silva, C., & Taubin, G. (1999). The ball-pivoting algorithm for surface reconstruction. IEEE Transactions on Visualization and Computer Graphics, 5(4), 349-359.

  3. CGAL Documentation: Advancing Front Surface Reconstruction. https://doc.cgal.org/latest/Advancing_front_surface_reconstruction/