Novel view synthesis(NVS)
New Topic For Me to explore! 对我来说正式开启3D Image~
首先我是看了两篇review了解了这个topic的主要任务:
- advancements in radiance field techniques for volumetric video generation: a technical overview
- From Capture to Display: A Survey on Volumetric Video
另外同步阅读了huggingface的tutorial:https://huggingface.co/learn/computer-vision-course/unit8/3d-vision/nvs 。这篇博客将NVS描述为这样一个任务:
generate views from new camera angles that are plausibly consistent with a set of images.
我们在对一个场景进行3D还原时,首先的输入是一系列相机在不同的视角拍摄的静态图片,通过这些图片我们对该场景下的人物以及物体进行3D建模,但相机个数是有限的,如何推算出某个没有相机的角度上的view,这就是NVS这个任务要做的事情。
很多方法在这个topic上提出来,大致可以分成两类:1)generate an intermediate three-dimensional representation, which is rendered from a new viewing direction. 比如PixelNeFRF 2)direclty generated new views without an intermediate 3D representaion, 比如Zero123
NeRF
NeRF: Representing Scenes as Neural Radiance Fields for View Synthesis
2020年出的一篇文章,下面这句话就是它这个算法的精华:
Our algorithm represents a scene using a fully-connected (nonconvolutional) deep network, whose input is a single continuous 5D coordinate (spatial location (x, y, z) and viewing direction (θ, φ)) and whose output is the volume density and view-dependent emitted radiance at that spatial location
LLFF 数据集
在查看NeRF的github code(Pytorch)时,也有tensorflow版本,移步官方repo。发现作者使用了两个数据集,其中一个就是LLFF,本着学习的原则,先把LLFF数据集搞清楚。
LLFF全称为Local light Field Fusion,也是提出了一个NVS的算法。LLFF的主旨思想是:
present a simple and reliable method for view synthesis from a set of input images captured by a handheld camera in an irregular grid pattern.
简单说就是:该方法可以从一系列手持拍摄的静态图片生成一个scene,这个scene可以理解为一个3D的场景,可以用VR眼镜看的那种。
该LLFF repo提供了非常详细的安装教程,令我比较感兴趣的是,它可以基于自己拍摄的一些静态图片生成一个scene。先来看看它的这份代码。
我们的输入是从一系列的images开始的,首先第一步
- recover cammera poses
这一步采用COLMAP 实现了一个 struture from emotion的pipeline。这一步的输入是一系列的静态图像,输出的是这个场景下的 6-DoF camera poses和 near/far depth bounds。
Structure-from-Motion (SfM) is the process of reconstructing 3D structure from its projections into a series of images. The input is a set of overlapping images of the same object, taken from different viewpoints. The output is a 3-D reconstruction of the object, and the reconstructed intrinsic and extrinsic camera parameters of all images.

COLMAP使用的方法依赖于Structure-from-Motion Revisited这篇文章,它将SfM分成三个步骤:
- feature detection and extraction
这一步好理解,特征抽取,利用一个apprearance descriptor f
- feature matching and geometric verification
feature matching利用前一步的features找出这一系列图片中的same scene part。原文是这样写的:
The na ̈ıve approach tests every image pair for scene overlap; it searches for feature correspondences by finding the most similar feature in image I(a) for every feature in image I(b), using a similarity metric comparing the appearance fj of the features
简单理解就是根据上一步抽取的features去一一比对每一对image pair,寻找出每一对image pair中的相似feature,从而找出same scene part。
geometric verification 我觉得有点稍微难理解。上一步只是确认了每一张图片中在apperance上相似的scene part,但有可能不是指代的这个场景下的同一个object(Point), 所以就需要去verify上一步的match是否准确,怎么verify呢?通过projective geometry去预估transformation
Since matching is based solely on appearance, it is not guaranteed that corresponding features actually map to the same scene point
- structure and motion reconstruction
LLFF中实现上述第一步骤的脚本为imgs2poses.py
,
看该源代码就是基于COLMAP来做的。试着运行该脚本,测试数据用repo内的download_data.sh
下载的数据。运行完后可以看到如下输出:

其中images是source images,testscene下除了images这个文件夹,其他都是COLMAP生成的。具体含义参考COLMAP的document
logs文件内容:
1 | Need to run COLMAP |
仔细分析一下imgs2poses.py
,一共用COLMAP执行了三条terminal命令:
1 | # extract features |
对照colmap cli的guidebook,
作者使用了前三个命令,dense部分没有继续生成。想要知道它output出来的这些.bin文件含义,需要搞明白database.db
内有什么东西,它是feature
extraction的产物。db文件可以用vscode的插件打开,它包含7个table:

keypoints表格中,我下图截图的data部分里才是所有feature的信息,内有每一个feature所在的X,Y坐标。
COLMAP uses the convention that the upper left image corner has coordinate (0, 0) and the center of the upper left most pixel has coordinate (0.5, 0.5)
COLMAP在表示图像坐标时,采用了一种特定的坐标系统,其中图像的左上角被定义为坐标原点 (0, 0)。而“the center of the upper left most pixel has coordinate (0.5, 0.5)” 指的是图像中最左上角的像素的中心位置被赋予了坐标 (0.5, 0.5)。

在这两张表格中,rows表示的数值是number of detected features per image, 如果rows=0, 那么这个image没有feature
在运行命令colmap exhaustive_matcher --database_path ./data/testscene/database.db
后,db文件内的matchs这张表会出现值(之前没有),每一行会表示一张图片和另外一张图片的匹配结果,rows的值表示match上特征点的个数。
第三个命令colmap mapper...
实现了sparse
3D的重建,在前面两个命令产生的结果上(feature
extraction&match)。运行完第三条命令后,文件夹内的内容是这样的:

第三条命令产生的结果放在sparse
文件夹内。
如果用LLFF
repo内的imgs2poses.py
,脚本运行后会发现在testscene文件夹下还会出现一个poses_bounds.npy
的文件,查看imgs2poses.py
中的gen_poses方法,会发现在执行完colmap的上面三条命令后又再次运行了load_colmap_data()
和save_poses()
。其中的load_colmap_data()
对colmap三条命令的结果,也就是三个bin文件做了读取和处理:
1 | def load_colmap_data(realdir): |
这里readme中也对pose_bounds.npy
的内容进行了解释。这个文件很重要,可以看到nerf中导入LLFF数据集也是从该文件中导入的数据,导入方式只有三行代码:
1 | poses_arr = np.load(os.path.join(basedir, 'poses_bounds.npy')) |
我本来也是先看的nerf的代码,看到这里导入LLFF数据集的方式没看懂,主要是不知道pose_bounds.npy内存储的内容的具体含义,所以才补充了LLFF的上面的诸多知识空白。LLFF的作者在readme中对这个文件的含义进行了解释:
This file stores a numpy array of size Nx17 (where N is the number of input images).Each row of length 17 gets reshaped into a 3x5 pose matrix and 2 depth values that bound the closest and farthest scene content from that point of view.
这里有一个比较重要的概念”相机的姿态“(cammera poses),可以参考阅读:
其中
T_{cw}
为该点从世界坐标系变换到相机坐标系的变换矩阵,T_{wc}
为该点从相机坐标系变换到世界坐标系的变换矩阵。它们二者都可以用来表示相机的位姿,前者称为相机的外参。实践当中使用
T_{cw}
来表示相机位姿更加常见。然而在可视化程序中使用T_{wc}
来表示相机位姿更为直观,因为此时它的平移向量即为相机原点在世界坐标系中的坐标。视觉 Slam 十四讲中的第五讲的 joinMap 使用的就是T_{wc}
来表示相机位姿进行点云拼接。
poses = poses_arr[:, :-2].reshape([-1, 3, 5]).transpose([1,2,0])
取前15个数,去掉最后2个near和far,并reshape成(N,3,5)的矩阵:
┌ ┐ │ R11 R12 R13 t1 hwf1 │ │ R21 R22 R23 t2 hwf2 │ │ R31 R32 R33 t3 hwf3 │ └ ┘
其中R部分是旋转矩阵,t是平移向量。
最终
poses.shape = (3, 5, N)
,其中:
poses[:, :4, :]
→3x4
的 相机外参 (R|t
)poses[:, 4:5, :]
→3x1
的 相机内参[h, w, f]
bds = poses_arr[:, -2:].transpose([1,0])
:取出
最后两列,即 near
和 far
深度边界,形状是 (N, 2)
。
.transpose([1,0])
:交换轴顺序,使
bds.shape = (2, N)
:
- 第一维 (2):表示 最近 (
near
) 和 最远 (far
) 深度 - 第二维 (N):对应 N 张图像
- Generate MPIs
脚本为imgs2mpi2.py
。这一步是采用训练好的tensorflow模型对第一步骤的结果进行MPI的生成。这一步实现的结果如下:

- Render novel views