Cómo estimar la profundidad a partir de una sola imagen |  por Jacob Marks, Ph.D.  |  enero de 2024

Ejecute y evalúe modelos de estimación de profundidad monocular con Hugging Face y FiftyOne

Mapas de calor de profundidad monocular generados con Marigold en imágenes de profundidad v2 de NYU. Imagen cortesía del autor.

Los humanos ven el mundo a través de dos ojos. Uno de los principales beneficios de este binocular La visión es la capacidad de percibir. profundidad — qué tan cerca o lejos están los objetos. El cerebro humano infiere la profundidad de los objetos comparando las imágenes capturadas por el ojo izquierdo y derecho al mismo tiempo e interpretando las disparidades. Este proceso se conoce como estereopsis.

Así como la percepción de la profundidad desempeña un papel crucial en la visión y la navegación humanas, la capacidad de estimar la profundidad es fundamental para una amplia gama de aplicaciones de visión por computadora, desde la conducción autónoma hasta la robótica e incluso la realidad aumentada. Sin embargo, una serie de consideraciones prácticas, desde limitaciones espaciales hasta restricciones presupuestarias, a menudo limitan estas aplicaciones a una sola cámara.

Estimación de profundidad monocular (MDE) es la tarea de predecir la profundidad de una escena a partir de una sola imagen. El cálculo de profundidad a partir de una sola imagen es inherentemente ambiguo, ya que existen múltiples formas de proyectar la misma escena 3D en el plano 2D de una imagen. Como resultado, MDE es una tarea desafiante que requiere (ya sea explícita o implícitamente) tener en cuenta muchas señales como el tamaño del objeto, la oclusión y la perspectiva.

En esta publicación, ilustraremos cómo cargar y visualizar datos de mapas de profundidad, ejecutar modelos de estimación de profundidad monoculares y evaluar predicciones de profundidad. Lo haremos utilizando datos del SOL RGB-D conjunto de datos.

En particular, cubriremos lo siguiente:

Usaremos la Cara de Abrazo transformadores y difusores bibliotecas para inferencia, Cincuenta y uno para la gestión y visualización de datos, y imagen-scikit para métricas de evaluación. Todas estas bibliotecas son de código abierto y de uso gratuito. Descargo de responsabilidad: trabajo en Voxel51, el mantenedor principal de una de estas bibliotecas (FiftyOne).

Antes de comenzar, asegúrese de tener instaladas todas las bibliotecas necesarias:

pip install -U torch fiftyone diffusers transformers scikit-image

Luego importaremos los módulos que usaremos a lo largo de la publicación:

from glob import glob
import numpy as np
from PIL import Image
import torch

import fiftyone as fo
import fiftyone.zoo as foz
import fiftyone.brain as fob
from fiftyone import ViewField as F

El Conjunto de datos SUN RGB-D contiene 10,335 imágenes RGB-D, cada una de las cuales tiene una imagen RGB, una imagen de profundidad y elementos intrínsecos de cámara correspondientes. Contiene imágenes de la Profundidad de la Universidad de Nueva York v2berkeley B3DOy SOL3D conjuntos de datos. SOL RGB-D es una de las más populares ¡Conjuntos de datos para tareas de estimación de profundidad monocular y segmentación semántica!

💡Para este tutorial, solo usaremos las partes de profundidad v2 de NYU. La profundidad v2 de la Universidad de Nueva York es licencia permisiva para uso comercial (MIT), y puede ser descargado de Hugging Face directamente.

Descarga de los datos sin procesar

Primero, descargue el conjunto de datos SUN RGB-D desde aquí y descomprímelo, o usa el siguiente comando para descargarlo directamente:

curl -o sunrgbd.zip https://rgbd.cs.princeton.edu/data/SUNRGBD.zip

Y luego descomprímelo:

unzip sunrgbd.zip

Si desea utilizar el conjunto de datos para otras tareas, puede convertir completamente las anotaciones y cargarlas en su fiftyone.Dataset. Sin embargo, para este tutorial, solo usaremos las imágenes de profundidad, por lo que solo usaremos las imágenes RGB y las imágenes de profundidad (almacenadas en el depth_bfx subdirectorios).

Creando el conjunto de datos

Debido a que solo estamos interesados ​​en transmitir el mensaje, nos limitaremos a las primeras 20 muestras, que son todas de la parte NYU Depth v2 del conjunto de datos:

## create, name, and persist the dataset
dataset = fo.Dataset(name="SUNRGBD-20", persistent=True)

## pick out first 20 scenes
scene_dirs = glob("SUNRGBD/kv1/NYUdata/*")[:20]

samples = []

for scene_dir in scene_dirs:
## Get image file path from scene directory
image_path = glob(f"{scene_dir}/image/*")[0]

## Get depth map file path from scene directory
depth_path = glob(f"{scene_dir}/depth_bfx/*")[0]

depth_map = np.array(Image.open(depth_path))
depth_map = (depth_map * 255 / np.max(depth_map)).astype("uint8")

## Create sample
sample = fo.Sample(
filepath=image_path,
gt_depth=fo.Heatmap(map=depth_map),
)

samples.append(sample)

## Add samples to dataset
dataset.add_samples(samples);

Aquí estamos almacenando los mapas de profundidad como mapas de calor. Todo está representado en términos de normalizado, relativo distancias, donde 255 representa la distancia máxima en la escena y 0 representa la distancia mínima en la escena. Esta es una forma común de representar mapas de profundidad, aunque está lejos de ser la única forma de hacerlo. Si estuviéramos interesados ​​en absoluto distancias, podríamos almacenar parámetros de muestra para las distancias mínima y máxima en la escena, y usarlos para reconstruir las distancias absolutas a partir de las distancias relativas.

Visualización de datos reales sobre el terreno

Con mapas de calor almacenados en nuestras muestras, podemos visualizar los datos reales del terreno:

session = fo.launch_app(dataset, auto=False)
## then open tab to localhost:5151 in browser
Mapas de profundidad reales sobre el terreno para muestras del conjunto de datos SUN RGB-D. Imagen cortesía del autor.

Cuando se trabaja con mapas de profundidad, la combinación de colores y la opacidad del mapa de calor son importantes. Soy daltónico, así que encuentro que el viridis El mapa de colores con opacidad al máximo funciona mejor para mí.

Configuración de visibilidad para mapas de calor. Imagen cortesía del autor.

¿Verdad fundamental?

Al inspeccionar estas imágenes RGB y mapas de profundidad, podemos ver que hay algunas imprecisiones en los mapas de profundidad reales del terreno. Por ejemplo, en esta imagen, la grieta oscura que atraviesa el centro de la imagen es en realidad la más lejano parte de la escena, pero el mapa de profundidad de la verdad del terreno lo muestra como el más cercano parte de la escena:

Problema en los datos de profundidad reales del terreno para la muestra del conjunto de datos SUN RGB-D. Imagen cortesía del autor.

Este es uno de los desafíos clave para las tareas MDE: los datos reales sobre el terreno son difíciles de conseguir y, a menudo, ¡son ruidosos! Es esencial ser consciente de esto al evaluar sus modelos MDE.

Ahora que tenemos nuestro conjunto de datos cargado, podemos ejecutar modelos de estimación de profundidad monocular en nuestras imágenes RGB.

Durante mucho tiempo, los modelos más modernos para la estimación de profundidad monocular, como DORN y DensoProfundidad fueron construidos con redes neuronales convolucionales. Sin embargo, recientemente tanto los modelos basados ​​en transformadores como los DPT y GLPNy modelos basados ​​en difusión como Maravilla ¡Hemos logrado resultados notables!

En esta sección, le mostraremos cómo generar predicciones de mapas de profundidad MDE con DPT y Marigold. En ambos casos, opcionalmente puede ejecutar el modelo localmente con la biblioteca Hugging Face respectiva o ejecutarlo de forma remota con Reproducir exactamente.

Para ejecutar a través de Replicate, instale el cliente Python:

pip install replicate

Y exporte su token API replicado:

export REPLICATE_API_TOKEN=r8_<your_token_here>

💡 Con Replicate, el modelo puede tardar un minuto en cargarse en la memoria del servidor (problema de arranque en frío), pero una vez que lo hace, la predicción solo debería tardar unos segundos. Dependiendo de sus recursos informáticos locales, la ejecución en el servidor puede brindarle enormes aceleraciones en comparación con la ejecución local, especialmente para Marigold y otros enfoques de estimación de profundidad basados ​​en difusión.

Estimación de profundidad monocular con DPT

El primer modelo que ejecutaremos es un transformador de predicción densa (DPT). Los modelos DPT han encontrado utilidad tanto en MDE como en la segmentación semántica, tareas que requieren predicciones “densas” a nivel de píxeles.

El siguiente punto de control utiliza Midasque devuelve el mapa de profundidad inversapor lo que tenemos que invertirlo nuevamente para obtener un mapa de profundidad comparable.

Para ejecutar localmente con transformersprimero cargamos el modelo y el procesador de imágenes:

from transformers import AutoImageProcessor, AutoModelForDepthEstimation

## swap for "Intel/dpt-large" if you'd like
pretrained = "Intel/dpt-hybrid-midas"

image_processor = AutoImageProcessor.from_pretrained(pretrained)
dpt_model = AutoModelForDepthEstimation.from_pretrained(pretrained)

A continuación, encapsulamos el código para realizar inferencias en una muestra, incluido el procesamiento previo y posterior:

def apply_dpt_model(sample, model, label_field):
image = Image.open(sample.filepath)
inputs = image_processor(images=image, return_tensors="pt")

with torch.no_grad():
outputs = model(**inputs)
predicted_depth = outputs.predicted_depth

prediction = torch.nn.functional.interpolate(
predicted_depth.unsqueeze(1),
size=image.size[::-1],
mode="bicubic",
align_corners=False,
)

output = prediction.squeeze().cpu().numpy()
## flip b/c MiDaS returns inverse depth
formatted = (255 - output * 255 / np.max(output)).astype("uint8")

sample[label_field] = fo.Heatmap(map=formatted)
sample.save()

Aquí, estamos almacenando predicciones en un label_field campo en nuestras muestras, representado con un mapa de calor al igual que las etiquetas de verdad del terreno.

Tenga en cuenta que en el apply_dpt_model() función, entre el paso hacia adelante del modelo y la generación del mapa de calor, observe que hacemos una llamada a torch.nn.functional.interpolate(). Esto se debe a que el paso hacia adelante del modelo se ejecuta en una versión reducida de la imagen y queremos devolver un mapa de calor que tenga el mismo tamaño que la imagen original.

¿Por qué necesitamos hacer esto? Si sólo queremos *mirar* los mapas de calor, esto no importaría. Pero si queremos comparar los mapas de profundidad reales del terreno con las predicciones del modelo por píxel, debemos asegurarnos de que tengan el mismo tamaño.

Todo lo que queda por hacer es recorrer el conjunto de datos:

for sample in dataset.iter_samples(autosave=True, progress=True):
apply_dpt_model(sample, dpt_model, "dpt")

session = fo.launch_app(dataset)

Mapas de profundidad relativa predichos por un modelo híbrido MiDaS DPT en imágenes de muestra SUN RGB-D. Imagen cortesía del autor.

Para ejecutar con Replicate, puede usar este modelo. Así es como se ve la API:

import replicate

## example application to first sample
rgb_fp = dataset.first().filepath

output = replicate.run(
"cjwbw/midas:a6ba5798f04f80d3b314de0f0a62277f21ab3503c60c84d4817de83c5edfdae0",
input={
"model_type": "dpt_beit_large_512",
"image":open(rgb_fp, "rb")
}
)
print(output)

Estimación de profundidad monocular con caléndula

A raíz de su tremendo éxito en contextos de conversión de texto a imagen, los modelos de difusión se están aplicando a una gama cada vez más amplia de problemas. Maravilla “Reutiliza” modelos de generación de imágenes basados ​​en difusión para la estimación de profundidad monocular.

Para ejecutar Marigold localmente, necesitarás clonar el repositorio de git:

git clone https://github.com/prs-eth/Marigold.git

Este repositorio presenta una nueva tubería de difusores, MarigoldPipelinelo que facilita la aplicación de Marigold:

## load model
from Marigold.marigold import MarigoldPipeline
pipe = MarigoldPipeline.from_pretrained("Bingxin/Marigold")

## apply to first sample, as example
rgb_image = Image.open(dataset.first().filepath)
output = pipe(rgb_image)
depth_image = output['depth_colored']

Entonces es necesario el posprocesamiento de la imagen de profundidad de salida.

En su lugar, para ejecutar a través de Replicate, podemos crear un apply_marigold_model() funcionar en analogía con el caso DPT anterior e iterar sobre las muestras en nuestro conjunto de datos:

import replicate
import requests
import io

def marigold_model(rgb_image):
output = replicate.run(
"adirik/marigold:1a363593bc4882684fc58042d19db5e13a810e44e02f8d4c32afd1eb30464818",
input={
"image":rgb_image
}
)
## get the black and white depth map
response = requests.get(output[1]).content
return response

def apply_marigold_model(sample, model, label_field):
rgb_image = open(sample.filepath, "rb")
response = model(rgb_image)
depth_image = np.array(Image.open(io.BytesIO(response)))[:, :, 0] ## all channels are the same
formatted = (255 - depth_image).astype("uint8")
sample[label_field] = fo.Heatmap(map=formatted)
sample.save()

for sample in dataset.iter_samples(autosave=True, progress=True):
apply_marigold_model(sample, marigold_model, "marigold")

session = fo.launch_app(dataset)

Mapas de profundidad relativa predichos con el punto final Marigold en imágenes de muestra SUN RGB-D. Imagen cortesía del autor.

Ahora que tenemos predicciones de múltiples modelos, ¡evaluémoslas! aprovecharemos scikit-image aplicar tres métricas simples comúnmente utilizadas para la estimación de profundidad monocular: error cuadrático medio (RMSE), relación pico señal-ruido (PSNR), y índice de similitud estructural (SSIM).

💡Las puntuaciones más altas de PSNR y SSIM indican mejores predicciones, mientras que las puntuaciones más bajas de RMSE indican mejores predicciones.

Tenga en cuenta que los valores específicos a los que llego son consecuencia de los pasos específicos previos y posteriores al procesamiento que realicé a lo largo del camino. ¡Lo que importa es el rendimiento relativo!

Definiremos la rutina de evaluación:

from skimage.metrics import peak_signal_noise_ratio, mean_squared_error, structural_similarity

def rmse(gt, pred):
"""Compute root mean squared error between ground truth and prediction"""
return np.sqrt(mean_squared_error(gt, pred))

def evaluate_depth(dataset, prediction_field, gt_field):
"""Run 3 evaluation metrics for all samples for `prediction_field`
with respect to `gt_field`"""
for sample in dataset.iter_samples(autosave=True, progress=True):
gt_map = sample[gt_field].map
pred = sample[prediction_field]
pred_map = pred.map
pred["rmse"] = rmse(gt_map, pred_map)
pred["psnr"] = peak_signal_noise_ratio(gt_map, pred_map)
pred["ssim"] = structural_similarity(gt_map, pred_map)
sample[prediction_field] = pred

## add dynamic fields to dataset so we can view them in the App
dataset.add_dynamic_sample_fields()

Y luego aplique la evaluación a las predicciones de ambos modelos:

evaluate_depth(dataset, "dpt", "gt_depth")
evaluate_depth(dataset, "marigold", "gt_depth")

Calcular el rendimiento promedio para un determinado modelo/métrica es tan simple como llamar al conjunto de datos mean()método en ese campo:

print("Mean Error Metrics")
for model in ["dpt", "marigold"]:
print("-"*50)
for metric in ["rmse", "psnr", "ssim"]:
mean_metric_value = dataset.mean(f"{model}.{metric}")
print(f"Mean {metric} for {model}: {mean_metric_value}")
Mean Error Metrics
--------------------------------------------------
Mean rmse for dpt: 49.8915828817003
Mean psnr for dpt: 14.805904629602551
Mean ssim for dpt: 0.8398022368184576
--------------------------------------------------
Mean rmse for marigold: 104.0061165272178
Mean psnr for marigold: 7.93015537185192
Mean ssim for marigold: 0.42766803372861134

Todas las métricas parecen coincidir en que DPT supera a Marigold. Sin embargo, es importante señalar que estas métricas no son perfectas. Por ejemplo, RMSE es muy sensible a valores atípicos y SSIM no es muy sensible a errores pequeños. Para una evaluación más exhaustiva, podemos filtrar por estas métricas en la aplicación para visualizar qué está haciendo bien y qué está haciendo mal el modelo, o dónde las métricas no logran capturar el rendimiento del modelo.

Finalmente, activar y desactivar máscaras es una excelente manera de visualizar las diferencias entre la verdad fundamental y las predicciones del modelo:

Comparación visual de mapas de calor predichos por los dos modelos MDE y la verdad del terreno. Imagen cortesía del autor.

En resumen, aprendimos cómo ejecutar modelos de estimación de profundidad monocular en nuestros datos, cómo evaluar las predicciones utilizando métricas comunes y cómo visualizar los resultados. También aprendimos que la estimación de la profundidad monocular es una tarea notoriamente difícil.

La calidad y cantidad de los datos son factores muy limitantes; Los modelos a menudo tienen dificultades para generalizarse a nuevos entornos; y las métricas no siempre son buenos indicadores del desempeño del modelo. Los valores numéricos específicos que cuantifican el rendimiento del modelo pueden depender en gran medida de su proceso de procesamiento. E incluso su evaluación cualitativa de los mapas de profundidad previstos puede verse fuertemente influenciada por sus esquemas de color y escalas de opacidad.

Si hay algo que aprendes de esta publicación, espero que sea esto: ¡es de misión crítica que mires los mapas de profundidad en sí, y no solo las métricas!