经典的NeRF(Neural Radiance Fields)在固定光照、干净采集的照片集上表现惊艳,但一旦把输入换成游客在景点随手拍摄的照片,问题就来了:同一段楼梯在有的照片里沐浴阳光,有的照片里阴云密布;拍摄时总有路人从镜头前经过,遮挡了建筑物本身。如果直接用原始NeRF去拟合这些数据,训练出的场景往往带有半透明的鬼影人物和浑浊的色彩。NeRF-W(NeRF in the Wild)正是为解决这类问题而生,它在保留NeRF核心框架的同时,引入了外观嵌入与瞬态分量两个关键设计,让神经辐射场能够驾驭“野外”这种不受控的采集条件。

一、为什么原始NeRF在野外数据上会失败
要理解NeRF-W的价值,先要弄清原始NeRF的假设。NeRF用一个多层感知机(MLP)表示场景,输入空间坐标和观察方向,输出体密度和视角相关的颜色,通过体渲染公式将沿光线采样点的预测值积分为最终像素颜色。这个表示隐含了一个重要前提:场景是静态的、外观是恒定的。也就是说,同一条光线无论来自哪张训练图片,它对应的场景内容都应当一致。
野外数据同时打破了两条前提。第一是外观变化:不同照片在不同时间、不同天气、不同相机参数下拍摄,白平衡、曝光、色调差异巨大,同一个物理点在不同照片中的像素值可能相差很远。第二是瞬态干扰:行人、车辆、临时搭建物只出现在部分照片中,属于“时有时无”的内容。NeRF的静态假设会让它试图用一个统一的密度场去解释所有照片,结果就是路人被摊薄成低密度的半透明鬼影,混合光照让整体颜色变得浑浊发灰。此外,摄影采集中的配准误差与遮挡差异也会引入噪声,这些噪声如果不加区分地参与损失计算,会严重拖累重建质量。
二、NeRF-W的核心设计:外观嵌入与瞬态分量
NeRF-W对NeRF的改进可以概括为一句话:静态部分大家共享,变化部分各自建模。具体来说,它把网络拆成三个协同工作的部分:静态NeRF分支、每张图独立的外观嵌入,以及瞬态密度颜色场。
首先看外观处理。NeRF-W为每张训练图片分配一个可学习的低维向量,称为外观嵌入,可以类比深度学习中的身份向量。这个嵌入与MLP输出的特征拼接后,经过一个额外的小型颜色头,映射为最终的RGB值。由于每张照片有自己独立的嵌入,网络可以针对每张图调整全局色调、曝光和白平衡,静态几何仍然由共享的场景网络负责。这相当于把光度不一致从三维重建问题中剥离出来,变成一个可优化的每图参数,效果远好于事后做颜色校正。
其次是瞬态分量。NeRF-W额外引入一个独立的瞬态网络,同样接收空间坐标,输出瞬态密度和瞬态颜色,且不依赖观察方向。这个分支专门负责解释那些只在个别照片中出现的内容,比如路过的行人。关键在于分配机制:如何决定一个物体该由静态场解释还是瞬态场解释?NeRF-W的答案是让模型自己学会分配——在训练初期两者都可以拟合,但静态场必须被所有照片共享,只有内容在绝大多数视角下都出现时,用静态场解释才是“划算”的;反之,只在少数照片中出现的内容,用每图独立的瞬态场解释代价更小。这种隐式的竞争机制最终会收敛到合理的分配结果。
不确定性建模:让损失函数更宽容
NeRF-W还借鉴了主动NeRF的做法,为每个像素预测不确定性。这个不确定性以信息论中的散度形式融入损失函数:对于不确定性高的像素(如被遮挡区域、配准误差大的区域),模型会自动降低其损失权重。这在实践中非常重要——照片集中总有几张存在严重的自遮挡或对齐误差,如果没有不确定性机制,这些像素会把模型带偏。
把三个部分组合起来,NeRF-W的损失函数包含静态项、瞬态项和不确定性正则项,可以写成如下形式:
import torch
import torch.nn.functional as F
def nerfw_loss(rgb_static, rgb_transient, sigma_beta, target, coeff=0.01):
"""
rgb_static: 静态分支预测的RGB
rgb_transient: 瞬态分支预测的RGB
sigma_beta: 预测的不确定性(对数空间)
target: 真实像素值
"""
beta = torch.exp(sigma_beta) # 转为正值的不确定性
rgb_total = rgb_static + rgb_transient
# 带不确定性的加权回归损失
loss = torch.mean((beta**2) * torch.abs(rgb_total - target))
# 不确定性正则项,防止beta无限增大来逃避拟合
loss += coeff * torch.mean(beta)
return loss上面代码中值得注意的细节是正则项的存在。如果没有对beta的惩罚,模型会陷入一个退化解:把所有像素的不确定性都推得很大,损失趋近于零却什么也没学到。系数coeff控制模型对“困难像素”的容忍程度,实践上通常取一个较小的值即可。
三、网络结构与训练流程要点
NeRF-W的场景网络结构与原始NeRF基本一致:位置编码、若干全连接层、skip connection,视角相关的静态颜色通过特征拼接得到。区别在于静态颜色头输出的是一个特征向量而非直接RGB,该特征再与外观嵌入拼接,经过一个独立的颜色映射网络得到最终颜色。瞬态网络则更小巧,输入只有位置编码,输出瞬态密度、瞬态颜色和瞬态不确定性。
训练流程上有几点经验值得注意。第一,外观嵌入的维度不必太大,通常几十维就够表达全局色调差异,维度过高会让每张图学出各自的几何从而破坏共享性。第二,瞬态分支通常需要在训练若干迭代后才逐渐发挥作用,初期主要靠静态场拟合,随着不一致内容暴露出来,瞬态场才接管这些区域。第三,在渲染新视角时,外观嵌入需要显式指定:可以固定为某张参考图片的嵌入,也可以对不同嵌入做插值,从而控制渲染结果的“天气与时刻”,这是NeRF-W提供的一个有趣能力——同一个几何可以在晴朗和阴天的外观之间平滑过渡。
以下是简化版NeRF-W模型的前向传播示意,帮助理解数据流:
class NeRFW(torch.nn.Module):
def __init__(self, num_images, appearance_dim=48):
super().__init__()
# 每张图片一个可学习的外观嵌入
self.appearance_embed = torch.nn.Embedding(num_images, appearance_dim)
self.static_mlp = StaticMLP(out_dim=32) # 输出特征而非RGB
self.transient_mlp = TransientMLP() # 输出瞬态密度/颜色/不确定性
# 外观映射网络:特征+嵌入 -> RGB
self.color_head = ColorHead(in_dim=32 + appearance_dim)
def forward(self, xyz, dirs, image_idx):
feat, sigma = self.static_mlp(xyz, dirs)
a_embed = self.appearance_embed(image_idx)
rgb_static = self.color_head(torch.cat([feat, a_embed], dim=-1))
t_sigma, t_rgb, t_beta = self.transient_mlp(xyz)
return rgb_static, sigma, t_rgb, t_sigma, t_beta体渲染阶段需要分别沿光线积分静态分量和瞬态分量。渲染新视角时瞬态分支直接跳过——我们只希望看到干净的建筑本身,这正是NeRF-W能够在渲染结果中“擦除”路人的原因。
四、效果对比与实践建议
在著名的Brandenburg Gate等基准数据集上,NeRF-W相比原始NeRF的提升非常直观:原始NeRF的渲染结果中,被人群遮挡的区域呈现模糊的鬼影,整体色调偏灰;NeRF-W则能干净地重建出建筑本体,人物几乎完全消失,色彩也与参考照片一致。定量指标上,PSNR通常有数个dB的提升,且提升主要来自那些遮挡严重的困难视角。
如果你打算在自己的照片集上实践,有几点建议。数据方面,尽量使用高质量的相机位姿估计工具(如COLMAP)获取位姿和畸变参数,位姿误差对野外数据的影响比实验室数据更致命。超参数方面,外观嵌入维度、瞬态网络的容量、不确定性正则系数都值得做小规模网格搜索。算力方面,NeRF-W的参数量约为原始NeRF的两倍左右,训练时间也相应增长,一张主流消费级GPU训练单场景通常需要一整天量级,耐心是必要的。
NeRF-W的思想也深刻影响了后续工作。Ha-NeRF、NeRF in the Shadow等后续研究进一步处理了更极端的光照变化与阴影问题;基于哈希网格的Instant-NGP与NeRF-W的结合大幅缩短了训练时间;而以3D Gaussian Splatting为代表的新一代表示也开始借鉴每图外观嵌入的设计。可以说,“共享静态、独立外观、瞬态剥离”这三板斧已经成为处理不受控采集数据的通用范式,即使你不直接使用NeRF-W,理解它的设计思路对做任何真实数据上的神经渲染工作都大有帮助。
NeRF-WNeural Radiance Fields三维重建修改时间:2026-09-16 05:10:47