一、算法概述
三维表面重建是从离散点云数据恢复连续表面的过程。这里实现三种经典算法:
- 泊松表面重建 (Poisson Surface Reconstruction)
- Alpha Shapes 算法
- 移动立方体算法 (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参数敏感 | 点云密度均匀 |
| 移动立方体 | 速度快,实现简单 | 阶梯状伪影 | 规则采样数据 |