nerfstudio 损失函数体系深度解析:从 MipNeRF-360 采样损失到深度与法线监督
【免费下载链接】nerfstudioA collaboration friendly studio for NeRFs项目地址: https://gitcode.com/GitHub_Trending/ne/nerfstudio
导读:损失函数是 NeRF 训练的优化核心,决定了几何重建的精度与渲染质量。本文以 nerfstudio 官方 API 参考文档 losses.rst 为骨架,深入剖析其在nerfstudio/model_components/losses.py中的全部实现:包括 MipNeRF-360 的 interlevel/distortion 采样损失、Ref-NeRF 法线损失、Depth-supervised NeRF 与 Urban Radiance Fields 深度监督、monosdf 单目深度损失、显式网格的 TV 正则等,并结合 nerfacto、depth-nerfacto、tensorf 等模型中的实际调用与测试用例,帮助你理解每类损失的数学动机、源码实现与调参方法。
一、损失模块总览:一个函数库而非框架
在 nerfstudio 中,损失函数并非由框架统一强制调用,而是作为一个"工具箱"提供给各模型自行组合。核心文件是 nerfstudio/model_components/losses.py,其 docstring 只有一句话:"Collection of Losses"(损失函数的集合)。
模块开头的关键定义如下:
L1Loss = nn.L1Loss MSELoss = nn.MSELoss LOSSES = {"L1": L1Loss, "MSE": MSELoss} EPS = 1.0e-7L1Loss、MSELoss直接复用 PyTorch 的nn.L1Loss与nn.MSELoss,用于最基础的 RGB 颜色损失;LOSSES字典是一个小型注册表,把字符串"L1"、"MSE"映射到对应损失类,方便配置系统按名字选取;EPS = 1.0e-7是全局数值稳定常数,用于所有可能出现除零或log(0)的公式中。
除了这两个基础损失,模块还包含约十余个函数式损失与三个nn.Module类,它们按用途可以划分为六大类:采样/分布损失(interlevel、distortion)、法线监督损失(orientation、pred_normal、monosdf)、深度监督损失(ds-nerf、URF、ranking)、单目深度专用损失(MiDaS、Gradient、ScaleAndShiftInvariant)、显式网格正则(TV loss),以及梯度缩放工具。下文逐一展开。
二、RGB 主损失与损失组合流程
所有以光度重建为目标的 NeRF 模型(nerfacto、vanilla_nerf、mipnerf、instant-ngp 等)都以渲染像素与真实像素之间的 L1 或 MSE 作为主损失。以 nerfacto.py 的get_loss_dict为例:
loss_dict["rgb_loss"] = self.rgb_loss(gt_rgb, pred_rgb) if self.training: loss_dict["interlevel_loss"] = self.config.interlevel_loss_mult * interlevel_loss( outputs["weights_list"], outputs["ray_samples_list"] ) loss_dict["distortion_loss"] = self.config.distortion_loss_mult * metrics_dict["distortion"]可以看到训练损失是一个加权求和的字典:rgb_loss打底,再叠加采样类正则项。每个项的权重由模型配置控制,例如 nerfacto 的默认配置为(见 nerfacto.py):
| 配置项 | 默认值 | 含义 |
|---|---|---|
interlevel_loss_mult | 1.0 | Proposal 网络蒸馏损失(interlevel loss)权重 |
distortion_loss_mult | 0.002 | MipNeRF-360 畸变损失权重 |
orientation_loss_mult | 0.0001 | Ref-NeRF 法线朝向损失权重 |
pred_normal_loss_mult | 0.001 | 预测法线监督损失权重 |
注意各个量级差异巨大(0.002、0.0001),这是因为不同损失项的数值范围不同,调参时应先观察各项量级再缩放,而不是直接改大改小。
三、采样与分布正则:MipNeRF-360 的两大支柱
3.1 interlevel_loss:Proposal 网络的蒸馏损失
多级采样(coarse-to-fine / proposal)架构中,浅层网络负责快速估计密度分布,深层网络负责精细渲染。MipNeRF-360 提出用interlevel loss(即 proposal loss)约束浅层分布的权重视图与深层一致。
源码实现(losses.py):
def interlevel_loss(weights_list, ray_samples_list) -> torch.Tensor: c = ray_samples_to_sdist(ray_samples_list[-1]).detach() w = weights_list[-1][..., 0].detach() loss_interlevel = 0.0 for ray_samples, weights in zip(ray_samples_list[:-1], weights_list[:-1]): sdist = ray_samples_to_sdist(ray_samples) cp = sdist wp = weights[..., 0] loss_interlevel += torch.mean(lossfun_outer(c, w, cp, wp)) return loss_interlevel关键细节:
- 以最后一层(最精细)的
ray_samples和weights作为监督目标,且通过.detach()切断梯度,避免深层网络被浅层反向影响; - 对前面每一层分别计算
lossfun_outer并累加取均值; ray_samples_to_sdist把采样的区间端点转换为s空间(标准化射线距离)坐标(num_rays, num_samples + 1)。
核心计算lossfun_outer(losses.py)度量浅层直方图权重w超过深层上包络w_env的程度:
w_outer = outer(t[..., :-1], t[..., 1:], t_env[..., :-1], t_env[..., 1:], w_env) return torch.clip(w - w_outer, min=0) ** 2 / (w + EPS)outer函数(losses.py)是一个高效实现:通过torch.cumsum构造权重累积分布cy1,再用torch.searchsorted在浅层区间边界处二分查找落在深层包络中的累计质量,避免显式构造双重循环。这也是该方法被 MipNeRF-360 官方代码(Google Research 的stepfun.py)采用的经典技巧。
在模型中的使用:除 nerfacto 外,neus_facto.py、generfacto.py、semantic_nerfw.py 均在训练时叠加该项。在 method_configs.py 中可以看到某些配置把interlevel_loss_mult设为 100.0、distortion_loss_mult设为 1.0,说明对于不同方法需要按数值量级大幅调整权重。
3.2 distortion_loss 与 nerfstudio_distortion_loss:消除"漂浮"伪影
MipNeRF-360 的畸变损失鼓励单条射线上的权重分布紧凑、不产生分散在空白区域的权重(对应训练初期常见的"floaters"伪影)。原始公式为:
$$\mathcal{L}(\mathbf{s}, \mathbf{w}) = \iint \mathbf{w}\mathbf{s}(u),\mathbf{w}\mathbf{s}(v),|u-v|,du,dv$$
即对所有样本对的权重乘积乘以距离间隔加权求和,物理含义是"惩罚权重在射线上的分散程度"。
模块中提供了两个变体:
distortion_loss(weights_list, ray_samples_list)(losses.py):直接取最后一层样本计算lossfun_distortion(losses.py),后者把区间分为"inter"(跨区间对)与"intra"(区间内)两部分求和,其中intra项系数为 1/3;nerfstudio_distortion_loss(ray_samples, densities, weights)(losses.py):nerfstudio 风格的 ray-based 实现,接受RaySamples与可选的densities或weights——若只给densities,会先通过ray_samples.get_weights(densities)计算权重;若同时传入两者则断言报错。它基于spacing_starts/spacing_ends的中点差计算,最终形状为(*bs, 1)。
nerfacto 中 distortion 的调用路径值得注意:它先在get_metrics_dict中计算(nerfacto.py),再在get_loss_dict中乘权重(nerfacto.py),这种"先记指标、后计损失"的模式便于训练日志中单独观测该项数值。
四、法线监督:Ref-NeRF 与 monosdf 的思路
4.1 orientation_loss:可见法线必须朝向相机
出自 Ref-NeRF 的朝向损失(losses.py),惩罚"被观察到的表面法线背对相机"的情况:
def orientation_loss(weights, normals, viewdirs): w = weights n = normals v = viewdirs * -1 # 视线方向反向,即"朝向相机"的方向 n_dot_v = (n * v[..., None, :]).sum(dim=-1) return (w[..., 0] * torch.fmin(torch.zeros_like(n_dot_v), n_dot_v) ** 2).sum(dim=-1)- 视线方向取反得到指向相机的向量
v; - 只惩罚
n_dot_v < 0(法线背向相机)的部分,用torch.fmin(0, n_dot_v)截断正值; - 按权重
w[..., 0]加权,即可见性越高的样本对损失贡献越大。
4.2 pred_normal_loss:预测法线与密度法线一致性
当网络同时输出预测法线(PRED_NORMALS)时,pred_normal_loss(losses.py)约束其与从密度场计算的解析法线一致:
return (weights[..., 0] * (1.0 - torch.sum(normals * pred_normals, dim=-1))).sum(dim=-1)1 - n·n_pred等价于余弦距离,权重加权同上。
nerfacto 中的完整用法(nerfacto.py)展示了"计算法线→监督"的流水线:
if self.training and self.config.predict_normals: outputs["rendered_orientation_loss"] = orientation_loss( weights.detach(), field_outputs[FieldHeadNames.NORMALS], ray_bundle.directions) outputs["rendered_pred_normal_loss"] = pred_normal_loss( weights.detach(), field_outputs[FieldHeadNames.NORMALS].detach(), field_outputs[FieldHeadNames.PRED_NORMALS])注意weights.detach()与NORMALS.detach():pred_normal_loss 只更新预测法线分支,不反向干扰密度与权重分支;而 orientation_loss 不 detach 法线,让密度场也能通过解析法线收到梯度。
4.3 monosdf_normal_loss:与单目法线对齐
针对表面重建模型(如 neus),monosdf_normal_loss(losses.py)同时用L1 角度误差 + 余弦相似度约束体积渲染法线normal_pred与单目预测法线normal_gt(monosdf 论文 Eq.14):
l1 = torch.abs(normal_pred - normal_gt).sum(dim=-1).mean() cos = (1.0 - torch.sum(normal_pred * normal_gt, dim=-1)).mean() return l1 + cos两者先各自 L2 归一化。该损失在 base_surface_model.py 中被调用,权重由mono_normal_loss_mult配置控制。
五、深度监督:从稀疏深度到单目深度
深度信息能显著改善 NeRF 的几何质量。nerfstudio 通过枚举DepthLossType(losses.py)支持三种深度损失风格:
class DepthLossType(Enum): DS_NERF = 1 URF = 2 SPARSENERF_RANKING = 3DS_NERF:Depth-supervised NeRF(Deng et al., 2022)的分布匹配损失;URF:Urban Radiance Fields(Rematas et al., 2022)的 LiDAR 损失;SPARSENERF_RANKING:SparseNeRF 的深度排序损失。
顶层入口是分发函数depth_loss(losses.py),它会先处理非欧氏深度:当is_euclidean=False时,将 ground-truth 深度乘以射线方向模长directions_norm归一化,再按类型分发。
5.1 DS_NERF:软高斯分布的负对数似然
ds_nerf_depth_loss(losses.py)假设射线终止深度附近呈高斯分布,鼓励权重集中在 GT 深度周围:
depth_mask = termination_depth > 0 loss = -torch.log(weights + EPS) * torch.exp(-((steps - termination_depth[:, None]) ** 2) / (2 * sigma)) * lengths loss = loss.sum(-2) * depth_mask return torch.mean(loss)sigma为深度不确定度(可学习),控制高斯峰的宽度;lengths(相邻采样步长)作为积分权重;- 仅对
termination_depth > 0的有效像素计算。
5.2 URF:期望深度 + 视线方向双重约束
urban_radiance_field_depth_loss(losses.py)更复杂,包含三部分:
- 期望深度损失:
(termination_depth - predicted_depth) ** 2,直接监督网络预测的期望深度; - 视线附近损失:在
[depth - sigma, depth + sigma]区间内,让权重分布匹配以 0 为中心、标准差sigma / URF_SIGMA_SCALE_FACTOR的高斯分布(其中URF_SIGMA_SCALE_FACTOR = 3.0,losses.py); - 空域损失:对
steps < termination_depth - sigma(遮挡物之前的区域)惩罚权重平方,强制权重为 0。
这三项求和后同样乘以有效像素掩码。整体体现 URF 对 LiDAR 深度"前无遮挡、后有物体"的物理建模。
5.3 SPARSENERF_RANKING:无需精确深度的排序损失
depth_ranking_loss(losses.py)是 SparseNeRF 提出的方案,只要求深度相对关系正确,因此对稀疏/粗略深度尤其鲁棒:
dpt_diff = gt_depth[::2, :] - gt_depth[1::2, :] out_diff = rendered_depth[::2, :] - rendered_depth[1::2, :] + m differing_signs = torch.sign(dpt_diff) != torch.sign(out_diff) return torch.nanmean((out_diff[differing_signs] * torch.sign(out_diff[differing_signs])))- 依赖
PairPixelSampler的成对采样布局:batch 中相邻两个样本来自彼此邻近的像素; - 若批次为奇数,先裁掉最后一个样本;
m = 1e-4是 margin,避免渲染深度恰好相等;- 只对 GT 与渲染符号不一致的样本对惩罚,且惩罚值与符号保持一致方向。
5.4 深度损失在 depth-nerfacto 中的装配
depth_nerfacto.py 展示了完整的深度监督流水线:
- 配置项
depth_loss_type: DepthLossType = DepthLossType.DS_NERF选择类型,depth_loss_mult: float = 1e-3控制权重; - 若使用伪深度(pseudodepth),模块级常量
FORCE_PSEUDODEPTH_LOSS与PSEUDODEPTH_COMPATIBLE_LOSSES(losses.py)会强制校验类型必须为SPARSENERF_RANKING(depth_nerfacto.py); DS_NERF/URF走depth_loss记入metrics_dict["depth_loss"],SPARSENERF_RANKING走depth_ranking_loss记入metrics_dict["depth_ranking"],最后统一乘depth_loss_mult进入loss_dict。
六、单目深度专用损失:MiDaS / Gradient / ScaleAndShiftInvariant
这三者专为归一化的单目深度图设计(来自 MiDaS 与 monosdf 相关工作),作为nn.Module类存在,供表面模型使用。
6.1 MiDaSMSELoss:掩码 MSE 数据项
MiDaSMSELoss 是 MiDaS 论文的数据项:逐像素 MSE 后按掩码求和,再通过masked_reduction归约。关键点:
reduction_type支持"image"或"batch",决定是"按单图归一化"还是"按整个批次归一化";- 分母为
2 * summed_mask(源码注释写着"multiply by 2 magic number?",是继承自原实现的系数); - 底层归约函数
masked_reduction位于 utils/math.py,"batch"模式直接sum / sum(mask),"image"模式则逐图除以有效像素数后再取均值,两者都处理了除零保护。
6.2 GradientLoss:多尺度梯度一致性
GradientLoss 实现论文 Equation 11 的梯度匹配项:在 disparity 空间中比较预测与 GT 的 x/y 方向差分,并用掩码乘积过滤无效邻域。scales参数控制多尺度(step = pow(2, scale)逐级降采样,默认 4 级),使锐利不连续处与 GT 对齐。
6.3 ScaleAndShiftInvariantLoss:尺度平移不变组合
ScaleAndShiftInvariantLoss 解决单目深度绝对尺度未知的问题:先用最小二乘闭式解求出把预测深度映射到 GT 的scale/shift,再在其上计算损失。其核心公式如下:
scale, shift = normalized_depth_scale_and_shift(prediction, target, mask) self.__prediction_ssi = scale.view(-1, 1, 1) * prediction + shift.view(-1, 1, 1) total = self.__data_loss(self.__prediction_ssi, target, mask) # MiDaS MSE 项 if self.__alpha > 0: total += self.__alpha * self.__regularization_loss(...) # 梯度项normalized_depth_scale_and_shift(utils/math.py)通过 2×2 线性方程组A x = b的闭式解求 scale/shift,对行列式为 0 的像素置零,避免退化;alpha(默认 0.5)平衡数据项与正则项;scales(默认 4)控制梯度正则的尺度数;- 暴露
prediction_ssi属性,便于外部读取归一化后的预测深度; - 该损失在 base_surface_model.py 中以
ScaleAndShiftInvariantLoss(alpha=0.5, scales=1)实例化,配合mono_depth_loss_mult权重使用,并在 base_surface_model.py 中对深度做了* 50 + 0.5的归一化预处理。
七、显式网格正则:TV 损失(Total Variation)
对 TensoRF 等使用显式特征网格的方法,TV 损失鼓励相邻网格体素值平滑,抑制高频噪声。实现见 losses.py:
h_tv = torch.pow((grids[:, :, 1:, :] - grids[:, :, :-1, :]), 2).sum() w_tv = torch.pow((grids[:, :, :, 1:] - grids[:, :, :, :-1]), 2).sum() return 2 * (h_tv / h_tv_count + w_tv / w_tv_count) / number_of_grids- 分别计算行方向(height)与列方向(width)相邻格差的平方和;
- 除以各自元素计数做平均,再乘以 2、除以网格数
number_of_grids得到总体均值,保证与网格分辨率、通道数无关。
使用示例:TensoRF 模型在 tensorf.py 中对密度平面与颜色平面系数分别施加tv_loss(键名为tv_reg_density、tv_reg_color)。测试用例 test_losses.py 验证了其正确性:全 1 网格 TV 为 0;棋盘格(相邻差 1)网格 TV 恰好为2 * (1 + 1) = 4.0。另外,splatfacto 中 3DGS 的球谐网格也通过bilateral_grid_tv_loss(lib_bilagrid.py)使用同类思想。
八、梯度缩放工具:scale_gradients_by_distance_squared
这不是损失,而是与损失密切配合的训练技巧,来自论文"Radiance Field Gradient Scaling for Unbiased Near-Camera Training"。实现在 losses.py:
ray_dist = (ray_samples.frustums.starts + ray_samples.frustums.ends) / 2 scaling = torch.square(ray_dist).clamp(0, 1) for key, value in field_outputs.items(): out[key], _ = cast(Tuple[Tensor, Tensor], _GradientScaler.apply(value, scaling))- 按样本距相机距离的平方(截断到 [0, 1])缩放各 field 输出的梯度,缓解近相机区域训练偏差;
- 通过自定义
torch.autograd.Function——_GradientScaler(losses.py)——实现"前向不变、仅反向乘系数":forward原样返回 value 与 scaling,backward返回output_grad * scaling; - 注意缩放仅作用于射线区间 [0, 1] 内。
该方法被广泛使用:nerfacto(nerfacto.py)、instant-ngp(instant_ngp.py)、mipnerf(mipnerf.py)、vanilla_nerf(vanilla_nerf.py)、tensorf(tensorf.py)都在 field 前向输出后立即调用,是各方法共享的通用技巧。
九、各模型损失装配速查表
| 模型 | 使用的损失(来自 losses.py) | 参考源码 |
|---|---|---|
| nerfacto | rgb(L1/MSE)、interlevel、distortion、orientation、pred_normal、梯度缩放 | nerfacto.py |
| depth-nerfacto | 上述全部 + ds_nerf/URF/ranking 深度损失 | depth_nerfacto.py |
| mipnerf / vanilla_nerf / instant-ngp | rgb + 梯度缩放 | mipnerf.py |
| tensorf | rgb + TV 正则(密度/颜色平面)+ 梯度缩放 | tensorf.py |
| neus_facto | interlevel + monosdf 法线/深度(经 base_surface_model) | neus_facto.py、base_surface_model.py |
| generfacto | interlevel、distortion、orientation(带迭代退火权重) | generfacto.py |
| semantic_nerfw | interlevel、distortion | semantic_nerfw.py |
其中 generfacto 展示了更精细的调度:orientation_loss_mult支持(start, end)二元组,通过np.interp在orientation_loss_mult_range指定的迭代区间内线性插值,实现"训练初期小权重、后期大权重"的退火策略(generfacto.py)。
十、工程实践要点
- 数值稳定性:所有涉及对数或除法的损失(ds_nerf 的
-log(weights + EPS)、lossfun_outer 的/ (w + EPS))都以EPS = 1.0e-7兜底,自行扩展损失时建议沿用; - 梯度隔离:interlevel_loss 对监督目标
.detach()、pred_normal_loss 对权重与解析法线.detach()、深度损失的 GT 侧天然无梯度——正确区分"被监督对象"与"梯度来源"是组合多损失的关键; - 权重量级差异:distortion(0.002)、orientation(0.0001)这类小权重对应大数值的分布损失,调参应先记录各分项量级(nerfacto 将 distortion 放入
get_metrics_dict正是为此); - 深度类型匹配:使用伪深度时
depth_loss_type必须是SPARSENERF_RANKING,否则 depth_nerfacto.py 会直接报错提示; - 可测试性:tv_loss 有明确的单元测试(test_losses.py),验证了"常值网格 TV=0、棋盘格 TV=4"的精确数值,可作为理解其余函数式损失数值行为的参照。
结语
nerfstudio 的损失模块是"多篇经典论文的工程化集合":MipNeRF-360 的 interlevel/distortion 解决了采样分布问题,Ref-NeRF 与 monosdf 的法线损失约束了表面朝向,DS-NeRF/URF/SparseNeRF 三类深度损失覆盖了从稠密 LiDAR 到稀疏排序的监督需求,而 TV 损失与梯度缩放则是显式网格和统一训练稳定性的基石。理解每个损失的数学动机、数值量级与梯度流向,是在 nerfacto、tensorf、neus 等不同方法间迁移调参的基础。
【免费下载链接】nerfstudioA collaboration friendly studio for NeRFs项目地址: https://gitcode.com/GitHub_Trending/ne/nerfstudio
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考