forked from IVRL/VolRecon
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Fangjinhua Wang
committed
Mar 30, 2023
1 parent
bf32fe4
commit 868d242
Showing
34 changed files
with
3,819 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,127 @@ | ||
# VolRecon (CVPR 2023) | ||
# VolRecon | ||
|
||
> Code of paper 'VolRecon: Volume Rendering of Signed Ray Distance Functions for Generalizable Multi-View Reconstruction' (CVPR 2023) | ||
### [Project](https://fangjinhuawang.github.io/VolRecon/) | [arXiv](https://arxiv.org/abs/2212.08067) | ||
|
||
![teaser](./imgs/teaser.jpg) | ||
|
||
>**Abstract:** The success of the Neural Radiance Fields (NeRF) in novel view synthesis has inspired researchers to propose neural implicit scene reconstruction. However, most existing neural implicit reconstruction methods optimize perscene parameters and therefore lack generalizability to new scenes. We introduce VolRecon, a novel generalizable implicit reconstruction method with Signed Ray Distance Function (SRDF). To reconstruct the scene with fine details and little noise, VolRecon combines projection features aggregated from multi-view features, and volume features interpolated from a coarse global feature volume. Using a ray transformer, we compute SRDF values of sampled points on a ray and then render color and depth. On DTU dataset, VolRecon outperforms SparseNeuS by about 30% in sparse view reconstruction and achieves comparable accuracy as MVSNet in full view reconstruction. Furthermore, our approach exhibits good generalization performance on the large-scale ETH3D benchmark. | ||
If you find this project useful for your research, please cite: | ||
|
||
``` | ||
@misc{ren2022volrecon, | ||
title={VolRecon: Volume Rendering of Signed Ray Distance Functions for Generalizable Multi-View Reconstruction}, | ||
author={Yufan Ren and Fangjinhua Wang and Tong Zhang and Marc Pollefeys and Sabine Süsstrunk}, | ||
journal={CVPR}, | ||
year={2023} | ||
} | ||
``` | ||
|
||
|
||
## Installation | ||
|
||
### Requirements | ||
|
||
* python 3.8 | ||
* CUDA 10.2 | ||
|
||
``` | ||
conda create --name volrecon python=3.8 pip | ||
conda activate volrecon | ||
pip install -r requirements.txt | ||
``` | ||
|
||
|
||
|
||
## Reproducing Sparse View Reconstruction on DTU | ||
|
||
* Download pre-processed [DTU dataset](https://drive.google.com/file/d/1cMGgIAWQKpNyu20ntPAjq3ZWtJ-HXyb4/view?usp=sharing). The dataset is organized as follows: | ||
``` | ||
root_directory | ||
├──cameras | ||
├── 00000000_cam.txt | ||
├── 00000001_cam.txt | ||
└── ... | ||
├──pair.txt | ||
├──scan24 | ||
├──scan37 | ||
├── image | ||
│ ├── 000000.png | ||
│ ├── 000001.png | ||
│ └── ... | ||
└── mask | ||
├── 000.png | ||
├── 001.png | ||
└── ... | ||
``` | ||
|
||
Camera file ``cam.txt`` stores the camera parameters, which includes extrinsic, intrinsic, minimum depth and depth range interval: | ||
``` | ||
extrinsic | ||
E00 E01 E02 E03 | ||
E10 E11 E12 E13 | ||
E20 E21 E22 E23 | ||
E30 E31 E32 E33 | ||
intrinsic | ||
K00 K01 K02 | ||
K10 K11 K12 | ||
K20 K21 K22 | ||
DEPTH_MIN DEPTH_INTERVAL | ||
``` | ||
|
||
``pair.txt `` stores the view selection result. For each reference image, 10 best source views are stored in the file: | ||
``` | ||
TOTAL_IMAGE_NUM | ||
IMAGE_ID0 # index of reference image 0 | ||
10 ID0 SCORE0 ID1 SCORE1 ... # 10 best source images for reference image 0 | ||
IMAGE_ID1 # index of reference image 1 | ||
10 ID0 SCORE0 ID1 SCORE1 ... # 10 best source images for reference image 1 | ||
... | ||
``` | ||
|
||
* In `script/eval_dtu.sh`, set `DATASET` as the root directory of the dataset, set `OUT_DIR` as the directory to store the rendered depth maps. `CKPT_FILE` is the path of the checkpoint file (default as our model pretrained on DTU). Run `bash eval_dtu.sh` on GPU. By Default, 3 images (`--test_n_view 3`) in image set 0 (`--set 0`) are used for testing. | ||
* In ``tsdf_fusion.sh``, set `ROOT_DIR` as the directory that stores the rendered depth maps. Run `bash tsdf_fusion.sh` on GPU to get the reconstructed meshes in `mesh` directory. | ||
* For quantitative evaluation, download [SampleSet](http://roboimagedata.compute.dtu.dk/?page_id=36) and [Points](http://roboimagedata.compute.dtu.dk/?page_id=36) from DTU's website. Unzip them and place `Points` folder in `SampleSet/MVS Data/`. The structure looks like: | ||
``` | ||
SampleSet | ||
├──MVS Data | ||
└──Points | ||
``` | ||
* Following SparseNeuS, we clean the raw mesh with object masks by running: | ||
``` | ||
python evaluation/clean_mesh.py --root_dir "PATH_TO_DTU_TEST" --n_view 3 --set 0 | ||
``` | ||
* Get the quantitative results by running evaluation code: | ||
``` | ||
python evaluation/dtu_eval.py --dataset_dir "PATH_TO_SampleSet_MVS_Data" | ||
``` | ||
* Note that you can change `--set` in ``eval_dtu.sh`` and `--set` during mesh cleaning to use different image sets (0 or 1). By default, image set 0 is used. The average performance of sets 0 and 1 | ||
|
||
|
||
|
||
## Training on DTU | ||
|
||
* Download pre-processed [DTU's training set](https://drive.google.com/file/d/1eDjh-_bxKKnEuz5h-HXS7EDJn59clx6V/view) and [Depths_raw](https://virutalbuy-public.oss-cn-hangzhou.aliyuncs.com/share/cascade-stereo/CasMVSNet/dtu_data/dtu_train_hr/Depths_raw.zip) (both provided by MVSNet). Then organize the dataset as follows: | ||
``` | ||
root_directory | ||
├──Cameras | ||
├──Rectified | ||
└──Depths_raw | ||
``` | ||
* In ``train_dtu.sh``, set `DATASET` as the root directory of dataset; set `LOG_DIR` as the directory to store the checkpoints. | ||
|
||
* Train the model by running `bash train_dtu.sh` on GPU. | ||
|
||
|
||
|
||
## Acknowledgement | ||
|
||
Part of the code is based on [SparseNeuS](https://github.com/xxlong0/SparseNeuS) and [IBRNet](https://github.com/googleinterns/IBRNet). | ||
|
||
|
||
|
||
Code will be available before end of March. |
Binary file not shown.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
from .transformer import LocalFeatureTransformer | ||
from .position_encoding import PositionEncodingSine |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
""" | ||
Linear Transformer proposed in "Transformers are RNNs: Fast Autoregressive Transformers with Linear Attention" | ||
Modified from: https://github.com/idiap/fast-transformers/blob/master/fast_transformers/attention/linear_attention.py | ||
""" | ||
|
||
import torch | ||
from torch.nn import Module, Dropout | ||
|
||
|
||
def elu_feature_map(x): | ||
return torch.nn.functional.elu(x) + 1 | ||
|
||
|
||
class LinearAttention(Module): | ||
def __init__(self, eps=1e-6): | ||
super().__init__() | ||
self.feature_map = elu_feature_map | ||
self.eps = eps | ||
|
||
def forward(self, queries, keys, values, q_mask=None, kv_mask=None): | ||
""" Multi-Head linear attention proposed in "Transformers are RNNs" | ||
Args: | ||
queries: [N, L, H, D] | ||
keys: [N, S, H, D] | ||
values: [N, S, H, D] | ||
q_mask: [N, L] | ||
kv_mask: [N, S] | ||
Returns: | ||
queried_values: (N, L, H, D) | ||
""" | ||
Q = self.feature_map(queries) | ||
K = self.feature_map(keys) | ||
|
||
# set padded position to zero | ||
if q_mask is not None: | ||
Q = Q * q_mask[:, :, None, None] | ||
if kv_mask is not None: | ||
K = K * kv_mask[:, :, None, None] | ||
values = values * kv_mask[:, :, None, None] | ||
|
||
v_length = values.size(1) | ||
values = values / v_length # prevent fp16 overflow | ||
KV = torch.einsum("nshd,nshv->nhdv", K, values) # (S,D)' @ S,V | ||
Z = 1 / (torch.einsum("nlhd,nhd->nlh", Q, K.sum(dim=1)) + self.eps) | ||
queried_values = torch.einsum("nlhd,nhdv,nlh->nlhv", Q, KV, Z) * v_length | ||
|
||
return queried_values.contiguous() | ||
|
||
|
||
class FullAttention(Module): | ||
def __init__(self, use_dropout=False, attention_dropout=0.1): | ||
super().__init__() | ||
self.use_dropout = use_dropout | ||
self.dropout = Dropout(attention_dropout) | ||
|
||
def forward(self, queries, keys, values, q_mask=None, kv_mask=None): | ||
""" Multi-head scaled dot-product attention, a.k.a full attention. | ||
Args: | ||
queries: [N, L, H, D] | ||
keys: [N, S, H, D] | ||
values: [N, S, H, D] | ||
q_mask: [N, L] | ||
kv_mask: [N, S] | ||
Returns: | ||
queried_values: (N, L, H, D) | ||
""" | ||
|
||
# Compute the unnormalized attention and apply the masks | ||
QK = torch.einsum("nlhd,nshd->nlsh", queries, keys) | ||
if kv_mask is not None: | ||
QK.masked_fill_(~(q_mask[:, :, None, None] * kv_mask[:, None, :, None]), float('-inf')) | ||
|
||
# Compute the attention and the weighted average | ||
softmax_temp = 1. / queries.size(3)**.5 # sqrt(D) | ||
A = torch.softmax(softmax_temp * QK, dim=2) | ||
if self.use_dropout: | ||
A = self.dropout(A) | ||
|
||
queried_values = torch.einsum("nlsh,nshd->nlhd", A, values) | ||
|
||
return queried_values.contiguous() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import math | ||
import torch | ||
from torch import nn | ||
|
||
|
||
class PositionEncodingSine(nn.Module): | ||
""" | ||
This is a sinusoidal position encoding that generalized to 2-dimensional images | ||
""" | ||
|
||
def __init__(self, d_model, max_shape=(256, 256), temp_bug_fix=False): | ||
""" | ||
Args: | ||
max_shape (tuple): for 1/8 featmap, the max length of 256 corresponds to 2048 pixels | ||
temp_bug_fix (bool): As noted in this [issue](https://github.com/zju3dv/LoFTR/issues/41), | ||
the original implementation of LoFTR includes a bug in the pos-enc impl, which has little impact | ||
on the final performance. For now, we keep both impls for backward compatability. | ||
We will remove the buggy impl after re-training all variants of our released models. | ||
""" | ||
super().__init__() | ||
pe = torch.zeros((d_model, *max_shape)) | ||
y_position = torch.ones(max_shape).cumsum(0).float().unsqueeze(0) | ||
x_position = torch.ones(max_shape).cumsum(1).float().unsqueeze(0) | ||
if temp_bug_fix: | ||
div_term = torch.exp(torch.arange(0, d_model//2, 2).float() * (-math.log(10000.0) / (d_model//2))) | ||
else: # a buggy implementation (for backward compatability only) | ||
div_term = torch.exp(torch.arange(0, d_model//2, 2).float() * (-math.log(10000.0) / d_model//2)) | ||
div_term = div_term[:, None, None] # [C//4, 1, 1] | ||
pe[0::4, :, :] = torch.sin(x_position * div_term) | ||
pe[1::4, :, :] = torch.cos(x_position * div_term) | ||
pe[2::4, :, :] = torch.sin(y_position * div_term) | ||
pe[3::4, :, :] = torch.cos(y_position * div_term) | ||
|
||
# self.register_buffer('pe', pe.unsqueeze(0), persistent=False) # [1, C, H, W] | ||
self.register_buffer('pe', pe.unsqueeze(0)) # [1, C, H, W] | ||
|
||
def forward(self, x): | ||
""" | ||
Args: | ||
x: [N, C, H, W] | ||
""" | ||
return x + self.pe[:, :, :x.size(2), :x.size(3)].to(x.device) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import copy | ||
import torch | ||
import torch.nn as nn | ||
from .linear_attention import LinearAttention, FullAttention | ||
|
||
#Ref: https://github.com/zju3dv/LoFTR/blob/master/src/loftr/loftr_module/transformer.py | ||
class LoFTREncoderLayer(nn.Module): | ||
def __init__(self, | ||
d_model, | ||
nhead, | ||
attention='linear'): | ||
super(LoFTREncoderLayer, self).__init__() | ||
|
||
self.dim = d_model // nhead | ||
self.nhead = nhead | ||
|
||
# multi-head attention | ||
self.q_proj = nn.Linear(d_model, d_model, bias=False) | ||
self.k_proj = nn.Linear(d_model, d_model, bias=False) | ||
self.v_proj = nn.Linear(d_model, d_model, bias=False) | ||
self.attention = LinearAttention() if attention == 'linear' else FullAttention() | ||
self.merge = nn.Linear(d_model, d_model, bias=False) | ||
|
||
# feed-forward network | ||
self.mlp = nn.Sequential( | ||
nn.Linear(d_model*2, d_model*2, bias=False), | ||
nn.ReLU(), | ||
nn.Linear(d_model*2, d_model, bias=False), | ||
) | ||
|
||
# norm and dropout | ||
self.norm1 = nn.LayerNorm(d_model) | ||
self.norm2 = nn.LayerNorm(d_model) | ||
|
||
def forward(self, x, source, x_mask=None, source_mask=None): | ||
""" | ||
Args: | ||
x (torch.Tensor): [N, L, C] | ||
source (torch.Tensor): [N, S, C] | ||
x_mask (torch.Tensor): [N, L] (optional) | ||
source_mask (torch.Tensor): [N, S] (optional) | ||
""" | ||
bs = x.size(0) | ||
query, key, value = x, source, source | ||
|
||
# multi-head attention | ||
query = self.q_proj(query).view(bs, -1, self.nhead, self.dim) # [N, L, (H, D)] | ||
key = self.k_proj(key).view(bs, -1, self.nhead, self.dim) # [N, S, (H, D)] | ||
value = self.v_proj(value).view(bs, -1, self.nhead, self.dim) | ||
message = self.attention(query, key, value, q_mask=x_mask, kv_mask=source_mask) # [N, L, (H, D)] | ||
message = self.merge(message.view(bs, -1, self.nhead*self.dim)) # [N, L, C] | ||
message = self.norm1(message) | ||
|
||
# feed-forward network | ||
message = self.mlp(torch.cat([x, message], dim=2)) | ||
message = self.norm2(message) | ||
|
||
return x + message | ||
|
||
|
||
class LocalFeatureTransformer(nn.Module): | ||
"""A Local Feature Transformer (LoFTR) module.""" | ||
|
||
def __init__(self, d_model, nhead, layer_names, attention): | ||
super(LocalFeatureTransformer, self).__init__() | ||
|
||
self.d_model = d_model | ||
self.nhead = nhead | ||
self.layer_names = layer_names | ||
encoder_layer = LoFTREncoderLayer(d_model, nhead, attention) | ||
self.layers = nn.ModuleList([copy.deepcopy(encoder_layer) for _ in range(len(self.layer_names))]) | ||
self._reset_parameters() | ||
|
||
def _reset_parameters(self): | ||
for p in self.parameters(): | ||
if p.dim() > 1: | ||
nn.init.xavier_uniform_(p) | ||
|
||
def forward(self, feat0, feat1=None, mask0=None, mask1=None): | ||
""" | ||
Args: | ||
feat0 (torch.Tensor): [N, L, C] | ||
feat1 (torch.Tensor): [N, S, C] | ||
mask0 (torch.Tensor): [N, L] (optional) | ||
mask1 (torch.Tensor): [N, S] (optional) | ||
""" | ||
assert self.d_model == feat0.size(2), "the feature number of src and transformer must be equal" | ||
|
||
for layer, name in zip(self.layers, self.layer_names): | ||
if name == 'self': | ||
feat0 = layer(feat0, feat0, mask0, mask0) | ||
# if feat1 is not None: | ||
# feat1 = layer(feat1, feat1, mask1, mask1) | ||
elif name == 'cross': | ||
feat0 = layer(feat0, feat1, mask0, mask1) | ||
# feat1 = layer(feat1, feat0, mask1, mask0) | ||
else: | ||
raise KeyError | ||
# if feat1 is not None: | ||
# return feat0, feat1 | ||
# else: | ||
return feat0 |
Oops, something went wrong.