Transformers documentation
マスクの生成
マスクの生成
マスク生成とは、画像に対して意味のあるマスクを生成するタスクです。 このタスクは画像セグメンテーションと非常に似ていますが、多くの違いがあります。画像セグメンテーションモデルはラベル付きデータセットで学習され、学習中に確認したクラスに限定されます。画像が与えられると、マスクのセットと対応するクラスを返します。
マスク生成モデルは大量のデータで学習され、2つのモードで動作します。
- Prompting モード: このモードでは、モデルは画像とプロンプトを入力として受け取ります。プロンプトは、対象オブジェクト内の2次元の点位置(XY 座標)または、オブジェクトを囲むバウンディングボックスです。Prompting モードでは、モデルはプロンプトが指し示しているオブジェクトのマスクのみを返します。
- Segment Everythingモード: Segment Everythingでは、画像が与えられると、モデルは画像内のすべてのマスクを生成します。そのために、推論用に点のグリッドを生成し、画像上に重ねます。
- Video Inference: モデルは動画と、動画フレーム内の点またはボックスのプロンプトを受け取り、その対象を動画全体にわたってトラッキングします。Video Inferenceの実行方法については、SAM 2 docsを参照してください。
Segment Anything Model (SAM) および Segment Anything Model 2 (SAM2) によってマスク生成タスクがサポートされており、Segment Anything Model 2 (SAM2) によって動画推論がサポートされています。SAM は、Vision Transformer ベースの画像エンコーダ、プロンプトエンコーダ、および双方向トランスフォーマーマスクデコーダから構成される強力なモデルです。画像とプロンプトがエンコードされ、デコーダはこれらの埋め込みを受け取って有効なマスクを生成します。一方、SAM 2 はマスクを追跡するためのメモリモジュールを追加することで SAM を拡張しています。

SAMは、大規模なデータカバレッジを有するセグメンテーション用の強力な基盤モデルとして機能します。SA-1Bという、100万枚の画像と11億個のマスクを含むデータセットで学習されています。
このガイドでは、次のことを学びます。
- バッチ処理を有効にして、セグメントエブリシングモードで推論を実行します。
- ポイントプロンプティングモードで推論を実行します。
- ボックスプロンプティングモードで推論を実行します。
まず、transformersをインストールします。
pip install -q transformers
マスク生成パイプライン
マスク生成モデルの推論を行う最も簡単な方法は、mask-generation パイプラインを使用することです。
>>> from transformers import pipeline
>>> checkpoint = "facebook/sam2-hiera-base-plus"
>>> mask_generator = pipeline(model=checkpoint, task="mask-generation")画像を見てみましょう。
from PIL import Image
import requests
img_url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/bee.jpg"
image = Image.open(requests.get(img_url, stream=True).raw).convert("RGB")
すべてをセグメントしましょう。points-per-batch は、すべてをセグメントするモードにおけるポイントの並列推論を有効にします。これにより推論が高速化されますが、メモリ消費量が増加します。また、SAM は画像ではなく、ポイントに対してのみバッチ処理を有効にします。pred_iou_thresh は IoU の信頼度閾値であり、この閾値を超えるマスクのみが返されます。
masks = mask_generator(image, points_per_batch=128, pred_iou_thresh=0.88)masks は次のようになります。
{'masks': [tensor([[False, False, False, ..., True, True, True],
[False, False, False, ..., True, True, True],
[False, False, False, ..., True, True, True],
...,
[False, False, False, ..., False, False, False], ..
'scores': tensor([0.9874, 0.9793, 0.9780, 0.9776, ... 0.9016])}これらは次のように可視化できます。
import matplotlib.pyplot as plt
plt.imshow(image, cmap='gray')
for i, mask in enumerate(masks["masks"]):
plt.imshow(mask, cmap='viridis', alpha=0.1, vmin=0, vmax=1)
plt.axis('off')
plt.show()以下は、グレースケールで表示された元の画像にカラフルなマップを重ねたものです。非常に印象的です。

モデル推論
ポイントプロンプティング
パイプラインを使わずにモデルを使用することもできます。そのためには、モデルとプロセッサーを初期化します。
from transformers import SamModel, SamProcessor
from accelerate import Accelerator
import torch
device = Accelerator().device
model = SamModel.from_pretrained("facebook/sam-vit-base").to(device)
processor = SamProcessor.from_pretrained("facebook/sam-vit-base")ポイントプロンプティングを行うには、入力ポイントをプロセッサーに渡し、プロセッサーの出力をモデルに渡して推論を実行します。モデルの出力を後処理するには、出力と、プロセッサーの初期出力から取得したoriginal_sizesを渡します。プロセッサーが画像をリサイズするため、出力を補間する必要があるため、これらを渡す必要があります。
input_points = [[[2592, 1728]]] # point location of the bee
inputs = processor(image, input_points=input_points, return_tensors="pt").to(device)
with torch.no_grad():
outputs = model(**inputs)
masks = processor.image_processor.post_process_masks(outputs.pred_masks.cpu(), inputs["original_sizes"].cpu())masks の出力で、3つのマスクを視覚化できます。
import matplotlib.pyplot as plt
import numpy as np
fig, axes = plt.subplots(1, 4, figsize=(15, 5))
axes[0].imshow(image)
axes[0].set_title('Original Image')
mask_list = [masks[0][0][0].numpy(), masks[0][0][1].numpy(), masks[0][0][2].numpy()]
for i, mask in enumerate(mask_list, start=1):
overlayed_image = np.array(image).copy()
overlayed_image[:,:,0] = np.where(mask == 1, 255, overlayed_image[:,:,0])
overlayed_image[:,:,1] = np.where(mask == 1, 0, overlayed_image[:,:,1])
overlayed_image[:,:,2] = np.where(mask == 1, 0, overlayed_image[:,:,2])
axes[i].imshow(overlayed_image)
axes[i].set_title(f'Mask {i}')
for ax in axes:
ax.axis('off')
plt.show()
Box Prompting
ポイントプロンプティングと同様の方法で、ボックスプロンプティングも実行できます。画像とともに、[x_min, y_min, x_max, y_max]形式のリストとして入力ボックスをprocessorに渡すだけです。プロセッサの出力を取得してモデルに直接渡し、その後、出力を再度後処理します。
# bounding box around the bee
box = [2350, 1600, 2850, 2100]
inputs = processor(
image,
input_boxes=[[[box]]],
return_tensors="pt"
).to(model.device)
with torch.no_grad():
outputs = model(**inputs)
mask = processor.image_processor.post_process_masks(
outputs.pred_masks.cpu(),
inputs["original_sizes"].cpu(),
)[0][0][0].numpy()以下に示すように、ハチの周囲のバウンディングボックスを視覚化できます。
import matplotlib.patches as patches
fig, ax = plt.subplots()
ax.imshow(image)
rectangle = patches.Rectangle((2350, 1600), 500, 500, linewidth=2, edgecolor='r', facecolor='none')
ax.add_patch(rectangle)
ax.axis("off")
plt.show()
以下に推論の出力を確認できます。
fig, ax = plt.subplots()
ax.imshow(image)
ax.imshow(mask, cmap='viridis', alpha=0.4)
ax.axis("off")
plt.show()
マスク生成のためのファインチューニング
画像マッティングのために、MicroMat データセットの一部で SAM2.1 をファインチューニングします。DICE loss を使用するには monai ライブラリのインストールが必要であり、トレーニング中のマスクのログ記録には trackio が必要です。
pip install -q datasets monai trackio
```
We can now load our dataset and take a look.
```python
from datasets import load_dataset
dataset = load_dataset("merve/MicroMat-mini", split="train")
dataset
# Dataset({
# features: ['image', 'mask', 'prompt', 'image_id', 'object_id', 'sample_idx', 'granularity',
# 'image_path', 'mask_path', 'prompt_path'], num_rows: 94
#})image、mask、prompt の列が必要です。train と test に分割します。
dataset = dataset.train_test_split(test_size=0.1)
train_ds = dataset["train"]
val_ds = dataset["test"]サンプルを見てみましょう。
train_ds[0] {'image': <PIL.PngImagePlugin.PngImageFile image mode=RGB size=2040x1356>,
'mask': <PIL.PngImagePlugin.PngImageFile image mode=L size=2040x1356>,
'prompt': '{"point": [[137, 1165, 1], [77, 1273, 0], [58, 1351, 0]], "bbox": [0, 701, 251, 1356]}',
'image_id': '0034',
'object_id': '34',
'sample_idx': 1,
'granularity': 'fine',
'image_path': '/content/MicroMat-mini/img/0034.png',
'mask_path': '/content/MicroMat-mini/mask/0034_34.png',
'prompt_path': '/content/MicroMat-mini/prompt/0034_34.json'}プロンプトは辞書の文字列なので、以下のようにバウンディングボックスを取得できます。
import json
json.loads(train_ds["prompt"][0])["bbox"]
# [0, 701, 251, 1356]例の画像、プロンプト、マスクを可視化します。
import matplotlib.pyplot as plt
import numpy as np
def show_mask(mask, ax):
color = np.array([0.12, 0.56, 1.0, 0.6])
mask = np.array(mask)
h, w = mask.shape
mask_image = mask.reshape(h, w, 1) * color.reshape(1, 1, 4)
ax.imshow(mask_image)
x0, y0, x1, y1 = eval(train_ds["prompt"][0])["bbox"]
ax.add_patch(
plt.Rectangle((x0, y0), x1 - x0, y1 - y0,
fill=False, edgecolor="lime", linewidth=2))
example = train_ds[0]
image = np.array(example["image"])
ground_truth_mask = np.array(example["mask"])
fig, ax = plt.subplots()
ax.imshow(image)
show_mask(ground_truth_mask, ax)
ax.set_title("Ground truth mask")
ax.set_axis_off()
plt.show() ここで、データ読み込み用のデータセットを定義できます。SAMDataset はデータセットをラップし、各サンプルを SAM プロセッサが期待する形式に整形します。そのため、生の画像やマスクの代わりに、トレーニングにそのまま使える処理済み画像、バウンディングボックス、正解マスクが得られます。
デフォルトでは、processor は画像をリサイズするため、画像とマスクに加えて、元のサイズも返します。また、マスクの値が [0, 255] の範囲にあるため、マスクを二値化する必要があります。
from torch.utils.data import Dataset
import torch
class SAMDataset(Dataset):
def __init__(self, dataset, processor):
self.dataset = dataset
self.processor = processor
def __len__(self):
return len(self.dataset)
def __getitem__(self, idx):
item = self.dataset[idx]
image = item["image"]
prompt = eval(item["prompt"])["bbox"]
inputs = self.processor(image, input_boxes=[[prompt]], return_tensors="pt")
inputs["ground_truth_mask"] = (np.array(item["mask"]) > 0).astype(np.float32)
inputs["original_image_size"] = torch.tensor(image.size[::-1])
return inputsプロセッサーとデータセットをこれを使って初期化できます。
from transformers import Sam2Processor
processor = Sam2Processor.from_pretrained("facebook/sam2.1-hiera-small")
train_dataset = SAMDataset(dataset=train_ds, processor=processor)
```
We need to define a data collator that will turn varying size of ground truth masks to batches of reshaped masks in same shape. We reshape them using nearest neighbor interpolation. We also make batched tensors for rest of the elements in the batch. If your masks are all of same size, feel free to skip this step.
```python
import torch.nn.functional as F
def collate_fn(batch, target_hw=(256, 256)):
pixel_values = torch.cat([item["pixel_values"] for item in batch], dim=0)
original_sizes = torch.stack([item["original_sizes"] for item in batch])
input_boxes = torch.cat([item["input_boxes"] for item in batch], dim=0)
ground_truth_masks = torch.cat([
F.interpolate(
torch.as_tensor(x["ground_truth_mask"]).unsqueeze(0).unsqueeze(0).float(),
size=(256, 256),
mode="nearest"
)
for x in batch
], dim=0).long()
return {
"pixel_values": pixel_values,
"original_sizes": original_sizes,
"input_boxes": input_boxes,
"ground_truth_mask": ground_truth_masks,
"original_image_size": torch.stack([item["original_image_size"] for item in batch]),
}
from torch.utils.data import DataLoader
train_dataloader = DataLoader(
train_dataset,
batch_size=4,
shuffle=True,
collate_fn=collate_fn,
)データローダーが生成する内容を確認してみましょう。
batch = next(iter(train_dataloader))
for k,v in batch.items():
print(k,v.shape)
# pixel_values torch.Size([4, 3, 1024, 1024])
# original_sizes torch.Size([4, 1, 2])
# input_boxes torch.Size([4, 1, 4])
# ground_truth_mask torch.Size([4, 1, 256, 256])
#original_image_size torch.Size([4, 2])次に、モデルを読み込み、マスクデコーダーのみを学習するために、ビジョンとプロンプトエンコーダーをフリーズします。
from transformers import Sam2Model
model = Sam2Model.from_pretrained("facebook/sam2.1-hiera-small")
for name, param in model.named_parameters():
if name.startswith("vision_encoder") or name.startswith("prompt_encoder"):
param.requires_grad_(False)
```
We can now define the optimizer and the loss function.
```python
from torch.optim import Adam
import monai
optimizer = Adam(model.mask_decoder.parameters(), lr=1e-5, weight_decay=0)
seg_loss = monai.losses.DiceCELoss(sigmoid=True, squared_pred=True, reduction='mean')学習前にモデルの性能を確認してみましょう。
import matplotlib.pyplot as plt
item = val_ds[1]
img = item["image"]
bbox = json.loads(item["prompt"])["bbox"]
inputs = processor(images=img, input_boxes=[[bbox]], return_tensors="pt").to(model.device)
with torch.no_grad():
outputs = model(**inputs)
masks = processor.post_process_masks(outputs.pred_masks.cpu(), inputs["original_sizes"])[0]
preds = masks.squeeze(0)
mask = (preds[0] > 0).cpu().numpy()
overlay = np.asarray(img, dtype=np.uint8).copy()
overlay[mask] = 0.55 * overlay[mask] + 0.45 * np.array([0, 255, 0], dtype=np.float32)
plt.imshow(overlay)
plt.axis("off")
plt.show()
トレーニングの途中でモデルの改善を監視できるようにするため、予測結果をtrackioに記録する必要があります。
from PIL import Image
import trackio
import json
@torch.no_grad()
def predict_fn(img, bbox):
inputs = processor(images=img, input_boxes=[[bbox]], return_tensors="pt").to(model.device)
with torch.no_grad():
outputs = model(**inputs)
masks = processor.post_process_masks(outputs.pred_masks.cpu(), inputs["original_sizes"])[0]
return masks
def log_eval_masks_trackio(dataset, indices, step, predict_fn, project=None, sample_cap=8):
logs = {"eval/step": int(step)}
for idx in indices[:sample_cap]:
item = dataset[idx]
img = item["image"]
bbox = json.loads(item["prompt"])["bbox"]
preds = predict_fn(img, bbox)
preds = preds.squeeze(0)
mask = (preds[0] > 0).cpu().numpy()
overlay = np.asarray(img, dtype=np.uint8).copy()
overlay[mask] = 0.55 * overlay[mask] + 0.45 * np.array([0, 255, 0], dtype=np.float32)
logs[f"{idx}/overlay"] = trackio.Image(overlay, caption="overlay")
trackio.log(logs)これでトレーニングループを書き、トレーニングを実行できます。
loss と評価マスクを trackio で記録している点に注目してください。
from tqdm import tqdm
from statistics import mean
import trackio
import torch
num_epochs = 30
device = torch.accelerator.current_accelerator().type if torch.accelerator.is_available() else "cpu"
model.to(device)
model.train()
trackio.init(project="mask-eval")
for epoch in range(num_epochs):
epoch_losses = []
for batch in tqdm(train_dataloader):
outputs = model(pixel_values=batch["pixel_values"].to(device),
input_boxes=batch["input_boxes"].to(device),
multimask_output=False)
predicted_masks = outputs.pred_masks.squeeze(1)
ground_truth_masks = batch["ground_truth_mask"].float().to(device)
loss = seg_loss(predicted_masks, ground_truth_masks)
optimizer.zero_grad()
loss.backward()
optimizer.step()
epoch_losses.append(loss.item())
log_eval_masks_trackio(dataset=val_ds, indices=[0, 3, 6, 9], step=epoch, predict_fn=predict_fn, project="mask-eval")
print(f'Epoch: {epoch}')
print(f'Mean loss: {mean(epoch_losses)}')
trackio.log({"loss": mean(epoch_losses)})
trackio.finish()学習したモデルをテストしてみましょう。
import matplotlib.pyplot as plt
item = val_ds[1]
img = item["image"]
bbox = json.loads(item["prompt"])["bbox"]
inputs = processor(images=img, input_boxes=[[bbox]], return_tensors="pt").to(model.device)
with torch.no_grad():
outputs = model(**inputs)
preds = processor.post_process_masks(outputs.pred_masks.cpu(), inputs["original_sizes"])[0]
preds = preds.squeeze(0)
mask = (preds[0] > 0).cpu().numpy()
overlay = np.asarray(img, dtype=np.uint8).copy()
overlay[mask] = 0.55 * overlay[mask] + 0.45 * np.array([0, 255, 0], dtype=np.float32)
plt.imshow(overlay)
plt.axis("off")
plt.show()わずか20エポック、小規模なデータセットでの学習だけで、大幅な改善が見られました!
