单目深度估计
单目深度估计是一种计算机视觉任务,它涉及从单个图像预测场景的深度信息。换句话说,它是从单个相机视角估计场景中物体距离的过程。
单目深度估计有各种应用,包括 3D 重建、增强现实、自动驾驶和机器人技术。这是一个具有挑战性的任务,因为它要求模型理解场景中物体之间的复杂关系以及相应的深度信息,这可能受光照条件、遮挡和纹理等因素的影响。
深度估计有两大类
**绝对深度估计**:此任务变体旨在从相机提供精确的深度测量值。该术语可与度量深度估计互换使用,其中深度以米或英尺等精确测量值提供。绝对深度估计模型输出深度图,其中数值表示真实世界的距离。
**相对深度估计**:相对深度估计旨在预测场景中物体或点的深度顺序,而不提供精确的测量值。这些模型输出深度图,该图指示场景的哪些部分彼此更近或更远,而无需实际距离 A 和 B。
在本指南中,我们将了解如何使用 Depth Anything V2(一个最先进的零样本相对深度估计模型)和 ZoeDepth(一个绝对深度估计模型)进行推理。
查看 深度估计 任务页面以查看所有兼容的架构和检查点。
在开始之前,我们需要安装最新版本的 Transformers
pip install -q -U transformers
深度估计管道
尝试使用支持深度估计的模型进行推理的最简单方法是使用相应的 pipeline()。从 Hugging Face Hub 上的检查点 实例化管道
>>> from transformers import pipeline
>>> import torch
>>> device = "cuda" if torch.cuda.is_available() else "cpu"
>>> checkpoint = "depth-anything/Depth-Anything-V2-base-hf"
>>> pipe = pipeline("depth-estimation", model=checkpoint, device=device)
接下来,选择要分析的图像
>>> from PIL import Image
>>> import requests
>>> url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/bee.jpg"
>>> image = Image.open(requests.get(url, stream=True).raw)
>>> image
将图像传递给管道。
>>> predictions = pipe(image)
管道返回一个包含两个条目的字典。第一个称为 predicted_depth
,是一个张量,其值是以米为单位表达的每个像素的深度。第二个称为 depth
,是一个 PIL 图像,它可视化深度估计结果。
让我们看一下可视化结果
>>> predictions["depth"]
手动进行深度估计推理
既然您已经了解如何使用深度估计管道,那么让我们看看如何手动复制相同的结果。
首先从 Hugging Face Hub 上的检查点 加载模型和关联的处理器。这里我们将使用与之前相同的检查点
>>> from transformers import AutoImageProcessor, AutoModelForDepthEstimation
>>> checkpoint = "Intel/zoedepth-nyu-kitti"
>>> image_processor = AutoImageProcessor.from_pretrained(checkpoint)
>>> model = AutoModelForDepthEstimation.from_pretrained(checkpoint).to(device)
使用 image_processor
准备图像输入以供模型使用,它将处理必要的图像转换,例如调整大小和归一化
>>> pixel_values = image_processor(image, return_tensors="pt").pixel_values.to(device)
将准备好的输入传递给模型
>>> import torch
>>> with torch.no_grad():
... outputs = model(pixel_values)
让我们后处理并可视化结果。
我们需要填充然后调整输出的大小,以便预测的深度图与原始图像具有相同的尺寸。调整大小后,我们将从深度中删除填充区域。
>>> import numpy as np
>>> import torch.nn.functional as F
>>> predicted_depth = outputs.predicted_depth.unsqueeze(dim=1)
>>> height, width = pixel_values.shape[2:]
>>> height_padding_factor = width_padding_factor = 3
>>> pad_h = int(np.sqrt(height/2) * height_padding_factor)
>>> pad_w = int(np.sqrt(width/2) * width_padding_factor)
>>> if predicted_depth.shape[-2:] != pixel_values.shape[-2:]:
>>> predicted_depth = F.interpolate(predicted_depth, size= (height, width), mode='bicubic', align_corners=False)
>>> if pad_h > 0:
predicted_depth = predicted_depth[:, :, pad_h:-pad_h,:]
>>> if pad_w > 0:
predicted_depth = predicted_depth[:, :, :, pad_w:-pad_w]
现在我们可以可视化结果(下面的函数取自 GaussianObject 框架)。
import matplotlib
def colorize(value, vmin=None, vmax=None, cmap='gray_r', invalid_val=-99, invalid_mask=None, background_color=(128, 128, 128, 255), gamma_corrected=False, value_transform=None):
"""Converts a depth map to a color image.
Args:
value (torch.Tensor, numpy.ndarray): Input depth map. Shape: (H, W) or (1, H, W) or (1, 1, H, W). All singular dimensions are squeezed
vmin (float, optional): vmin-valued entries are mapped to start color of cmap. If None, value.min() is used. Defaults to None.
vmax (float, optional): vmax-valued entries are mapped to end color of cmap. If None, value.max() is used. Defaults to None.
cmap (str, optional): matplotlib colormap to use. Defaults to 'magma_r'.
invalid_val (int, optional): Specifies value of invalid pixels that should be colored as 'background_color'. Defaults to -99.
invalid_mask (numpy.ndarray, optional): Boolean mask for invalid regions. Defaults to None.
background_color (tuple[int], optional): 4-tuple RGB color to give to invalid pixels. Defaults to (128, 128, 128, 255).
gamma_corrected (bool, optional): Apply gamma correction to colored image. Defaults to False.
value_transform (Callable, optional): Apply transform function to valid pixels before coloring. Defaults to None.
Returns:
numpy.ndarray, dtype - uint8: Colored depth map. Shape: (H, W, 4)
"""
if isinstance(value, torch.Tensor):
value = value.detach().cpu().numpy()
value = value.squeeze()
if invalid_mask is None:
invalid_mask = value == invalid_val
mask = np.logical_not(invalid_mask)
# normalize
vmin = np.percentile(value[mask],2) if vmin is None else vmin
vmax = np.percentile(value[mask],85) if vmax is None else vmax
if vmin != vmax:
value = (value - vmin) / (vmax - vmin) # vmin..vmax
else:
# Avoid 0-division
value = value * 0.
# squeeze last dim if it exists
# grey out the invalid values
value[invalid_mask] = np.nan
cmapper = matplotlib.colormaps.get_cmap(cmap)
if value_transform:
value = value_transform(value)
# value = value / value.max()
value = cmapper(value, bytes=True) # (nxmx4)
# img = value[:, :, :]
img = value[...]
img[invalid_mask] = background_color
# return img.transpose((2, 0, 1))
if gamma_corrected:
# gamma correction
img = img / 255
img = np.power(img, 2.2)
img = img * 255
img = img.astype(np.uint8)
return img
>>> result = colorize(predicted_depth.cpu().squeeze().numpy())
>>> Image.fromarray(result)