SAM3 深掘り総括 - 設計思想から実装まで
前々回でDetector、前回でTrackerを深掘りしてきました。最終回となる今回は、これまでの知見を総括し、SAM3の設計思想、実装のポイント、そして今後への展望をまとめます。
1. 振り返り:最初の疑問から
全ては 「なぜVideoでは288×288がそのままリサイズされるだけなのか?」 という疑問から始まりました。
Image処理:Detector → 1008×1008(Learned PixelDecoder/FPN)
Video処理:Tracker → 288×288(Shared ViT + Tracker Neck)
この疑問を追っていく中で、SAM3の設計思想が見えてきました。
2. 設計思想の考察
2.1 なぜTrackerは288×288なのか?
答え:タスクの難易度が違うから
[Detector の仕事]
「この画像に何がある?どこにある?」
→ 問題を解く(Problem Solving)
→ 高解像度特徴 + 学習可能なアップサンプリングが必要
[Tracker の仕事]
「前のフレームのこれ、次のフレームではどこ?」
→ 答えを確認する(Answer Verification)
→ 低解像度でも十分、シンプルなF.interpolateで OK
具体例で考える:
プロンプトフレーム(初回):
入力:画像 + テキスト"person"
Detector の仕事:
- この画像のどこに人がいるか探す(難しい!)
- ViT特徴(1008×1008)で探索
- Learned Queriesで100箇所チェック
- PixelDecoder/FPNで高品質マスク生成
次のフレーム(追跡):
入力:画像 + 前フレームのマスク位置
Tracker の仕事:
- 「さっきここにいた人、今どこ?」(比較的簡単)
- ViTから抽出したTracker用特徴(288×288)で十分
- 前のメモリ(6フレーム)から予測
- F.interpolateでリサイズで OK
計算コスト vs 品質のトレードオフ:
| Detector | Tracker | |
|---|---|---|
| タスク | 検出(難) | 追跡(易) |
| 解像度 | 1008×1008 | 288×288 |
| アップサンプリング | Learned (FPN) | F.interpolate |
| バックボーン | ViT (Shared) | ViT (Shared) |
| 特徴抽出 | PixelDecoder | Tracker Neck |
| 計算コスト | 高 | 低(Neck以降) |
| 品質 | 最高品質必須 | 十分な品質 |
Trackerが毎フレーム動くことを考えると、288×288は賢い選択です。
2.2 なぜDetectorとTrackerを分けたのか?
答え:役割が根本的に違うから
情報の流れの違い
[Detector]
テキスト"person" ──┐
├→ 意味的マッチング → マスク生成
画像特徴 ──────────┘
(新しい概念を理解する必要がある)
[Tracker]
前フレームマスク ───┐
├→ 幾何学的マッチング → マスク伝播
画像特徴 ─────────┘
(形と位置を追うだけ)
統合モデルにしなかった理由
もし統合したら:
# 仮想的な統合モデル
unified_model(
image,
text_prompt, # 毎フレーム処理(重い)
prev_masks, # メモリ管理が複雑
geometric_prompt
)
# → テキスト処理のオーバーヘッドが毎フレーム発生
# → メモリ機構とテキスト理解の干渉
# → デバッグ・改善が困難
分離することで:
- Detector: テキスト理解に特化、新規検出に集中、
- Tracker: 時間的一貫性に特化、メモリ機構を最適化
- 統合レイヤー: IoUマッチングでシンプルに結合
モジュール設計の美学ですね。
合わせてDetectorはそれ単体でマスクを生成できるので単体でもend-to-endの学習可能です。
3. SAM1 → SAM2 → SAM3 の進化
進化の系譜
[SAM1] (2023)
- 画像のみ
- プロンプト:点、ボックス、マスク
- セマンティック理解なし
[SAM2] (2024)
- ビデオ追加
- メモリ機構導入
- 時間的一貫性
- まだテキストなし
[SAM3] (2025)
- オープンボキャブラリー
- Detector + Tracker 統合
- テキスト + 幾何学 + Exemplar
- 270K概念対応
SAM3の革新性
1.概念レベルのプロンプト
SAM1/2: 「ここをセグメント」(位置指定)
SAM3: 「personをすべてセグメント」(概念指定)
*加えてSAM3はインスタンスセグメンテーションをしています。
2.統合アーキテクチャ
Image: Detector のみ
Video: Detector + Tracker(デュアルバックボーン)
3.スケーラビリティ
Learned Queries: 100スロットで並列検出
メモリバンク: 6フレーム + 16ポインター
4. Image vs Video 処理の違い総まとめ
4.1 処理フローの比較
Image処理:
入力画像
↓
ViTバックボーン(1008×1008)
↓
Detector
├─ テキスト特徴とクロスアテンション
├─ Learned Queries(100スロット)
└─ PixelDecoder/FPN(学習済み)
↓
出力マスク [N, 1008, 1008]
Video処理:
各フレーム
↓
┌─────────────────┬─────────────────┐
│ Detector │ Tracker │
│ (ViT 1008) │ (ViT -> 288) │
├─────────────────┼─────────────────┤
│ テキスト使う │ テキスト使わない │
│ 新規検出 │ 既存追跡 │
│ 学習済みFPN │ F.interpolate │
└────────────────┴──────────────────┘
↓ ↓
新規マスク 既存マスク
↓ ↓
IoUマッチング(N×M)
↓
統合マスク [M, 1008, 1008]
(obj_id付き)
4.2 使い分けの指針
| シーン | 推奨 | 理由 |
|---|---|---|
| 単一画像 | Image | 高品質、シンプル |
| 短い動画(<10フレーム) | Image(各フレーム) | オーバーヘッド小 |
| 長い動画 | Video | メモリ機構で一貫性 |
| リアルタイム | Video | Tracker軽量 |
| 高品質静止画 | Image | Detector最大活用 |
5. 実装・カスタマイズ例
5.1 マスク統合の実装
前回少し出ていた、Reconditioning部分には幾分か改良案があります。例えば新しく検出されたおそらく同一インスタンス(IoUで判断)をmaxで追加していくなど:
# sam3/model/sam3_video_base.py の build_outputs()
# Part 3: Reconditioningの改造
if reconditioned_obj_ids is not None and len(reconditioned_obj_ids) > 0:
for obj_id in reconditioned_obj_ids:
det_idx = trk_id_to_max_iou_high_conf_det.get(obj_id)
if det_idx is not None:
# Detectorマスク取得
det_mask = det_out["mask"][det_idx]
det_mask_resized = F.interpolate(
det_mask.unsqueeze(0).unsqueeze(0).float(),
size=(orig_vid_height, orig_vid_width),
mode="bilinear",
align_corners=False,
).squeeze(0)
# 既存のTrackerマスク
trk_mask = obj_id_to_mask[obj_id]
# === ここを変更 ===
# 元:置き換え
# obj_id_to_mask[obj_id] = det_mask_resized
# 新:max統合
obj_id_to_mask[obj_id] = torch.max(
trk_mask.float(),
det_mask_resized
) > 0
# または:加算統合(要正規化)
# combined = (trk_mask.float() + det_mask_resized) / 2
# obj_id_to_mask[obj_id] = combined > 0.5
効果:
- Trackerの時間的一貫性 + Detectorの高品質
- エッジのガタつき軽減の可能性
自分でも試してみましたが、この後5.4で述べてるようにReconditioningがデフォルト値のままだと効果は分かりづらいので適宜そちらも変更する必要があります。
5.2 カスタムマッチング戦略
IoU以外のマッチング戦略:
def custom_matching(det_masks, trk_masks, det_scores, trk_scores):
"""
カスタムマッチング:IoU + 中心距離 + スコア加重
"""
N, M = len(det_masks), len(trk_masks)
scores = torch.zeros(N, M)
for i, det_mask in enumerate(det_masks):
for j, trk_mask in enumerate(trk_masks):
# IoU
iou = compute_iou(det_mask, trk_mask)
# 中心距離(正規化)
det_center = get_mask_center(det_mask)
trk_center = get_mask_center(trk_mask)
dist = torch.norm(det_center - trk_center)
dist_score = 1.0 / (1.0 + dist)
# スコア加重
conf_score = (det_scores[i] + trk_scores[j]) / 2
# 統合スコア
scores[i, j] = (
0.5 * iou +
0.3 * dist_score +
0.2 * conf_score
)
# Hungarian algorithmでマッチング
matches = hungarian_algorithm(scores)
return matches
5.3 プロンプト戦略
効果的なプロンプトの使い方:
# 戦略1: 階層的プロンプト
# 粗→細で段階的に
predictor.add_prompt(text="vehicle") # 粗い概念
predictor.propagate(...)
predictor.add_prompt(text="car", frame_idx=10) # 細かい概念
# 戦略2: ハイブリッドプロンプト
# テキスト + ポイントの組み合わせ
predictor.add_prompt(text="person") # 自動検出
predictor.add_prompt( # 手動追加
points=[[x, y]],
obj_id=new_id,
frame_idx=frame_idx
)
# 戦略3: リファインメント
# 粗いマスク → ポイントで修正
predictor.propagate(...) # 自動追跡
# 間違いを発見
predictor.add_prompt( # ポイントで修正
points=[[x1, y1], [x2, y2]],
point_labels=[1, 0], # positive, negative
obj_id=existing_id,
frame_idx=error_frame
)
5.4 定期的なReconditioning - Trackerのリフレッシュ
長時間の追跡では、Trackerの予測が徐々にドリフト(誤差の蓄積)します。SAM3はこれを防ぐため、定期的にDetectorのマスクでTrackerを更新します。
デフォルト設定
# model_builder.py
recondition_every_nth_frame=16 # 16フレームごとにリフレッシュ
実行条件
# sam3_video_base.py
should_recondition_periodic = (
self.recondition_every_nth_frame > 0 # 有効
and frame_idx % self.recondition_every_nth_frame == 0 # 16の倍数
and len(trk_id_to_max_iou_high_conf_det) > 0 # マッチあり
)
Reconditioningの処理
def _recondition_masklets(...):
HIGH_CONF_THRESH = 0.8 # logits閾値(実際には比較的低め)
for trk_obj_id, det_idx in trk_id_to_max_iou_high_conf_det.items():
# 1. Detectorの新しいマスク取得
new_mask = det_out["mask"][det_idx]
# 2. Trackerのスコアが閾値以上かチェック
obj_score = tracker_obj_scores_global[obj_idx] # logits値
if obj_score > HIGH_CONF_THRESH:
# 3. Trackerの内部状態を更新!
self.tracker.add_new_mask(
inference_state=inference_state,
frame_idx=frame_idx,
obj_id=trk_obj_id,
mask=new_mask_binary, # Detectorのマスクで上書き
)
# 4. メモリエンコーダー再実行
self.tracker.propagate_in_video_preflight(
tracker_state, run_mem_encoder=True
)
ポイント:
-
obj_score > 0.8(logits)は比較的低い閾値- logitsは通常7〜8以上になる
- つまり、ほとんどのオブジェクトがreconditioningの対象
- DetectorのマスクでTrackerのメモリを直接更新
- これにより、ドリフトを防ぎつつ時間的一貫性を維持
仕組みの全体像
フレーム 0: Detector検出 → Tracker初期化
フレーム 1-15: Trackerで追跡(誤差が少しずつ蓄積)
フレーム 16: Reconditioning
├─ Detector再検出
├─ Trackerとマッチング
└─ Trackerのメモリをリフレッシュ
フレーム 17-31: Trackerで追跡(リセットされた状態から)
フレーム 32: Reconditioning
...
メリット:
- Trackerのドリフト防止
- 長時間追跡の精度向上
- Detectorの高品質を定期的に活用
設定のカスタマイズ:
# より頻繁に(計算コスト増)
recondition_every_nth_frame=8
# 無効化(ablation study用)
recondition_every_nth_frame=0
# スコア閾値の調整
HIGH_CONF_THRESH = 2.0 # より厳しく
6. パフォーマンス考察
6.1 メモリ使用量
理論値(FP32、バッチサイズ1):
# Detector(毎フレーム)
ViT特徴: 1008×1008×256 ≈ 1GB
Detector出力: 100×1008×1008 ≈ 400MB
# Tracker(毎フレーム)
Tracker特徴: 288×288×256 ≈ 85MB
メモリバンク: 6フレーム×obj数×特徴 ≈ 可変
オブジェクトポインター: 16×256 ≈ 16KB(無視できる)
# 合計(10オブジェクト追跡時)
≈ 1.5GB + メモリバンク(〜500MB) ≈ 2GB
最適化のヒント:
-
offload_output_to_cpu_for_eval=True:長い動画向け -
memory_temporal_stride_for_eval > 1:メモリフレーム間引き - FP16/BF16:メモリ半減
6.2 計算ボトルネック
プロファイリング結果(推定):
Detector: 55%
├─ ViTバックボーン: 25% (Shared)
├─ Cross-Attention: 20%
└─ PixelDecoder: 10%
Tracker: 35%
├─ Tracker Neck: 20%
└─ Memory Attention: 15%
統合レイヤー: 10%
└─ IoUマッチング: 10%
高速化のポイント:
- Detectorが支配的 →
allow_new_detections=Falseで無効化(propagation時) - バッチ処理でスループット向上
- コンパイル(torch.compile)
7. 未解決の疑問と展望
7.1 残った疑問
Q1: なぜSAM3はHieraではなくViTなのか?
SAM2では高速なHieraが採用されていました。なぜSAM3ではViTに戻ったのでしょうか?
推測:
- Detectorの精度優先: オープンボキャブラリー検出には強力なViTDetが必要
- 統合の効率化: DetectorとTrackerでバックボーンを共有することで、メモリ効率と実装の単純さを優先(Dual BackboneだとVRAMが倍になる)
- Hieraの役割: Tracker用の軽量な特徴量は、ViT出力からNeck(FPN)を通して生成することで模倣
Q2: Learned Queriesは100で十分?
最大100オブジェクトの制限、実用上問題ない?
考察:
- SA-Co評価では99.8%のケースで十分
- 混雑シーンでは不足の可能性
- 可変スロット数は今後の課題
Q3: メモリ選択戦略は最適?
固定6フレームより、動的選択の方が良い?
SAM2Longの試み:
-
use_memory_selection=Trueオプション - 有効なフレームのみ選択
- まだ研究段階
8. 総括
8.1 SAM3の設計思想
3回に渡るの深掘りから見えた設計思想:
1. タスク分離
- Detector = Problem Solver
- Tracker = Answer Verifier
- 統合レイヤー = Simple Arbiter
2. **計算効率と品質のバランス**
- Detector: 高コスト・高品質(必要な時だけ)
- Tracker: 低コスト・十分品質(常時動作)
3. **モジュール性**
- 各コンポーネント独立
- 改善・デバッグが容易
- 拡張性が高い
8.2 最後に
最初の疑問「なぜ288×288?」から始まった探求は、SAM3の設計思想の深い理解へと繋がりました。
288×288は単なる低解像度ではなく、
「追跡」というタスクに最適化された
賢い設計判断だった
SAM3は、画像とビデオのセグメンテーションを統合した、現時点での最先端モデルです。完璧ではありませんが、その設計思想とトレードオフは、今後のモデル開発の参考になるでしょう。
SAM4が出る頃には、また新しい発見があるはずです。楽しみですね!
参考資料
- https://github.com/facebookresearch/sam3
- https://ai.meta.com/research/publications/sam-3-segment-anything-with-concepts/
- SAM2論文: https://ai.meta.com/sam2/
- Hiera論文: https://arxiv.org/abs/2306.00989
- DETR論文: https://arxiv.org/abs/2005.12872
- コード:sam3/model/sam3_video_base.py, sam3/model/sam3_tracker_base.py, sam3/model/sam3_image.py

