【机器学习】基于NeRF的3D动画渲染

1.引言

1.1.NeRF框架简介

1.1.1. 什么是NeRF框架

NeRF框架,全称为神经辐射场(Neural Radiance Fields)框架,是一个基于深度学习的三维场景重建和渲染技术。

  1. 基本概念
  • NeRF利用全连接神经网络(又称多层感知机MLP)的权重来表示3D场景。
  • 它基于辐射场概念、光线可逆及相机成像原理,通过二维图像来重建和渲染三维场景。
  1. 工作原理
  • NeRF从一组稀疏的输入视图优化连续的体积场景函数。
  • 输入包括3D位置(x, y, z)和2D观察方向(θ, Φ),输出是颜色(r, g, b)和体积密度(α)。
  • 使用体积渲染技术来渲染新视图,从而生成复杂场景的新视图。
  1. 应用场景
  • 在自动驾驶领域,NeRF被应用于感知、三维重建、同时定位和地图构建(SLAM)以及模拟等方面。
  • 在三维重建中,NeRF可以分为动态场景重建、表面重建和逆渲染等主要方法。
  1. 技术特点
  • NeRF是一个计算密集型算法,处理复杂场景可能需要数小时或数天。
  • 它能够从给定的连续视点获得逼真的图像渲染,是视图合成任务的一个主要方法。
  • 最近的发展包括需要更少的图像来训练模型进行视图合成的方法,以及能够从无约束和动态场景表示中生成视图的方法。

总体而言, NeRF框架通过深度学习技术,实现了从二维图像到三维场景的精确重建和渲染,为计算机图形学、虚拟现实和增强现实等领域带来了重要突破。

1.1.2.NeRF的优势和劣势
优势:
  1. 高质量的三维重建:NeRF框架能够利用稀疏的输入视图集优化底层连续的体积场景函数,实现高质量的三维重建。这种方法可以生成无空洞、细节丰富的模型,并且能够综合复杂场景视图的最佳结果。
  2. 广泛的应用领域:NeRF在多个领域具有广泛的应用前景,包括3D建模、自动驾驶、导航系统、VR和AR等。特别是在自动驾驶领域,NeRF被用于创建用于大规模训练的虚拟环境,如NVIDIA DRIVE Sim平台,可以重建整个城市区域的3D环境。
  3. 高度逼真的渲染:NeRF生成的图像质量高,具有逼真的视觉效果。它可以从任意新的视角连续地渲染真实感视图,为用户提供沉浸式的体验。
劣势:
  1. 计算资源需求高:NeRF是一个计算密集型算法,对于复杂场景的处理可能需要数小时或数天的时间。这限制了其在实时应用中的使用。
  2. 依赖多视图数据:原始的NeRF模型需要多个视图的图像作为监督学习的输入。如果多视角数据不足,模型可能无法准确估计体积表征,导致生成的场景质量下降。
  3. 处理光线差异的能力有限:NeRF在处理光线差异方面存在不足,特别是在处理遮挡和阴影等光线交互时表现较差。这可能导致生成的图像不够真实。
  4. 泛化能力有待提高:NeRF在泛化能力方面还有待提高。原始的NeRF模型主要针对静态场景,对于动态场景和变化光线的处理能力有限。
  5. 速度和效率问题:虽然NeRF可以生成高质量的图像,但其训练和渲染速度相对较慢。这限制了其在需要快速生成结果的场景中的应用。

综上所述,NeRF框架在三维重建和渲染方面具有显著的优势,但也需要解决计算资源需求高、依赖多视图数据、处理光线差异的能力有限以及泛化能力有待提高等问题。随着技术的不断发展,未来的研究可能会进一步改进NeRF框架的性能和适用性。

1.2 NeRF用于3D渲染概述

NeRF是一种基于深度学习的三维场景重建和渲染技术。它通过学习一个连续的体积辐射场来表示场景,能够从任意视角准确地渲染出高质量的3D场景图像。NeRF的提出为计算机图形学、增强现实(AR)、虚拟现实(VR)以及电影和游戏制作等领域带来了重要的应用价值。

1.2.1工作原理
  1. 场景表示:NeRF通过一组从不同视角拍摄的2D图片,学习场景的连续体积密度和颜色分布。这个过程不是生成一个传统意义上的3D模型文件,而是训练一个深度学习模型,该模型能够根据输入的3D位置(x, y, z)和观察方向(θ, φ)来预测该位置的颜色(RGB值)和体积密度(σ)。
  2. 图像渲染:一旦3D场景被重建,就可以通过设置特定的摄像机参数(如位置、朝向和视角等)来从任意视角渲染2D图像。渲染过程模拟了光线从摄像机通过场景到达观察者眼睛的路径,通过计算沿这些路径的多个点的颜色和密度,然后综合这些信息来生成最终的像素颜色,从而形成完整的2D图像。
1.2.2.技术特点
  1. 高质量渲染:NeRF可以生成高质量的3D场景重建结果,包括光照效果。由于其对场景的高度表达能力,可以捕捉复杂的几何结构和光照情况。
  2. 高效性:NeRF通过少量图像或单视图图像就能进行高分辨率3D场景重建,并且可以利用深度学习和神经网络的优势,通过计算和调整残差来获得真实世界的渲染结果。
  3. 灵活性:NeRF不仅适用于静态场景,还可以通过改进算法来处理动态场景和变化的光线条件。
1.2.3.优势和不足

优势:

  • 能够从已有的2D图片中重建出高度逼真的3D场景。
  • 能够从场景中任意视角生成高质量的2D图像。
  • 适用于多个领域,如计算机视觉、增强现实、虚拟现实等。

不足:

  • 计算资源需求高,对于复杂场景的处理可能需要大量时间和计算资源。
  • 处理光线差异的能力有限,在处理遮挡和阴影等光线交互时表现较差。
  • 泛化能力有待提高,对于动态场景和变化光线的处理能力有限。

综合来看,NeRF作为一种基于深度学习的3D渲染技术,在多个领域都展现出了巨大的潜力和应用价值。尽管它还存在一些不足和挑战,但随着技术的不断发展和改进,相信NeRF将在未来发挥更加重要的作用。

2.NeRF实现3D渲染的过程

2.1.设置

import os  
  
# 设置Keras后端为TensorFlow  
os.environ["KERAS_BACKEND"] = "tensorflow"  
  
# 为了获得可复现的结果,设置随机种子  
import tensorflow as tf  
tf.random.set_seed(42)  
  
# 导入Keras及其layers模块  
import keras  
from keras import layers  
  
# 导入其他所需的库  
import os  
import glob  
import imageio.v2 as imageio  
import numpy as np  
from tqdm import tqdm  
import matplotlib.pyplot as plt  
  
# 初始化全局变量  
# AUTOTUNE是TensorFlow的一个特殊值,用于告诉tf.data API自动选择最优的并行度  
AUTO = tf.data.AUTOTUNE  
# 批次大小设置为5  
BATCH_SIZE = 5  
# 样本数量设置为32(此处的NUM_SAMPLES可能在后续代码中用于确定数据集的某个大小,具体取决于上下文)  
NUM_SAMPLES = 32  
# 位置编码的维度设置为16(这通常用于NeRF中的位置编码部分)  
POS_ENCODE_DIMS = 16  
# 训练轮数设置为20  
EPOCHS = 20  

2.2.数据预处理

2.2.1 加载数据
# 导入所需的库
import numpy as np
import matplotlib.pyplot as plt
from tensorflow import keras

# 定义数据文件的URL
url = (
    "http://cseweb.ucsd.edu/~viscomp/projects/LF/papers/ECCV20/nerf/tiny_nerf_data.npz"
)

# 使用Keras的get_file函数下载数据文件,如果本地不存在该文件的话
data = keras.utils.get_file(origin=url)

# 使用numpy的load函数加载npz文件
data = np.load(data)

# 从加载的数据中提取图像
images = data["images"]

# 获取图像的维度信息
im_shape = images.shape
# 获取图像数量、高度、宽度和通道数
num_images, H, W, _ = images.shape

# 从数据文件中提取相机姿态和焦距
poses, focal = data["poses"], data["focal"]

# 从数据集中随机选择一张图像进行可视化展示
random_image = images[np.random.randint(low=0, high=num_images)]
plt.imshow(random_image)
plt.show()

代码功能总结:

  1. 导入库:代码开始部分导入了所需的NumPy、Matplotlib.pyplot和Keras库。

  2. 定义数据URL:设置数据文件的URL,这个URL指向一个.npz文件,这是一种压缩格式,常用于存储NumPy数组。

  3. 下载数据:使用keras.utils.get_file函数下载数据文件。如果本地已经存在该文件,则不会重复下载。

  4. 加载数据:使用np.load函数加载.npz文件中的数据。

  5. 提取图像:从加载的数据字典中提取出图像数组。

  6. 获取图像维度:获取图像数组的形状,包括图像数量、高度、宽度和颜色通道数。

  7. 提取相机姿态和焦距:从数据中提取相机的姿态(位置和方向)和焦距,这些信息对于3D场景理解很重要。

  8. 随机图像可视化:使用Matplotlib的imshow函数从图像数组中随机选择一张图像进行显示。

这段代码主要用于下载、加载和可视化NeRF数据集,为后续的3D场景重建或渲染任务做准备。

2.2.2.建立数据管道
import tensorflow as tf

# 定义位置编码函数,将位置编码为其对应的傅里叶特征。
def encode_position(x):
    positions = [x]  # 存储位置编码
    for i in range(POS_ENCODE_DIMS):  # POS_ENCODE_DIMS为位置编码的维度
        for fn in [tf.sin, tf.cos]:  # 使用正弦和余弦函数进行编码
            positions.append(fn(2.0**i * x))
    return tf.concat(positions, axis=-1)  # 沿最后一个轴拼接编码向量

# 计算光线的起点和方向向量。
def get_rays(height, width, focal, pose):
    i, j = tf.meshgrid(tf.range(width), tf.range(height), indexing="xy")  # 创建光线的网格
    transformed_i = (i - width * 0.5) / focal  # 归一化x坐标
    transformed_j = (j - height * 0.5) / focal  # 归一化y坐标
    directions = tf.stack([transformed_i, -transformed_j, -tf.ones_like(i)], axis=-1)  # 方向向量
    camera_matrix = pose[:3, :3]  # 相机矩阵
    height_width_focal = pose[:3, -1]  # 相机的高度、宽度和焦距
    transformed_dirs = directions[..., None, :]  # 扩展维度以进行矩阵乘法
    camera_dirs = transformed_dirs * camera_matrix  # 应用相机矩阵
    ray_directions = tf.reduce_sum(camera_dirs, axis=-1)  # 方向向量的合成
    ray_origins = tf.broadcast_to(height_width_focal, tf.shape(ray_directions))  # 光线的起点
    return (ray_origins, ray_directions)  # 返回起点和方向向量

# 渲染并展平光线。
def render_flat_rays(ray_origins, ray_directions, near, far, num_samples, rand=False):
    t_vals = tf.linspace(near, far, num_samples)  # 沿光线的采样点
    if rand:  # 如果需要随机采样
        noise = tf.random.uniform(shape=ray_origins.shape[:-1] + [num_samples]) * (far - near) / num_samples
        t_vals = t_vals + noise
    rays = ray_origins[..., None, :] + ray_directions[..., None, :] * t_vals[..., None]  # 计算光线上的点
    rays_flat = tf.reshape(rays, [-1, 3])  # 展平光线
    rays_flat = encode_position(rays_flat)  # 对展平的光线进行位置编码
    return (rays_flat, t_vals)  # 返回展平的光线和采样点

# 将单个相机姿态映射到展平的光线和采样点。
def map_fn(pose):
    (ray_origins, ray_directions) = get_rays(height=H, width=W, focal=focal, pose=pose)
    (rays_flat, t_vals) = render_flat_rays(
        ray_origins=ray_origins,
        ray_directions=ray_directions,
        near=2.0,
        far=6.0,
        num_samples=NUM_SAMPLES,
        rand=True,
    )
    return (rays_flat, t_vals)

# 创建训练集划分。
split_index = int(num_images * 0.8)
train_images = images[:split_index]
val_images = images[split_index:]
train_poses = poses[:split_index]
val_poses = poses[split_index:]

# 制作训练数据管道。
train_img_ds = tf.data.Dataset.from_tensor_slices(train_images)
train_pose_ds = tf.data.Dataset.from_tensor_slices(train_poses)
train_ray_ds = train_pose_ds.map(map_fn, num_parallel_calls=AUTO)
training_ds = tf.data.Dataset.zip((train_img_ds, train_ray_ds))
train_ds = (
    training_ds.shuffle(BATCH_SIZE)
    .batch(BATCH_SIZE, drop_remainder=True, num_parallel_calls=AUTO)
    .prefetch(AUTO)
)

# 制作验证数据管道。
val_img_ds = tf.data.Dataset.from_tensor_slices(val_images)
val_pose_ds = tf.data.Dataset.from_tensor_slices(val_poses)
val_ray_ds = val_pose_ds.map(map_fn, num_parallel_calls=AUTO)
validation_ds = tf.data.Dataset.zip((val_img_ds, val_ray_ds))
val_ds = (
    validation_ds.shuffle(BATCH_SIZE)
    .batch(BATCH_SIZE, drop_remainder=True, num_parallel_calls=AUTO)
    .prefetch(AUTO)
)
  1. 位置编码encode_position 函数使用傅里叶特征对空间位置进行编码,这在NeRF中用于表示位置信息。

  2. 光线计算get_rays 函数计算从相机出发的光线的起点和方向,这是通过将图像坐标转换为相机坐标系并应用相机矩阵来完成的。

  3. 渲染和展平光线render_flat_rays 函数在给定的体积场景中沿光线均匀或随机地采样点,并将这些点展平为一个向量。

  4. 映射函数map_fn 函数将相机的姿态映射到展平的光线和采样点,这是通过组合前面定义的函数来完成的。

  5. 数据集划分:代码将图像和相机姿态数组分为训练集和验证集。

  6. 数据管道制作:代码使用TensorFlow的Dataset API创建了训练和验证的数据管道,包括数据的打乱、批处理和预取。

这些函数和步骤为NeRF模型的构建和训练准备了必要的数据结构和处理流程。通过这种方式,可以有效地从一组图像和相应的相机姿态中生成用于训练的输入数据。

2.3.建立NeRF模型

NeRF模型主要由一个多层感知机(MLP)构成,其中使用了ReLU作为非线性激活函数。为了获得多视图一致性的表示,该模型在预测体积密度时仅依赖于3D位置坐标x。首先,模型将输入的3D坐标x通过一个包含8个全连接层(每层使用ReLU激活函数和64个神经元)的网络进行处理,并输出体积密度sigma和一个256维的特征向量。

接下来,为了预测依赖于视图的RGB颜色,模型将上述得到的256维特征向量与相机射线的观察方向进行拼接。然后,将拼接后的结果输入到一个额外的全连接层(使用ReLU激活函数和128个神经元)中,最终输出与视图相关的RGB颜色值。

请注意,这里我们使用了64个神经元作为全连接层的尺寸,以简化模型的实现,尽管论文中提到了使用256个神经元。在实际应用中,可以根据具体需求和计算资源进行调整。

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

# 定义获取NeRF模型的函数,该模型是一个多层感知机(MLP)。
def get_nerf_model(num_layers, num_pos):
    inputs = keras.Input(shape=(num_pos, 2 * 3 * POS_ENCODE_DIMS + 3))
    x = inputs
    for i in range(num_layers):
        # 每层使用64个单元和ReLU激活函数的密集连接层。
        x = layers.Dense(units=64, activation="relu")(x)
        # 每4层添加一次残差连接。
        if i % 4 == 0 and i > 0:
            x = layers.concatenate([x, inputs], axis=-1)
    # 输出层,预测4个值:RGB颜色和体积密度。
    outputs = layers.Dense(units=4)(x)
    return keras.Model(inputs=inputs, outputs=outputs)

# 根据模型预测生成RGB图像和深度图。
def render_rgb_depth(model, rays_flat, t_vals, rand=True, train=True):
    # 从NeRF模型获取预测结果并重塑形状。
    if train:
        predictions = model(rays_flat)
    else:
        predictions = model.predict(rays_flat)
    predictions = tf.reshape(predictions, shape=(BATCH_SIZE, H, W, NUM_SAMPLES, 4))

    # 将预测结果分割为RGB和体积吸收系数sigma。
    rgb = tf.sigmoid(predictions[..., :-1])
    sigma_a = tf.nn.relu(predictions[..., -1])

    # 获取相邻采样点之间的距离。
    delta = t_vals[..., 1:] - t_vals[..., :-1]
    
    if rand:
        # 如果使用随机采样策略,添加一个大的数值以处理最后一个采样点。
        delta = tf.concat([delta, tf.broadcast_to([1e10], shape=(BATCH_SIZE, H, W, 1))], axis=-1)
        alpha = 1.0 - tf.exp(-sigma_a * delta)
    else:
        # 否则,为最后一个采样点添加一个大的数值。
        delta = tf.concat([delta, tf.broadcast_to([1e10], shape=(BATCH_SIZE, 1))], axis=-1)
        alpha = 1.0 - tf.exp(-sigma_a * delta[:, None, None, :])

    # 计算透射率。
    exp_term = 1.0 - alpha
    epsilon = 1e-10  # 一个小的数值以避免计算中的除零错误。
    transmittance = tf.math.cumprod(exp_term + epsilon, axis=-1, exclusive=True)
    weights = alpha * transmittance
    # 计算加权的RGB值。
    rgb = tf.reduce_sum(weights[..., None] * rgb, axis=-2)

    # 根据权重和采样点t值计算深度图。
    if rand:
        depth_map = tf.reduce_sum(weights * t_vals, axis=-1)
    else:
        depth_map = tf.reduce_sum(weights * t_vals[:, None, None], axis=-1)
    
    return (rgb, depth_map)
  1. NeRF模型生成get_nerf_model 函数创建了一个多层感知机模型,该模型接受位置编码作为输入,并输出RGB颜色和体积密度。

  2. 渲染RGB和深度图render_rgb_depth 函数根据NeRF模型的预测结果,生成RGB图像和深度图。它首先从模型获取预测,然后计算沿光线的透射率和权重,最后累积这些权重与RGB值的乘积来得到最终的图像颜色,并计算深度图。

  3. 随机采样:在计算过程中,可以选择是否使用随机采样策略,这有助于提高渲染的连续性和真实感。

  4. 训练与推理:函数能够处理模型的训练阶段和推理阶段,其中训练阶段直接使用模型调用,而推理阶段使用predict方法。

这些函数为NeRF模型的构建、训练和渲染提供了完整的流程,使得可以从一组给定的光线和采样点生成逼真的3D场景图像。

2.4.训练模型

训练步骤被实现为一个自定义的keras.Model子类的部分,以便我们可以利用model.fit功能进行训练。

import os
import glob
import imageio
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

# 定义NeRF模型类,继承自keras.Model。
class NeRF(keras.Model):
    def __init__(self, nerf_model):
        super().__init__()
        self.nerf_model = nerf_model

    def compile(self, optimizer, loss_fn):
        super().compile()
        self.optimizer = optimizer
        self.loss_fn = loss_fn
        # 初始化损失和PSNR的跟踪器。
        self.loss_tracker = keras.metrics.Mean(name="loss")
        self.psnr_metric = keras.metrics.Mean(name="psnr")

    def train_step(self, inputs):
        # 获取图像和光线数据。
        (images, rays) = inputs
        (rays_flat, t_vals) = rays

        with tf.GradientTape() as tape:
            # 从模型获取预测的RGB值。
            rgb, _ = render_rgb_depth(
                model=self.nerf_model, rays_flat=rays_flat, t_vals=t_vals, rand=True
            )
            # 计算损失。
            loss = self.loss_fn(images, rgb)

        # 获取可训练变量及其梯度。
        trainable_variables = self.nerf_model.trainable_variables
        gradients = tape.gradient(loss, trainable_variables)

        # 应用梯度,优化模型。
        self.optimizer.apply_gradients(zip(gradients, trainable_variables))

        # 计算重建图像与源图像之间的PSNR。
        psnr = tf.image.psnr(images, rgb, max_val=1.0)

        # 更新损失和PSNR的跟踪器。
        self.loss_tracker.update_state(loss)
        self.psnr_metric.update_state(psnr)
        return {"loss": self.loss_tracker.result(), "psnr": self.psnr_metric.result()}

    def test_step(self, inputs):
        # 验证步骤与训练步骤类似,但不进行梯度更新。
        # ...

    @property
    def metrics(self):
        return [self.loss_tracker, self.psnr_metric]  # 返回模型的度量指标


# 测试模型并生成GIF动图的回调函数。
class TrainMonitor(keras.callbacks.Callback):
    def on_epoch_end(self, epoch, logs=None):
        # ...
        # 绘制预测图像、深度图和损失曲线,并保存图像。
        # ...

# 创建GIF动图的函数。
def create_gif(path_to_images, name_gif):
    filenames = glob.glob(path_to_images)
    filenames = sorted(filenames)
    images = []
    for filename in tqdm(filenames):
        images.append(imageio.imread(filename))
    kargs = {"duration": 0.25}
    imageio.mimsave(name_gif, images, "GIF", **kargs)

# 实例化NeRF模型,编译并训练。
# ...

# 训练模型并保存中间结果,以便生成训练过程中的GIF动图。
# ...
  1. NeRF模型类:定义了一个NeRF类,继承自keras.Model,封装了NeRF模型的训练和评估逻辑。

  2. 编译方法:在compile方法中,初始化优化器、损失函数和性能度量指标。

  3. 训练步骤train_step方法定义了模型的训练逻辑,包括前向传播、损失计算、梯度计算和优化器应用。

  4. 评估步骤test_step方法定义了模型的评估逻辑,与训练逻辑类似,但不包括梯度更新。

  5. 回调函数TrainMonitor类在每个训练周期结束时,生成预测图像、深度图和损失曲线,并保存为图像。

  6. 生成GIFcreate_gif函数将训练过程中保存的图像合成为GIF动图。

  7. 训练与评估:最后,实例化NeRF模型,进行编译,并使用提供的数据集进行训练和评估。

这段代码提供了一个完整的NeRF模型训练流程,包括模型定义、训练、评估和结果可视化。通过TrainMonitor回调函数,可以在训练过程中生成可视化的中间结果,并通过GIF动图展示模型学习过程。

2.5.推理预测

在推理阶段,我们的目标是让训练好的模型根据已学习到的知识生成场景中未见过的视图。尽管在训练过程中模型只接收了场景的106个视图,但这些视图并不足以覆盖场景的所有角度。然而,一个经过充分训练的模型应当有能力使用这一有限的视图集合来表示整个3D场景的布局和结构。

为了实现这一目标,我们向模型提供不同的相机姿态(即相机位置和朝向),并请求它生成与这些姿态相对应的2D图像。通过这种方式,如果我们为模型提供覆盖360度视角的所有可能姿态,它应该能够提供一幅全景视图,展示从周围各个角度观察到的整个场景。这种能力使得NeRF模型在虚拟现实、增强现实以及计算机图形学等领域具有广泛的应用前景。

import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

# 假设model是已经训练好的NeRF模型实例

# 获取训练好的NeRF模型进行推理。
nerf_model = model.nerf_model
# 使用render_rgb_depth函数生成测试图像的重建图像和深度图。
test_recons_images, depth_maps = render_rgb_depth(
    model=nerf_model,
    rays_flat=test_rays_flat,
    t_vals=test_t_vals,
    rand=True,
    train=False,
)

# 创建子图。
fig, axes = plt.subplots(nrows=5, ncols=3, figsize=(10, 20))

# 对于每个子图轴,显示原始图像、重建图像和深度图。
for ax, ori_img, recons_img, depth_map in zip(
    axes, test_imgs, test_recons_images, depth_maps
):
    # 显示原始图像。
    ax[0].imshow(keras.utils.array_to_img(ori_img))
    ax[0].set_title("Original")

    # 显示重建图像。
    ax[1].imshow(keras.utils.array_to_img(recons_img))
    ax[1].set_title("Reconstructed")

    # 显示深度图,使用'inferno'颜色映射。
    ax[2].imshow(keras.utils.array_to_img(depth_map[..., None]), cmap="inferno")
    ax[2].set_title("Depth Map")

代码功能总结:

  1. 模型推理:从训练好的NeRF模型中获取nerf_model,并使用它对测试光线数据进行推理,生成重建的RGB图像和深度图。

  2. 子图创建:使用Matplotlib的subplots函数创建5行3列的子图布局。

  3. 图像显示:遍历每一个子图轴,对于原始图像、重建图像和深度图,执行以下操作:

    • 使用imshow函数显示图像。
    • 将图像转换为PIL图像格式,以便显示(通过keras.utils.array_to_img函数)。
    • 设置每个子图的标题,分别为"Original"(原始图像)、“Reconstructed”(重建图像)和"Depth Map"(深度图)。
  4. 深度图颜色映射:为深度图指定inferno颜色映射,这是一种从黑色到白色的连续颜色渐变,适合表示深度信息。

这段代码提供了一个可视化NeRF模型推理结果的方法,通过比较原始图像和重建图像,以及观察深度图,可以直观地评估模型的性能。

2.6.渲染3D场景

我们将利用已训练好的模型来合成全新的3D视图,并将这些视图连续拼接起来,从而生成一个环绕360度的全景视频,全面展示三维场景的各个角度。

import numpy as np
import tensorflow as tf
import imageio
from tqdm import tqdm

# 定义沿t方向的平移矩阵。
def get_translation_t(t):
    matrix = [
        [1, 0, 0, 0],
        [0, 1, 0, 0],
        [0, 0, 1, t],
        [0, 0, 0, 1],
    ]
    return tf.convert_to_tensor(matrix, dtype=tf.float32)

# 定义绕x轴的旋转矩阵。
def get_rotation_phi(phi):
    matrix = [
        [1, 0, 0, 0],
        [0, tf.cos(phi), -tf.sin(phi), 0],
        [0, tf.sin(phi), tf.cos(phi), 0],
        [0, 0, 0, 1],
    ]
    return tf.convert_to_tensor(matrix, dtype=tf.float32)

# 定义绕y轴的旋转矩阵。
def get_rotation_theta(theta):
    matrix = [
        [tf.cos(theta), 0, -tf.sin(theta), 0],
        [0, 1, 0, 0],
        [tf.sin(theta), 0, tf.cos(theta), 0],
        [0, 0, 0, 1],
    ]
    return tf.convert_to_tensor(matrix, dtype=tf.float32)

# 根据球坐标theta, phi和t获取相机到世界坐标的矩阵。
def pose_spherical(theta, phi, t):
    c2w = get_translation_t(t)
    c2w = get_rotation_phi(phi / 180.0 * np.pi) @ c2w
    c2w = get_rotation_theta(theta / 180.0 * np.pi) @ c2w
    # 调整坐标轴以匹配NeRF的坐标系。
    c2w = np.array([[-1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]]) @ c2w
    return c2w

# 存储RGB帧、展平的光线和对应的t值。
rgb_frames = []
batch_flat = []
batch_t = []

# 遍历不同的theta值生成场景。
for index, theta in tqdm(enumerate(np.linspace(0.0, 360.0, 120, endpoint=False))):
    # 获取相机到世界的矩阵。
    c2w = pose_spherical(theta, -30.0, 4.0)

    # 获取光线的起点和方向。
    ray_oris, ray_dirs = get_rays(H, W, focal, c2w)
    # 渲染展平的光线。
    rays_flat, t_vals = render_flat_rays(ray_oris, ray_dirs, near=2.0, far=6.0, num_samples=NUM_SAMPLES, rand=False)

    # 根据批次大小处理光线数据。
    if index % BATCH_SIZE == 0 and index > 0:
        batched_flat = tf.stack(batch_flat, axis=0)
        batch_flat = [rays_flat]

        batched_t = tf.stack(batch_t, axis=0)
        batch_t = [t_vals]

        # 使用NeRF模型渲染RGB图像。
        rgb, _ = render_rgb_depth(nerf_model, batched_flat, batched_t, rand=False, train=False)

        # 规范化并存储RGB帧。
        temp_rgb = [np.clip(255 * img, 0.0, 255.0).astype(np.uint8) for img in rgb]
        rgb_frames.extend(temp_rgb)
    else:
        batch_flat.append(rays_flat)
        batch_t.append(t_vals)

# 将RGB帧写入视频文件。
rgb_video = "rgb_video.mp4"
imageio.mimwrite(rgb_video, rgb_frames, fps=30, quality=7, macro_block_size=None)
  1. 矩阵生成函数:定义了生成平移、旋转矩阵的函数,这些矩阵用于创建相机到世界坐标的转换矩阵。

  2. 相机到世界坐标矩阵pose_spherical函数根据给定的球坐标theta、phi和t,创建相应的相机到世界坐标的矩阵。

  3. 场景生成:通过遍历不同的theta值,生成不同视角下的场景。

  4. 光线处理:对于每个视角,获取光线的起点和方向,然后渲染展平的光线。

  5. 批次处理:根据设定的批次大小,将光线数据累积并批量处理,以生成RGB图像。

  6. 视频生成:使用imageio.mimwrite函数,将生成的RGB帧写入视频文件。

这段代码演示了如何使用NeRF模型从多个视角生成场景,并最终将渲染结果合成为视频。通过调整theta值,可以围绕对象旋转并观察其不同角度的渲染图像。最终生成的视频可以在常用的视频播放器中查看。

3. 总结和展望

3.1 总结

本文详细介绍了NeRF(Neural Radiance Fields,神经辐射场)框架的理论基础、实现过程和应用前景。NeRF作为一种前沿的3D场景重建和渲染技术,通过深度学习的方法,成功地从二维图像中恢复出三维场景,并能够从任意视角渲染出高质量的3D图像。

  1. NeRF框架简介:包括基本概念、工作原理、应用场景和技术特点。
  2. NeRF的优势和劣势:分析了NeRF在高质量三维重建、逼真渲染、多领域应用等方面的优势,以及在计算资源需求、多视图数据依赖、光线处理能力、泛化能力等方面的劣势。
  3. 3D渲染实现过程:从数据预处理、数据管道建立、NeRF模型建立、模型训练到推理预测,提供了详细的实现步骤和代码示例。
  4. 渲染3D场景:展示了如何使用训练好的NeRF模型合成新的3D视图,并生成全景视频。

3.2 展望

尽管NeRF在三维视觉领域取得了显著的成果,但仍存在一些挑战和改进空间,未来的研究可能会集中在以下几个方面:

  1. 计算效率优化:NeRF的计算成本较高,未来的工作可能会探索更高效的算法或硬件加速方法,以提高渲染速度。

  2. 数据需求降低:减少模型训练所需的视图数量,开发更少依赖数据的方法,以降低数据采集的难度和成本。

  3. 动态场景支持:扩展NeRF以处理动态场景和时间变化,捕捉和渲染场景中的运动和变形。

  4. 光线交互改进:提高NeRF处理复杂光线交互(如遮挡、阴影)的能力,以生成更真实的渲染效果。

  5. 泛化能力提升:增强模型的泛化性,使其能够适应不同的场景和条件,包括不同的光照环境和复杂的几何结构。

  6. 多模态应用探索:结合其他类型的传感器数据,如深度信息、运动捕捉数据等,以丰富场景表示并提高重建精度。

  7. 交互式应用开发:开发基于NeRF的交互式应用,如虚拟现实(VR)和增强现实(AR)中的实时3D场景编辑和探索。

  8. 工业和学术界的合作:促进工业界和学术界之间的合作,共享数据、模型和工具,推动NeRF技术的发展和应用。

随着技术的不断进步和创新,NeRF及其衍生技术有望在未来的计算机图形学、虚拟现实、增强现实等领域发挥更加关键的作用,为用户带来更加丰富和逼真的三维视觉体验。

参考文献

[1]“NeRF Example.” Keras. 访问日期:2024年6月15日. https://keras.io/examples/vision/nerf/.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/713804.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【git使用四】git分支理解与操作(详解)

目录 (1)理解git分支 主分支(主线) 功能分支 主线和分支关系 将分支合并到主分支 快速合并 非快速合并 git代码管理流程 (2)理解git提交对象 提交对象与commitID Git如何保存数据 示例讲解 &a…

Bio-Info每日一题:Rosalind-07-Mendel‘s First Law(孟德尔第一定律 python实现)

🎉 进入生物信息学的世界,与Rosalind一起探索吧!🧬 Rosalind是一个在线平台,专为学习和实践生物信息学而设计。该平台提供了一系列循序渐进的编程挑战,帮助用户从基础到高级掌握生物信息学知识。无论你是初…

C++前期概念(重)

目录 命名空间 命名空间定义 1. 正常的命名空间定义 2. 命名空间可以嵌套 3.头文件中的合并 命名空间使用 命名空间的使用有三种方式: 1:加命名空间名称及作用域限定符(::) 2:用using将命名空间中某个成员引入 3:使用using namespa…

Milvus Cloud 问答机器人 上线!构建企业级的 Chatbot

01. 背景 早些时候我们在社区微信群发出了一份关于Milvus Cloud 自动问答机器人的调研问卷。 调研受到了社区同学的积极响应,很快我们就收到了很多热心用户的回复。 基于这些回复,我们整理出了 Milvus Cloud Chatbot 的形态: 以功能使用和文档查询为核心 提供聊天和搜索双形…

【尚庭公寓SpringBoot + Vue 项目实战】图片上传(十)

【尚庭公寓SpringBoot Vue 项目实战】图片上传(十) 文章目录 【尚庭公寓SpringBoot Vue 项目实战】图片上传(十)1、图片上传流程2、图片上传接口查看3、代码开发3.1、配置Minio Client3.2、开发上传图片接口 4、异常处理 1、图片…

【每日刷题】Day66

【每日刷题】Day66 🥕个人主页:开敲🍉 🔥所属专栏:每日刷题🍍 🌼文章目录🌼 1. 小乐乐改数字_牛客题霸_牛客网 (nowcoder.com) 2. 牛牛的递增之旅_牛客题霸_牛客网 (nowcoder.com)…

算法体系-20 第二十节暴力递归到动态规划

前言 动态规划模型从尝试暴力递归到傻缓存到动态规划 四种模型和体系班两种模型一共六种模型 0.1 从左往右模型 0.2 范围讨论模型范围尝试模型 (这种模型特别在乎讨论开头如何如何 结尾如何如何) 玩家博弈问题,玩家玩纸牌只能那左或者右 0.3 …

Docker Jenkins(改错版本)

Devops:它强调开发(Development)和运维(Operations)团队之间的协作.实现更快,更可靠的软件交付部署. JenKins是一个开源的自动化服务器,广泛用于构建,测试和部署软件项目.它是持续集成(CI)和持续交付/部署(CD)的工具.JenKins是实现DevOps实践的重要工具. 前端项目部署一般流程:…

【javaEE-有关CPU进程和线程实现的并发编程及二者的区别】

🔥🔥🔥有关进程并发编程开发的成本问题 这次之前其实我们所有的写的程序都是使用单核心来运行的,但是一般我们的计算机都有很多核心,如果我们编程的时候,只使用一个核心的话,其实这是一个非常大…

通俗范畴论2 有向图与准范畴

退一步海阔天空,在正式进入范畴论之前,我们可以重新审视一下我们是如何认识世界的,有了这个对人类认识世界过程的底层理解,可以帮助我们更好地理解范畴论。 对于人类认识世界,最神奇的一点就是这个世界居然是可以认识…

【C语言】解决C语言报错:Race Condition

文章目录 简介什么是Race ConditionRace Condition的常见原因如何检测和调试Race Condition解决Race Condition的最佳实践详细实例解析示例1:缺乏适当的同步机制示例2:错误使用条件变量 进一步阅读和参考资料总结 简介 Race Condition(竞争条…

element-ui input输入框和多行文字输入框字体不一样

页面中未作样式修改,但是在项目中使用element-ui input输入框和多行文字输入框字体不一样,如下图所示: 这是因为字体不一致引起的,如果想要为Element UI的输入框设置特定的字体,你可以在你的样式表中添加以下CSS代码…

尚品汇-(二)

本地域名解析器:当我们在浏览器输入域名的时候,它首先找的不是远程的DNS,而是去本地的host中去找这个域名有没有对应的,如果有对应的,那么就根据对应的ip进行访问 一:环境安装 1.安装JAVA 运行环境 第一…

MySQL之优化服务器设置(四)

优化服务器设置 InnoDB的IO配置 双写缓冲(Doublewrite Buffer) InnoDB用双写缓冲来避免页没写完整所导致的数据损坏。当一个磁盘写操作不能完整地完成时,不完整的页写入就可能发生,16KB的页可能只有一部分被写到磁盘上。有多种多样的原因(崩溃、Bug&am…

Obsidian 工作区Workspace:实现切换和管理工作区的多任务处理插件

工作区 工作区是Obsidian 的核心插件之一,旨在帮助用户更好地管理和组织他们的工作环境。 功能简介 工作区保存和切换:Workspace 插件允许用户保存当前的窗口布局和打开的笔记状态,用户可以随时切换到不同的工作区,这样可以根据…

Matlab|基于手肘法的kmeans聚类数的精确识别【K-means聚类】

主要内容 在电力系统调度研究过程中,由于全年涉及的风、光和负荷曲线较多,为了分析出典型场景,很多时候就用到聚类算法,而K-means聚类就是常用到聚类算法,但是对于K-means聚类算法,需要自行指定分类数&…

Google Earth Engine(GEE)——计算闪闪红星的ndvi的值和折线图(时序分析)

函数: ui.Chart.image.doySeries(imageCollection, region, regionReducer, scale, yearReducer, startDay, endDay)

中小学电子教材下载办法(202406最简单的)

官方版本 现在能阅读电子教材的官方网站挺多的,例如 人民教育出版社-电子教材,还有 国家中小学智慧教育平台 ,其他还有很多可在阅读的网站。由于平台的原因不能直接贴链接,大家可以通过搜索关键词找到网站。 如何下载 据我所知…

idea搜索只显示100条、如何修改idea搜索的条数

文章目录 一、老版本的IDEA(2021年之前的版本)二、新版本的IDEA(2021年及之后的版本)2.1、方式一2.2、方式二 如下图:idea搜索的时候默认只显示100条 要解决IDEA搜索只显示100条的问题,可以通过修改搜索结…