1. 先理解本文要解决什么问题:点云生成为什么需要分治
点云生成是三维视觉和图形学里的基础问题。给定一个物体类别,或者一个局部几何约束,模型要输出一组空间点坐标,让这些点尽量贴近真实物体表面。早期方法大多直接回归点坐标,或者把问题建模成点集分布拟合。但当点云点数增多、结构变复杂时,直接生成会遇到两个问题:一是高分辨率点云需要大量输出通道,内存和计算开销快速上升;二是点云内部不同区域的结构差异很大,单一网络很难同时刻画平坦表面、尖锐边缘和复杂拓扑。
“Learning to Tessellate: Point Cloud Generation via Recursive Spectral Partitioning”这篇论文提出了一条完全不同的技术路线:不直接生成点,而是递归地将三维空间划分成若干子区域,让每个子区域不断细分,直到生成的区域内部足够简单,再生成点。这套思路的核心是“递归谱划分”,也就是对空间区域做谱聚类式的二分或分治。换个通俗说法:它不期望一个模型一口吃成胖子,而是像画地图一样,先把大区域切成小区域,再让小区域各自处理自己的细节。
这篇文章适合以下读者:
- 正在做点云补全、点云生成或三维形状重建的研究者;
- 想在生成模型中引入结构化先验的工程师;
- 对谱方法、空间划分、树结构和生成模型感兴趣的人;
- 想复现论文实验,但卡在数据集准备、模型训练和评估指标上的学生。
读完这篇文章,你会理解递归谱划分生成点云的整体流程,掌握最基本的数学表达和伪代码,能够搭建一个简化的复现项目,并知道评估点云生成结果时要用哪些指标、遇到常见报错该怎么排查。
2. 核心概念拆解:点云、谱划分和递归细分的配合关系
2.1 点云生成的难点在于结构不是均匀分布的
点云是一组三维坐标集合,例如一个桌子的点云可能包含桌面大片平坦区域、桌腿的四条细长结构、桌边的锐利棱线。如果用一个全连接网络直接输出 N 个点的三维坐标,网络需要从全局向量中恢复所有局部细节。对于简单类别(如球体)或许可行,但对于椅子、汽车、飞机这类复杂类别,全局到局部的映射会变得非常不稳定。
另一个常见做法是使用深度生成模型,比如对抗生成网络或扩散模型,直接估计点云分布。这类方法效果不错,但训练复杂,而且很难控制生成点云的拓扑结构。递归谱划分方法把问题重新定义成“划分 - 递归 - 生成”三步:先用谱聚类找到当前区域的自然分割边界,再对每个子区域递归执行相同操作,最终在叶子区域生成点。这样做的好处是,每个网络只需要负责一个小而简单的局部区域,生成难度被大幅降低。
2.2 谱划分是什么:用拉普拉斯矩阵切分空间
谱聚类是机器学习里的经典方法。给定一组点,构造它们之间的相似度图,然后计算图的拉普拉斯矩阵,取最小特征值对应的特征向量作为低维嵌入,最后对嵌入做聚类。在点云生成的场景里,谱划分不是对输入点云聚类,而是对“空间区域”做划分。
具体含义是:当前有一个包围盒或一个空间区域,我们希望把它切成两个子区域,使得两个子区域之间的连接尽量少,每个子区域内部的几何结构尽量一致。这就需要定义区域内部的连接关系。论文中的一个常见做法是,在当前区域里采样一组密度点或体素中心,建立近邻图,然后计算拉普拉斯矩阵的 Fiedler 向量,也就是第二小特征值对应的特征向量。Fiedler 向量的正负号可以把区域划成两半。
这种划分方式不是简单的按坐标轴切分,而是按几何连通性切分。两个空间上靠近但被狭长缝隙隔开的区域,有可能被谱划分分到不同子区域;而两个空间距离较远但通过窄带连接的区域,谱划分会尽量在窄带处切开。这是它优于均匀网格划分和八叉树划分的关键点。
2.3 递归细分:从全局到局部的树状结构
递归谱划分会生成一棵二叉树。根节点代表整个物体的空间范围,每次划分把当前节点分成左右两个孩子节点,直到达到预设的深度或叶子节点内部点数足够少。每个叶子节点对应一个局部区域,网络在这些叶子区域上分别生成点。
树的深度直接影响生成点云的分辨率和结构粒度。深度越深,叶子区域越小,生成的点越精细,但需要更多的划分步骤,计算开销也更大。浅层树适合低分辨率点云,深层树适合高分辨率点云。这种结构本质上是一种自适应空间层次结构,类似八叉树,但划分边界不是固定的坐标轴平面,而是由数据驱动的谱边界。
表:递归谱划分与常见空间划分方式对比
| 划分方式 | 划分边界 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 均匀网格 / 体素 | 固定坐标轴平面 | 实现简单、GPU 友好 | 忽略几何结构、稀疏区域浪费 | 规则扫描数据、体素占位预测 |
| 八叉树 | 固定中点切分 | 自适应局部细分、结构清晰 | 边界不是几何边界 | 大场景、LiDAR 点云 |
| 谱划分 | 数据驱动的几何边界 | 能贴合几何连通性、切分更自然 | 需要构造图并求解特征向量 | 复杂物体点云生成与补全 |
实际工程中,八叉树是无符号距离场和神经辐射场常用的加速结构,而谱划分更适合做几何感知的递归分解。二者并不冲突,只是谱划分在“同一深度下保留更多几何语义”上更有优势。
2.4 为什么叫“Tessellate”
Tessellate 原本指把平面或空间铺满瓦片、镶嵌成 mosaic 图案。在图形学里,曲面细分也叫 tessellation,指把大面片拆成小三角形或小四边形。本文标题里的 Tessellate 传达的意思是:用递归划分把三维空间铺成由多个子区域组成的拼图,每个子区域是拼图的一块瓦片,最后这些瓦片上的点拼成完整物体形状。
理解这一点很重要,因为后续所有模块都是围绕“空间划分”设计的:编码器要输出区域特征,划分模块要决定怎么切,叶子生成器要在小区域里补点。整条链路不是“输入全局形状、输出点云”的端到端黑箱,而是“全局理解加上局部决策”的结构化生成过程。
3. 方法框架:递归谱划分生成点云的整体流程
论文总体框架可以拆成四个环节:
- 输入编码:使用点云编码器(例如 PointNet 或 PointNet++)提取全局和局部特征。
- 谱划分预测:一个划分网络根据当前区域特征,预测划分矩阵或划分方向,决定将区域一分为二。
- 递归执行:对划分出的子区域重复预测,直到达到最大深度。
- 叶子点生成:在每个叶子区域生成固定数量的点,合并所有叶子输出得到最终点云。
这里要区分两个概念:谱划分本身和划分网络。谱划分是目标函数中的先验或正则,而划分网络是学习如何预测划分。传统的谱聚类需要显式构造相似度矩阵并做特征分解,计算代价高且不可导。论文的改进是让网络学习预测“等价于谱划分结果”的划分方式,这样既保留谱划分的几何优点,又能在神经网络里端到端训练。
3.1 编码器:如何得到区域特征
对于当前区域,先把它内部的点坐标转换到局部坐标系。假设当前节点包围盒中心为 c,边长为 s,可以把每个点归一化到 [-1, 1] 范围,再输入编码器。编码器输出一个特征向量,代表当前区域的几何概况。这个特征向量会被用于两个地方:预测划分、生成叶子点。
如果采用 PointNet++,还可以在不同半径的邻域内聚合特征,这样划分网络能感知局部密度变化和边缘结构。
3.2 划分网络:从特征到划分矩阵
划分网络输入区域特征,输出一个划分矩阵。这个矩阵的规模与当前区域内部采样的点数相关。论文中常用做法是,把当前区域均匀采样 K 个点,每个采样点获得一个二分类概率,概率值把采样点分成两组。两组采样点各自构成子区域的近似表示。
为了让划分结果接近谱划分,损失函数中要加入谱划分一致性约束。一种可行方式是:先对当前区域构造近邻图,计算拉普拉斯矩阵,求解 Fiedler 向量,然后用网络输出的划分向量去匹配 Fiedler 向量的符号。也就是说,网络在模仿谱聚类的结果,但推理时可以绕开特征分解,直接吃特征出划分。
损失函数示意:
# 伪代码:谱划分匹配损失 def spectral_partition_loss(sample_coords, pred_partition): # 根据采样点坐标构造近邻图 adj = build_knn_graph(sample_coords, k=8) L = compute_laplacian(adj) _, fiedler_vec = smallest_eigenvectors(L, k=2) target = sign(fiedler_vec) return binary_cross_entropy(pred_partition, target)这段代码说明:训练阶段我们需要先算一次谱聚类结果作为监督信号;推理阶段不使用这个模块,模型直接输出划分。因为 Fiedler 向量只是一个软指导,真正要让网络学会划分,还需要加结构一致性损失,让划分后两个子区域都尽量紧凑。
3.3 叶子生成器:在局部区域里补点
到达最大深度后,叶子区域不再划分,而是由生成器直接输出固定数量的点。生成器输入当前区域特征,输出一个 M x 3 的张量,代表 M 个点的局部坐标。为了实现高分辨率点云,M 可以和叶子区域大小、物体复杂度相关。论文中通常设置每个叶子生成 32、64 或 128 个点,具体数量由实验决定。
叶子生成器可以使用简单的多层感知机,也可以使用条件生成网络。如果希望点云分布更均匀,可以加入排斥损失或最远点采样约束。
3.4 整个递归过程的伪代码
def generate_point_cloud(encoder, partition_net, leaf_net, max_depth, num_points_per_leaf): root_feature = encoder(input_points) # 全局或初始区域特征 regions = [(root_bbox, root_feature, 0)] final_points = [] while regions: bbox, feature, depth = regions.pop() if depth == max_depth: local_points = leaf_net(feature, num_points_per_leaf) final_points.append(transform_to_global(local_points, bbox)) else: partition = partition_net(feature) left_bbox, right_bbox = split_bbox(bbox, partition, sample_points_in_bbox(bbox)) left_feature = encoder(sample_points_in_bbox(left_bbox)) right_feature = encoder(sample_points_in_bbox(right_bbox)) regions.append((left_bbox, left_feature, depth + 1)) regions.append((right_bbox, right_feature, depth + 1)) return concatenate(final_points)这段流程展示了解码阶段,也就是从根节点开始递归展开二叉树。实际训练时,输入点云会被先经过真实划分生成训练样本,网络学习每个节点上如何划分。推理时才用预测的划分去展开树。
4. 复现准备:环境、数据集和目录结构
正式开始复现之前,先把环境对齐。论文涉及 PyTorch、点云处理、几何计算和可视化工具,下面是一份在常见项目中能跑通的配置清单。
4.1 环境要求
建议使用 NVIDIA GPU,显存不低于 8GB。训练时显存开销来自编码器、划分网络和叶子生成器的特征计算,以及每层递归产生的中间特征。如果资源有限,可以先降低 batch size、降低最大深度、减小每个叶子生成的点数量。
python -m venv tessellate_env source tessellate_env/bin/activate pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 pip install numpy scipy matplotlib open3d tensorboard tqdm pip install pointnet2_ops # 如果使用 PointNet++ 加速算子如果没有 GPU,CPU 也可以跑通小规模演示,但完整训练会非常慢。建议先跑一个深度为 3、每叶子 32 点的玩具实验验证代码逻辑,再上完整配置。
4.2 数据集准备:ShapeNet 与最小演示数据
论文在 ShapeNet 的多个类别上做实验,包括椅子、飞机、汽车等。ShapeNet 数据集规模较大,下载和处理都需要额外脚本。如果只是复现主流程,可以先用三类数据做一个 Mini 版:
- ShapeNetCore.v1 中的 chair、airplane、car 三个类别;
- 每类取 100 个模型,使用官方点云采样脚本生成 2048 个点;
- 训练和测试按 8:2 划分。
如果暂时没有数据集,可以用简单几何体生成调试数据,比如在单位球面上采样点、在立方体表面采样点。这样能验证代码逻辑是否出错,但不代表模型真正学会生成复杂形状。
4.3 项目目录结构
一个清晰的项目结构能减少排查成本。下面目录可以按实际项目调整:
tessellate/ ├── configs/ # 配置文件 ├── data/ # 数据集 ├── datasets/ # 数据加载类 ├── models/ # 编码器、划分网络、叶子生成器 ├── utils/ # 几何计算、可视化、指标计算 ├── scripts/ # 训练与评估脚本 ├── train.py ├── evaluate.py └── README.md# configs/train.yaml 示例 data: root: ./data/shapenet_mini categories: [chair, airplane, car] num_points: 2048 model: encoder: pointnet2 max_depth: 4 points_per_leaf: 32 hidden_dim: 128 train: batch_size: 8 learning_rate: 0.001 epochs: 200 device: cuda log_dir: ./logs这里的max_depth: 4表示递归 4 层,叶子数量最多为 2 的 4 次方,也就是 16 个叶子区域。如果数据形状简单,可以减少到 3 层。
5. 最小可运行实现:用 PyTorch 写一个简化版本
下面给出一个可以运行的最小实现,它不追求完整复现论文的每一处细节,而是还原最主要的递归谱划分生成链路。这个简化版本包括:数据样本、编码器、划分网络、叶子生成器、训练循环和可视化输出。
5.1 编码器:简单 PointNet 风格
import torch import torch.nn as nn import torch.nn.functional as F class PointNetEncoder(nn.Module): def __init__(self, input_dim=3, hidden_dim=128): super().__init__() self.mlp1 = nn.Sequential( nn.Linear(input_dim, 64), nn.ReLU(), nn.Linear(64, 128), nn.ReLU(), ) self.mlp2 = nn.Sequential( nn.Linear(128, 256), nn.ReLU(), nn.Linear(256, hidden_dim), ) def forward(self, points): # points: [B, N, 3] x = self.mlp1(points) # [B, N, 128] x = x.max(dim=1).values # 全局最大池化,[B, 128] x = self.mlp2(x) # [B, hidden_dim] return xPointNet 编码器对点序不敏感,可处理任意点数的输入。这里最大池化把整片区域的信息压缩成一个全局特征,适合做区域级决策。
5.2 划分网络:输出二分类概率
class PartitionNet(nn.Module): def __init__(self, feature_dim=128, num_sample_points=64): super().__init__() self.num_sample_points = num_sample_points self.fc = nn.Sequential( nn.Linear(feature_dim + 3, 128), nn.ReLU(), nn.Linear(128, 64), nn.ReLU(), nn.Linear(64, 1), ) def forward(self, region_feature, candidate_points): # region_feature: [B, feature_dim] # candidate_points: [B, K, 3] B, K, _ = candidate_points.shape feat = region_feature.unsqueeze(1).expand(-1, K, -1) # [B, K, feature_dim] x = torch.cat([candidate_points, feat], dim=-1) logits = self.fc(x).squeeze(-1) # [B, K] return logits划分网络把区域特征和每个候选点的坐标拼在一起,对每个候选点预测一个二分概率。候选点是当前包围盒内均匀采样的点,用来近似区域的几何范围。K 越大,划分边界可以表达得越精细,但网络输出维度也越高。
5.3 谱划分目标:构造图拉普拉斯并计算 Fiedler 向量
import numpy as np from scipy.spatial import cKDTree from scipy.sparse.linalg import eigsh def fiedler_vector(points, k=8): # points: [N, 3] tree = cKDTree(points) dist, idx = tree.query(points, k=k + 1) adj = np.zeros((len(points), len(points))) for i in range(len(points)): for j in idx[i][1:]: adj[i, j] = 1.0 deg = np.diag(adj.sum(axis=1)) L = deg - adj vals, vecs = eigsh(L, k=2, which='SM') fiedler = vecs[:, 1] return fiedler这段代码构造了 k 近邻图,计算拉普拉斯矩阵,取第二小特征向量作为划分方向的监督信号。eigsh要求矩阵可进行稀疏特征分解,点数太多时耗时很高,所以训练时通常对区域采样少量点来计算。
5.4 叶子生成器:从特征直接生成点坐标
class LeafGenerator(nn.Module): def __init__(self, feature_dim=128, num_points=32): super().__init__() self.num_points = num_points self.fc = nn.Sequential( nn.Linear(feature_dim, 256), nn.ReLU(), nn.Linear(256, num_points * 3), ) def forward(self, feature): # feature: [B, feature_dim] points = self.fc(feature).view(-1, self.num_points, 3) return torch.tanh(points) # 限制在 [-1, 1],便于归一化叶子生成器输出的点限制在 [-1, 1] 范围内,因为训练时区域内部坐标做了归一化。推理时需要把局部坐标乘上区域边长,再加上区域中心,变换回全局坐标。
5.5 训练循环示例
def train_step(model, optimizer, points, target_points, num_sample=64): B, N, _ = points.shape total_loss = 0.0 region_feature = model.encoder(points) candidate = torch.rand(B, num_sample, 3) * 2 - 1 # 归一化采样候选点 partition_logits = model.partition_net(region_feature, candidate) real_fiedler = fiedler_vector_batch(points, num_sample) partition_loss = F.binary_cross_entropy_with_logits( partition_logits, real_fiedler.unsqueeze(-1) ) leaf_points = model.leaf_net(region_feature) reconstruction_loss = chamfer_distance(leaf_points, target_points) loss = partition_loss + reconstruction_loss optimizer.zero_grad() loss.backward() optimizer.step() return loss.item()训练时同时优化划分目标和叶子点重建目标。划分目标让网络学会如何切分区域,重建目标让叶子生成器在局部区域生成合理点。二者缺一不可,如果只算重建损失,划分网络可能退化成随机划分;如果只算划分损失,叶子生成器无法生成正确形状。
6. 关键参数与工程细节
6.1 最大深度的影响
最大深度是递归谱划分最重要的超参数之一。它决定树的高度,也就是划分次数。深度增大一方面能提高生成点云的分辨率和几何复杂度,另一方面会导致训练时特征分解和递归展开的计算量指数级上升。常见的设定在 3 到 5 之间,具体要看目标点云的点数和形状复杂度。
表:最大深度与叶子数量对应关系
| 最大深度 | 叶子数量上限 | 每叶子生成点数 | 输出总点数下限 | 适用场景 |
|---|---|---|---|---|
| 3 | 8 | 32 | 256 | 低分辨率、近景简单物体 |
| 4 | 16 | 32 | 512 | 中等分辨率 |
| 5 | 32 | 32 | 1024 | 高分辨率、完整物体生成 |
| 6 | 64 | 16 | 1024 | 高分辨率但每叶子较少点 |
6.2 候选点数量 K
划分网络对候选点做二分类,候选点数量 K 决定了划分边界的精度。K 太小,划分边界粗糙,容易出现严重锯齿;K 太大,内存和计算开销上升,而且训练时为目标计算 Fiedler 向量的成本也会增加。建议从 64 开始,若生成点云边界不清晰可以提升到 128 或 256。
6.3 近邻图参数 k
构造近邻图时 k 的选择影响拉普拉斯矩阵和图连通性。k 太小,图可能断裂,Fiedler 向量无法稳定表达划分;k 太大,图会过度连接,划分失去局部性。常见取值在 4 到 12 之间。数据分布稀疏时适当增大 k。
6.4 损失权重的平衡
论文通常把划分损失和重建损失结合起来。划分损失权重过大会导致网络只关注如何把空间切开,而不关心最终生成点云好不好;重建损失权重过大会导致划分网络失去监督信号。一个稳妥做法是先让重建损失主导训练,待点云大致成型后,再逐渐提高划分损失的权重。
7. 运行验证与常见问题排查
7.1 如何验证训练是否正常
训练时重点观察三个信号:
- 训练损失是否稳定下降。如果损失震荡剧烈,可能需要降低学习率;
- 划分准确率是否为 50% 左右起步。因为二分任务随机猜也是 50%,训练初期接近 50% 是正常的,长期不上升说明监督信号或网络结构有问题;
- 叶子生成点是否逐渐形成局部形状。可以周期性地保存可视化图片,用 Open3D 展示中间结果。
import open3d as o3d import numpy as np def visualize_points(points, path="output.ply"): pcd = o3d.geometry.PointCloud() pcd.points = o3d.utility.Vector3dVector(points) o3d.io.write_point_cloud(path, pcd)训练阶段建议每 10 个 epoch 保存一次可视化点云,这样能直观看到递归划分是否在逐步细化。
7.2 常见问题排查
表:训练和推理过程中的典型问题
| 问题现象 | 常见原因 | 检查方式 | 处理建议 |
|---|---|---|---|
| 训练损失不下降 | 学习率过大或数据未归一化 | 打印输入坐标范围、检查梯度范数 | 将点云归一化到 [-1, 1],降低学习率 |
| 划分准确率一直 50% | 划分网络没收到有效特征 | 检查区域特征是否被梯度截断 | 确认编码器输出与划分网络正确连接 |
| 生成点云只有几个点 | 叶子生成器输出 NaN | 检查 loss 是否出现 NaN,检查候选点是否包含异常值 | 在特征中加 LayerNorm,或减小学习率 |
| 谱特征分解失败 | 近邻图不连通 | 检查近邻图是否有孤立点 | 增大 k,或使用带权邻接图 |
| 显存不足 | batch size 或候选点数量过大 | 观察显存占用日志 | 降低 batch size、降低 K、降低最大深度 |
| 生成点云边界模糊 | 划分不够精细 | 可视化划分结果 | 增大 K 或加深递归层数 |
7.3 为什么谱分解不能在推理时用
训练时使用谱分解是为了提供监督信号,但推理时如果还执行特征分解,速度会非常慢,而且不可导。关键是让网络在推理时通过“看特征”直接预测划分结果。这样推理阶段只需要一次前向传播,复杂度由网络结构决定,而不是由特征分解算法决定。这也是该方法和直接在推理时跑谱聚类的本质区别。
训练时,Fiedler 向量只作为软目标,不需要完全精确。如果追求训练稳定,可以先对每个区域预计算谱划分结果保存到磁盘,作为离线标签。这样训练时不需要在线做特征分解,速度会快很多。
8. 评估指标与实验对比
8.1 Chamfer Distance
Chamfer Distance(CD)是点云生成最常用的指标。它计算两个点云之间双向最近邻距离的平均值。数值越低,说明生成点云与真实点云越接近。
def chamfer_distance(pred, gt): from torch.cdist import cdist dist = cdist(pred, gt) # [B, N_pred, N_gt] min_pred_to_gt = dist.min(dim=2).values.mean(dim=1) min_gt_to_pred = dist.min(dim=1).values.mean(dim=1) return (min_pred_to_gt + min_gt_to_pred) / 2Chamfer Distance 对点云密度变化比较敏感。生成点云如果只在部分区域集中,另一个方向的距离会很大,所以它能有效反映覆盖度。
8.2 Earth Mover‘s Distance
推土机距离(EMD)计算两个点集之间的最小匹配代价。它比 Chamfer Distance 对分布保真度要求更高,但计算代价也更高。高分辨率点云上使用 EMD 往往需要二次分配算法,显存和耗时都会上升。论文中通常同时报告 CD 和 EMD,以平衡“几何覆盖度”和“分布保真度”。
8.3 其他指标
- F-score:在给定阈值下,生成点云和真实点云之间互相匹配的点比例;
- 点到面距离:如果真实数据是网格模型,可以把生成点投影到真实表面,计算点到面的距离;
- 均匀度:检查生成点云在物体表面是否分布均匀,避免点聚集在某个局部区域。
8.4 与八叉树方法、直接回归方法的对比角度
递归谱划分方法的核心优势在于划分边界适应几何结构。八叉树方法切分固定中点,对于细长结构效率不高;直接回归方法没有显式空间层次,难以扩展至高分辨率。论文里的实验通常会展示不同深度下 CD 和 EMD 的下降趋势,以及可视化对比图中点云是否保持完整结构。
9. 常见坑与最佳实践
9.1 三个容易踩的坑
第一个坑是区域归一化不一致。编码器输入点云坐标如果有的区域归一化到 [-1, 1],有的区域没有归一化,网络会学到错误的尺度信息。每个子区域在递归时,都要先减去区域中心,再除以区域半径或边长。
第二个坑是谱分解算法的随机性。Fiedler 向量的符号并不唯一,同一个划分可能得到正负相反的向量。如果不做符号对齐,网络无法稳定学习。训练时可以根据划分后两簇点数的多少来固定正负,例如约定正号表示点数较多的一侧。
第三个坑是叶子生成器直接回归全局坐标。局部坐标和全局坐标差异较大,如果叶子生成器直接输出全局点,网络要同时拟合位置和形状,更容易过拟合。更稳妥的方法是让叶子生成器输出局部坐标,推理时再做仿射变换。
9.2 最佳实践清单
- 训练前先确认所有输入点云都在统一坐标系中;
- 每个区域递归时保存包围盒中心和边长,推理时用于坐标还原;
- 使用 TensorBoard 监控划分损失和重建损失的变化趋势;
- 谱划分监督信号的 k 近邻图使用 numba 加速,避免 Python 循环太慢;
- 训练阶段定期保存 checkpoint,并记录深度、划分矩阵和生成点云的中间结果;
- 评估时不要在训练集上做 F-score,要使用独立的测试集;
- 如果显存不足,优先降低 batch size,其次降低 K,不要一开始就降低最大深度。
9.3 学习环境与生产环境差异
学习阶段可以只用几十个模型验证代码,训练 10 个 epoch 观察损失是否下降。生产环境或正式实验需要完整 ShapeNet 数据、更长的训练时间、更精细的参数搜索和严格的评估流程。此外,生产环境还需要加入日志记录、断点续训、分布式训练支持和模型版本管理。
以下是一份发布前检查清单,适合正式实验前使用:
- [ ] 数据是否划分训练集、验证集、测试集
- [ ] 是否统一了点云坐标范围和原点位置
- [ ] 谱划分使用的近邻图参数是否记录在配置文件中
- [ ] 每个叶子生成点数量是否与总点数匹配
- [ ] 推理脚本是否能从 checkpoint 恢复完整结构
- [ ] 评估指标是否在测试集上运行
- [ ] 是否保存了可视化中间结果
- [ ] 是否记录了每个实验的超参数组合
10. 从论文到实际项目:扩展方向
如果想把递归谱划分用到实际项目里,可以从以下几个方向延伸:
10.1 点云补全
输入部分点云时,可以先对已知点提取特征,再在缺失区域递归谱划分生成点。划分网络可以利用已知点云分布预测哪里的空间结构最复杂,优先细划那些区域。
10.2 高分辨率点云生成
直接回归高分辨率点云需要很大输出头,而递归方法可以把高分辨率问题拆成多个小分辨率问题。每个叶子点数量固定,树的深度增加后总点数指数增长,但每个叶子生成器的输出维度不变,因此显存开销可控。
10.3 结合隐式神经场
谱划分可以作为隐式神经场网络的空间自适应采样策略。把空间按几何特征递归分区,每个叶子区域分配独立的隐式特征,就可以在保持精度的同时减少计算量。
10.4 条件生成
在编码器输入中加入类别标签或图片特征,递归谱划分生成器就能做条件点云生成。类别标签影响全局特征,进而影响划分和叶子生成结果。
这些方向都建立在同一个核心判断上:点云生成不应该一次到位,而应该先分解空间,再局部生成。递归谱划分提供了一种可学习、可端到端训练的空间分解方式,这是它和传统固定网格划分的关键区别。在实际项目中,最重要的不是复现论文每一处细节,而是理解什么时候该划分、划分到什么粒度、每个局部区域如何独立生成点。把这三点想清楚,即使不使用谱划分,用八叉树或均匀网格也能设计出结构合理的点云生成系统。