网络知识 娱乐 【环境搭建】instant-ngp的环境搭建和demo

【环境搭建】instant-ngp的环境搭建和demo

前言

NeRF近年火遍大江南北,但是训练时间长(五六小时起步)的问题一直是美中不足的地方。于是,各种魔改版本你方唱罢我登场,层出不穷。但是,instant-ngp一出,把大家给整破防了,居然特喵的能这么快?训练一个场景几分钟甚至几秒就能出结果,还能实时渲染新视角?还有可视化工具?逆天了呀!哪怕没了解过NeRF的人拿着GUI都能玩一玩!这篇文章给大家梳理一下环境搭建和一些README当中没有说明的隐藏使用,以及一些小demo。

环境搭建

代码地址:https://github.com/NVlabs/instant-ngp
其实NVlabs的README已经很详尽了,一般情况下跟着递归克隆仓库、创建conda虚拟环境、安装requirements和build一下就好了,我至今在多台不同配置(显卡、操作系统、python版本)的电脑上安装过,都是全流程无bug。如果很不幸遇到了的话,他们也已经总结得很详细了,应该不会有太大问题。样例数据集也很轻松就可以跑起来,相信大家一定没有问题!接下来直接上干货了。

用自己的数据集怎么跑?

这一块其实他们也有文档,点这里。和其他NeRF基本一个套路,拿到图片,跑colmap先做稀疏重建,拿到相机信息和一些图片的位姿信息,得到一个transform.json文件,这就是对这个场景的训练集了。值得注意的是,colmap本身是不够完善的,只凭借视觉,会有一些找不到共视关系的图被丢掉,或者分成不同的component,有可能100张图片最后json文件里只有30张。要解决这个问题有两个办法,一个就是重新拍,尽可能拍得密集切角度不要骤然转换得太厉害,一个办法就是引入GPS信息之类的,不通过colmap,咱直接有pose,那自己再写个脚本存成对应格式就可以了。

重建的话,有以下3种情况:

  1. 如果只有图片,则需先跑colmap,再进行重建
  2. 如果拥有图片和colmap组织形式的SFM数据(txt),只需要转一个transform.json文件
  3. 如果有视频,也可以通过视频重建,先跑colmap,这里先不讨论

GUI用起来虽然爽,但是也有局限,输命令能做到的事会更多一点,接下来讲一下命令行使用instant-ngp。

文件结构如下:
|
——instant-ngp
      |
      ——data
                |
                ———场景scene,比如fox
                             |
                             | ——— colmap
                             | ——— images

情况1:

进入到scene文件夹,调用colmap

python <path-to-instant-ngp>/scripts/colmap2nerf.py --colmap_matcher exhaustive --run_colmap --aabb_scale 16

然后,到instant-ngp目录下:

./build/testbed --mode nerf --scene data/nerf/fox/

情况2:

情况2:
进入到scene所在的文件夹:

python <path-to-instant-ngp>/scripts/sfm2nerf.py  --aabb_scale 16

然后,到instant-ngp目录下:

./build/testbed --mode nerf --scene data/xgrids/yoda/

如果有训练好的模型想直接打开而非从头学习的话:
./build/testbed --mode nerf --scene data/nerf/fox/ --snapshot=data/nerf/fox/base.msgpack

保存结果

这一步其实是让人头疼的地方,GUI点点点很爽,看起来也很方便,可是这么美好的场景想要保存、要导出却犯了难,怎么办呢?

导出mesh

使用的是marching cube,效果比较差:

python scripts/run.py --scene data/nerf/fox/ --mode nerf --load_snapshot data/nerf/fox/base.msgpack  --save_mesh data/nerf/fox/mesh.obj

导出图片

–scene 场景的路径
–mode 模式,选nerf即可
–load_snapshot 保存的训练好的模型
–screenshot_transforms 需要渲染的角度,数据结构和nerf的json格式一样
–screenshot_frames 渲染哪一帧,如果想全部渲染就不要这个参数,会默认渲染全部
–screenshot_dir 渲染好的图片存储的位置
–width 图片宽度
–height 图片高度

样例数据Nerf fox:
python scripts/run.py --scene data/nerf/fox/ --mode nerf --load_snapshot data/nerf/fox/base.msgpack --screenshot_transforms data/nerf/fox/transforms.json --screenshot_frames 1 --screenshot_dir data/nerf/fox/screenshot --width 2048 --height 2048 --n_steps 0

自己的数据:
python scripts/run.py --scene <path-to-scene> --mode nerf --load_snapshot <path-to-snapshot> --screenshot_transforms <path-to-scene>/transforms.json --screenshot_frames 1 --screenshot_dir <path-to-screenshot-folder> --width 2048 --height 2048 --n_steps 0

导出视频

最简单的办法自然就是把前面训练集的transform.json对应的那些视角渲染的图片串起来了,但是这样有两个问题。第一,受限于拍摄的不可靠性,无法确认所有训练集图片渲染出来的也很好,人为再挑选太麻烦。第二,我们可能会想要看到沿着某个路径行走或者向四周旋转查看的效果,我们应该提供人为选择一些新视角图片然后再渲染的功能。基于此,我写了一个通过在GUI里自己选择的渲染视角来合成一个视频的脚本。
在GUI当中选择新的视角,然后在camera path窗口add,这样这个视角下的相机参数会被添加到base_cam.json当中。
运行rendervideo.py

python scripts/rendervideo.py --scene <scene_path> --n_seconds <seconds> --fps <fps> --render_name <name> --width <resolution_width> --height <resolution_height>

比如:
python scripts/rendervideo.py --scene data/nerf/fox --n_seconds 10 --fps 60 --render_name foxvideo --width 1920 --height 1080

然后mp4视频都会被保存在instant-ngp下(而非各自数据集下),所以注意命名的时候就要做好区分。
rendervideo.py的代码如下:

#!/usr/bin/env python3

# Copyright (c) 2020-2022, NVIDIA CORPORATION.  All rights reserved.
#
# NVIDIA CORPORATION and its licensors retain all intellectual property
# and proprietary rights in and to this software, related documentation
# and any modifications thereto.  Any use, reproduction, disclosure or
# distribution of this software and related documentation without an express
# license agreement from NVIDIA CORPORATION is strictly prohibited.

import os, sys, shutil
import argparse
from tqdm import tqdm
import common
import pyngp as ngp # noqa
import numpy as np

"""Render the video based on specific camera poses.

    Render a series of images from specific views and then generate a smoothing video.

    Args:
    scene:
        The scene to load. Can be the scene's name or a full path to the training data.
    width:
        Resolution width of video
    height:
        Resolution width of video
    n_seconds:
        Number of seconds
    fps:
        FPS of the video
    render_name:
        Name of video.

    Returns:
    None. A video will be saved at scene root path
"""
def render_video(resolution, numframes, scene, name, spp, fps, exposure=0):
	testbed = ngp.Testbed(ngp.TestbedMode.Nerf)
	testbed.load_snapshot(os.path.join(scene, "base.msgpack"))
	testbed.load_camera_path(os.path.join(scene, "base_cam.json"))

	if 'temp' in os.listdir():
		shutil.rmtree('temp')
	os.makedirs('temp')	

	for i in tqdm(list(range(min(numframes,numframes+1))), unit="frames", desc=f"Rendering"):
		testbed.camera_smoothing = i > 0
		frame = testbed.render(resolution[0], resolution[1], spp, True, float(i)/numframes, float(i + 1)/numframes, fps, shutter_fraction=0.5)
		common.write_image(f"temp/{i:04d}.jpg", np.clip(frame * 2**exposure, 0.0, 1.0), quality=100)

	os.system(f"ffmpeg -i temp/%04d.jpg -vf "fps={fps}" -c:v libx264 -pix_fmt yuv420p {name}_test.mp4")
	shutil.rmtree('temp')


def parse_args():
	parser = argparse.ArgumentParser(description="render neural graphics primitives testbed")
	parser.add_argument("--scene", "--training_data", default="", help="The scene to load. Can be the scene's name or a full path to the training data.")

	parser.add_argument("--width", "--screenshot_w", type=int, default=1920, help="Resolution width of the render video")
	parser.add_argument("--height", "--screenshot_h", type=int, default=1080, help="Resolution height of the render video")
	parser.add_argument("--n_seconds", type=int, default=1, help="Number of seconds")
	parser.add_argument("--fps", type=int, default=60, help="number of fps")
	parser.add_argument("--render_name", type=str, default="", help="name of the result video")

	args = parser.parse_args()
	return args

if __name__ == "__main__":
	args = parse_args()	
	render_video([args.width, args.height], args.n_seconds*args.fps, args.scene, args.render_name, spp=8, fps=args.fps)