SAM2による画像セグメンテーション
事前準備
基本環境の構築
Python, Windsurfをインストールしていない場合の手順(インストール済みの場合は実行不要)。
- 管理者権限でコマンドプロンプトを起動する(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)。
- 以下のコマンドをそれぞれ実行する(winget コマンドは1つずつ実行)。
REM Python をシステム領域にインストール
winget install --scope machine --id Python.Python.3.12 -e --silent
REM Windsurf をシステム領域にインストール
winget install --scope machine --id Codeium.Windsurf -e --silent
REM Python のパス設定
set "PYTHON_PATH=C:\Program Files\Python312"
set "PYTHON_SCRIPTS_PATH=C:\Program Files\Python312\Scripts"
echo "%PATH%" | find /i "%PYTHON_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_PATH%" /M >nul
echo "%PATH%" | find /i "%PYTHON_SCRIPTS_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_SCRIPTS_PATH%" /M >nul
REM Windsurf のパス設定
set "WINDSURF_PATH=C:\Program Files\Windsurf"
if exist "%WINDSURF_PATH%" (
echo "%PATH%" | find /i "%WINDSURF_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%WINDSURF_PATH%" /M >nul
)
必要なパッケージのインストール
コマンドプロンプトを管理者として実行(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する。
pip install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
pip install sam2 opencv-python numpy requests
SAM2画像セグメンテーションプログラム
# SAM2画像セグメンテーションプログラム
# 特徴技術名: SAM2 (Segment Anything Model 2)
# 出典: Ravi, N., Gabeur, V., Hu, Y.-T., Hu, R., Ryali, C., Ma, T., Khedr, H., Rädle, R., Rolland, C., Pan, J., Alwala, K. V., Carion, N., Wu, C.-Y., Girshick, R., Dollár, P., & Feichtenhofer, C. (2024). SAM 2: Segment Anything in Images and Videos. arXiv:2408.00714
# 特徴機能: プロンプタブル・セグメンテーション - 点プロンプトを用いて画像内の任意のオブジェクトを高精度にセグメント化し、グリッドベースの網羅的検出を実現
# 学習済みモデル: SAM2 Hiera-Tiny - 最新のSegment Anything Model 2、元のSAMより6倍高速化と精度向上を実現、44FPSでのリアルタイム処理対応、URL: https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_tiny.pt
# 方式設計:
# - 関連利用技術: PyTorch(深層学習フレームワーク、GPU加速推論)、OpenCV(画像処理、カメラアクセス)、NumPy(数値計算)、Requests(モデルダウンロード)
# - 入力と出力: 入力: 動画(ユーザは「0:動画ファイル,1:カメラ,2:サンプル動画」のメニューで選択.0:動画ファイルの場合はtkinterでファイル選択.1の場合はOpenCVでカメラが開く.2の場合はhttps://github.com/opencv/opencv/blob/master/samples/data/vtest.aviを使用)、出力: OpenCV画面でリアルタイムセグメンテーション結果表示(色分けされたマスクと輪郭線)、1秒間隔で検出オブジェクト数をprint()表示、プログラム終了時にresult.txtファイルに保存
# - 処理手順: 1)入力フレームの前処理(1024x1024へのリサイズとパディング)、2)グリッドベース点プロンプト生成(20pixel間隔)、3)SAM2ImagePredictorによる各点でのセグメンテーション実行、4)全マスクの論理和統合と連結成分分析、5)面積閾値フィルタリングと色分け表示
# - 前処理、後処理: 前処理)1024x1024へのリサイズとパディング、テンソル正規化、後処理)連結成分による物体分離、最小面積フィルタリング
# - 追加処理: グリッドベースのプロンプト生成により全画像を網羅的にセグメンテーション、SAM2の統一アーキテクチャによる高速処理で精度向上
# - 調整を必要とする設定値: グリッドポイント間隔(現在20pixel)- セグメンテーションの細かさを制御、最小面積閾値(現在1000pixel)- 小さなノイズ除去レベルを制御
# 将来方策: グリッドポイント間隔の動的調整機能 - フレーム内のオブジェクト密度を分析し、密度が高い領域では間隔を狭く、低い領域では広く設定する適応的グリッド生成
# その他の重要事項: Windows環境でのDirectShowバックエンド使用、バッファクリアによる最新フレーム取得、Apache 2.0ライセンスでオープンソース公開、SA-Vデータセット使用
# 前準備: pip install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
# pip install sam2 opencv-python numpy requests
import cv2
import numpy as np
import torch
import requests
import tkinter as tk
from tkinter import filedialog
import urllib.request
import time
from pathlib import Path
from sam2.build_sam import build_sam2
from sam2.sam2_image_predictor import SAM2ImagePredictor
# 設定定数
CHECKPOINT_NAME = 'sam2_hiera_tiny.pt'
BASE_URL = 'https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_tiny.pt'
CHUNK_SIZE = 8192
SEED = 42
# セグメンテーション設定
INPUT_SIZE = 1024
GRID_SPACING = 20 # グリッドポイント間隔(調整可能)
GRID_START = 100
GRID_END = 900
MIN_AREA_THRESHOLD = 1000 # 最小面積閾値(調整可能)
# ファイル出力設定
TIME_FORMAT = '%Y-%m-%d %H:%M:%S'
FILE_ENCODING = 'utf-8'
def process_frame(frame, predictor, device):
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
obj_count = 0
with torch.no_grad():
h, w = rgb.shape[:2]
scale = INPUT_SIZE / max(h, w)
new_h, new_w = int(h * scale), int(w * scale)
resized = cv2.resize(rgb, (new_w, new_h))
padded = np.zeros((INPUT_SIZE, INPUT_SIZE, 3), dtype=np.uint8)
padded[:new_h, :new_w] = resized
predictor.set_image(padded)
# グリッドポイント生成
points = [[x, y] for y in range(GRID_START, GRID_END, GRID_SPACING)
for x in range(GRID_START, GRID_END, GRID_SPACING)]
all_masks = np.zeros((INPUT_SIZE, INPUT_SIZE), dtype=bool)
for point in points:
masks, _, _ = predictor.predict(
point_coords=np.array([point]),
point_labels=np.array([1]),
multimask_output=False
)
if len(masks) > 0:
all_masks |= masks[0].astype(bool)
mask_img = np.zeros((h, w, 3), dtype=np.uint8)
result_frame = frame.copy()
if all_masks.sum() > 0:
combined_mask = all_masks[:new_h, :new_w]
resized_masks = cv2.resize(combined_mask.astype(np.uint8), (w, h))
num_labels, labels = cv2.connectedComponents(resized_masks)
for i in range(1, num_labels):
mask = labels == i
if mask.sum() > MIN_AREA_THRESHOLD:
obj_count += 1
color = np.random.randint(0, 255, 3).tolist()
mask_img[mask] = color
# 輪郭線描画
contours, _ = cv2.findContours(mask.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(result_frame, contours, -1, color, 2)
return cv2.addWeighted(result_frame, 0.6, mask_img, 0.4, 0), obj_count
# 乱数シード設定(再現性確保)
np.random.seed(SEED)
torch.manual_seed(SEED)
# weightsフォルダの作成
weights_dir = Path('weights')
weights_dir.mkdir(exist_ok=True)
# SAM2モデルダウンロード
model_path = weights_dir / CHECKPOINT_NAME
if not model_path.exists():
print(f'ダウンロード中: {CHECKPOINT_NAME}')
try:
response = requests.get(BASE_URL, stream=True)
response.raise_for_status()
with open(model_path, 'wb') as file:
for chunk in response.iter_content(chunk_size=CHUNK_SIZE):
if chunk:
file.write(chunk)
print(f'ダウンロード完了: {model_path}')
except Exception as e:
print(f'モデルのダウンロードに失敗しました: {BASE_URL}')
print(f'エラー: {e}')
exit()
else:
print(f'既存ファイルを使用: {CHECKPOINT_NAME}')
# デバイス設定(GPU/CPUフォールバック)
try:
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
if device.type == 'cuda':
# GPU動作確認
torch.cuda.FloatTensor(1)
except Exception:
device = torch.device('cpu')
print(f'使用デバイス: {device}')
# SAM2モデル初期化
sam2_model = build_sam2('sam2_hiera_t.yaml', str(model_path), device=device)
predictor = SAM2ImagePredictor(sam2_model)
# プログラム説明表示
print('=== SAM2画像セグメンテーションプログラム ===')
print('概要: SAM2を使用して動画内のオブジェクトをリアルタイムでセグメント化します')
print('操作: qキーで終了')
print('出力: セグメンテーション結果をOpenCV画面に表示、検出数を1秒毎に表示')
print('=====================================')
print('0: 動画ファイル')
print('1: カメラ')
print('2: サンプル動画')
choice = input('選択: ')
temp_file = None
if choice == '0':
root = tk.Tk()
root.withdraw()
path = filedialog.askopenfilename()
if not path:
exit()
cap = cv2.VideoCapture(path)
elif choice == '1':
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
elif choice == '2':
url = 'https://github.com/opencv/opencv/blob/master/samples/data/vtest.avi?raw=true'
filename = 'vtest.avi'
try:
urllib.request.urlretrieve(url, filename)
temp_file = filename
cap = cv2.VideoCapture(filename)
except Exception as e:
print(f'動画のダウンロードに失敗しました: {url}')
print(f'エラー: {e}')
exit()
else:
print('無効な選択です')
exit()
# タイマーと結果記録用変数
last_time = time.time()
results = []
# メイン処理
try:
while True:
cap.grab()
ret, frame = cap.retrieve()
if not ret:
break
result, obj_count = process_frame(frame, predictor, device)
cv2.imshow('SAM2セグメンテーション結果', result)
# 1秒間隔での表示
current_time = time.time()
if current_time - last_time >= 1.0:
print(f'検出オブジェクト数: {obj_count}個')
results.append(f'{time.strftime(TIME_FORMAT)} - 検出オブジェクト数: {obj_count}個')
last_time = current_time
if cv2.waitKey(1) & 0xFF == ord('q'):
break
finally:
cap.release()
cv2.destroyAllWindows()
if temp_file:
Path(temp_file).unlink()
# 結果をファイルに保存
if results:
with open('result.txt', 'w', encoding=FILE_ENCODING) as f:
f.write('\n'.join(results))
print('result.txtに保存しました')
print('処理完了')
実行と結果
- 上記のプログラムを実行する
- プログラム実行時には、SAM2モデルがダウンロードされる。
- セグメンテーションが開始される。
- 検出された物体が色分けされ、輪郭線が描かれ,元画像に重ねて表示される。
- 'q'キーで処理が終了する。
実験・探求
パラメータ調整実験
グリッドベースのポイントプロンプトの密度を変更することで、セグメンテーションの精度と処理速度のバランスを調整できる。プログラム内のrange(100, 900, 20)の値を変更し、密なグリッド(range(100, 900, 10))や粗いグリッド(range(100, 900, 50))を試すことができる。
最小面積フィルタの閾値(現在は1000)を変更することで、検出される物体の大きさを制御できる。小さな物体も検出したい場合は値を小さくし、大きな物体のみを検出したい場合は値を大きくする。
段階的検証実験
基礎実験として単一物体をWebカメラの前に配置し、検出性能を確認する。透明な物体、反射する物体、複雑な形状の物体、単色の物体など、特徴の異なる物体での検出結果を比較する。
応用実験として複数の物体を同時に配置し、重なり合った物体や類似した色の物体がどのように区別されるかを観察する。限界確認実験として照明条件を変更し、明るい環境と暗い環境での性能差を確認する。背景の複雑さによる影響も比較する。