【大模型】 NeRF论文详细解读
NeRF(Neural Radiance Fields)是一种使用神经网络表示3D场景的方法,能够从稀疏的多视角图像中学习场景的连续表示,并合成任意新视角的高质量图像。新视角合成是NeRF的最终目标:给定训练时未见过的相机位姿,生成该视角下的图像。1. 数据加载├─ 加载多视角图像├─ 加载相机参数(内参、外参)└─ 划分训练集/验证集/测试集2. 模型初始化├─ 创建粗网络(Coarse Net
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 训练输入
数据准备
-
多视角图像集合
- 输入:同一场景的多张不同视角的RGB图像
- 典型数量:20-100张图像
- 格式:H×W×3的RGB图像
-
相机参数
- 内参(Intrinsics): 焦距f、图像中心(cx, cy)
- 外参(Extrinsics): 每张图像对应的相机位姿(旋转矩阵R和平移向量t)
- 通常表示为4×4的变换矩阵(camera-to-world,c2w)
-
场景边界
- 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] (高度、宽度、焦距)
训练时的数据流
-
射线生成 (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, ...)- 从相机中心出发,为每个像素生成一条射线
- 射线方向由相机内参和外参计算得到
-
射线采样 (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坐标
-
网络查询
# 代码参考: https://github.com/yenchenlin/nerf-pytorch/blob/master/run_nerf.py#L385 raw = network_query_fn(pts, viewdirs, network_fn)- 将采样点的3D坐标和视角方向输入网络
- 网络输出每个点的密度σ和RGB颜色
2.2 训练输出
网络输出
-
粗网络(Coarse Network)输出
- rgb0: 粗网络渲染的RGB图像 [batch_size, 3]
- disp0: 粗网络渲染的视差图 [batch_size]
- acc0: 粗网络渲染的累积不透明度 [batch_size]
-
精细网络(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 推理输入
-
新视角的相机参数
- 相机内参(K矩阵)
- 相机外参(c2w变换矩阵)
- 图像尺寸(H, W)
-
训练好的NeRF模型
- 粗网络权重
- 精细网络权重(如果使用)
3.2 推理输出
- 渲染图像
- 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=1∑NTi(1−exp(−σ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=1∑i−1σjδj)
- TiT_iTi: 到第i个采样点的累积透射率
δi=ti+1−ti \delta_i = t_{i+1} - t_i δi=ti+1−ti
- δi\delta_iδi: 相邻采样点之间的距离
αi=1−exp(−σiδi) \alpha_i = 1 - \exp(-\sigma_i \delta_i) αi=1−exp(−σ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
关键理解点
- 累积透射率T(t): 表示从射线起点到位置t的光线未被遮挡的概率
- 不透明度α: 每个采样点对最终颜色的贡献程度
- 权重weights: 综合考虑不透明度和累积透射率,决定每个采样点的颜色贡献
- 可微性: 整个过程是可微分的,支持端到端训练
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(2L−1πp),cos(2L−1π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维
作用机制
-
多尺度表示: 不同频率捕获不同尺度的细节
- 低频(2^0, 2^1): 捕获全局结构
- 高频(2^8, 2^9): 捕获精细纹理
-
频率混叠: 帮助网络区分相近的空间位置
-
实验验证: 论文中ablation study显示,不使用位置编码会导致结果模糊,细节丢失
4.3 View Synthesis(新视角合成)
流程概述
新视角合成是NeRF的最终目标:给定训练时未见过的相机位姿,生成该视角下的图像。
完整流程
-
射线生成
# 代码参考: 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 -
分层采样(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 ) -
体素渲染
- 使用raw2outputs函数将采样点的预测转换为最终图像
关键优势
- 视角一致性: 通过输入视角方向,网络能够学习视角相关的效果(如镜面反射)
- 细节保持: 分层采样确保在重要区域有足够的采样密度
- 连续表示: 可以合成任意视角,不受训练视角限制
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)=N1∑i=1N∥Ai−Bi∥22: 均方误差
展开形式:
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=∣R∣1r∈R∑∥C^c(r)−C(r)∥22+∣R∣1r∈R∑∥C^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设计特点
- 简单有效: 仅使用MSE损失,无需复杂的正则化项
- 双网络监督: 同时监督粗网络和精细网络,确保两阶段采样都有效
- 像素级监督: 对每个像素的RGB值进行监督
训练策略
- 随机射线采样: 每个iteration随机采样一批射线(默认32×32×4=4096条)
- 学习率衰减: 使用指数衰减
# 代码参考: 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)) - 训练迭代: 通常训练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 网络特点
-
分离设计:
- 密度σ仅依赖3D位置
- RGB颜色依赖3D位置和视角方向
-
跳跃连接: 在第4层加入跳跃连接,帮助网络学习高频细节
-
轻量级: 网络参数量约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 数据集
-
Synthetic NeRF Dataset
- 8个合成场景(lego, chair, drums等)
- 100张训练图像,200张测试图像
- 白色背景
-
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的核心贡献
- 连续表示: 使用神经网络表示连续的3D场景
- 可微渲染: 体素渲染过程完全可微,支持端到端训练
- 视角依赖: 通过输入视角方向,学习视角相关的效果
- 高质量合成: 能够从稀疏输入生成高质量新视角
9.2 关键技术点
- 位置编码: 帮助网络学习高频细节
- 分层采样: 提高采样效率,关注重要区域
- 体素渲染: 将3D表示转换为2D图像的核心方法
- 简单损失: 仅使用MSE损失即可达到优秀效果
9.3 局限性
- 训练时间长: 单个场景需要数小时训练
- 推理速度慢: 渲染一张图像需要数秒
- 静态场景: 原始NeRF只能处理静态场景
- 需要精确位姿: 对相机参数精度要求高
9.4 后续改进方向
- Instant-NGP: 使用哈希编码加速训练
- Mip-NeRF: 解决混叠问题
- D-NeRF: 处理动态场景
- NeRF in the Wild: 处理无约束照片集合
十、参考文献
- Mildenhall, B., et al. “NeRF: Representing Scenes as Neural Radiance Fields for View Synthesis.” ECCV 2020.
- 代码仓库: https://github.com/yenchenlin/nerf-pytorch
- 官方实现: https://github.com/bmild/nerf
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)