CoolFace
Apppublic

selmee/depth-pro

sourceHugging Faceupdated 2y agoView on Hugging Face
4likes
depth_pro.py301 linesDownload Raw Back to depth_pro
1# Copyright (C) 2024 Apple Inc. All Rights Reserved.
2# Depth Pro: Sharp Monocular Metric Depth in Less Than a Second
3
4
5from __future__ import annotations
6
7from dataclasses import dataclass
8from typing import Mapping, Optional, Tuple, Union
9
10import torch
11from torch import nn
12from torchvision.transforms import (
13    Compose,
14    ConvertImageDtype,
15    Lambda,
16    Normalize,
17    ToTensor,
18)
19
20from .network.decoder import MultiresConvDecoder
21from .network.encoder import DepthProEncoder
22from .network.fov import FOVNetwork
23from .network.vit_factory import VIT_CONFIG_DICT, ViTPreset, create_vit
24
25
26@dataclass
27class DepthProConfig:
28    """Configuration for DepthPro."""
29
30    patch_encoder_preset: ViTPreset
31    image_encoder_preset: ViTPreset
32    decoder_features: int
33
34    checkpoint_uri: Optional[str] = None
35    fov_encoder_preset: Optional[ViTPreset] = None
36    use_fov_head: bool = True
37
38
39DEFAULT_MONODEPTH_CONFIG_DICT = DepthProConfig(
40    patch_encoder_preset="dinov2l16_384",
41    image_encoder_preset="dinov2l16_384",
42    checkpoint_uri="./checkpoints/depth_pro.pt",
43    decoder_features=256,
44    use_fov_head=True,
45    fov_encoder_preset="dinov2l16_384",
46)
47
48
49def create_backbone_model(
50    preset: ViTPreset
51) -> Tuple[nn.Module, ViTPreset]:
52    """Create and load a backbone model given a config.
53
54    Args:
55    ----
56        preset: A backbone preset to load pre-defind configs.
57
58    Returns:
59    -------
60        A Torch module and the associated config.
61
62    """
63    if preset in VIT_CONFIG_DICT:
64        config = VIT_CONFIG_DICT[preset]
65        model = create_vit(preset=preset, use_pretrained=False)
66    else:
67        raise KeyError(f"Preset {preset} not found.")
68
69    return model, config
70
71
72def create_model_and_transforms(
73    config: DepthProConfig = DEFAULT_MONODEPTH_CONFIG_DICT,
74    device: torch.device = torch.device("cpu"),
75    precision: torch.dtype = torch.float32,
76) -> Tuple[DepthPro, Compose]:
77    """Create a DepthPro model and load weights from `config.checkpoint_uri`.
78
79    Args:
80    ----
81        config: The configuration for the DPT model architecture.
82        device: The optional Torch device to load the model onto, default runs on "cpu".
83        precision: The optional precision used for the model, default is FP32.
84
85    Returns:
86    -------
87        The Torch DepthPro model and associated Transform.
88
89    """
90    patch_encoder, patch_encoder_config = create_backbone_model(
91        preset=config.patch_encoder_preset
92    )
93    image_encoder, _ = create_backbone_model(
94        preset=config.image_encoder_preset
95    )
96
97    fov_encoder = None
98    if config.use_fov_head and config.fov_encoder_preset is not None:
99        fov_encoder, _ = create_backbone_model(preset=config.fov_encoder_preset)
100
101    dims_encoder = patch_encoder_config.encoder_feature_dims
102    hook_block_ids = patch_encoder_config.encoder_feature_layer_ids
103    encoder = DepthProEncoder(
104        dims_encoder=dims_encoder,
105        patch_encoder=patch_encoder,
106        image_encoder=image_encoder,
107        hook_block_ids=hook_block_ids,
108        decoder_features=config.decoder_features,
109    )
110    decoder = MultiresConvDecoder(
111        dims_encoder=[config.decoder_features] + list(encoder.dims_encoder),
112        dim_decoder=config.decoder_features,
113    )
114    model = DepthPro(
115        encoder=encoder,
116        decoder=decoder,
117        last_dims=(32, 1),
118        use_fov_head=config.use_fov_head,
119        fov_encoder=fov_encoder,
120    ).to(device)
121
122    if precision == torch.half:
123        model.half()
124
125    transform = Compose(
126        [
127            ToTensor(),
128            Lambda(lambda x: x.to(device)),
129            Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]),
130            ConvertImageDtype(precision),
131        ]
132    )
133
134    if config.checkpoint_uri is not None:
135        state_dict = torch.load(config.checkpoint_uri, map_location="cpu")
136        missing_keys, unexpected_keys = model.load_state_dict(
137            state_dict=state_dict, strict=True
138        )
139
140        if len(unexpected_keys) != 0:
141            raise KeyError(
142                f"Found unexpected keys when loading monodepth: {unexpected_keys}"
143            )
144
145        # fc_norm is only for the classification head,
146        # which we would not use. We only use the encoding.
147        missing_keys = [key for key in missing_keys if "fc_norm" not in key]
148        if len(missing_keys) != 0:
149            raise KeyError(f"Keys are missing when loading monodepth: {missing_keys}")
150
151    return model, transform
152
153
154class DepthPro(nn.Module):
155    """DepthPro network."""
156
157    def __init__(
158        self,
159        encoder: DepthProEncoder,
160        decoder: MultiresConvDecoder,
161        last_dims: tuple[int, int],
162        use_fov_head: bool = True,
163        fov_encoder: Optional[nn.Module] = None,
164    ):
165        """Initialize DepthPro.
166
167        Args:
168        ----
169            encoder: The DepthProEncoder backbone.
170            decoder: The MultiresConvDecoder decoder.
171            last_dims: The dimension for the last convolution layers.
172            use_fov_head: Whether to use the field-of-view head.
173            fov_encoder: A separate encoder for the field of view.
174
175        """
176        super().__init__()
177
178        self.encoder = encoder
179        self.decoder = decoder
180    
181        dim_decoder = decoder.dim_decoder
182        self.head = nn.Sequential(
183            nn.Conv2d(
184                dim_decoder, dim_decoder // 2, kernel_size=3, stride=1, padding=1
185            ),
186            nn.ConvTranspose2d(
187                in_channels=dim_decoder // 2,
188                out_channels=dim_decoder // 2,
189                kernel_size=2,
190                stride=2,
191                padding=0,
192                bias=True,
193            ),
194            nn.Conv2d(
195                dim_decoder // 2,
196                last_dims[0],
197                kernel_size=3,
198                stride=1,
199                padding=1,
200            ),
201            nn.ReLU(True),
202            nn.Conv2d(last_dims[0], last_dims[1], kernel_size=1, stride=1, padding=0),
203            nn.ReLU(),
204        )
205
206        # Set the final convoultion layer's bias to be 0.
207        self.head[4].bias.data.fill_(0)
208
209        # Set the FOV estimation head.
210        if use_fov_head:
211            self.fov = FOVNetwork(num_features=dim_decoder, fov_encoder=fov_encoder)
212
213    @property
214    def img_size(self) -> int:
215        """Return the internal image size of the network."""
216        return self.encoder.img_size
217
218    def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor, Optional[torch.Tensor]]:
219        """Decode by projection and fusion of multi-resolution encodings.
220
221        Args:
222        ----
223            x (torch.Tensor): Input image.
224
225        Returns:
226        -------
227            The canonical inverse depth map [m] and the optional estimated field of view [deg].
228
229        """
230        _, _, H, W = x.shape
231        print("Width:", W)
232        print("Height:", H)
233        assert H == self.img_size and W == self.img_size
234
235        encodings = self.encoder(x)
236        features, features_0 = self.decoder(encodings)
237        canonical_inverse_depth = self.head(features)
238
239        fov_deg = None
240        if hasattr(self, "fov"):
241            fov_deg = self.fov.forward(x, features_0.detach())
242
243        return canonical_inverse_depth, fov_deg
244
245    @torch.no_grad()
246    def infer(
247        self,
248        x: torch.Tensor,
249        f_px: Optional[Union[float, torch.Tensor]] = None,
250        interpolation_mode="bilinear",
251    ) -> Mapping[str, torch.Tensor]:
252        """Infer depth and fov for a given image.
253
254        If the image is not at network resolution, it is resized to 1536x1536 and
255        the estimated depth is resized to the original image resolution.
256        Note: if the focal length is given, the estimated value is ignored and the provided
257        focal length is use to generate the metric depth values.
258
259        Args:
260        ----
261            x (torch.Tensor): Input image
262            f_px (torch.Tensor): Optional focal length in pixels corresponding to `x`.
263            interpolation_mode (str): Interpolation function for downsampling/upsampling. 
264
265        Returns:
266        -------
267            Tensor dictionary (torch.Tensor): depth [m], focallength [pixels].
268
269        """
270        if len(x.shape) == 3:
271            x = x.unsqueeze(0)
272        _, _, H, W = x.shape
273        resize = H != self.img_size or W != self.img_size
274
275        if resize:
276            x = nn.functional.interpolate(
277                x,
278                size=(self.img_size, self.img_size),
279                mode=interpolation_mode,
280                align_corners=False,
281            )
282
283        canonical_inverse_depth, fov_deg = self.forward(x)
284        if f_px is None:
285            f_px = 0.5 * W / torch.tan(0.5 * torch.deg2rad(fov_deg.to(torch.float)))
286        
287        inverse_depth = canonical_inverse_depth * (W / f_px)
288        f_px = f_px.squeeze()
289
290        if resize:
291            inverse_depth = nn.functional.interpolate(
292                inverse_depth, size=(H, W), mode=interpolation_mode, align_corners=False
293            )
294
295        depth = 1.0 / torch.clamp(inverse_depth, min=1e-4, max=1e4)
296
297        return {
298            "depth": depth.squeeze(),
299            "focallength_px": f_px,
300        }
301