一、论文简介
论文讨论了大规模预训练产生的视觉基础模型在处理任意图像时的强大能力,这些模型不仅能够完成训练任务,其中间表示还对其他视觉任务(如检测和分割)有用。研究者们提出了一个问题:这些模型是否能够表示物体的三维结构。他们通过一系列实验,使用特定任务的探针和零样本推理程序来分析这些模型的3D感知能力,并发现当前模型存在一些限制。这个实验旨在评估模型对图像中可见表面表示的能力,具体包括两个任务:深度估计(Monocular Depth Estimation)和表面法线估计(Surface Normal Estimation)。
二、深度估计(Monocular Depth Estimation)
任务:预测图像中每个像素点的深度。
数据集:使用NYUv2数据集评估场景级性能,NAVI数据集评估对象级性能。
输入:单张RGB图像;输出:图像中每个像素点的深度
网络结构:使用AdaBins的二进制预测结果,在模型的多层特征图基础上,构建一个类似于DPT解码器的多尺度探测器,用于密集预测。通过训练密集探针来预测每个像素点的深度。
1.预训练模型特征提取
对于一张待估计深度的图像,使用一个预训练的视觉模型(例如,一个视觉变换器或卷积神经网络)来提取图像的特征。这些特征通常在模型的中间层获得,以捕捉到图像的高层语义信息。
2.深度预测网络(Dense Probe)
设计一个密集探针(dense probe)网络,这个网络将从预训练模型中提取的特征映射到深度图。这个探针网络可以是一个简单的全连接层,或者是一个更复杂的网络结构,如多层感知机(MLP)或卷积层。使用AdaBins方法来训练这个探针网络(AdaBins是一种基于分箱的深度预测技术,它将深度范围划分为一系列离散的“bins”,并学习将图像特征映射到这些bins的概率分布),度量预测深度和真实深度之间的差距。
3. 损失函数和优化
AdaBins方法使用特定的损失函数来训练网络,这个损失函数同时考虑了深度值的回归和分类任务;使用AdamW优化器进行训练,这是一种带有权重衰减的随机梯度下降变体,有助于防止过拟合并提高训练稳定性;采用线性预热和余弦衰减学习率调度器进行学习率调度,这意味着在训练初期逐步增加学习率,然后在训练后期逐渐减小学习率,以促进模型收敛。
4.深度图生成
对于输入图像中的每个像素,探针网络预测一个深度值或一个深度bins的概率分布。并根据预测的概率分布,为每个像素选择最有可能的深度值,或者通过某种方式(如取期望值)从概率分布中得到一个单一的深度估计值。
5.评估方法
使用均方根预测误差(RMSE)和不同阈值下的召回率来评估深度估计的准确性,将预测的深度图与真实深度图(第二列)进行比较,以验证模型的性能。
三、表面法线估计(Surface Normal Estimation)
任务:预测每个像素点的表面法线方向。
数据集:NYUv2数据集:该数据集提供了与表面法线相关的注释,用于评估室内场景的表面法线估计性能。
NAVI数据集:该数据集包含了对象实例在多种场景和方向中的表面法线注释,用于评估对象级别的表面法线估计性能。
输入:单张RGB图像;输出:图像中每个像素点表面法线方向
1.同深度估计进行预训练模型特征提取
2.表面法线预测网络(Surface Normal Prediction Network)
设计一个网络结构,将从预训练模型中提取的特征映射到表面法线的预测。这个网络可以是一个简单的全连接层,或者是一个更复杂的网络结构,如多层感知机(MLP)或卷积层。使用Bae等人提出的不确定性感知的角度损失函数来训练网络,以预测法线的方向。
3.表面法线图生成
对于输入图像中的每个像素,网络预测一个表面法线的方向向量。并将预测的法线向量归一化,以确保它们具有单位长度。
4.评估方法
使用均方根角度预测误差(RMSE)和不同角度阈值下的召回率来评估表面法线估计的准确性。将预测的表面法线图与真实表面法线图(如果有的话)进行比较,以验证模型的性能。
四、相关代码解析
1.深度估计
深度估计是一个复杂的计算机视觉任务,通常涉及到机器学习或深度学习技术。以下是一个简单的示例,使用Python和OpenCV库来从单个RGB图像中估计深度。
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, Dataset
from torchvision import transforms
from PIL import Image# 定义一个自定义的数据集
class DepthDataset(Dataset):def __init__(self, image_paths, transform=None):self.image_paths = image_pathsself.transform = transformdef __len__(self):return len(self.image_paths)def __getitem__(self, idx):image_path = self.image_paths[idx]image = Image.open(image_path)if self.transform:image = self.transform(image)# 假设我们有一个对应的深度图,这里我们随机生成一个作为示例depth = torch.rand(1, 1, image.size[1], image.size[0])return image, depth# 定义CNN模型
class DepthCNN(nn.Module):def __init__(self):super(DepthCNN, self).__init__()self.conv1 = nn.Conv2d(3, 16, kernel_size=3, stride=2, padding=1)self.conv2 = nn.Conv2d(16, 32, kernel_size=3, stride=2, padding=1)self.conv3 = nn.Conv2d(32, 64, kernel_size=3, stride=2, padding=1)self.fc1 = nn.Linear(64*16*16, 1024)self.fc2 = nn.Linear(1024, 1) # 假设深度图是单通道的def forward(self, x):x = torch.relu(self.conv1(x))x = torch.relu(self.conv2(x))x = torch.relu(self.conv3(x))x = x.view(x.size(0), -1) # Flatten the tensorx = torch.relu(self.fc1(x))x = self.fc2(x)return x# 实例化数据集和数据加载器
image_paths = ['path_to_your_image1.jpg', 'path_to_your_image2.jpg'] # 替换为实际图像路径
transform = transforms.Compose([transforms.ToTensor()])
dataset = DepthDataset(image_paths, transform)
dataloader = DataLoader(dataset, batch_size=2, shuffle=True)# 实例化模型
model = DepthCNN()# 定义损失函数和优化器
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)# 训练模型
for epoch in range(10): # 迭代10个epochfor images, depths in dataloader:optimizer.zero_grad()outputs = model(images)loss = criterion(outputs, depths)loss.backward()optimizer.step()print(f'Epoch [{epoch+1}/10], Loss: {loss.item():.4f}')
在这个示例中,我们定义了一个DepthDataset
类来加载图像和对应的深度图。然后,我们定义了一个DepthCNN
类来构建CNN模型。模型包含三个卷积层和两个全连接层。我们使用均方误差损失(MSELoss)作为损失函数,并使用Adam优化器来更新模型权重。
2.表面法线估计
表面法线分析是计算机视觉中的一个高级任务,通常涉及到从RGB图像中估计表面的法线向量。这通常需要复杂的深度学习模型,比如卷积神经网络(CNN)。以下是一个使用PyTorch框架的简化示例,展示了如何构建一个CNN模型来进行表面法线分析。
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, Dataset
from torchvision import transforms
from PIL import Image# 定义一个自定义的数据集
class NormalDataset(Dataset):def __init__(self, image_paths, transform=None):self.image_paths = image_pathsself.transform = transformdef __len__(self):return len(self.image_paths)def __getitem__(self, idx):image_path = self.image_paths[idx]image = Image.open(image_path)if self.transform:image = self.transform(image)# 假设我们有一个对应的法线图,这里我们随机生成一个作为示例# 法线图通常有三个通道,分别对应x, y, z坐标normal = torch.rand(3, image.size[1], image.size[0])return image, normal# 定义CNN模型
class NormalCNN(nn.Module):def __init__(self):super(NormalCNN, self).__init__()self.conv1 = nn.Conv2d(3, 16, kernel_size=3, stride=2, padding=1)self.conv2 = nn.Conv2d(16, 32, kernel_size=3, stride=2, padding=1)self.conv3 = nn.Conv2d(32, 64, kernel_size=3, stride=2, padding=1)self.fc1 = nn.Linear(64*16*16, 256)self.fc2 = nn.Linear(256, 3) # 法线有三个分量def forward(self, x):x = torch.relu(self.conv1(x))x = torch.relu(self.conv2(x))x = torch.relu(self.conv3(x))x = x.view(x.size(0), -1) # Flatten the tensorx = torch.relu(self.fc1(x))x = self.fc2(x)return x# 实例化数据集和数据加载器
image_paths = ['path_to_your_image1.jpg', 'path_to_your_image2.jpg'] # 替换为实际图像路径
transform = transforms.Compose([transforms.ToTensor()])
dataset = NormalDataset(image_paths, transform)
dataloader = DataLoader(dataset, batch_size=2, shuffle=True)# 实例化模型
model = NormalCNN()# 定义损失函数和优化器
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)# 训练模型
for epoch in range(10): # 迭代10个epochfor images, normals in dataloader:optimizer.zero_grad()outputs = model(images)loss = criterion(outputs, normals)loss.backward()optimizer.step()print(f'Epoch [{epoch+1}/10], Loss: {loss.item():.4f}')
在这个示例中,我们定义了一个NormalDataset
类来加载图像和对应的法线图。然后,我们定义了一个NormalCNN
类来构建CNN模型。模型包含三个卷积层和两个全连接层。我们使用均方误差损失(MSELoss)作为损失函数,并使用Adam优化器来更新模型权重。