Phase 04 - Lesson 26

Estimativa de Profundidade e Geometria Monocular

Um mapa de profundidade é uma imagem de canal único onde cada pixel é uma distância em relação à câmera. Prevê-lo a partir de um único frame RGB costumava ser impossível sem estéreo ou LiDAR. Em 2026, um encoder ViT congelado mais uma cabeça leve chega a poucos por cento da verdade de referência.

Tipo: Construir + Usar Linguagens: Python Pré-requisitos: Fase 4 Lição 14 (ViT), Fase 4 Lição 17 (Visão Auto-Supervisionada), Fase 4 Lição 07 (U-Net) Tempo: ~60 minutos

Objetivos de Aprendizagem

  • Distinguir profundidade relativa e métrica e indicar qual delas cada modelo de produção (MiDaS, Marigold, Depth Anything V3, ZoeDepth) resolve
  • Usar o Depth Anything V3 (backbone DINOv2) para prever profundidade de imagens únicas arbitrárias sem calibração
  • Explicar por que a profundidade monocular funciona a partir de uma única imagem (pistas de perspectiva, gradientes de textura, prioris aprendidos) e o que ela não consegue recuperar (escala absoluta, geometria oclusa)
  • Elevar detecções 2D para pontos 3D usando um mapa de profundidade e parâmetros intrínsecos da câmera pinhole

O Problema

A profundidade é o eixo ausente na visão computacional 2D. Dado o RGB, você sabe onde as coisas aparecem no plano da imagem; você não sabe a que distância elas estão. Sensores de profundidade (rigs estéreo, LiDAR, time-of-flight) resolvem isso diretamente, mas são caros, frágeis e limitados em alcance.

A estimativa de profundidade monocular — prever profundidade a partir de um único frame RGB — costumava produzir saída borrada e não confiável. Em 2026, grandes encoders pré-treinados mudaram isso: o Depth Anything V3 usa um backbone DINOv2 congelado e produz mapas de profundidade que generalizam por domínios internos, externos, médicos e de satélite. O Marigold reformula a profundidade como um problema de difusão condicional. O ZoeDepth regride distâncias métricas reais.

A profundidade também é a ponte entre a detecção 2D e o entendimento 3D: multiplique os pixels de uma caixa detectada pela profundidade e você eleva o objeto 2D para uma nuvem de pontos 3D. Esse é o núcleo de todo sistema de oclusão de RA, todo pipeline de desvio de obstáculos e todo robô que "pega a xícara".

O Conceito

Profundidade relativa vs métrica

  • Profundidade relativa — valores z ordenados sem unidade do mundo real. "O pixel A está mais perto que o pixel B, mas a razão das distâncias não está ancorada em metros."
  • Profundidade métrica — distância absoluta em metros em relação à câmera. Exige que o modelo tenha aprendido a relação estatística entre as pistas da imagem e a distância real.

MiDaS e Depth Anything V3 produzem profundidade relativa. O Marigold produz profundidade relativa. ZoeDepth, UniDepth e Metric3D produzem profundidade métrica. Modelos métricos são sensíveis aos parâmetros intrínsecos da câmera; modelos relativos não são.

O padrão encoder-decoder

flowchart LR
    IMG["Imagem (H x W x 3)"] --> ENC["Encoder ViT congelado<br/>(DINOv2 / DINOv3)"]
    ENC --> FEATS["Features densas<br/>(H/14, W/14, d)"]
    FEATS --> DEC["Decoder de profundidade<br/>(upsampler conv,<br/>estilo DPT)"]
    DEC --> DEPTH["Mapa de profundidade<br/>(H, W, 1)"]

    style ENC fill:#dbeafe,stroke:#2563eb
    style DEC fill:#fef3c7,stroke:#d97706
    style DEPTH fill:#dcfce7,stroke:#16a34a

O Depth Anything V3 congela o encoder e treina apenas o decoder de estilo DPT. O encoder fornece features ricas; o decoder as interpola de volta para a resolução da imagem e regride a profundidade.

Por que uma única imagem produz profundidade afinal

Uma imagem 2D contém muitas pistas monoculares que se correlacionam com a profundidade:

  • Perspectiva — linhas paralelas em 3D convergem em 2D.
  • Gradiente de textura — superfícies distantes têm textura menor e mais densa.
  • Ordem de oclusão — objetos mais próximos ocluem os mais distantes.
  • Constância de tamanho — objetos conhecidos (carros, humanos) dão escala aproximada.
  • Perspectiva atmosférica — objetos distantes parecem mais nebulosos e azulados em cenas externas.

Um ViT treinado em bilhões de imagens internaliza essas pistas. Com dados suficientes e um backbone forte, a profundidade monocular atinge precisão razoável sem qualquer supervisão 3D explícita.

O que a profundidade monocular não consegue fazer

  • Escala métrica absoluta sem parâmetros intrínsecos ou um objeto conhecido na cena. A rede consegue prever "a xícara está duas vezes mais longe que a colher" sem saber se a xícara está a 1 m ou 10 m de distância.
  • Geometria oclusa — a parte de trás de uma cadeira não é vista e não pode ser inferida de forma confiável.
  • Superfícies verdadeiramente sem textura / reflexivas — espelhos, vidro, paredes uniformes. A rede reporta profundidade plausível, mas errada.

Depth Anything V3 em 2026

  • DINOv2 ViT-L/14 puro como encoder (congelado).
  • Decoder DPT.
  • Treinado em pares de imagens com pose de fontes diversas (nenhuma supervisão explícita de profundidade necessária além da consistência fotométrica).
  • Prevê geometria espacialmente consistente a partir de um número arbitrário de entradas visuais, com ou sem poses de câmera conhecidas.
  • SOTA em profundidade monocular, geometria de qualquer vista, renderização visual e estimativa de pose de câmera.

Este é o modelo plug-and-play para chamar quando você precisa de profundidade em 2026.

Marigold — difusão para profundidade

O Marigold (Ke et al., CVPR 2024) reformula a estimativa de profundidade como difusão condicional imagem-para-imagem. Condicionamento: RGB. Alvo: mapa de profundidade. Usa uma U-Net pré-treinada do Stable Diffusion 2 como backbone. Os mapas de profundidade de saída são excepcionalmente nítidos nas bordas dos objetos. Trade-off: inferência mais lenta que modelos feed-forward (10-50 passos de denoising).

Parâmetros intrínsecos e a câmera pinhole

Para elevar um pixel (u, v) com profundidade d para um ponto 3D (X, Y, Z) em coordenadas de câmera:

fx, fy, cx, cy = camera intrinsics
X = (u - cx) * d / fx
Y = (v - cy) * d / fy
Z = d

Os parâmetros intrínsecos vêm de metadados EXIF, um padrão de calibração ou um estimador de intrínsecos monocular (Perspective Fields, UniDepth). Sem parâmetros intrínsecos, você ainda pode renderizar uma nuvem de pontos assumindo um FOV de 60-70° e principais de resolução moderada — utilizável para visualização, não para medição.

Avaliação

Duas métricas padrão:

  • AbsRel (erro relativo absoluto): mean(|d_pred - d_gt| / d_gt). Menor é melhor. 0,05-0,1 para modelos de produção.
  • delta < 1.25 (acurácia de limiar): fração de pixels onde max(d_pred/d_gt, d_gt/d_pred) < 1.25. Maior é melhor. 0,9+ para SOTA.

Para profundidade relativa (Depth Anything V3, MiDaS), a avaliação usa versões invariantes a escala e deslocamento de ambas as métricas.

Construa

Passo 1: Métricas de profundidade

import torch

def abs_rel_error(pred, target, mask=None):
    if mask is not None:
        pred = pred[mask]
        target = target[mask]
    return (torch.abs(pred - target) / target.clamp(min=1e-6)).mean().item()


def delta_accuracy(pred, target, threshold=1.25, mask=None):
    if mask is not None:
        pred = pred[mask]
        target = target[mask]
    ratio = torch.maximum(pred / target.clamp(min=1e-6), target / pred.clamp(min=1e-6))
    return (ratio < threshold).float().mean().item()

Sempre mascare pixels de profundidade inválidos (zero, NaN, saturados) antes da avaliação.

Passo 2: Alinhamento de escala e deslocamento

Para modelos de profundidade relativa, alinhe a previsão à verdade de referência antes de computar as métricas. Ajuste por mínimos quadrados de a * pred + b = target:

def align_scale_shift(pred, target, mask=None):
    if mask is not None:
        p = pred[mask]
        t = target[mask]
    else:
        p = pred.flatten()
        t = target.flatten()
    A = torch.stack([p, torch.ones_like(p)], dim=1)
    coeffs, *_ = torch.linalg.lstsq(A, t.unsqueeze(-1))
    a, b = coeffs[:2, 0]
    return a * pred + b

Execute align_scale_shift antes de abs_rel_error ao avaliar MiDaS / Depth Anything.

Passo 3: Elevar a profundidade para uma nuvem de pontos

import numpy as np

def depth_to_point_cloud(depth, intrinsics):
    H, W = depth.shape
    fx, fy, cx, cy = intrinsics
    v, u = np.meshgrid(np.arange(H), np.arange(W), indexing="ij")
    z = depth
    x = (u - cx) * z / fx
    y = (v - cy) * z / fy
    return np.stack([x, y, z], axis=-1)


depth = np.random.uniform(0.5, 4.0, (240, 320))
intr = (320.0, 320.0, 160.0, 120.0)
pc = depth_to_point_cloud(depth, intr)
print(f"point cloud shape: {pc.shape}  (H, W, 3)")

Uma função, toda aplicação elevada para 3D. Exporte a nuvem de pontos para .ply e abra no MeshLab ou CloudCompare.

Passo 4: Teste de fumaça com uma cena de profundidade sintética

def synthetic_depth(size=96):
    yy, xx = np.meshgrid(np.arange(size), np.arange(size), indexing="ij")
    # Floor: linear gradient from near (top) to far (bottom)
    depth = 1.0 + (yy / size) * 4.0
    # Box in the middle: closer
    mask = (np.abs(xx - size / 2) < size / 6) & (np.abs(yy - size * 0.6) < size / 6)
    depth[mask] = 2.0
    return depth.astype(np.float32)


gt = torch.from_numpy(synthetic_depth(96))
pred = gt + 0.3 * torch.randn_like(gt)  # simulated prediction
aligned = align_scale_shift(pred, gt)
print(f"before align  absRel = {abs_rel_error(pred, gt):.3f}")
print(f"after align   absRel = {abs_rel_error(aligned, gt):.3f}")

Passo 5: Uso do Depth Anything V3 (referência)

import torch
from transformers import pipeline
from PIL import Image

pipe = pipeline(task="depth-estimation", model="LiheYoung/depth-anything-v2-large")

image = Image.open("street.jpg").convert("RGB")
out = pipe(image)
depth_np = np.array(out["depth"])

Três linhas. out["depth"] é um PIL em escala de cinza; converta para numpy para cálculos. Para o Depth Anything V3 especificamente, troque o id do modelo assim que for lançado; a API permanece inalterada.

Use

  • Depth Anything V3 (Meta AI / ByteDance, 2024-2026) — o padrão para profundidade relativa. O modelo de backbone ViT-large mais rápido em produção.
  • Marigold (ETH, 2024) — maior qualidade visual, inferência lenta.
  • UniDepth (ETH, 2024) — profundidade métrica com estimativa de parâmetros intrínsecos da câmera.
  • ZoeDepth (Intel, 2023) — profundidade métrica; mais antigo, ainda confiável.
  • MiDaS v3.1 — legado mas estável; boa baseline para comparação.

Padrão típico de integração:

  1. Chega um frame RGB.
  2. O modelo de profundidade produz o mapa de profundidade.
  3. O detector produz caixas.
  4. Eleve os centroides das caixas pela profundidade para 3D; combine com a nuvem de pontos se disponível.
  5. A jusante: oclusão de RA, planejamento de trajeto, estimativa de tamanho de objeto, substituição de estéreo.

Para uso em tempo real, o Depth Anything V2 Small (quantizado INT8) atinge ~30 fps em uma GPU de consumo a 518x518.

Entregue

Esta lição produz:

  • outputs/prompt-depth-model-picker.md — escolhe entre Depth Anything V3, Marigold, UniDepth, MiDaS dada a latência, a necessidade de métrica-vs-relativa e o tipo de cena.
  • outputs/skill-depth-to-pointcloud.md — uma skill que constrói nuvens de pontos a partir de mapas de profundidade com tratamento correto de parâmetros intrínsecos e exportação para .ply.

Exercícios

  1. (Fácil) Execute o Depth Anything V2 em quaisquer 10 imagens da sua mesa. Salve a profundidade como PNGs em escala de cinza e inspecione. Identifique um objeto cuja profundidade prevista pareça errada e explique por que as pistas monoculares falharam.
  2. (Médio) Dado RGB + profundidade do Depth Anything V2, eleve para uma nuvem de pontos e renderize com open3d. Compare duas cenas (interna / externa) e note qual parece mais crível.
  3. (Difícil) Tire cinco pares de imagens que diferem apenas pela posição de um objeto conhecido (por exemplo, uma garrafa movida 30 cm para mais perto). Use o UniDepth para prever a profundidade métrica em ambas. Reporte o delta de distância previsto vs os 30 cm reais.

Termos-Chave

Termo O que as pessoas dizem O que realmente significa
Profundidade monocular "Profundidade de imagem única" Estimativa de profundidade a partir de um frame RGB, sem estéreo ou LiDAR
Profundidade relativa "Profundidade ordenada" Valores z ordenados sem unidades do mundo real
Profundidade métrica "Distância absoluta" Profundidade em metros; requer calibração ou um modelo treinado com supervisão métrica
AbsRel "Erro relativo absoluto" Média de
Acurácia delta "delta < 1.25" Fração de pixels com previsão dentro de 25% da verdade de referência
Câmera pinhole "fx, fy, cx, cy" O modelo de câmera usado para elevar (u, v, d) para (X, Y, Z)
DPT "Dense Prediction Transformer" O decoder baseado em conv usado sobre encoders ViT congelados para profundidade
Backbone DINOv2 "A razão de funcionar" Features auto-supervisionadas que generalizam por domínios sem rótulos de profundidade

Leitura Adicional

0 lifetime access. Curriculum based on AI Engineering from Scratch by Rohit Ghumare (MIT, used under attribution).