developy commited on
Commit
bc3fcfa
·
verified ·
1 Parent(s): 5e6ed86

fix(da2): normalize RGB input and depth prior for ApDepth

Browse files
Files changed (1) hide show
  1. DA2/depth_anything_v2/dpt.py +91 -16
DA2/depth_anything_v2/dpt.py CHANGED
@@ -192,43 +192,118 @@ class DepthAnythingV2(nn.Module):
192
  depth = F.interpolate(depth[:, None], (h, w), mode="bilinear", align_corners=True)[0, 0]
193
 
194
  return depth.cpu().numpy()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
 
196
  @torch.no_grad()
197
  def infer_batch(self, images: torch.Tensor):
 
 
 
 
 
 
 
 
 
198
  assert images.ndim == 4 and images.size(1) == 3, \
199
  f"Expect input shape [B,3,H,W], but got {images.shape}"
200
 
201
- device = next(self.parameters()).device
202
- images = images.to(device)
 
 
 
 
 
203
 
204
- B, C, H, W = images.shape
205
 
206
  new_H = int((H + 13) // 14 * 14)
207
  new_W = int((W + 13) // 14 * 14)
208
 
209
  if new_H != H or new_W != W:
210
- images_resized = F.interpolate(images, size=(new_H, new_W), mode='bilinear', align_corners=True)
211
- else:
212
- images_resized = images
 
 
 
 
 
 
 
 
 
213
 
214
  patch_h, patch_w = new_H // 14, new_W // 14
215
 
216
  features = self.pretrained.get_intermediate_layers(
217
- images_resized,
218
  self.intermediate_layer_idx[self.encoder],
219
- return_class_token=True
220
  )
221
 
222
- depth = self.depth_head(features, patch_h, patch_w)
223
- depth = F.relu(depth)
224
-
225
- depth = F.interpolate(depth, size=(H, W), mode="bilinear", align_corners=True)
226
-
227
- # return depth # [B,1,H,W]
228
 
229
- depth_3c = depth.repeat(1, 3, 1, 1) # [B,3,H,W]
 
 
 
230
 
231
- return depth_3c
232
 
233
 
234
 
 
192
  depth = F.interpolate(depth[:, None], (h, w), mode="bilinear", align_corners=True)[0, 0]
193
 
194
  return depth.cpu().numpy()
195
+
196
+ @staticmethod
197
+ def _prepare_project_rgb_for_dinov2(
198
+ images: torch.Tensor, model_dtype: torch.dtype
199
+ ) -> torch.Tensor:
200
+ """Convert ApDepth RGB tensors to the input domain expected by DINOv2.
201
+
202
+ ApDepth creates ``rgb_norm`` in ``src/dataset/base_depth_dataset.py`` as
203
+ ``rgb / 255 * 2 - 1``, so this method receives RGB in [-1, 1]. The
204
+ official DA2 ``image2tensor`` path below instead starts from RGB in
205
+ [0, 1] and applies ImageNet mean/std normalization. Passing ApDepth's
206
+ [-1, 1] tensor directly to DINOv2 skips both conversions and is the
207
+ input-range mismatch fixed here.
208
+ """
209
+ if not images.is_floating_point():
210
+ raise TypeError(f"Expected floating-point RGB, got {images.dtype}")
211
+
212
+ images_01 = ((images.to(dtype=model_dtype) + 1.0) * 0.5).clamp_(0.0, 1.0)
213
+ mean = images_01.new_tensor([0.485, 0.456, 0.406]).view(1, 3, 1, 1)
214
+ std = images_01.new_tensor([0.229, 0.224, 0.225]).view(1, 3, 1, 1)
215
+ return (images_01 - mean) / std
216
+
217
+ @staticmethod
218
+ def _relative_disparity_to_apdepth_range(
219
+ disparity: torch.Tensor, output_dtype: torch.dtype
220
+ ) -> torch.Tensor:
221
+ """Map DA2 relative disparity to ApDepth's depth/VAE convention.
222
+
223
+ DA2's relative model emits an unbounded disparity-like value: larger is
224
+ nearer. ApDepth's ``ScaleShiftDepthNormalizer`` uses [-1, 1] with
225
+ ``far_plane_at_max=True``: -1 is near and +1 is far. SD2's VAE also
226
+ expects an image-like tensor in [-1, 1]. Returning raw DA2 output from
227
+ ``infer_batch`` therefore mismatched both its direction and range.
228
+
229
+ The same 2%/98% robust per-image normalization used for the training GT
230
+ removes DA2's arbitrary relative scale. The subtraction from one then
231
+ changes disparity (near-is-large) into depth (far-is-large).
232
+ """
233
+ disparity_fp32 = disparity.float()
234
+ flat = disparity_fp32.flatten(1)
235
+ low_q = torch.quantile(flat, 0.02, dim=1).view(-1, 1, 1, 1)
236
+ high_q = torch.quantile(flat, 0.98, dim=1).view(-1, 1, 1, 1)
237
+ disparity_01 = (disparity_fp32 - low_q) / (
238
+ high_q - low_q
239
+ ).clamp_min(1e-6)
240
+ disparity_01 = disparity_01.clamp_(0.0, 1.0)
241
+
242
+ depth_01 = 1.0 - disparity_01
243
+ depth_m11 = depth_01.mul(2.0).sub(1.0)
244
+ return depth_m11.to(dtype=output_dtype)
245
 
246
  @torch.no_grad()
247
  def infer_batch(self, images: torch.Tensor):
248
+ """Build a three-channel DA2 prior compatible with ApDepth's SD2 VAE.
249
+
250
+ Args:
251
+ images: ApDepth RGB tensor [B, 3, H, W] in [-1, 1].
252
+
253
+ Returns:
254
+ Relative depth [B, 3, H, W] in [-1, 1], where -1 is near and +1
255
+ is far. This is a relative prior; it is not metric depth in metres.
256
+ """
257
  assert images.ndim == 4 and images.size(1) == 3, \
258
  f"Expect input shape [B,3,H,W], but got {images.shape}"
259
 
260
+ model_parameter = next(self.parameters())
261
+ device = model_parameter.device
262
+ output_dtype = images.dtype
263
+ # Resize in fp32 because CUDA antialiased bicubic kernels are not
264
+ # available for every fp16/bf16 PyTorch build. DINO input is cast to
265
+ # the actual DA2 weight dtype after resizing.
266
+ images = images.to(device=device, dtype=torch.float32)
267
 
268
+ _, _, H, W = images.shape
269
 
270
  new_H = int((H + 13) // 14 * 14)
271
  new_W = int((W + 13) // 14 * 14)
272
 
273
  if new_H != H or new_W != W:
274
+ images = F.interpolate(
275
+ images,
276
+ size=(new_H, new_W),
277
+ mode="bicubic",
278
+ align_corners=False,
279
+ antialias=True,
280
+ )
281
+
282
+ # RANGE CONTRACT 1: ApDepth [-1, 1] RGB -> DA2/DINOv2 ImageNet input.
283
+ images_dinov2 = self._prepare_project_rgb_for_dinov2(
284
+ images, model_dtype=model_parameter.dtype
285
+ )
286
 
287
  patch_h, patch_w = new_H // 14, new_W // 14
288
 
289
  features = self.pretrained.get_intermediate_layers(
290
+ images_dinov2,
291
  self.intermediate_layer_idx[self.encoder],
292
+ return_class_token=True,
293
  )
294
 
295
+ # DA2 relative output is disparity-like (near is large), not metric depth.
296
+ disparity = F.relu(self.depth_head(features, patch_h, patch_w))
297
+ disparity = F.interpolate(
298
+ disparity, size=(H, W), mode="bilinear", align_corners=False
299
+ )
 
300
 
301
+ # RANGE CONTRACT 2: raw DA2 disparity -> ApDepth/VAE depth in [-1, 1].
302
+ depth_m11 = self._relative_disparity_to_apdepth_range(
303
+ disparity, output_dtype=output_dtype
304
+ )
305
 
306
+ return depth_m11.repeat(1, 3, 1, 1)
307
 
308
 
309