オブジェクト分離 は何をしますか?
動画から1つの被写体を背景から分離します。すべてユーザーのコンピューター上で実行され、アップロードは行われません。結果は、被写体が緑色で強調表示された同じ動画と、フレームごとのマスクがディスクに保存されます。
Pythonをその場で記述して実行します。以下のコードブロックはテスト済みです。ゼロから始めるのではなく、これらを適応させてください。
1. 削除可能なフォルダにセットアップ
Python 3.10以上とffmpegが必要です。プロジェクトフォルダ内に仮想環境をインストールすれば、そのフォルダを削除するだけで全てを削除できます。
python3 -m venv .object-separation
.object-separation/bin/pip install torch transformers opencv-python-headless pillow numpy
Windowsの場合は、.object-separation\Scripts\pipを使用します。WindowsでNVIDIAカードを使用する場合、最初にpytorch.orgからCUDA対応のtorchをインストールしてください。通常のpipだと遅いCPUビルドがインストールされます。以下のPythonコードはすべて、仮想環境のpythonで実行してください。
2. コンピューターをスキャンしてモデルを選択
最初にスキャンを実行し、ユーザーに検出内容(デバイス、選択したモデル、ダウンロードサイズ、想定速度)を伝えてください。
| マシン | モデル | ダウンロードサイズ |
|---|---|---|
| NVIDIA GPU 12GB以上 | facebook/sam2.1-hiera-large | 898 MB |
| NVIDIA GPU 12GB未満、またはApple Silicon | facebook/sam2.1-hiera-base-plus | 323 MB |
| GPUなし(CPUのみ) | facebook/sam2.1-hiera-tiny | 156 MB |
CPUでも動作しますが遅いです:1フレームあたり約5秒なので、30fpsの10秒クリップで約30分かかります。メモリ使用量は2GB未満です。GPUの方がはるかに高速です。クリップが長くGPUがない場合は、必要な部分のみ分離することを提案してください。
# ユーザーのリクエストから設定します。フレーム番号は0から始まります。動画は固定フレームレートである必要があります。
VIDEO = "clip.mp4"
START, END = 0, 89 # 分離するフレーム範囲。クリックポイントはSTARTフレームで選択します。
PTS = [[480, 270], [100, 60]] # 960ピクセル幅でのクリックポイント:最初に被写体、次に除外するもの
LBL = [1, 0] # 1 = これが被写体、0 = これは被写体ではない
import os, platform, subprocess, numpy as np, torch, cv2
from transformers import Sam2VideoModel, Sam2VideoProcessor
print("system:", platform.system(), platform.machine())
if torch.cuda.is_available():
gb = torch.cuda.get_device_properties(0).total_memory / 1e9
print("gpu:", torch.cuda.get_device_name(0), f"{gb:.0f} GB")
dev, MODEL = "cuda", "facebook/sam2.1-hiera-" + ("large" if gb >= 12 else "base-plus")
elif getattr(torch.backends, "mps", None) and torch.backends.mps.is_available():
dev, MODEL = "mps", "facebook/sam2.1-hiera-base-plus"
else:
dev, MODEL = "cpu", "facebook/sam2.1-hiera-tiny"
print("device:", dev, "| model:", MODEL)
model = Sam2VideoModel.from_pretrained(MODEL).to(dev).eval() # 初回実行時にダウンロード
proc = Sam2VideoProcessor.from_pretrained(MODEL)
W = 960 # 作業幅。SAM 2自体が1024にリサイズするため、小さい幅にしても高速化しません。
3. クリックポイントを選択
フレームSTARTを座標グリッドとともに保存して確認します。被写体上に2〜3点を選択し、除外するもの(指、パッケージ、テーブルなど)にラベル0の点を追加します。選択内容が明らかでない場合は、ユーザーに伝えてください。
def read_frames(video, a, b):
cap = cv2.VideoCapture(video); cap.set(cv2.CAP_PROP_POS_FRAMES, a); out = []
for _ in range(a, b + 1):
ok, im = cap.read()
if not ok: break
out.append(cv2.cvtColor(cv2.resize(im, (W, int(im.shape[0] * W / im.shape[1]))), cv2.COLOR_BGR2RGB))
return out
def preview(frame, path="work/preview.jpg"): # ラベル付きグリッド付きフレーム。クリックポイント選択用
os.makedirs("work", exist_ok=True)
im = cv2.cvtColor(read_frames(VIDEO, frame, frame)[0], cv2.COLOR_RGB2BGR)
for x in range(0, im.shape[1], 80):
cv2.line(im, (x, 0), (x, im.shape[0]), (0, 255, 255), 1); cv2.putText(im, str(x), (x + 2, 12), 0, 0.4, (0, 255, 255), 1)
for y in range(0, im.shape[0], 60):
cv2.line(im, (0, y), (im.shape[1], y), (0, 255, 255), 1); cv2.putText(im, str(y), (2, y - 2), 0, 0.4, (0, 255, 255), 1)
cv2.imwrite(path, im); return path
4. 被写体を分離
動画は60フレームのチャンクで処理されるため、メモリ使用量を抑えられます。各チャンクは前のチャンクの最後のマスクから開始します。まずは短い範囲(約20フレーム)で試し、確認してから全体を実行してください。
def separate_chunk(frames, points=None, labels=None, mask=None):
H = frames[0].shape[0]
s = proc.init_video_session(video=frames, inference_device=dev, video_storage_device="cpu",
inference_state_device="cpu", dtype=torch.float32)
if mask is None:
proc.add_inputs_to_inference_session(inference_session=s, frame_idx=0, obj_ids=1,
input_points=[[points]], input_labels=[[labels]])
else:
proc.add_inputs_to_inference_session(inference_session=s, frame_idx=0, obj_ids=1,
input_masks=mask.astype(np.uint8))
res = {}
with torch.inference_mode():
model(inference_session=s, frame_idx=0)
for o in model.propagate_in_video_iterator(s, start_frame_idx=0):
m = proc.post_process_masks([o.pred_masks], original_sizes=[[H, W]], binarize=True)[0]
res[o.frame_idx] = m.squeeze().cpu().numpy().astype(bool)
return res
def separate(start, end, pts, lbl, ch=60):
out, a, seed = {}, start, None
while a < end:
b = min(end, a + ch)
fr = read_frames(VIDEO, a, b)
r = separate_chunk(fr, points=pts, labels=lbl) if seed is None else separate_chunk(fr, mask=seed)
for k, m in r.items():
out[a + k] = m
seed, a = r[len(fr) - 1], b
return out
masks = separate(START, END, PTS, LBL)
os.makedirs("work/masks/subject", exist_ok=True)
for f, m in masks.items():
cv2.imwrite(f"work/masks/subject/{f:05d}.png", m.astype(np.uint8) * 255)
5. 被写体を緑色にした動画を出力
def green(f, img, fill=0.35): # 被写体に緑の色合い、グロー、明るいエッジを付与
m = cv2.GaussianBlur(cv2.resize(masks[f].astype(np.float32), (img.shape[1], img.shape[0])), (5, 5), 0)
edge = cv2.morphologyEx((m > 0.5).astype(np.uint8), cv2.MORPH_GRADIENT, np.ones((7, 7), np.uint8)).astype(np.float32)
glow = np.clip(cv2.GaussianBlur(edge, (37, 37), 0) * 2.5, 0, 1)[..., None]
col = np.array([20, 255, 57], np.float32) # BGRの緑
img = img * (1 - m[..., None] * fill) + col * m[..., None] * fill + col * glow * 0.9
return img * (1 - edge[..., None]) + np.array([220, 255, 200], np.float32) * edge[..., None]
def render(a, b, out, fn): # a..b フレームを元の音声とともに書き出し
cap = cv2.VideoCapture(VIDEO); cap.set(cv2.CAP_PROP_POS_FRAMES, a)
fps = cap.get(cv2.CAP_PROP_FPS)
ff = subprocess.Popen(["ffmpeg", "-v", "error", "-y", "-f", "rawvideo", "-pix_fmt", "bgr24",
"-s", f"{int(cap.get(3))}x{int(cap.get(4))}", "-r", str(fps), "-i", "-",
"-ss", f"{a / fps:.4f}", "-t", f"{(b - a + 1) / fps:.4f}", "-i", VIDEO,
"-map", "0:v", "-map", "1:a?", "-c:v", "libx264", "-crf", "18", "-pix_fmt", "yuv420p",
"-c:a", "aac", "-shortest", out], stdin=subprocess.PIPE)
for f in range(a, b + 1):
ok, img = cap.read()
if not ok: break
ff.stdin.write(np.clip(fn(f, img.astype(np.float32)), 0, 255).astype(np.uint8).tobytes())
ff.stdin.close(); ff.wait()
render(START, END, "work/separated.mp4", green)
結果を引き渡す前に、いくつかのフレーム(開始、中間、手や物体が前を横切る瞬間)を確認してください。緑色が被写体以外に漏れている場合は、その箇所にラベル0のポイントを追加してステップ4を再実行します。被写体の一部が欠けている場合は、該当箇所にラベル1のポイントを追加します。
ファイルの保存先をユーザーに伝えてください:work/separated.mp4(被写体が緑色の動画)とwork/masks/subject/(フレームごとのマスク。白=被写体)。マスクはディスクに残るため、ユーザーが次に分離した被写体を使いたい場合、再分離せずにこれらのマスクから開始できます。
6. クリーンアップ(任意)
ユーザーが完了したら、すべてを削除できます:
- 仮想環境:
.object-separationフォルダを削除 - モデル:
~/.cache/huggingface/hub/models--facebook--sam2.1-hiera-*を削除(Windowsの場合:C:\Users\<名前>\.cache\huggingface\hub)
注意事項
- マスクはハードエッジです。物体や手では非常にきれいですが、柔らかい髪の毛では精度が低下します。
- 被写体に付着しているもの(パッケージ、剥離フィルムなど)は被写体の一部として扱われます。除外する場合はラベル0のポイントを追加してください。
- 動画が可変フレームレートの場合は、事前に変換してください:
ffmpeg -i in.mp4 -vsync cfr -r 30 out.mp4。 - インターネット接続が必要なのは初回実行時のみで、モデルをダウンロードします。