Tracker の中身を追う - sam3 深掘り
前回は Detector の仕組みを見ました。Detector は Learned Queries を使って画像から最大100個のオブジェクト候補を検出する DETR-style のモデルでした。
今回は Tracker を深掘りします。Video処理で時間的に一貫したセグメンテーションを実現するのがTrackerの役割です。そして調べていくと、意外な発見がありました。
${\huge \textsf{Trackerはテキストプロンプト使わない}}$
自分的にはこれは結構驚きだった・・・
1. DetectorとTrackerの役割分担
まず基本的な構成を確認しておきましょう。
Sam3VideoBase
├── Detector (Sam3ImageOnVideo)
│ └── テキスト・幾何学プロンプトで新規検出
│ └── 解像度: 1008×1008
│ └── バックボーン: ViT (Shared) + Detector Neck
│
└── Tracker (Sam3TrackerPredictor)
└── 既存オブジェクトを時間的に追跡
└── 解像度: 288×288 (latent)
└── バックボーン: ViT (Shared) + Tracker Neck (SAM2-style)
重要なのは、両方が同じViTバックボーンを共有しているということです。
- Detector: 毎フレーム新しいオブジェクトを検出
- Tracker: 既存のオブジェクトを追跡
そして最後に統合レイヤー(Sam3VideoBase)が両者の出力をマージします。
2. Trackerへの入力 - 何が渡される?
新しい幾何学プロンプト(ポイント)がTrackerに渡される時、何が入力されるのでしょうか?
入力の構成
# Trackerへの入力
track_step(
current_vision_feats, # 現在のTracker用特徴 [B, C, 288, 288]
point_inputs, # ポイント座標 [B, P, 2] (UV座標)
output_dict, # 過去のメモリ
use_prev_mem_frame=True, # メモリ使用フラグ
)
1. 現在フレームの画像特徴
- 共通ViTバックボーンからTracker専用Neckを通って出力された特徴
- サイズは288×288のlatent特徴(Detector用の高解像度特徴とは異なるパス)
- SAM2の設計を踏襲しているが、バックボーン自体はSAM3のViTを使用
2. 前フレームのメモリ
最大 6フレーム分 のメモリが使われます:
num_maskmem = 7 # 現在フレーム + 過去6フレーム
# メモリの構成
- Spatial memory features: 過去のマスク特徴(最大6フレーム)
- Object pointers: オブジェクトの高レベル表現(最大16個)
メモリフレームの選択:
t_rel == 1: 直前のフレーム (frame_idx - 1)
t_rel >= 2: r フレーム間隔 (evaluation時に設定可能)
3. 幾何学プロンプト(ポイント)
重要なのは、単純なUV座標が渡されるということです:
point_inputs = {
"point_coords": [[x, y], ...], # [B, P, 2] 絶対ピクセル座標
"point_labels": [1, 0, ...], # [B, P] (1=positive, 0=negative)
}
エンコーディングはTracker内部で行われます:
- Prompt Encoder: Fourier Feature Encoding
- SAM Mask Decoder: 画像特徴とcross-attention
3. 衝撃の事実:テキストプロンプトはTrackerに渡らない
ここで重要な発見がありました。Trackerはテキストプロンプトを使いません。
毎フレーム実行される run_backbone_and_detection()
# Step 1: テキストをDetectorに渡す
text_outputs = self.detector.backbone.forward_text(
input_batch.find_text_batch
)
sam3_image_out = self.detector.forward_video_grounding_multigpu(
backbone_out={
"img_batch_all_stages": input_batch.img_batch,
**text_outputs, # ← テキスト特徴(毎フレーム)
},
...
)
# Step 3: Tracker用の特徴(テキストなし!)
tracker_backbone_out = {
"vision_features": tracker_backbone_fpn[-1], # Tracker用特徴のみ
"vision_pos_enc": ...,
"backbone_fpn": tracker_backbone_fpn,
}
役割分担:
- Detector: テキストプロンプトで新しいオブジェクトを検出(毎フレーム)
- Tracker: 幾何学情報とメモリで既存オブジェクトを追跡
これはSAM3の基本アーキテクチャで、Sam3VideoBaseを継承する全てのモデルで共通です。
4. 初回プロンプトの特別性
幾何学プロンプト(ポイント)を追加する時、実は2つのケースがあります。
ケース1: 初めて追跡するフレーム
is_init_cond_frame = frame_idx not in inference_state["frames_already_tracked"]
# → True (初回)
# メモリを使わない!
pix_feat_with_mem = current_vision_feats + self.no_mem_embed
特徴:
is_init_cond_frame=True-
メモリなし(
no_mem_embedのみ) - SAM-likeの単一フレームセグメンテーション
ケース2: 既に追跡済みのフレームに追加プロンプト
is_init_cond_frame = False # 既に追跡済み
# メモリを使う
pix_feat_with_mem = self._prepare_memory_conditioned_features(
current_vision_feats,
output_dict, # 前6フレーム + オブジェクトポインター16個
...
)
特徴:
is_init_cond_frame=False- メモリあり(前6フレーム + ポインター16個)
- SAM2-likeのinteractive refinement
通常のpropagation(プロンプトなし)
# propagate_in_video()内
current_out = self._run_single_frame_inference(
is_init_cond_frame=False, # 常にFalse
point_inputs=None, # プロンプトなし
use_prev_mem_frame=True, # メモリ使う
)
差分まとめ:
| ケース | メモリ | プロンプト | 動作 |
|---|---|---|---|
| 初回プロンプト | なし | ポイント | SAM-like |
| 追加プロンプト | あり | ポイント | SAM2-like refinement |
| Propagation | あり | なし | 自動追跡 |
5. DetectorとTrackerの統合 - IoUマッチング
毎フレーム、DetectorとTrackerの両方が動いています。では、どう統合されるのでしょうか?
統合のフロー
# 毎フレーム
Detector → N個のマスク [N, 1008, 1008] + スコア [N]
Tracker → M個のマスク [M, 288, 288] + obj_id [M]
# build_outputs()で統合
1. TrackerマスクをF.interpolateで1008×1008に
2. IoUマッチング(N×M総当たり)
3. マッチング判定と新規追加
IoUマッチングの詳細
# N×M IoUマトリックス計算
for each Detector mask (N個):
for each Tracker mask (M個):
iou = compute_iou(det_mask, trk_mask)
# マッチング判定
if max_iou > assoc_iou_thresh:
# 既存オブジェクト → Trackerで継続
use_tracker_mask(obj_id)
else:
# 新規オブジェクト
if detector_score > new_det_thresh:
# 新規性 + 信頼性 → 追加
add_new_object(new_obj_id)
判定基準:
- 新規性: IoUマッチングで既存Trackerとマッチしない
- 信頼性: Detectorスコア > 閾値
両方満たせば新しいobj_idで追加されます。
デフォルトの動作
# build_outputs()内
# Part 1: 既存オブジェクト(Tracker)
for obj_id, mask in zip(existing_obj_ids, tracker_masks):
obj_id_to_mask[obj_id] = mask # Trackerのまま
# Part 2: 新規オブジェクト(Detector)
for obj_id, mask in zip(new_obj_ids, detector_masks):
obj_id_to_mask[obj_id] = mask # Detectorから追加
# Part 3: Reconditioning(デフォルト無効)
if reconditioning_enabled:
# 条件満たせばDetectorで置き換え
obj_id_to_mask[obj_id] = detector_mask
デフォルト動作:
- 既存オブジェクト → Trackerで継続(Detectorで置き換えない)
- 新規オブジェクト → Detectorで追加
- 置き換え(Reconditioning) → 無効(
reconstruction_bbox_iou_thresh=0.0)
6. まとめ
Trackerの特徴
-
入力:
- Tracker用特徴(288×288 latent)
- 前フレームのメモリ(最大6フレーム + ポインター16個)
- 幾何学プロンプト(ポイント、UV座標)
- テキストプロンプトは使わない
-
動作モード:
- 初回プロンプト:メモリなし(SAM-like)
- 追加プロンプト:メモリあり(SAM2-like)
- Propagation:自動追跡
-
統合方式:
- IoU総当たりマッチング(N×M)
- 新規性(IoU)+ 信頼性(スコア)で判定
- デフォルトはTracker優先
DetectorとTrackerの役割分担
[Detector]
- テキストプロンプトで新規検出
- ViT特徴(1008×1008)
- 毎フレーム実行
- 学習されたPixelDecoder/FPN
[Tracker]
- 幾何学プロンプトで追跡
- Tracker用特徴(288×288)
- 前フレームのメモリ活用
- F.interpolateで1008×1008に
[統合レイヤー]
- IoUマッチング
- 既存→Tracker、新規→Detector
- obj_id管理
前回のDetectorと合わせて、SAM3のVideo処理の全体像が見えてきました。
参考資料
- https://github.com/facebookresearch/sam3
- https://ai.meta.com/research/publications/sam-3-segment-anything-with-concepts/
- コード:sam3/model/sam3_video_base.py, sam3/model/sam3_tracker_base.py