首页 / 资讯中心 / 文章详情

SALD符号无关学习与导数监督:中心差分重建隐式表面

SALD符号无关学习与导数监督:中心差分重建隐式表面 ★ FEATURED ARTICLE
前阵子整理资料时翻到一篇挺有意思的工作标题叫 SALD: Sign Agnostic Learning with Derivatives中文我一般叫它“符号无关学习 导数监督”。这名字乍看有点唬人但拆开看就是一件很朴素的事手里只有一堆点每个点只知道它在物体内部还是外部不知道离表面多远、也不知道表面朝哪怎么把物体表面重建出来。这个课题我前后折腾过挺久从最早的纯符号监督一路试到加导数项踩的坑不少正好借这篇论文拆解的机会把整条链路捋一遍。如果你正在做隐式神经表示、点云重建、三维表面提取或者只是想搞明白“为什么加了梯度约束模型就不炸了”这篇内容应该能省你不少时间。下面我会从问题定义开始讲纯符号监督为什么会翻车再讲 SALD 用中心差分做导数监督的两个关键改动最后给一套可以直接抄的复现流程和一张排错速查表。全程不绕弯子代码和参数都给到能跑的程度。1. 先把课题的目标和约束条件摆清楚1.1 隐式神经场到底在学什么隐式表示这件事本质上是让一个神经网络去拟合一个标量场。输入是三维坐标x (x, y, z)输出是一个实数f(x)。约定f(x) 0的点在物体内部f(x) 0的点在外部那么f(x) 0这一层等值面就是物体的表面。这跟我们熟悉的体素表示、网格表示完全不是一个思路——体素是把空间切成小方块存 0/1网格是存顶点和面片而隐式表示存的是一个连续函数你在任意位置查询都能得到一个值分辨率理论上不受限制。打个生活化的比方想象你在一间屋子里烘了一炉面包房间里每一点的温度是个连续变化的量面包表皮恰好是某个特定温度比如 180 度的等温面。你不需要把房间切成一格一格的温度表只要有一个能回答“这个位置多少度”的函数就够了等温面在哪儿一算就出来。隐式神经场干的就是这个事只不过“温度”是网络输出的标量等温面换成了零等值面。这样做的好处很实在内存占用跟分辨率解耦一张 512³ 的体素网格就是 1.3 亿个体素而一个 8 层 256 宽的 MLP 参数量也就几十万表面天生连续不会有体素那种阶梯感还能顺便拿到解析或数值的梯度用来自动算表面法向。代价也有——等值面得靠 marching cubes 之类的方法额外提取而且训练信号的构造比体素分类麻烦得多这也是后面所有问题的根源。1.2 训练信号其实分三档SALD 站在最便宜的那一档监督信号决定了这条路的难易程度我一般把它分成三档。第一档是符号距离真值也就是每个采样点不仅知道内外还知道到表面的最短距离和最近点方向。这是最理想的监督但获取成本极高合成数据可以做精确计算真实扫描数据基本只能近似而且一旦形状有噪声距离场的定义本身就不干净了。第二档是法向监督。点云配上每个点的法向信息量介于中间。法向可以用局部邻域做 PCA 主成分分析估出来或者从网格上直接读。问题在于真实扫描的点云噪声大、密度不均PCA 估出来的法向在薄壁和尖角处经常翻向用这种带噪法向去监督网络学出来的表面会带着噪声的“指纹”。第三档就是只有内外符号。这是 SALD 所在的档位也是获取成本最低的一档。你只要知道某个点在物体里还是外就能用。判定内外的办法很多对水密的网格可以做射线投射数交点奇偶多视角轮廓投票也能给出大致结果人工在切片工具上点几下也能标一批。这种方法不需要距离、不需要法向对局部噪声的容忍度也更高——毕竟一个点无论怎么抖只要没跨过表面标签就不变。注意内外标签看似便宜但它有个隐含前提——形状得是封闭的。开放曲面比如一张纸、一片布料没有明确的“内部”这套框架直接用会得到很诡异的结果后面第 6 节会专门说这件事。这里必须把sign agnostic这个词讲透。它的意思不是说“不需要符号”而是说训练损失里只出现网络输出的符号与标签的比较不出现任何具体的数值目标。损失项长这样标签是y ∈ {-1, 1}惩罚项是softplus(-y · f(x))只有当f(x)的符号和标签一致且幅度足够大时这一项才小。至于f(x)到底等于 -3 还是 -300损失根本不关心。这个特性带来两个后果好的一面是网络尺度自由不用担心距离真值的量纲问题坏的一面是解严重不唯一无穷多个函数都能把所有点的符号判对网络到底会收敛到哪一个完全取决于优化路径和隐式正则这就是后面一系列麻烦的起点。1.3 为什么这个课题值得单独拆一篇单纯从“能不能重建出形状”来看只靠符号监督的思路早有人做过也确实能跑出结果。但那类方法有个共同的毛病表面质量不稳定时好时坏换个随机种子结果能差出一大截而且表面法向基本不可用——梯度方向乱跳算出来的法向像一团静电噪声。SALD 这篇工作的价值在于它没有停留在“加个正则项试试”这种经验层面而是把“为什么纯符号监督下梯度不可控”这件事讲清楚了并且给出了一个只需要前向计算的修正方案。这个方案有个很实际的优点它不需要网络二阶可导不需要 autograd 的梯度图甚至能配合 ReLU 这种不光滑的激活函数使用。对显存紧张、或者想塞进现有训练框架里的人来说这一点比理论上的优雅更值钱。2. 老方案为什么翻车纯符号监督的动力学解析2.1 符号损失的最优解是一族阶跃函数先把损失写清楚。给定点x和内外标签y符号项通常写成L_sign softplus(-y * f(x))或者等价的 logistic 形式。这个损失对f(x)的幅度是没有上界的只要y · f(x)足够大损失就趋近于 0而且越大幅度越小。也就是说最小化这个损失的最优解不是一个“温和”的函数而是把所有内部点推到负无穷、所有外部点推到正无穷的极限形态——一个阶跃函数。这个结论听起来抽象但在训练曲线上表现得非常具体。你会看到符号损失很快掉到接近 0看起来“学得很好”但把等值面提出来一看表面上一堆小毛刺和碎片法向完全没法用。原因就在于网络为了让损失更小会不断加剧零等值面附近的陡峭程度过渡带越来越窄梯度越来越大。等到某个时刻浮点精度撑不住了表面就开始碎。更麻烦的是这个趋势没有自然停止的机制。你不加约束它就一直往陡里走。这也是为什么很多纯符号监督的实现必须靠早停——在损失还没彻底收敛的时候把训练掐掉靠“训练不充分”来换取表面的平滑。这种做法显然不可靠换个数据分布、换个随机种子那个“刚刚好”的时刻就变了。2.2 单侧差分随手算的“梯度”其实是有偏的既然符号项不管梯度那自然地想法就是补一个梯度项把梯度约束住。问题来了怎么算梯度最省事的做法是单侧差分。对每个点x随机采一个单位方向v那么方向导数可以近似成(f(x eps * v) - f(x)) / eps这个式子只需要两次前向不用 autograd实现起来三行代码。早期的纯符号监督方案里用的就是它然后拿这个估计值去跟某个目标值对齐。但这个估计量对阶跃状函数是失效的。设想f已经接近阶跃函数x附近有个跳变面。分三种情况跳变面完全落在[x, x eps*v]区间外那分子是 0估计值 0跳变面刚好落在区间内分子是跳变量在符号函数里就是 2估计值2/epseps越小这个值越大跳变面正好穿过x估计值又是另一回事。也就是说这个估计量测的根本不是“梯度”而是“边界有没有落在这个长度为 eps 的窗口里”——它更像一个边界探测算子噪声极大而且随着eps变小估计值的方差反而爆炸。从数值分析的角度看更清楚对光滑函数单侧差分的截断误差是一阶的也就是O(eps)而中心差分的截断误差是二阶的O(eps²)。对不连续函数单侧差分还会额外引入一个与边界位置相关的偏置项这个偏置不会随着eps缩小而消失它只是被放大了。拿一个有偏的估计量去做正则本质上是在优化一个错误的目标训练能不能稳全看运气。2.3 常见的“加了梯度项还是不稳”的四种成因我复盘过自己早期那些不成功的实验问题基本能归到四类。第一类是方向采样太少。每个点只采一个方向v那这一项估计的只是梯度在v上的投影其它方向完全没被约束。合理做法是每个点采多个方向或者每个 batch 重新采样让长期期望覆盖整个球面。单方向采样在小 batch 下梯度噪声极大表现出来就是 loss 曲线剧烈震荡。第二类是**eps与点云尺度不匹配**。eps太大差分窗口跨过了形状的细节特征表面被抹平、细结构糊成一团eps太小差分值落到浮点精度以下两个前向输出的差值被舍入误差吞掉梯度项变成纯噪声。这个尺度必须跟点云的最近邻平均间距挂钩不能拍脑袋定一个 0.01。第三类是在远离表面的地方也施加了强约束。理论上只有零等值面附近才需要严格约束梯度包围盒角落里那些点的梯度你根本不在乎。但如果你用一个全局平均远处大量的采样点会稀释掉表面附近的约束力度训练后期表现为“表面附近还是不够平滑但整体 loss 已经很低了”。第四类是激活函数选了 ReLU 又指望拿到光滑表面。ReLU 网络的输出是分段线性的零等值面提出来天然带棱面感。这不是损失函数能救的得换激活函数或者在提取后做平滑。3. SALD 的两个关键改动3.1 中心差分把估计量对称化SALD 最核心的一个改动就是把单侧差分换成中心差分。对同样的点x和方向vd_hat (f(x eps/2 * v) - f(x - eps/2 * v)) / eps就这一个符号的改动性质完全变了。对光滑函数两边的泰勒展开里一阶项符号相反相加时自动抵消剩下的主项是二阶的误差从O(eps)降到O(eps²)。这意味着同样的eps下估计精度高一个量级或者换句话说为了达到同样精度eps可以取大一档从而差分值更不容易被浮点噪声污染。对不连续函数中心差分的表现也更“讲道理”。对称取点意味着只要跳变面不在±eps/2这个小窗口内两侧同号差分值是 0只有当跳变面落在窗口内时才有非零响应而且响应幅度是2/eps符号取决于跳变方向。这个行为的含义是中心差分项实际上在度量“符号在eps邻域内变化的剧烈程度”。把它作为惩罚项就是在逼着网络在eps尺度上保持符号的单调一致从而把原来无限陡的阶跃抹成一个宽度与eps相关的过渡带。我觉得这个视角比“估计梯度”的说法更好理解中心差分不是在算梯度它是在给表面定义一个特征宽度。eps决定了你希望网络的过渡带有多宽也间接决定了重建表面的最小可分辨特征尺寸。这也解释了为什么后面eps的调参那么关键——它不只是一个数值精度参数它是一个几何尺度参数。提示中心差分需要三次前向x本身、xeps/2*v、x-eps/2*v。如果符号项和导数项共用同一个点集总前向次数是 3 次。想省算力的话符号项可以复用中心差分两个端点之一但会引入额外的耦合我实测下来还是老实做三次前向更稳。3.2 目标值设成 1一个尺度归一化的锚有了差分估计量接下来得决定往哪儿约束。SALD 的做法是把方向导数往1上拉损失写成平方形式L_der (d_hat - 1)²为什么是 1因为f的尺度是自由的符号项对幅度完全不敏感你不给它定个尺度它就一直往陡里跑。约束方向导数等于 1等价于说f沿着单位方向的变化率是 1这正好是符号距离函数的特征性质——在距离场里沿任意方向走单位长度距离值就变化单位量除了在“中轴”附近这个性质会失效。约束在eps的尺度上近似成立就足够把函数“校准”成一个近似的距离场了。这一步带来的额外收益是法向可用。既然f近似是距离场那它的梯度方向就是表面法向模长接近 1。你不需要额外训练一个法向头也不需要做 PCA直接在表面附近采样一批点用差分估出梯度归一化就是法向。这个性质在做泊松重建、做表面配准、做碰撞检测的时候非常省事。代价也得说清楚f只在eps尺度上近似是距离场离表面远了超过几个eps就不保证了。所以如果你的下游任务需要全空间的精确距离查询这个函数不能直接拿来用得做一次真正的距离变换。我在项目里就吃过这个亏一开始以为训练出来的f可以直接当距离场用来做碰撞检测结果在远离表面的地方误差大得离谱。3.3 整体损失与权重设计两项合起来就是L L_sign lambda * L_derlambda的角色是权衡“位置”和“形状”。符号项负责把零等值面推到正确位置导数项负责让表面光滑、法向可靠。这个权重不是随便给的我试下来的规律是lambda 取值现象适用场景0纯符号表面毛刺、法向不可用只做粗略内外分割0.01 ~ 0.05表面基本光滑细节保留好细节丰富的形状重建0.1 ~ 0.5表面非常光滑薄结构开始被抹噪声大、需要干净网格 1表面鼓胀、细节丢失、训练变慢一般不推荐lambda太大会出现一个很典型的现象表面整体“鼓”起来像充了气。原因很直接——你在远离表面的区域也强制梯度模长等于 1这等价于要求函数在整个空间都是距离场而真实形状的距离场在中轴附近本来就有梯度退化强行约束的结果就是零等值面被推向外侧寻找一个更“规整”的解。我的经验是lambda从 0.05 起调先保证表面不出毛刺再逐步加大到细节开始丢失为止然后退回去一档。3.4 和 eikonal 类方案的正面对比市面上另一条主流路线是用 autograd 算解析梯度然后直接约束||∇f|| 1也就是常说的 eikonal 正则。两条路线目标相似实现和代价差别不小维度解析梯度 eikonal中心差分SALD 路线梯度来源autograd 反向图纯前向三次调用对网络的要求需一阶可导最好二阶可导无要求ReLU 也能用显存开销高需要保留计算图中只需激活值实现复杂度中得处理create_graph低几行张量运算梯度方向的准确性精确有O(eps²)偏差数值稳定性高但二阶导可能爆依赖eps选取我个人的判断是如果你的网络本身光滑sine、softplus、tanh算力也够解析梯度那条路更省心收敛也更稳如果你是 ReLU 网络、显存吃紧、或者想把这块塞进一个不方便做高阶求导的框架里中心差分是更实际的选择。还有一个容易被忽略的点解析梯度那条路在f接近阶跃时二阶导会剧烈震荡反而需要额外的梯度裁剪中心差分天然对这种情况更钝感。4. 复现路线从一份点云到一张可用网格4.1 数据准备与内外标签的生成整个流程的第一步是把数据的尺度统一。我习惯把所有点云缩放到单位球内中心在原点最大半径 1这一步不是可选的因为eps、网络初始化、学习率全都跟尺度耦合。不归一化直接训练换一份数据就得重调一遍超参非常痛苦。内外标签的生成我用过三种办法。第一种是对水密网格做射线投射从每个点往随机方向发一条射线数交点数奇数是内、偶数是外这种方法准确率高但要求网格封闭第二种是多视角轮廓投票从若干视角渲染轮廓把点投影上去看落在轮廓内还是外投票决定标签这种方法对非水密形状也能凑合用但凹面区域容易错第三种是直接拿现成的带标注数据省事但可控性差。不管用哪种都建议抽 1% 的点人工看一眼标签翻转率超过 3% 的时候表面会明显偏离而且这种偏离在损失曲线上看不出来。4.2 采样器设计两个点集怎么配比训练用的点集一般由两部分组成。一部分是表面附近点从原始点云加一个小扰动生成扰动幅度在0.5 * eps量级这批点承担了精细形状的监督另一部分是全局随机点在包围盒内均匀采样这批点负责把大范围的符号关系理顺防止远处的函数乱飘。配比上我用 6:4 到 7:3 比较多。全是表面点的话远处没有约束等值面可能在外围“炸”出一堆孤岛全是随机点的话表面附近的采样密度不够细节出不来。每个 batch 总点数 4000 到 8000 就够再多收益递减明显。方向向量v的采样有个小坑用torch.randn然后归一化得到的是球面均匀分布如果直接在[-1, 1]上均匀采样三个分量再归一化会在对角线方向聚集。这个差异在方向数少的时候影响很大建议老老实实用高斯归一化。v torch.randn_like(x) v v / (v.norm(dim-1, keepdimTrue) 1e-8)4.3 eps 的选择一个能算出来的起点eps是这套方法里最需要认真对待的参数。我的做法是先算点云的最近邻平均间距然后拿它当基准import torch def mean_nn_dist(pc, chunk2048): pc pc.float() n pc.shape[0] total 0.0 for i in range(0, n, chunk): block pc[i:i chunk] d torch.cdist(block, pc) rows torch.arange(block.shape[0]) cols torch.arange(i, i block.shape[0]) d[rows, cols] float(inf) # 排除自身 total d.min(dim1).values.sum() return (total / n).item()归一化到单位球之后5 万个点均匀分布在球面上最近邻间距大概在 0.02 上下具体值跟分布有关用上面的函数算一下最靠谱。eps就从1.5 × d_nn起步调参范围放在[1, 3] × d_nn。这个区间的道理是eps小于 1 倍间距时差分窗口比采样间隙还小窗口内可能一个监督点都没有差分值全靠网络的隐式插值噪声大eps大于 3 倍间距时窗口跨过了多个采样点细节被平均掉薄壁结构直接消失。我曾经在一个壁厚只有 0.01 的模型上试过eps 0.08结果薄壁整个被填实了调回 0.03 才恢复。还有一个浮点精度的下限要注意。网络输出量级通常在 1 左右float32 的有效位数约 7 位两个输出的差值如果小于1e-5基本就被舍入误差淹没了。所以eps再小也别低于1e-3否则差分项等于在拟合噪声。4.4 网络结构与初始化我这套配置是 8 层 MLP每层 256 宽隐藏层用 softplus输出层线性。softplus 相对 ReLU 的好处是二阶导连续表面不会有硬棱相对 sine 激活的好处是不需要特殊初始化训崩的概率低很多。如果你想追求极致的细节可以把 softplus 换成带频率参数的 sine但初始化得按特殊分布来学习率也要降到1e-4以下调起来更麻烦。import torch.nn as nn class ImplicitField(nn.Module): def __init__(self, width256, depth8, in_dim3): super().__init__() layers [] last in_dim for _ in range(depth): layers.append(nn.Linear(last, width)) layers.append(nn.Softplus(beta100)) last width self.body nn.Sequential(*layers) self.head nn.Linear(width, 1) self._init_weights() def _init_weights(self): for m in self.body: if isinstance(m, nn.Linear): nn.init.xavier_uniform_(m.weight) nn.init.zeros_(m.bias) # 输出层用小初始化避免训练初期输出幅度过大 nn.init.normal_(self.head.weight, std1e-4) nn.init.zeros_(self.head.bias) def forward(self, x): return self.head(self.body(x)).squeeze(-1)输出层小初始化这一点很关键。如果输出层初始权重正常初始化初始网络的输出幅度会在 1 到 3 之间乱跳eps又小差分项一上来就是巨大的值梯度直接炸掉。用小初始化让初始输出接近 0等价于初始的零等值面是一张“到处都是”的退化曲面然后靠符号项慢慢把它塑形这个过程平稳得多。4.5 训练循环与损失实现核心的训练步骤大致是这样def sald_step(model, opt, x_surf, y_surf, x_rand, y_rand, eps0.03, lam0.05, n_dir1): x torch.cat([x_surf, x_rand], dim0) y torch.cat([y_surf, y_rand], dim0).float() # 符号项 logits model(x) loss_sign torch.nn.functional.softplus(-y * logits).mean() # 导数项中心差分 loss_der 0.0 for _ in range(n_dir): v torch.randn_like(x) v v / (v.norm(dim-1, keepdimTrue) 1e-8) f_plus model(x 0.5 * eps * v) f_minus model(x - 0.5 * eps * v) d_hat (f_plus - f_minus) / eps loss_der loss_der ((d_hat - 1.0) ** 2).mean() loss_der loss_der / n_dir loss loss_sign lam * loss_der opt.zero_grad() loss.backward() opt.step() return loss_sign.item(), loss_der.item()n_dir我在小规模实验里用 1正式训练用 2 到 3。多方向采样会让梯度项的方差明显下降训练曲线平滑很多代价是前向次数线性增长。优化器用 Adam学习率1e-3配余弦退火到1e-5训练 3 万到 5 万步。batch 里表面点和随机点合并送进去不用分两个优化器。有一个细节值得单独说符号项和导数项的点集要不要一致。我的做法是让它们共用同一批点实现简单而且同一个点既有符号约束又有导数约束耦合更强。但也有人把导数项只加在表面附近的点上理由是远离表面的地方梯度约束没意义只在表面附近加能提高有效约束密度。两种做法我都试过前者收敛更稳后者在精细模型上表面质量略好一点差异不算大看你的形状细节需求。4.6 提取网格与后处理训练完之后提网格在[-1.1, 1.1]³的包围盒里打256³或512³的规则网格逐点前向求值然后跑 marching cubes。包围盒要留 10% 的边防止形状贴边被切断。512³就是 1.34 亿个点直接一次前向显存肯定不够得按块循环每块 100 万点跑完拼起来。提完之后的后处理有三步。第一步去掉小的连通分量零等值面经常会在角落留几个孤岛按面片数筛掉就行。第二步做几轮拉普拉斯平滑softplus网络的表面虽然连续但仍然会有轻微的波纹平滑 2 到 3 轮就能压掉。第三步重算法向用网格拓扑算别用f的梯度算——后者的方向准但模长在远离表面处会偏混着用会出现明暗不均。4.7 怎么判断模型真的学到了光看损失曲线是不够的符号损失掉到 0.01 但表面一塌糊涂的情况太常见了。我一般看四个指标。法向一致性在表面附近采 1 万个点用中心差分估出梯度方向跟网格最近点的法向比夹角中位数夹角在 10 度以内就算合格。体积一致性从网格算封闭体积再跟标签统计估出来的体积比偏差超过 5% 说明有大块区域被系统性挤出去了。切片检查沿三个轴向各切几张截面肉眼看轮廓。这一步最土但最有用很多问题比如薄壁被填实、凹槽被抹平在数值指标上不明显一眼就能看出来。Chamfer 距离和 F-score如果手里有扫描真值这两项是标配。阈值一般取0.5 × d_nn在这个阈值下的 F-score 能到 0.9 以上基本可以交付了。5. 排错手册与踩坑记录5.1 症状、原因、对策速查表这套流程我跑过十几遍问题基本集中在下面这些症状上症状最可能的原因处理办法表面大量小毛刺和碎片导数项权重过低或eps过小lambda提到 0.1eps提到 2 倍间距表面整体鼓胀、细节模糊lambda过大全局梯度约束过度lambda降到 0.03 以下薄壁结构被填实eps大于薄壁厚度eps降到薄壁厚度的 1/3 以下训练后期 loss 震荡方向采样太少梯度项方差大n_dir提到 2 到 3loss 一开始就爆掉输出层初始化太大或学习率过高小初始化输出层lr 降到 3e-4表面有周期性条纹eps与网格分辨率接近混叠错开两者网格分辨率提一档凹面区域缺失内外标签在凹面处判错检查标签生成必要时局部重标法向方向乱跳网络用了 ReLU或eps过大换 softpluseps降到 1.5 倍间距提取时出现孤岛远处随机点采样不足全局点比例提到 0.4训练收敛但表面偏移整体数据未归一化或标签有系统性偏差归一化到单位球抽检标签5.2 三个最容易忽略的细节第一个是方向向量的分布。前面提过但值得再强调如果你贪快在[-1,1]³上均匀采样再归一化方向会往八个角聚集导致某些方向上的约束被过度加权某些方向上完全没约束。表现出来就是表面在某些朝向的面上特别光滑另一些朝向的面上还有毛刺。这个 bug 很隐蔽因为它不会让训练失败只是让结果变差你得盯着表面质量才能发现。第二个是 float16 混合精度不能用在差分上。混合精度训练能省不少显存但中心差分那个减法对精度太敏感了eps 0.03、f的差值在0.03量级float16 只有 3 位有效数字差值基本全是噪声。我的做法是主干用autocast差分那一段强制float32用.float()转一下再算代价可以接受。第三个是训练集和提取网格的采样不能共用一个随机数发生器的状态。听起来像废话但我真的踩过差分那一段用torch.randn_like采方向如果没给 generator 设种子提取阶段的大量采样会跟训练阶段共享同一套伪随机序列导致某些网格坐标上的取值跟训练时的采样点重合出现极窄的数值伪影。正式跑的时候提取阶段单独用一个torch.Generator就行。5.3 eps 和 lambda 的调参顺序很多人一上来就同时调两个参数结果调了半天不知道是谁的问题。我建议的顺序是先固定lambda 0.05把eps从3 × d_nn往下扫到1 × d_nn跑四五个点只看表面细节的保留程度和毛刺情况选一个细节最清楚的。然后固定eps把lambda从 0.01 往上扫到 0.2看表面光滑度和鼓胀程度选一个表面干净但细节还在的。最后两个参数微调一轮一般两轮就收敛了。每轮实验我建议只跑 1 万步不用跑满。eps和lambda的影响在 1 万步的时候已经能看出来跑满纯属浪费算力。等参数定了再跑完整训练。6. 这套方法的边界和我自己的使用心得6.1 什么时候不该用它这套方法的适用边界比想象中窄几种情况我建议直接换方案。需要精确距离查询的场景。前面说过f只在eps尺度上近似是距离场。如果你拿它去做碰撞检测、做光线步进、做物理仿真误差会随着距离增大迅速累积。这类任务还是老老实实算真正的距离变换。开放曲面和薄壳。内外标签的定义依赖封闭性一张纸、一片布、一个破裂的壳体都没有明确的“内部”。硬套这套框架网络会在开放边界附近乱长表面会往外“封口”。这种情况可以考虑把问题改成有向点云重建或者用双层壳的表示。厚度小于 eps 的结构。中心差分的窗口是eps比窗口还薄的结构会被直接抹平。我在一个带 0.01 厚度筋板的模型上试过eps压到 0.015 以下才能勉强看到筋板但那时候差分噪声已经很大了得配合更大的 batch 和更小的lambda才勉强能用。需要拓扑变化的重建。从一个单球开始重建多个分离部件隐式场在拓扑变化时会出现“桥接”现象两个部件之间会连一条薄薄的管子marching cubes 之后很难看。这类问题需要专门的拓扑处理策略不是调参能解决的。6.2 我在实际项目里的几个用法最后分享几个我觉得挺实用的小技巧。一是分阶段训练。前 5000 步只用符号项让零等值面先大致成形再打开导数项。这样做的好处是避免导数项在初期把函数往“到处都平”的方向拉。我试过一开始就开导数项收敛反而慢而且容易卡在把所有输出的梯度都压成 0 的平凡解上。二是给导数项加一个按到表面距离的权重。用上一次迭代的|f(x)|做近似距离权重取exp(-|f(x)| / (3 * eps))让表面附近的点主导约束。这个改动对小样本数据提升明显因为远离表面的随机点不再稀释约束。三是用重建后的网格反哺采样。第一轮训练出来的网格虽然粗糙但它给出了一个比原始点云更均匀的表面分布。从网格上采一批点混进第二轮训练的表面点集里能让表面更均匀薄壁处的孔洞也能补上。我一般这么迭代两轮第二轮之后的法向一致性会有可见提升。四是别迷信更深的网络。我试过 16 层结果比 8 层差原因是深网络的高频分量更难控制表面上会出现波纹状的伪影而且eps得调大一档才压得住。8 层 256 宽基本够用真要提细节加宽到 512 比加深划算。
阅读完成 · 觉得有帮助?
咨询建站