2.1 视觉的“大模型”时代:ViT的诞生与革新
在计算机视觉领域,卷积神经网络(CNN)曾是当之无愧的霸主。从LeNet到ResNet,CNN在图像分类、目标检测等任务上取得了巨大成功。然而,随着Transformer模型在自然语言处理(NLP)领域的崛起(如BERT、GPT),研究者们开始思考:Transformer能否也像处理文本序列一样处理图像呢?
答案是肯定的,这正是Vision Transformer (ViT) 的核心思想。ViT的出现,打破了CNN在视觉领域的主导地位,开启了视觉领域“大模型”的新纪元。
核心思想: ViT将图像视为一系列图像块(patches),然后将这些图像块序列输入到标准的Transformer编码器中进行处理。就像Transformer处理文本中的单词序列一样,它处理图像中的图像块序列。
原理详解:
- 图像分块(Patch Embedding): 将输入图像分割成固定大小的、不重叠的图像块(例如,一个224x224的图像被分成16x16的图像块,会得到14x14=196个图像块)。每个图像块被展平(flatten)并线性投影到一个嵌入维度,形成“图像块嵌入”。
- 类别Token (CLS Token): 在图像块嵌入序列的开头添加一个特殊的
[CLS]
token(可学习的嵌入),它的最终输出作为整个图像的表示,用于分类等下游任务。 - 位置编码(Positional Embedding): 为了保留图像块的空间信息,为每个图像块嵌入添加可学习的“位置编码”。这样,Transformer就能知道每个图像块在原始图像中的相对位置。
- Transformer编码器: 将带有位置编码的图像块序列输入到标准的Transformer编码器中。编码器由多个自注意力层和前馈网络层堆叠而成。自注意力机制允许模型在处理每个图像块时,能够考虑到图像中所有其他图像块的信息,从而捕捉全局依赖。
- 分类头: 编码器输出的
[CLS]
token的特征向量被送入一个简单的多层感知机(MLP)分类头,用于最终的图像分类任务。
ViT的革新之处:
- 全局感受野: 相较于CNN通过层层堆叠逐步扩大感受野,Transformer的自注意力机制天生就具备全局感受野,可以一步到位地捕捉图像中的长距离依赖关系。
- 可扩展性: 事实证明,Transformer在拥有足够大的数据集进行预训练时,其性能会随着模型规模的增大而显著提升,展现出强大的可扩展性。
- 统一架构: 将NLP和CV任务统一到Transformer架构下,为多模态学习提供了新的可能。
Python示例:简化版ViT实现
我们将实现一个非常简化的ViT模型,用于图像分类。为了可运行和理解,这里会用线性层代替多头自注意力,并使用简单的前馈网络。
Python
import torch
import torch.nn as nn
import torch.nn.functional as F
from einops import rearrange # 方便图像分块操作
import torchvision.transforms as T
from PIL import Image# 辅助函数:生成一个虚拟图像
def create_dummy_image(size=(224, 224)):return Image.new('RGB', size, color='red')# 1. Patch Embedding 和 Positional Embedding
class PatchEmbedding(nn.Module):def __init__(self, img_size, patch_size, in_channels, embed_dim):super().__init__()self.img_size = img_sizeself.patch_size = patch_sizeself.num_patches = (img_size // patch_size) ** 2self.proj = nn.Conv2d(in_channels, embed_dim, kernel_size=patch_size, stride=patch_size)def forward(self, x):# x: (B, C, H, W)x = self.proj(x) # (B, embed_dim, H_new, W_new)x = x.flatten(2) # (B, embed_dim, num_patches)x = x.transpose(1, 2) # (B, num_patches, embed_dim)return x# 2. 简化的Transformer Encoder Block
class SimplifiedTransformerBlock(nn.Module):def __init__(self, embed_dim, num_heads=8, mlp_ratio=4.):super().__init__()self.norm1 = nn.LayerNorm(embed_dim)# 简化注意力:用一个线性层来模拟多头自注意力的一部分功能# 实际的MultiheadAttention会更复杂self.attn = nn.Linear(embed_dim, embed_dim) self.norm2 = nn.LayerNorm(embed_dim)# MLP Blockhidden_features = int(embed_dim * mlp_ratio)self.mlp = nn.Sequential(nn.Linear(embed_dim, hidden_features),nn.GELU(),nn.Linear(hidden_features, embed_dim))def forward(self, x):# 简化版自注意力attn_output = self.attn(self.norm1(x))x = x + attn_output # Residual connection# MLPx = x + self.mlp(self.norm2(x)) # Residual connectionreturn x# 3. 简化版Vision Transformer
class SimpleViT(nn.Module):def __init__(self, img_size=224, patch_size=16, in_channels=3, num_classes=1000, embed_dim=768, num_layers=12, num_heads=12, mlp_ratio=4.):super().__init__()self.patch_embed = PatchEmbedding(img_size, patch_size, in_channels, embed_dim)num_patches = self.patch_embed.num_patchesself.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dim))self.pos_embed = nn.Parameter(torch.zeros(1, num_patches + 1, embed_dim)) # +1 for CLS tokenself.blocks = nn.Sequential(*[SimplifiedTransformerBlock(embed_dim, num_heads, mlp_ratio)for _ in range(num_layers)])self.norm = nn.LayerNorm(embed_dim)self.head = nn.Linear(embed_dim, num_classes) # 分类头# 初始化位置编码(这里只是简单初始化,实际会更复杂)nn.init.trunc_normal_(self.pos_embed, std=.02)nn.init.trunc_normal_(self.cls_token, std=.02)self.apply(self._init_weights)def _init_weights(self, m):if isinstance(m, nn.Linear):nn.init.trunc_normal_(m.weight, std=.02)if isinstance(m, nn.Linear) and m.bias is not None:nn.init.constant_(m.bias, 0)elif isinstance(m, nn.LayerNorm):nn.init.constant_(m.bias, 0)nn.init.constant_(m.weight, 1.0)def forward(self, x):B = x.shape[0]x = self.patch_embed(x) # (B, num_patches, embed_dim)cls_tokens = self.cls_token.expand(B, -1, -1) # (B, 1, embed_dim)x = torch.cat((cls_tokens, x), dim=1) # (B, num_patches + 1, embed_dim)x = x + self.pos_embed # Add positional embeddingfor blk in self.blocks:x = blk(x)x = self.norm(x)# 取出 CLS token 的特征进行分类return self.head(x[:, 0])# --- 运行示例 ---
# 图像预处理
transform = T.Compose([T.Resize((224, 224)),T.ToTensor(),T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])# 创建一个虚拟图像
dummy_img = create_dummy_image()
input_tensor = transform(dummy_img).unsqueeze(0) # Add batch dimension (1, 3, 224, 224)# 初始化模型
# 注意:这里为了可运行,将 num_layers 和 num_heads 设小
# 实际ViT模型非常大
model = SimpleViT(img_size=224, patch_size=16, in_channels=3, num_classes=10, embed_dim=128, num_layers=2, num_heads=4) print("--- 简化版ViT示例 ---")
print(f"Input tensor shape: {input_tensor.shape}")output = model(input_tensor)
print(f"Output logits shape: {output.shape}") # (Batch_size, num_classes)# 模拟训练
dummy_labels = torch.tensor([0]) # 假设标签是0
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)for epoch in range(5):optimizer.zero_grad()outputs = model(input_tensor)loss = criterion(outputs, dummy_labels)loss.backward()optimizer.step()print(f"Epoch {epoch+1}, Loss: {loss.item():.4f}")
代码说明:
PatchEmbedding
: 将输入图像分割成不重叠的patch_size
xpatch_size
大小的图像块,然后通过卷积层将其投影到embed_dim
维度,并展平、转置,形成Transformer所需的序列。SimplifiedTransformerBlock
: 这是一个高度简化的Transformer编码器层。真正的Transformer Block包含Multi-Head Self-Attention,这里为了演示将注意力部分简化为一个线性层。核心思想是残差连接和LayerNorm。SimpleViT
:cls_token
:一个可学习的特殊嵌入,用于聚合整个图像的信息。pos_embed
:可学习的位置编码,为每个图像块(包括CLS token)提供位置信息。blocks
:堆叠的简化Transformer编码器块。head
:最终的分类头,用于从CLS token的输出中预测类别。
- 运行示例: 创建一个虚拟图像,经过预处理后输入模型,并模拟一个简单的训练过程。
2.2 自监督视觉学习的先锋:MoCo与DINO
ViT虽然强大,但它通常需要海量的标注数据进行预训练。为了解决标注数据的依赖,自监督学习在视觉领域也大放异彩。MoCo (Momentum Contrast) 和 DINO (Self-Distillation with No Labels) 是其中的佼佼者。
2.2.1 MoCo (Momentum Contrast)
核心思想: MoCo通过维护一个动态的“负样本队列”和“动量编码器”,将对比学习推向了大规模预训练。它解决了对比学习中负样本数量的限制,使得模型能学到更好的特征表示。
原理详解:
MoCo的优势:
- 高效利用负样本: 解决了单批次负样本数量不足的问题。
- 稳定训练: 动量更新使得键编码器输出的特征更加稳定,避免了因为负样本更新过快而导致的训练不稳定。
- 高性能: 预训练后的模型在多种下游任务上表现出色。
2.2.2 DINO (Self-Distillation with No Labels)
核心思想: DINO通过自蒸馏(Self-Distillation)的方式进行自监督学习,而无需负样本。它训练一个“学生”网络去匹配一个“教师”网络的输出,而教师网络的参数则是学生网络参数的动量平均。
原理详解: DINO的核心是“学生-教师”范式:
- 学生网络 (Student Network): 接受一个图像增强后的视图作为输入,并进行常规的梯度更新。
- 教师网络 (Teacher Network): 接受同一图像的另一个(通常是更强的)增强视图作为输入。教师网络的参数是学生网络参数的指数移动平均,与MoCo的动量编码器类似。教师网络不进行梯度更新。
- 目标: 学生网络的目标是预测教师网络的输出。损失函数通常是交叉熵(或者更精确地说是Kullback-Leibler散度),它鼓励学生网络输出与教师网络输出的概率分布相匹配。
- 中心化 (Centering) 和锐化 (Sharpening): DINO还引入了中心化(防止模型崩溃为常数输出)和锐化(增加输出分布的区分度)的操作,进一步提升训练效果。
DINO的优势:
- 无需负样本: 简化了训练流程,避免了负样本选择的复杂性。
- 性能优异: 在许多基准测试中取得了与MoCo相当甚至更好的性能。
- 强大的特征可视化能力: 预训练的DINO模型学习到的特征具有出色的语义聚类和可解释性,即使不经过微调也能在图像分割等任务上表现出强大的零样本能力。
Python示例:非常简化版的DINO
我们使用一个极简的DINO框架来演示其核心概念,省略了大部分细节。
Python
import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision.transforms as T
from PIL import Image# 辅助函数:生成一个虚拟图像
def create_dummy_image(size=(32, 32)):return Image.new('RGB', size, color='green')# 1. 定义简单的编码器(作为学生和教师的骨干)
class SimpleDINOEncoder(nn.Module):def __init__(self, in_channels=3, out_dim=128):super().__init__()self.conv1 = nn.Conv2d(in_channels, 32, kernel_size=3, stride=1, padding=1)self.relu = nn.ReLU()self.pool = nn.MaxPool2d(kernel_size=2, stride=2)self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1)self.flatten = nn.Flatten()self.fc1 = nn.Linear(64 * 8 * 8, 256) # Assuming 32x32 input -> 8x8 after 2 poolsself.fc2 = nn.Linear(256, out_dim)def forward(self, x):x = self.pool(self.relu(self.conv1(x)))x = self.pool(self.relu(self.conv2(x)))x = self.flatten(x)x = self.relu(self.fc1(x))x = self.fc2(x)return x# 2. 定义DINO的Projection Head
class DINOHead(nn.Module):def __init__(self, in_dim, out_dim, hidden_dim=256, bottleneck_dim=64):super().__init__()self.mlp = nn.Sequential(nn.Linear(in_dim, hidden_dim),nn.GELU(),nn.Linear(hidden_dim, bottleneck_dim),nn.GELU(),nn.Linear(bottleneck_dim, out_dim) # 输出的维度是教师/学生网络的输出维度)self.temp = nn.Parameter(torch.ones(1) * 0.07) # Temperature parameter for sharpeningdef forward(self, x):x = self.mlp(x)# 注意:这里我们省略了DINO中的centering和sharpening的复杂逻辑# 实际DINO会有一个中心化(moving average of teacher output)和温度调整return x / self.temp.exp() # 简单模拟温度缩放# 3. 更新教师网络参数的动量机制
@torch.no_grad()
def update_teacher_params(student_model, teacher_model, m):"""m: momentum coefficient"""for param_s, param_t in zip(student_model.parameters(), teacher_model.parameters()):param_t.data = param_t.data * m + param_s.data * (1. - m)# 图像增强
transform_dino = T.Compose([T.RandomResizedCrop(32, scale=(0.4, 1.0)), # DINO常用不同尺度的裁剪T.RandomHorizontalFlip(),T.ToTensor(),T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])# 初始化模型
out_dim = 128 # 输出特征维度
student_encoder = SimpleDINOEncoder(out_dim=out_dim)
teacher_encoder = SimpleDINOEncoder(out_dim=out_dim)
student_head = DINOHead(out_dim, out_dim) # 头的输出维度与教师网络输出维度一致
teacher_head = DINOHead(out_dim, out_dim)# 初始化教师网络参数与学生网络一致
teacher_encoder.load_state_dict(student_encoder.state_dict())
teacher_head.load_state_dict(student_head.state_dict())# 教师网络不计算梯度
for p in teacher_encoder.parameters():p.requires_grad = False
for p in teacher_head.parameters():p.requires_grad = Falseoptimizer = torch.optim.Adam(list(student_encoder.parameters()) + list(student_head.parameters()), lr=1e-3)# 模拟数据
dummy_img_dino = create_dummy_image()print("\n--- 简化版DINO示例 ---")
for epoch in range(10): # 简化训练10个epochoptimizer.zero_grad()# 生成两个增强视图view_1 = transform_dino(dummy_img_dino).unsqueeze(0)view_2 = transform_dino(dummy_img_dino).unsqueeze(0)# 学生网络输出student_output_v1 = student_head(student_encoder(view_1)) # (B, out_dim)student_output_v2 = student_head(student_encoder(view_2))# 教师网络输出 (在无梯度模式下)with torch.no_grad():teacher_output_v1 = teacher_head(teacher_encoder(view_1))teacher_output_v2 = teacher_head(teacher_encoder(view_2))# DINO损失:学生网络预测教师网络输出的交叉熵# F.log_softmax 后面接 F.softmax 实际上是KL散度# DINO实际会使用更复杂的交叉熵,这里简化loss = 0.5 * (F.cross_entropy(student_output_v1, teacher_output_v2.detach().softmax(dim=-1)) +F.cross_entropy(student_output_v2, teacher_output_v1.detach().softmax(dim=-1)))loss.backward()optimizer.step()# 更新教师网络参数update_teacher_params(student_encoder, teacher_encoder, m=0.996)update_teacher_params(student_head, teacher_head, m=0.996)print(f"Epoch {epoch+1}, Loss: {loss.item():.4f}")
代码说明:
SimpleDINOEncoder
: 简单的CNN骨干,作为学生和教师的编码器。DINOHead
: 预测头,包含MLP和温度参数。实际DINO中会有中心化(centering)操作,防止模型崩溃,这里为简化省略。update_teacher_params
: 实现教师网络参数的动量更新。- DINO损失: 学生网络对教师网络输出的交叉熵损失。注意教师网络的输出
detach().softmax()
,表示教师网络不参与梯度计算,且其输出经过softmax后作为学生网络的目标分布。
2.3 掩码自编码器:MAE引领视觉预训练新范式
在上一篇中我们提到了掩码重建。MAE (Masked Autoencoders) 是将掩码重建思想成功引入视觉领域的代表性工作。
核心思想: MAE通过对图像进行高比例的随机掩码(例如75%),然后训练编码器仅处理可见的图像块,解码器则负责根据编码器的输出和掩码信息重建原始图像的像素。
MAE的优势:
- 高效训练: 编码器只处理少部分可见的图像块,大大降低了计算成本,使得在大型数据集上进行预训练变得经济高效。
- 强大的表示学习: 高比例的掩码迫使模型学习到更全局、更高层次的语义特征,因为要重建大量缺失信息需要模型对图像内容有深刻的理解。
- 通用性: 预训练后的MAE模型可以轻松迁移到各种下游任务,并且表现出色。
Python示例:见专栏第一篇1.3节,其“简化版掩码重建(MAE启发)”已经涵盖了MAE的核心原理与实现。
2.4 普适的视觉分割利器:Segment Anything Model (SAM)
核心思想: SAM (Segment Anything Model) 是一个提示驱动(promptable) 的图像分割基础模型。它能够对图像中的任何物体进行分割,并且其核心能力在于能够对各种交互提示(如点击点、边框、文本提示) 做出响应,生成高质量的分割掩码。
原理详解: SAM的设计理念旨在解决图像分割的通用性问题,即训练一个模型能够分割任何图像中的任何物体。其核心架构包括:
- 图像编码器(Image Encoder): 预训练的Vision Transformer(如MAE)用于将图像编码成高质量的特征嵌入。这个编码器在推理时是固定的,只计算一次。
- 提示编码器(Prompt Encoder): 负责将各种提示(点、框、文本、掩码)编码成嵌入向量。
- 稀疏提示(Sparse Prompts): 如点(X, Y坐标)、框(边界框),通过位置编码和学习到的嵌入进行编码。
- 密集提示(Dense Prompts): 如粗糙的掩码,通过卷积层进行编码。
- 掩码解码器(Mask Decoder): 这是一个轻量级的Transformer解码器,它接收图像编码器输出的图像嵌入、提示编码器输出的提示嵌入,然后预测出高质量的物体掩码。解码器设计为高效且可以多次运行,以生成多个可能的分割结果。
SAM的创新之处:
- 提示工程: 将传统的“分割一切”任务转化为“给定提示,分割提示所指的一切”的范式,极大地提升了模型的灵活性和用户交互性。
- 庞大数据集: SAM伴随一个巨大的、高质量的SA-1B数据集发布,该数据集包含了11亿个掩码,是模型泛化能力的关键。
- 零样本迁移: 凭借其强大的预训练能力和提示机制,SAM在许多新的分割任务上表现出卓越的零样本和少样本能力。
Python示例:SAM的推理流程(基于预训练模型)
由于SAM模型庞大且复杂,从头训练不切实际。这里我们将演示如何使用Meta官方提供的预训练SAM模型进行推理,展示其核心功能。
步骤:
- 安装必要的库。
- 下载预训练的SAM模型权重。
- 加载图像并转换为模型输入。
- 使用
SamAutomaticMaskGenerator
进行全图分割(无需提示)。 - 使用
SamPredictor
进行提示驱动分割(点或框提示)。
首先,确保你安装了必要的库:
Bash
pip install opencv-python pycocotools matplotlib
pip install segment-anything
然后,你需要下载SAM的预训练权重文件。你可以从Meta的GitHub页面下载sam_vit_h_4b8939.pth
(或sam_vit_b
等较小版本)并放在你的代码目录下。
Python
import torch
import numpy as np
import matplotlib.pyplot as plt
import cv2
from segment_anything import sam_model_registry, SamAutomaticMaskGenerator, SamPredictor
from PIL import Image# 辅助函数:显示图片和掩码
def show_mask(mask, ax, random_color=False):if random_color:color = np.concatenate([np.random.random(3), np.array([0.6])], axis=0)else:color = np.array([30/255, 144/255, 255/255, 0.6])h, w = mask.shape[-2:]mask_image = mask.reshape(h, w, 1) * color.reshape(1, 1, -1)ax.imshow(mask_image)def show_points(coords, labels, ax, marker_size=375):pos_points = coords[labels==1]neg_points = coords[labels==0]ax.scatter(pos_points[:, 0], pos_points[:, 1], color='green', marker='*', s=marker_size, edgecolor='white', linewidth=1.25)ax.scatter(neg_points[:, 0], neg_points[:, 1], color='red', marker='*', s=marker_size, edgecolor='white', linewidth=1.25) def show_box(box, ax):x0, y0, x1, y1 = boxax.add_patch(plt.Rectangle((x0, y0), x1 - x0, y1 - y0, edgecolor='green', facecolor=(0,0,0,0), lw=2)) # --- SAM 运行示例 ---
print("\n--- SAM模型推理示例 ---")# 1. 加载模型
sam_checkpoint = "sam_vit_b.pth" # 确保你已下载此文件
model_type = "vit_b" # 对应下载的模型类型device = "cuda" if torch.cuda.is_available() else "cpu"sam = sam_model_registry[model_type](checkpoint=sam_checkpoint)
sam.to(device=device)# 2. 加载图像 (这里使用一张简单的图像)
# 你可以用自己的图片路径替换
image_path = "path/to/your/image.jpg" # 替换为你的图片路径
# 如果没有图片,可以生成一张简单的图像
try:image = cv2.imread(image_path)if image is None: # 如果图片路径无效,使用虚拟图片print(f"Warning: Image not found at {image_path}. Using a dummy image.")dummy_img_sam = Image.new('RGB', (500, 500), color = 'yellow')# 在虚拟图片上画个圈或方块,方便分割draw = ImageDraw.Draw(dummy_img_sam)draw.ellipse((100, 100, 400, 400), fill='blue', outline='black')draw.rectangle((50, 50, 150, 150), fill='red', outline='black')image = np.array(dummy_img_sam)image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) # Convert to BGR for opencvexcept FileNotFoundError:print(f"Warning: Image not found at {image_path}. Using a dummy image.")from PIL import ImageDrawdummy_img_sam = Image.new('RGB', (500, 500), color = 'yellow')draw = ImageDraw.Draw(dummy_img_sam)draw.ellipse((100, 100, 400, 400), fill='blue', outline='black')draw.rectangle((50, 50, 150, 150), fill='red', outline='black')image = np.array(dummy_img_sam)image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) # Convert to BGR for opencvimage_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # SAM期望RGB图像# 3. 自动掩码生成器 (Segment Anything Anywhere)
print("\n--- 自动分割所有物体 ---")
mask_generator = SamAutomaticMaskGenerator(sam)
masks = mask_generator.generate(image_rgb)print(f"Found {len(masks)} masks.")# 可视化所有自动生成的掩码
fig, ax = plt.subplots(1, 2, figsize=(10, 5))
ax[0].imshow(image_rgb)
ax[0].set_title("Original Image")
ax[0].axis('off')ax[1].imshow(image_rgb)
for mask_data in masks:show_mask(mask_data["segmentation"], ax[1], random_color=True)
ax[1].set_title("Automatic Segmentation")
ax[1].axis('off')
plt.show()# 4. 提示驱动的分割 (Segment Anything with Prompts)
print("\n--- 提示驱动分割 (点提示) ---")
predictor = SamPredictor(sam)
predictor.set_image(image_rgb)# 示例:通过点提示进行分割
# 假设我们想分割一个在图像中心附近的物体
input_point = np.array([[250, 250]]) # 坐标 (x, y)
input_label = np.array([1]) # 1表示前景点masks_prompt, scores, logits = predictor.predict(point_coords=input_point,point_labels=input_label,multimask_output=True, # 可以生成多个可能的掩码
)# 可视化点提示分割结果
for i, (mask, score) in enumerate(zip(masks_prompt, scores)):plt.figure(figsize=(6,6))plt.imshow(image_rgb)show_mask(mask, plt.gca())show_points(input_point, input_label, plt.gca())plt.title(f"Mask {i+1}, Score: {score:.3f}")plt.axis('off')plt.show()print("\n--- 提示驱动分割 (框提示) ---")
# 示例:通过框提示进行分割
# 假设我们想分割一个框住的物体 (x_min, y_min, x_max, y_max)
input_box = np.array([100, 100, 400, 400]) masks_box, scores_box, logits_box = predictor.predict(point_coords=None,point_labels=None,box=input_box[None, :], # 注意需要加batch维度multimask_output=True,
)# 可视化框提示分割结果
for i, (mask, score) in enumerate(zip(masks_box, scores_box)):plt.figure(figsize=(6,6))plt.imshow(image_rgb)show_mask(mask, plt.gca())show_box(input_box, plt.gca())plt.title(f"Mask {i+1}, Score: {score:.3f}")plt.axis('off')plt.show()
代码说明:
- 模型加载: 通过
sam_model_registry
加载预训练的SAM模型。确保sam_checkpoint
路径正确且模型权重文件已下载。 - 图像处理: SAM期望RGB格式的图像。
SamAutomaticMaskGenerator
: 这是SAM的一个高级接口,可以自动在整张图像中检测并分割出所有潜在的物体,无需任何提示。它会生成一系列字典,每个字典包含一个分割掩码、其置信度等信息。SamPredictor
: 这是SAM的核心推理接口,允许你根据提供的提示(点、框)来预测分割掩码。predictor.set_image()
:在进行预测前,需要先将图像输入编码器,生成图像嵌入。predictor.predict()
:根据输入的点坐标point_coords
、点标签point_labels
(1表示前景,0表示背景)、边界框box
来生成掩码。multimask_output=True
表示可以返回多个可能的掩码。
- 可视化函数:
show_mask
、show_points
、show_box
是辅助函数,用于将分割结果、点和框可视化在图像上。
总结
本篇专栏深入探讨了视觉基础模型,从革命性的ViT开始,到自监督学习领域的MoCo和DINO,再到普适性的分割模型SAM。我们看到:
- ViT 成功将Transformer引入视觉领域,开启了图像处理的“大模型”时代。
- MoCo和DINO 作为自监督学习的典范,解决了视觉大模型对海量标注数据的依赖,使得模型能够从无标签数据中学习到强大的视觉表示。
- MAE 进一步提升了自监督学习的效率和效果,尤其在高掩码比例下表现出色。
- SAM 凭借其提示驱动的通用分割能力和庞大的数据集,极大地推动了图像分割的边界,实现了对“任何物体”的分割。
这些视觉基础模型为未来的计算机视觉应用奠定了坚实的基础。它们不仅在传统任务上取得了SOTA性能,更开启了零样本、少样本学习以及更灵活、更智能的视觉交互模式。