NeRF论文详细解读

论文信息

标题: NeRF: Representing Scenes as Neural Radiance Fields for View Synthesis
作者: Ben Mildenhall, Pratul P. Srinivasan, Matthew Tancik, Jonathan T. Barron, Ravi Ramamoorthi, Ren Ng
发表: ECCV 2020
论文链接: https://arxiv.org/abs/2003.08934
代码参考路径: https://github.com/yenchenlin/nerf-pytorch

注意: 本文档中的数学公式使用LaTeX格式($$...$$$...$),需要支持MathJax或KaTeX的Markdown渲染器才能正确显示。大多数现代Markdown渲染器(如GitHub、Typora、VS Code等)都支持此格式。


一、论文概述

NeRF(Neural Radiance Fields)是一种使用神经网络表示3D场景的方法,能够从稀疏的多视角图像中学习场景的连续表示,并合成任意新视角的高质量图像。

核心思想

NeRF将3D场景表示为一个连续的5D函数:

  • 输入: 3D位置坐标 (x, y, z) + 2D视角方向 (θ, φ)
  • 输出: 体素密度 σ + RGB颜色 c

通过训练一个全连接神经网络来学习这个映射关系,然后使用可微分的体素渲染(Volume Rendering)将神经辐射场渲染成2D图像。


二、训练输入输出

2.1 训练输入

数据准备
  1. 多视角图像集合

    • 输入:同一场景的多张不同视角的RGB图像
    • 典型数量:20-100张图像
    • 格式:H×W×3的RGB图像
  2. 相机参数

    • 内参(Intrinsics): 焦距f、图像中心(cx, cy)
    • 外参(Extrinsics): 每张图像对应的相机位姿(旋转矩阵R和平移向量t)
    • 通常表示为4×4的变换矩阵(camera-to-world,c2w)
  3. 场景边界

    • near: 最近采样距离
    • far: 最远采样距离
    • 用于定义体素渲染的采样范围
代码实现参考
# 代码参考: https://github.com/yenchenlin/nerf-pytorch/blob/master/run_nerf.py
# 数据加载部分 - 对于Blender数据集
images, poses, render_poses, hwf, i_split = load_blender_data(
    args.datadir, args.half_res, args.testskip
)
# images: [N, H, W, 4] (RGBA)
# poses: [N, 3, 4] (camera-to-world变换矩阵)
# hwf: [H, W, focal] (高度、宽度、焦距)
训练时的数据流
  1. 射线生成 (Ray Generation)

    # 代码参考: https://github.com/yenchenlin/nerf-pytorch/blob/master/run_nerf.py#L760-L762
    rgb, disp, acc, extras = render(H, W, K, chunk=args.chunk, 
                                    rays=batch_rays, ...)
    
    • 从相机中心出发,为每个像素生成一条射线
    • 射线方向由相机内参和外参计算得到
  2. 射线采样 (Ray Sampling)

    # 代码参考: https://github.com/yenchenlin/nerf-pytorch/blob/master/run_nerf.py#L357-L379
    t_vals = torch.linspace(0., 1., steps=N_samples)
    z_vals = near * (1.-t_vals) + far * (t_vals)
    pts = rays_o[...,None,:] + rays_d[...,None,:] * z_vals[...,:,None]
    
    • 在每条射线上均匀采样N_samples个点(默认64个)
    • 每个采样点对应一个3D坐标
  3. 网络查询

    # 代码参考: https://github.com/yenchenlin/nerf-pytorch/blob/master/run_nerf.py#L385
    raw = network_query_fn(pts, viewdirs, network_fn)
    
    • 将采样点的3D坐标和视角方向输入网络
    • 网络输出每个点的密度σ和RGB颜色

2.2 训练输出

网络输出
  1. 粗网络(Coarse Network)输出

    • rgb0: 粗网络渲染的RGB图像 [batch_size, 3]
    • disp0: 粗网络渲染的视差图 [batch_size]
    • acc0: 粗网络渲染的累积不透明度 [batch_size]
  2. 精细网络(Fine Network)输出

    • rgb: 精细网络渲染的RGB图像 [batch_size, 3]
    • disp: 精细网络渲染的视差图 [batch_size]
    • acc: 精细网络渲染的累积不透明度 [batch_size]
代码实现
# 代码参考: https://github.com/yenchenlin/nerf-pytorch/blob/master/run_nerf.py#L262-L305
def raw2outputs(raw, z_vals, rays_d, raw_noise_std=0, white_bkgd=False):
    """
    raw: [num_rays, num_samples, 4] - 网络原始输出
         - raw[...,:3]: RGB颜色(经过sigmoid)
         - raw[...,3]: 体素密度σ
    """
    rgb = torch.sigmoid(raw[...,:3])  # [N_rays, N_samples, 3]
    alpha = raw2alpha(raw[...,3] + noise, dists)  # [N_rays, N_samples]
    weights = alpha * cumprod(1.-alpha + 1e-10)  # [N_rays, N_samples]
    rgb_map = torch.sum(weights[...,None] * rgb, -2)  # [N_rays, 3]
    return rgb_map, disp_map, acc_map, weights, depth_map

三、推理输入输出

3.1 推理输入

  1. 新视角的相机参数

    • 相机内参(K矩阵)
    • 相机外参(c2w变换矩阵)
    • 图像尺寸(H, W)
  2. 训练好的NeRF模型

    • 粗网络权重
    • 精细网络权重(如果使用)

3.2 推理输出

  1. 渲染图像
    • RGB图像:新视角的合成图像 [H, W, 3]
    • 视差图:深度信息的可视化 [H, W]
    • 累积不透明度图:透明度信息 [H, W]
代码实现
# 代码参考: https://github.com/yenchenlin/nerf-pytorch/blob/master/run_nerf.py#L137-L175
def render_path(render_poses, hwf, K, chunk, render_kwargs, ...):
    """
    渲染一系列相机位姿对应的图像
    render_poses: [N, 3, 4] - 要渲染的相机位姿序列
    """
    rgbs = []
    for i, c2w in enumerate(render_poses):
        rgb, disp, acc, _ = render(H, W, K, chunk=chunk, 
                                   c2w=c2w[:3,:4], **render_kwargs)
        rgbs.append(rgb.cpu().numpy())
    return np.stack(rgbs, 0)  # [N, H, W, 3]

四、核心点详解

4.1 Volume Rendering(体素渲染原理)

理论基础

体素渲染是NeRF的核心,它将3D空间中的连续辐射场转换为2D图像。基本原理是沿着相机射线积分,累加每个采样点的颜色贡献。

数学公式

连续体素渲染方程

C(r)=∫tntfT(t)σ(r(t))c(r(t),d)dt C(\mathbf{r}) = \int_{t_n}^{t_f} T(t) \sigma(\mathbf{r}(t)) \mathbf{c}(\mathbf{r}(t), \mathbf{d}) dt C(r)=tntfT(t)σ(r(t))c(r(t),d)dt

其中:

  • C(r)C(\mathbf{r})C(r): 射线r的最终颜色
  • T(t)=exp⁡(−∫tntσ(r(s))ds)T(t) = \exp(-\int_{t_n}^{t} \sigma(\mathbf{r}(s)) ds)T(t)=exp(tntσ(r(s))ds): 累积透射率(transmittance)
  • σ(r(t))\sigma(\mathbf{r}(t))σ(r(t)): 位置r(t)\mathbf{r}(t)r(t)处的体素密度
  • c(r(t),d)\mathbf{c}(\mathbf{r}(t), \mathbf{d})c(r(t),d): 位置r(t)\mathbf{r}(t)r(t)、视角方向d\mathbf{d}d的颜色

离散化实现

C^(r)=∑i=1NTi(1−exp⁡(−σiδi))ci \hat{C}(\mathbf{r}) = \sum_{i=1}^{N} T_i (1 - \exp(-\sigma_i \delta_i)) \mathbf{c}_i C^(r)=i=1NTi(1exp(σiδi))ci

其中各符号定义如下:

Ti=exp⁡(−∑j=1i−1σjδj) T_i = \exp\left(-\sum_{j=1}^{i-1} \sigma_j \delta_j\right) Ti=exp(j=1i1σjδj)

  • TiT_iTi: 到第i个采样点的累积透射率

δi=ti+1−ti \delta_i = t_{i+1} - t_i δi=ti+1ti

  • δi\delta_iδi: 相邻采样点之间的距离

αi=1−exp⁡(−σiδi) \alpha_i = 1 - \exp(-\sigma_i \delta_i) αi=1exp(σiδi)

  • αi\alpha_iαi: 第i个采样点的不透明度
代码实现
# 代码参考: https://github.com/yenchenlin/nerf-pytorch/blob/master/run_nerf.py#L262-L305
def raw2outputs(raw, z_vals, rays_d, raw_noise_std=0, white_bkgd=False):
    # 计算采样点之间的距离
    dists = z_vals[...,1:] - z_vals[...,:-1]
    dists = torch.cat([dists, torch.Tensor([1e10]).expand(dists[...,:1].shape)], -1)
    dists = dists * torch.norm(rays_d[...,None,:], dim=-1)  # 转换为实际距离
    
    # RGB颜色(经过sigmoid激活)
    rgb = torch.sigmoid(raw[...,:3])  # [N_rays, N_samples, 3]
    
    # 计算不透明度 alpha = 1 - exp(-σ * δ)
    raw2alpha = lambda raw, dists, act_fn=F.relu: 1.-torch.exp(-act_fn(raw)*dists)
    alpha = raw2alpha(raw[...,3] + noise, dists)  # [N_rays, N_samples]
    
    # 计算权重 weights = alpha * cumprod(1-alpha)
    weights = alpha * torch.cumprod(
        torch.cat([torch.ones((alpha.shape[0], 1)), 1.-alpha + 1e-10], -1), 
        -1
    )[:, :-1]  # [N_rays, N_samples]
    
    # 加权求和得到最终颜色
    rgb_map = torch.sum(weights[...,None] * rgb, -2)  # [N_rays, 3]
    
    # 计算深度和视差
    depth_map = torch.sum(weights * z_vals, -1)
    disp_map = 1./torch.max(1e-10 * torch.ones_like(depth_map), 
                           depth_map / torch.sum(weights, -1))
    acc_map = torch.sum(weights, -1)
    
    # 白色背景处理
    if white_bkgd:
        rgb_map = rgb_map + (1.-acc_map[...,None])
    
    return rgb_map, disp_map, acc_map, weights, depth_map
关键理解点
  1. 累积透射率T(t): 表示从射线起点到位置t的光线未被遮挡的概率
  2. 不透明度α: 每个采样点对最终颜色的贡献程度
  3. 权重weights: 综合考虑不透明度和累积透射率,决定每个采样点的颜色贡献
  4. 可微性: 整个过程是可微分的,支持端到端训练

4.2 Positional Encoding(位置编码的作用)

为什么需要位置编码?

神经网络(特别是ReLU激活的MLP)倾向于学习低频函数,难以表示高频细节(如纹理、几何细节)。位置编码通过将输入映射到高维空间,帮助网络学习高频信息。

数学原理

位置编码公式

γ(p)=(sin⁡(20πp),cos⁡(20πp),sin⁡(21πp),cos⁡(21πp),...,sin⁡(2L−1πp),cos⁡(2L−1πp)) \gamma(\mathbf{p}) = (\sin(2^0 \pi \mathbf{p}), \cos(2^0 \pi \mathbf{p}), \sin(2^1 \pi \mathbf{p}), \cos(2^1 \pi \mathbf{p}), ..., \sin(2^{L-1} \pi \mathbf{p}), \cos(2^{L-1} \pi \mathbf{p})) γ(p)=(sin(20πp),cos(20πp),sin(21πp),cos(21πp),...,sin(2L1πp),cos(2L1πp))

其中:

  • p\mathbf{p}p: 输入坐标(3D位置或2D视角方向)
  • LLL: 编码频率的数量
  • 对于3D位置:L=10L=10L=10(multires=10)
  • 对于2D视角方向:L=4L=4L=4(multires_views=4)
代码实现
# 代码参考: https://github.com/yenchenlin/nerf-pytorch/blob/master/run_nerf_helpers.py#L14-L63
class Embedder:
    def __init__(self, **kwargs):
        self.kwargs = kwargs
        self.create_embedding_fn()
        
    def create_embedding_fn(self):
        embed_fns = []
        d = self.kwargs['input_dims']  # 输入维度(3D位置为3,2D方向为2)
        out_dim = 0
        
        # 包含原始输入
        if self.kwargs['include_input']:
            embed_fns.append(lambda x : x)
            out_dim += d
            
        max_freq = self.kwargs['max_freq_log2']  # L-1
        N_freqs = self.kwargs['num_freqs']  # L
        
        # 生成频率序列 [2^0, 2^1, ..., 2^(L-1)]
        if self.kwargs['log_sampling']:
            freq_bands = 2.**torch.linspace(0., max_freq, steps=N_freqs)
        else:
            freq_bands = torch.linspace(2.**0., 2.**max_freq, steps=N_freqs)
            
        # 对每个频率,应用sin和cos
        for freq in freq_bands:
            for p_fn in self.kwargs['periodic_fns']:  # [sin, cos]
                embed_fns.append(lambda x, p_fn=p_fn, freq=freq : p_fn(x * freq))
                out_dim += d
                
        self.embed_fns = embed_fns
        self.out_dim = out_dim
        
    def embed(self, inputs):
        return torch.cat([fn(inputs) for fn in self.embed_fns], -1)

# 获取编码器
def get_embedder(multires, i=0):
    if i == -1:
        return nn.Identity(), 3  # 不使用位置编码
    
    embed_kwargs = {
        'include_input' : True,
        'input_dims' : 3,
        'max_freq_log2' : multires-1,  # 对于multires=10,max_freq_log2=9
        'num_freqs' : multires,  # 10个频率
        'log_sampling' : True,
        'periodic_fns' : [torch.sin, torch.cos],
    }
    
    embedder_obj = Embedder(**embed_kwargs)
    embed = lambda x, eo=embedder_obj : eo.embed(x)
    return embed, embedder_obj.out_dim
编码维度计算

对于3D位置(multires=10):

  • 原始输入:3维
  • 每个频率:sin + cos = 2 × 3 = 6维
  • 10个频率:10 × 6 = 60维
  • 总维度:3 + 60 = 63维

对于2D视角方向(multires_views=4):

  • 原始输入:3维(归一化的方向向量)
  • 每个频率:sin + cos = 2 × 3 = 6维
  • 4个频率:4 × 6 = 24维
  • 总维度:3 + 24 = 27维
作用机制
  1. 多尺度表示: 不同频率捕获不同尺度的细节

    • 低频(2^0, 2^1): 捕获全局结构
    • 高频(2^8, 2^9): 捕获精细纹理
  2. 频率混叠: 帮助网络区分相近的空间位置

  3. 实验验证: 论文中ablation study显示,不使用位置编码会导致结果模糊,细节丢失


4.3 View Synthesis(新视角合成)

流程概述

新视角合成是NeRF的最终目标:给定训练时未见过的相机位姿,生成该视角下的图像。

完整流程
  1. 射线生成

    # 代码参考: https://github.com/yenchenlin/nerf-pytorch/blob/master/run_nerf_helpers.py#L153-L162
    def get_rays(H, W, K, c2w):
        # 生成像素网格坐标
        i, j = torch.meshgrid(torch.linspace(0, W-1, W), 
                             torch.linspace(0, H-1, H))
        i = i.t()
        j = j.t()
        
        # 计算射线方向(相机坐标系)
        dirs = torch.stack([(i-K[0][2])/K[0][0], 
                           -(j-K[1][2])/K[1][1], 
                           -torch.ones_like(i)], -1)
        
        # 转换到世界坐标系
        rays_d = torch.sum(dirs[..., np.newaxis, :] * c2w[:3,:3], -1)
        rays_o = c2w[:3,-1].expand(rays_d.shape)
        return rays_o, rays_d
    
  2. 分层采样(Hierarchical Volume Sampling)

    NeRF使用两阶段采样策略:

    阶段1:粗采样(Coarse Sampling)

    • 在射线上均匀采样N_samples个点(默认64个)
    • 通过粗网络预测每个点的密度和颜色
    • 计算权重分布

    阶段2:精细采样(Fine Sampling)

    • 根据粗网络的权重分布,使用逆变换采样(inverse transform sampling)
    • 在重要区域(高权重区域)额外采样N_importance个点(默认128个)
    • 合并粗采样和精细采样的点
    • 通过精细网络重新预测
    # 代码参考: https://github.com/yenchenlin/nerf-pytorch/blob/master/run_nerf.py#L388-L403
    if N_importance > 0:
        # 计算粗网络的权重分布
        z_vals_mid = .5 * (z_vals[...,1:] + z_vals[...,:-1])
        
        # 基于权重进行逆变换采样
        z_samples = sample_pdf(z_vals_mid, weights[...,1:-1], 
                               N_importance, det=(perturb==0.))
        
        # 合并采样点
        z_vals, _ = torch.sort(torch.cat([z_vals, z_samples], -1), -1)
        pts = rays_o[...,None,:] + rays_d[...,None,:] * z_vals[...,:,None]
        
        # 精细网络预测
        raw = network_query_fn(pts, viewdirs, network_fine)
        rgb_map, disp_map, acc_map, weights, depth_map = raw2outputs(
            raw, z_vals, rays_d, raw_noise_std, white_bkgd
        )
    
  3. 体素渲染

    • 使用raw2outputs函数将采样点的预测转换为最终图像
关键优势
  1. 视角一致性: 通过输入视角方向,网络能够学习视角相关的效果(如镜面反射)
  2. 细节保持: 分层采样确保在重要区域有足够的采样密度
  3. 连续表示: 可以合成任意视角,不受训练视角限制

4.4 Loss设计

Loss函数

NeRF使用简单的L2损失(MSE)来监督渲染图像与真实图像的差异。

数学公式

L=MSE(C^c,C)+MSE(C^f,C) \mathcal{L} = \text{MSE}(\hat{C}_c, C) + \text{MSE}(\hat{C}_f, C) L=MSE(C^c,C)+MSE(C^f,C)

其中:

  • C^c\hat{C}_cC^c: 粗网络渲染的颜色 [batch_size, 3]
  • C^f\hat{C}_fC^f: 精细网络渲染的颜色 [batch_size, 3]
  • CCC: 真实颜色 [batch_size, 3]
  • MSE(A,B)=1N∑i=1N∥Ai−Bi∥22\text{MSE}(A, B) = \frac{1}{N} \sum_{i=1}^{N} \|A_i - B_i\|_2^2MSE(A,B)=N1i=1NAiBi22: 均方误差

展开形式:

L=1∣R∣∑r∈R∥C^c(r)−C(r)∥22+1∣R∣∑r∈R∥C^f(r)−C(r)∥22 \mathcal{L} = \frac{1}{|\mathcal{R}|} \sum_{\mathbf{r} \in \mathcal{R}} \|\hat{C}_c(\mathbf{r}) - C(\mathbf{r})\|_2^2 + \frac{1}{|\mathcal{R}|} \sum_{\mathbf{r} \in \mathcal{R}} \|\hat{C}_f(\mathbf{r}) - C(\mathbf{r})\|_2^2 L=R1rRC^c(r)C(r)22+R1rRC^f(r)C(r)22

其中 R\mathcal{R}R 表示一个batch中的射线集合。

代码实现
# 代码参考: https://github.com/yenchenlin/nerf-pytorch/blob/master/run_nerf.py#L764-L775
# 计算损失
img_loss = img2mse(rgb, target_s)  # 精细网络损失
loss = img_loss
psnr = mse2psnr(img_loss)

# 如果有粗网络,也计算粗网络损失
if 'rgb0' in extras:
    img_loss0 = img2mse(extras['rgb0'], target_s)  # 粗网络损失
    loss = loss + img_loss0  # 总损失 = 粗网络损失 + 精细网络损失
    psnr0 = mse2psnr(img_loss0)

loss.backward()
optimizer.step()
辅助函数
# 代码参考: https://github.com/yenchenlin/nerf-pytorch/blob/master/run_nerf_helpers.py#L9-L11
img2mse = lambda x, y : torch.mean((x - y) ** 2)  # MSE损失
mse2psnr = lambda x : -10. * torch.log(x) / torch.log(torch.Tensor([10.]))  # PSNR指标
Loss设计特点
  1. 简单有效: 仅使用MSE损失,无需复杂的正则化项
  2. 双网络监督: 同时监督粗网络和精细网络,确保两阶段采样都有效
  3. 像素级监督: 对每个像素的RGB值进行监督
训练策略
  1. 随机射线采样: 每个iteration随机采样一批射线(默认32×32×4=4096条)
  2. 学习率衰减: 使用指数衰减
    # 代码参考: https://github.com/yenchenlin/nerf-pytorch/blob/master/run_nerf.py#L779-L784
    decay_rate = 0.1
    decay_steps = args.lrate_decay * 1000
    new_lrate = args.lrate * (decay_rate ** (global_step / decay_steps))
    
  3. 训练迭代: 通常训练200k iterations

五、网络架构

5.1 NeRF网络结构

NeRF使用一个简单的全连接MLP网络:

# 代码参考: https://github.com/yenchenlin/nerf-pytorch/blob/master/run_nerf_helpers.py#L67-L119
class NeRF(nn.Module):
    def __init__(self, D=8, W=256, input_ch=63, input_ch_views=27, 
                 output_ch=4, skips=[4], use_viewdirs=True):
        """
        D: 网络深度(层数),默认8层
        W: 网络宽度(每层神经元数),默认256
        input_ch: 位置编码后的维度(3D位置:63维)
        input_ch_views: 视角编码后的维度(2D方向:27维)
        skips: 跳跃连接的位置(第4层)
        use_viewdirs: 是否使用视角方向
        """
        super(NeRF, self).__init__()
        
        # 位置编码部分的网络(处理3D坐标)
        self.pts_linears = nn.ModuleList(
            [nn.Linear(input_ch, W)] + 
            [nn.Linear(W, W) if i not in self.skips 
             else nn.Linear(W + input_ch, W)  # 跳跃连接
             for i in range(D-1)]
        )
        
        # 视角方向部分的网络(处理视角方向)
        if use_viewdirs:
            self.views_linears = nn.ModuleList(
                [nn.Linear(input_ch_views + W, W//2)]
            )
            self.feature_linear = nn.Linear(W, W)
            self.alpha_linear = nn.Linear(W, 1)  # 输出密度
            self.rgb_linear = nn.Linear(W//2, 3)  # 输出RGB
        else:
            self.output_linear = nn.Linear(W, output_ch)
    
    def forward(self, x):
        input_pts, input_views = torch.split(x, 
                                            [self.input_ch, self.input_ch_views], 
                                            dim=-1)
        h = input_pts
        
        # 位置编码部分的前向传播
        for i, l in enumerate(self.pts_linears):
            h = self.pts_linears[i](h)
            h = F.relu(h)
            if i in self.skips:  # 跳跃连接
                h = torch.cat([input_pts, h], -1)
        
        if self.use_viewdirs:
            # 密度预测(仅依赖位置)
            alpha = self.alpha_linear(h)
            feature = self.feature_linear(h)
            
            # RGB预测(依赖位置和视角)
            h = torch.cat([feature, input_views], -1)
            for i, l in enumerate(self.views_linears):
                h = self.views_linears[i](h)
                h = F.relu(h)
            rgb = self.rgb_linear(h)
            outputs = torch.cat([rgb, alpha], -1)
        else:
            outputs = self.output_linear(h)
        
        return outputs  # [..., 4] (RGB + density)

5.2 网络特点

  1. 分离设计:

    • 密度σ仅依赖3D位置
    • RGB颜色依赖3D位置和视角方向
  2. 跳跃连接: 在第4层加入跳跃连接,帮助网络学习高频细节

  3. 轻量级: 网络参数量约5MB,适合单个场景的表示


六、训练流程总结

6.1 完整训练流程

1. 数据加载
   ├─ 加载多视角图像
   ├─ 加载相机参数(内参、外参)
   └─ 划分训练集/验证集/测试集

2. 模型初始化
   ├─ 创建粗网络(Coarse Network)
   ├─ 创建精细网络(Fine Network,可选)
   ├─ 初始化位置编码器
   └─ 创建优化器(Adam)

3. 训练循环(200k iterations)
   ├─ 随机采样一批射线(batch_rays)
   ├─ 对每条射线:
   │   ├─ 均匀采样N_samples个点(粗采样)
   │   ├─ 位置编码 + 视角编码
   │   ├─ 粗网络预测 → 计算权重分布
   │   ├─ 基于权重精细采样N_importance个点
   │   └─ 精细网络预测
   ├─ 体素渲染得到RGB图像
   ├─ 计算MSE损失(粗网络 + 精细网络)
   ├─ 反向传播
   └─ 更新学习率

4. 模型保存
   └─ 定期保存checkpoint

6.2 关键超参数

参数 默认值 说明
N_samples 64 粗采样点数
N_importance 128 精细采样点数
N_rand 4096 每个batch的射线数
multires 10 3D位置编码频率数
multires_views 4 视角方向编码频率数
netdepth 8 网络深度
netwidth 256 网络宽度
lrate 5e-4 初始学习率
lrate_decay 250 学习率衰减步数(×1000)

七、代码文件结构

7.1 主要文件

nerf-pytorch/  # GitHub: https://github.com/yenchenlin/nerf-pytorch
├── run_nerf.py              # 主训练脚本
├── run_nerf_helpers.py       # 辅助函数(网络、编码、渲染等)
├── load_blender.py          # Blender数据集加载
├── load_llff.py             # LLFF数据集加载
├── load_deepvoxels.py       # DeepVoxels数据集加载
├── load_LINEMOD.py          # LINEMOD数据集加载
├── configs/                 # 配置文件目录
│   ├── lego.txt
│   ├── fern.txt
│   └── ...
└── data/                     # 数据目录
    ├── nerf_synthetic/      # 合成数据集
    └── nerf_llff_data/       # 真实场景数据集

7.2 关键函数位置

功能 文件 行数
位置编码 run_nerf_helpers.py 14-63
NeRF网络 run_nerf_helpers.py 67-119
体素渲染 run_nerf.py 262-305
射线渲染 run_nerf.py 308-418
训练循环 run_nerf.py 534-872
损失计算 run_nerf.py 765-775
分层采样 run_nerf_helpers.py 196-239

八、实验与结果

8.1 数据集

  1. Synthetic NeRF Dataset

    • 8个合成场景(lego, chair, drums等)
    • 100张训练图像,200张测试图像
    • 白色背景
  2. Real Forward-Facing Scenes (LLFF)

    • 8个真实场景(fern, flower, horns等)
    • 20-62张训练图像
    • 360度视角

8.2 评估指标

  • PSNR (Peak Signal-to-Noise Ratio): 图像质量指标
  • SSIM (Structural Similarity Index): 结构相似性
  • LPIPS (Learned Perceptual Image Patch Similarity): 感知相似性

8.3 主要结果

  • 在合成数据集上,PSNR达到32+ dB
  • 能够合成高质量的新视角图像
  • 细节保持良好,视角一致性高

九、总结

9.1 NeRF的核心贡献

  1. 连续表示: 使用神经网络表示连续的3D场景
  2. 可微渲染: 体素渲染过程完全可微,支持端到端训练
  3. 视角依赖: 通过输入视角方向,学习视角相关的效果
  4. 高质量合成: 能够从稀疏输入生成高质量新视角

9.2 关键技术点

  1. 位置编码: 帮助网络学习高频细节
  2. 分层采样: 提高采样效率,关注重要区域
  3. 体素渲染: 将3D表示转换为2D图像的核心方法
  4. 简单损失: 仅使用MSE损失即可达到优秀效果

9.3 局限性

  1. 训练时间长: 单个场景需要数小时训练
  2. 推理速度慢: 渲染一张图像需要数秒
  3. 静态场景: 原始NeRF只能处理静态场景
  4. 需要精确位姿: 对相机参数精度要求高

9.4 后续改进方向

  • Instant-NGP: 使用哈希编码加速训练
  • Mip-NeRF: 解决混叠问题
  • D-NeRF: 处理动态场景
  • NeRF in the Wild: 处理无约束照片集合

十、参考文献

  1. Mildenhall, B., et al. “NeRF: Representing Scenes as Neural Radiance Fields for View Synthesis.” ECCV 2020.
  2. 代码仓库: https://github.com/yenchenlin/nerf-pytorch
  3. 官方实现: https://github.com/bmild/nerf

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐