经典三维表面重建算法(C语言实现)

一、算法概述

三维表面重建是从离散点云数据恢复连续表面的过程。这里实现三种经典算法:

  1. 泊松表面重建 (Poisson Surface Reconstruction)
  2. Alpha Shapes 算法
  3. 移动立方体算法 (Marching Cubes)

二、核心数据结构

c 复制代码
#ifndef SURFACE_RECONSTRUCTION_H
#define SURFACE_RECONSTRUCTION_H

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

// 三维点结构
typedef struct {
    float x, y, z;
} Point3D;

// 带法向量的点
typedef struct {
    Point3D pos;
    Point3D normal;
} PointNormal;

// 三角形面片
typedef struct {
    int v1, v2, v3;  // 顶点索引
    Point3D normal;  // 面片法向量
} Triangle;

// 网格模型
typedef struct {
    Point3D *vertices;      // 顶点数组
    Triangle *triangles;    // 三角形数组
    int vertex_count;       // 顶点数量
    int triangle_count;     // 三角形数量
} Mesh;

// 八叉树节点
typedef struct OctreeNode {
    Point3D center;         // 节点中心
    float size;            // 节点尺寸
    struct OctreeNode *children[8]; // 子节点
    int point_count;       // 节点内的点数
    int *point_indices;     // 点索引数组
    float indicator;       // 指示函数值
} OctreeNode;

// 泊松重建参数
typedef struct {
    int depth;              // 八叉树深度
    float scale;            // 采样间距
    float iso_value;        // 等值面值
} PoissonParams;

#endif

三、泊松表面重建算法

c 复制代码
#include "surface_reconstruction.h"
#include <gsl/gsl_vector.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_solver.h>

// 创建八叉树
OctreeNode* create_octree(Point3D *points, int *indices, int count, 
                         Point3D center, float size, int depth) {
    OctreeNode *node = (OctreeNode*)malloc(sizeof(OctreeNode));
    node->center = center;
    node->size = size;
    node->point_count = count;
    node->indicator = 0.0f;
    
    if (count > 0) {
        node->point_indices = (int*)malloc(count * sizeof(int));
        memcpy(node->point_indices, indices, count * sizeof(int));
    } else {
        node->point_indices = NULL;
    }
    
    // 递归分割
    if (depth > 0 && count > 8) {
        int child_counts[8] = {0};
        int *child_indices[8];
        Point3D child_centers[8];
        
        // 初始化子节点
        for (int i = 0; i < 8; i++) {
            child_indices[i] = (int*)malloc(count * sizeof(int));
            child_centers[i].x = center.x + ((i & 1) ? size/4 : -size/4);
            child_centers[i].y = center.y + ((i & 2) ? size/4 : -size/4);
            child_centers[i].z = center.z + ((i & 4) ? size/4 : -size/4);
        }
        
        // 分配点到子节点
        for (int i = 0; i < count; i++) {
            int idx = indices[i];
            Point3D p = points[idx];
            int octant = (p.x > center.x) | 
                        ((p.y > center.y) << 1) | 
                        ((p.z > center.z) << 2);
            child_indices[octant][child_counts[octant]++] = idx;
        }
        
        // 递归创建子节点
        for (int i = 0; i < 8; i++) {
            if (child_counts[i] > 0) {
                node->children[i] = create_octree(points, child_indices[i], 
                                                 child_counts[i], 
                                                 child_centers[i], 
                                                 size/2, depth-1);
            } else {
                node->children[i] = NULL;
            }
            free(child_indices[i]);
        }
    } else {
        memset(node->children, 0, sizeof(node->children));
    }
    
    return node;
}

// 计算基函数权重
float basis_function(Point3D p, Point3D center, float size) {
    float dist = sqrtf(
        (p.x - center.x)*(p.x - center.x) +
        (p.y - center.y)*(p.y - center.y) +
        (p.z - center.z)*(p.z - center.z)
    );
    
    if (dist > size) return 0.0f;
    return 1.0f - dist/size;  // 线性衰减
}

// 构建泊松方程
void build_poisson_matrix(OctreeNode *root, PointNormal *points, 
                          gsl_matrix *A, gsl_vector *b, int *index_map) {
    static int node_counter = 0;
    
    if (root == NULL) return;
    
    // 为当前节点分配索引
    int node_idx = node_counter++;
    index_map[node_idx] = 1;
    
    // 遍历所有点,构建方程
    for (int i = 0; i < root->point_count; i++) {
        int point_idx = root->point_indices[i];
        PointNormal pn = points[point_idx];
        
        // 计算基函数对梯度的贡献
        for (int j = 0; j < 8; j++) {
            if (root->children[j] != NULL) {
                float weight = basis_function(pn.pos, root->children[j]->center, 
                                             root->children[j]->size);
                if (weight > 0) {
                    // 法向量约束:∇φ · n = 0
                    gsl_matrix_set(A, point_idx, node_idx, weight);
                    gsl_vector_set(b, point_idx, 
                                  pn.normal.x * weight + 
                                  pn.normal.y * weight + 
                                  pn.normal.z * weight);
                }
            }
        }
    }
    
    // 递归处理子节点
    for (int i = 0; i < 8; i++) {
        build_poisson_matrix(root->children[i], points, A, b, index_map);
    }
}

// 求解泊松方程
void solve_poisson(OctreeNode *root, PointNormal *points, int point_count) {
    int node_count = estimate_node_count(root);
    gsl_matrix *A = gsl_matrix_alloc(point_count, node_count);
    gsl_vector *b = gsl_vector_alloc(point_count);
    gsl_vector *x = gsl_vector_alloc(node_count);
    int *index_map = (int*)calloc(node_count, sizeof(int));
    
    // 构建矩阵
    build_poisson_matrix(root, points, A, b, index_map);
    
    // 使用共轭梯度法求解
    gsl_multifit_linear_workspace *workspace = gsl_multifit_linear_alloc(point_count, node_count);
    gsl_multifit_linear(A, b, x, workspace);
    
    // 将解赋值回八叉树节点
    assign_solution_to_nodes(root, x, index_map);
    
    // 清理
    gsl_matrix_free(A);
    gsl_vector_free(b);
    gsl_vector_free(x);
    gsl_multifit_linear_free(workspace);
    free(index_map);
}

// 提取等值面(Marching Cubes)
Mesh* extract_isosurface(OctreeNode *root, float iso_value) {
    Mesh *mesh = (Mesh*)malloc(sizeof(Mesh));
    mesh->vertex_count = 0;
    mesh->triangle_count = 0;
    
    // 遍历八叉树叶子节点
    traverse_and_extract(root, iso_value, mesh);
    
    return mesh;
}

四、Alpha Shapes 算法

c 复制代码
#include "surface_reconstruction.h"

// 计算两点距离
float point_distance(Point3D p1, Point3D p2) {
    return sqrtf(
        (p1.x - p2.x)*(p1.x - p2.x) +
        (p1.y - p2.y)*(p1.y - p2.y) +
        (p1.z - p2.z)*(p1.z - p2.z)
    );
}

// 计算四面体体积
float tetrahedron_volume(Point3D p1, Point3D p2, Point3D p3, Point3D p4) {
    Point3D v1 = {p2.x-p1.x, p2.y-p1.y, p2.z-p1.z};
    Point3D v2 = {p3.x-p1.x, p3.y-p1.y, p3.z-p1.z};
    Point3D v3 = {p4.x-p1.x, p4.y-p1.y, p4.z-p1.z};
    
    // 标量三重积
    return fabsf(v1.x*(v2.y*v3.z - v2.z*v3.y) +
                 v1.y*(v2.z*v3.x - v2.x*v3.z) +
                 v1.z*(v2.x*v3.y - v2.y*v3.x)) / 6.0f;
}

// Alpha Shapes 主算法
Mesh* alpha_shapes(Point3D *points, int count, float alpha) {
    Mesh *mesh = (Mesh*)malloc(sizeof(Mesh));
    mesh->vertices = (Point3D*)malloc(count * sizeof(Point3D));
    mesh->triangles = NULL;
    mesh->vertex_count = count;
    mesh->triangle_count = 0;
    
    // 复制顶点
    memcpy(mesh->vertices, points, count * sizeof(Point3D));
    
    // 构建Delaunay三角剖分(简化版)
    // 实际实现需要复杂的几何算法,这里提供框架
    
    // 遍历所有可能的四面体
    for (int i = 0; i < count; i++) {
        for (int j = i+1; j < count; j++) {
            for (int k = j+1; k < count; k++) {
                for (int l = k+1; l < count; l++) {
                    float vol = tetrahedron_volume(
                        points[i], points[j], points[k], points[l]
                    );
                    
                    // Alpha过滤:只保留体积小于alpha的四面体
                    if (vol < alpha && vol > 0) {
                        // 提取四面体的外表面
                        add_tetrahedron_faces(mesh, i, j, k, l);
                    }
                }
            }
        }
    }
    
    return mesh;
}

// 添加四面体面片
void add_tetrahedron_faces(Mesh *mesh, int i, int j, int k, int l) {
    // 添加6条边对应的4个三角形
    // 实际实现需要判断边的可见性
}

五、移动立方体算法(Marching Cubes)

c 复制代码
#include "surface_reconstruction.h"

// Marching Cubes 查找表
static const int edge_table[256] = {
    0x0, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c,
    // ... 完整的256个条目
};

static const int tri_table[256][16] = {
    {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
    {0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
    // ... 完整的三角形连接表
};

// 线性插值
Point3D interpolate_vertex(Point3D p1, Point3D p2, float val1, float val2, float iso) {
    float t = (iso - val1) / (val2 - val1);
    Point3D result = {
        p1.x + t * (p2.x - p1.x),
        p1.y + t * (p2.y - p1.y),
        p1.z + t * (p2.z - p1.z)
    };
    return result;
}

// Marching Cubes 核心函数
Mesh* marching_cubes(float ***field, int dim_x, int dim_y, int dim_z, float iso_value) {
    Mesh *mesh = (Mesh*)malloc(sizeof(Mesh));
    mesh->vertex_count = 0;
    mesh->triangle_count = 0;
    
    // 估计顶点数量
    int max_vertices = dim_x * dim_y * dim_z * 3;
    mesh->vertices = (Point3D*)malloc(max_vertices * sizeof(Point3D));
    mesh->triangles = (Triangle*)malloc(max_vertices * sizeof(Triangle));
    
    // 遍历体素
    for (int x = 0; x < dim_x-1; x++) {
        for (int y = 0; y < dim_y-1; y++) {
            for (int z = 0; z < dim_z-1; z++) {
                // 获取8个顶点的标量值
                float vals[8];
                vals[0] = field[x][y][z];
                vals[1] = field[x+1][y][z];
                vals[2] = field[x+1][y+1][z];
                vals[3] = field[x][y+1][z];
                vals[4] = field[x][y][z+1];
                vals[5] = field[x+1][y][z+1];
                vals[6] = field[x+1][y+1][z+1];
                vals[7] = field[x][y+1][z+1];
                
                // 计算立方体索引
                int cube_index = 0;
                if (vals[0] < iso_value) cube_index |= 1;
                if (vals[1] < iso_value) cube_index |= 2;
                if (vals[2] < iso_value) cube_index |= 4;
                if (vals[3] < iso_value) cube_index |= 8;
                if (vals[4] < iso_value) cube_index |= 16;
                if (vals[5] < iso_value) cube_index |= 32;
                if (vals[6] < iso_value) cube_index |= 64;
                if (vals[7] < iso_value) cube_index |= 128;
                
                // 如果立方体完全在等值面内或外,跳过
                if (cube_index == 0 || cube_index == 255) continue;
                
                // 计算交点
                Point3D vertices[12];
                if (edge_table[cube_index] & 1)
                    vertices[0] = interpolate_vertex(
                        (Point3D){x,y,z}, (Point3D){x+1,y,z},
                        vals[0], vals[1], iso_value);
                // ... 计算其他11条边
                
                // 生成三角形
                int *tri = tri_table[cube_index];
                for (int i = 0; tri[i] != -1; i += 3) {
                    mesh->triangles[mesh->triangle_count].v1 = 
                        add_vertex(mesh, vertices[tri[i]]);
                    mesh->triangles[mesh->triangle_count].v2 = 
                        add_vertex(mesh, vertices[tri[i+1]]);
                    mesh->triangles[mesh->triangle_count].v3 = 
                        add_vertex(mesh, vertices[tri[i+2]]);
                    mesh->triangle_count++;
                }
            }
        }
    }
    
    return mesh;
}

// 添加顶点(去重)
int add_vertex(Mesh *mesh, Point3D vertex) {
    // 检查是否已存在
    for (int i = 0; i < mesh->vertex_count; i++) {
        float dist = point_distance(vertex, mesh->vertices[i]);
        if (dist < 1e-6f) return i;
    }
    
    // 添加新顶点
    mesh->vertices[mesh->vertex_count] = vertex;
    return mesh->vertex_count++;
}

六、主程序与使用示例

c 复制代码
#include "surface_reconstruction.h"

int main() {
    printf("三维表面重建算法演示\n");
    printf("====================\n");
    
    // 1. 生成测试点云(球体)
    int point_count = 1000;
    PointNormal *points = (PointNormal*)malloc(point_count * sizeof(PointNormal));
    
    for (int i = 0; i < point_count; i++) {
        float theta = (float)rand() / RAND_MAX * 2 * M_PI;
        float phi = (float)rand() / RAND_MAX * M_PI;
        float radius = 1.0f;
        
        points[i].pos.x = radius * sinf(phi) * cosf(theta);
        points[i].pos.y = radius * sinf(phi) * sinf(theta);
        points[i].pos.z = radius * cosf(phi);
        
        // 法向量指向球心
        float mag = sqrtf(points[i].pos.x*points[i].pos.x +
                         points[i].pos.y*points[i].pos.y +
                         points[i].pos.z*points[i].pos.z);
        points[i].normal.x = -points[i].pos.x / mag;
        points[i].normal.y = -points[i].pos.y / mag;
        points[i].normal.z = -points[i].pos.z / mag;
    }
    
    // 2. 泊松重建
    printf("执行泊松表面重建...\n");
    PoissonParams params = {6, 0.1f, 0.5f};
    
    // 计算包围盒
    Point3D min_pt = {INFINITY, INFINITY, INFINITY};
    Point3D max_pt = {-INFINITY, -INFINITY, -INFINITY};
    for (int i = 0; i < point_count; i++) {
        min_pt.x = fminf(min_pt.x, points[i].pos.x);
        min_pt.y = fminf(min_pt.y, points[i].pos.y);
        min_pt.z = fminf(min_pt.z, points[i].pos.z);
        max_pt.x = fmaxf(max_pt.x, points[i].pos.x);
        max_pt.y = fmaxf(max_pt.y, points[i].pos.y);
        max_pt.z = fmaxf(max_pt.z, points[i].pos.z);
    }
    
    Point3D center = {
        (min_pt.x + max_pt.x) / 2,
        (min_pt.y + max_pt.y) / 2,
        (min_pt.z + max_pt.z) / 2
    };
    float size = fmaxf(fmaxf(max_pt.x-min_pt.x, max_pt.y-min_pt.y), max_pt.z-min_pt.z);
    
    // 创建八叉树
    int *indices = (int*)malloc(point_count * sizeof(int));
    for (int i = 0; i < point_count; i++) indices[i] = i;
    
    OctreeNode *octree = create_octree(points->pos, indices, point_count, 
                                      center, size, params.depth);
    
    // 求解泊松方程
    solve_poisson(octree, points, point_count);
    
    // 提取等值面
    Mesh *mesh = extract_isosurface(octree, params.iso_value);
    
    printf("重建完成!顶点数: %d, 三角形数: %d\n", 
           mesh->vertex_count, mesh->triangle_count);
    
    // 3. Alpha Shapes
    printf("\n执行Alpha Shapes重建...\n");
    Mesh *alpha_mesh = alpha_shapes(points->pos, point_count, 0.5f);
    
    // 4. 保存结果
    save_mesh_to_obj(mesh, "poisson_reconstruction.obj");
    save_mesh_to_obj(alpha_mesh, "alpha_shapes.obj");
    
    // 清理内存
    free(points);
    free(indices);
    free_mesh(mesh);
    free_mesh(alpha_mesh);
    
    return 0;
}

// 保存为OBJ文件
void save_mesh_to_obj(Mesh *mesh, const char *filename) {
    FILE *fp = fopen(filename, "w");
    if (!fp) return;
    
    fprintf(fp, "# 三维表面重建结果\n");
    fprintf(fp, "# 顶点数: %d, 三角形数: %d\n\n", 
            mesh->vertex_count, mesh->triangle_count);
    
    // 写入顶点
    for (int i = 0; i < mesh->vertex_count; i++) {
        fprintf(fp, "v %.6f %.6f %.6f\n", 
                mesh->vertices[i].x, 
                mesh->vertices[i].y, 
                mesh->vertices[i].z);
    }
    
    // 写入面片
    for (int i = 0; i < mesh->triangle_count; i++) {
        fprintf(fp, "f %d %d %d\n", 
                mesh->triangles[i].v1 + 1,
                mesh->triangles[i].v2 + 1,
                mesh->triangles[i].v3 + 1);
    }
    
    fclose(fp);
    printf("结果已保存到: %s\n", filename);
}

参考 经典三维表面重建算法 www.youwenfan.com/contentcsu/60768.html

七、编译与运行

7.1 编译命令(Linux/Mac)

bash 复制代码
gcc -o surface_recon surface_reconstruction.c -lm -lgsl -lblas

7.2 编译命令(Windows/MinGW)

bash 复制代码
gcc -o surface_recon surface_reconstruction.c -lm -lgsl -lcblas

7.3 依赖库安装

bash 复制代码
# Ubuntu/Debian
sudo apt-get install libgsl-dev

# macOS
brew install gsl

八、算法特点对比

算法 优点 缺点 适用场景
泊松重建 水密性好,细节丰富 需要法向量,计算量大 有机体、平滑表面
Alpha Shapes 保留原始点云拓扑 对alpha参数敏感 点云密度均匀
移动立方体 速度快,实现简单 阶梯状伪影 规则采样数据
相关推荐
Hello:CodeWorld1 小时前
高性能多线程数据采集与持久化方案设计与实现
开发语言·c++
程序猿编码1 小时前
Linux 高负载场景下 Web 服务访问日志极速定位工具实现解析(C/C++代码实现)
linux·服务器·c语言·前端·c++
无限进步_1 小时前
【C++】智能指针族谱:auto_ptr、unique_ptr、shared_ptr
java·开发语言·数据结构·c++·算法
LuminousCPP1 小时前
C 语言文件操作全攻略:从基础读写到随机访问与缓冲区原理
c语言·经验分享·笔记·文件操作
Brilliantwxx1 小时前
【C++】Stack和Queue(初认识和算法题OJ)
开发语言·c++·笔记·算法
ch.ju1 小时前
Java Programming Chapter 3——If the array is out of range
java·开发语言
枫叶丹41 小时前
【HarmonyOS 6.0】Desktop Extension Kit 正式接棒原状态栏服务,API 引用路径全面更新
开发语言·华为·harmonyos
fffzd1 小时前
C++入门(二)
开发语言·c++·算法·函数重载·引用·inline内联函数·nullptr