SAMによる画像セグメンテーション

【概要】 Meta AI ResearchのSAM(Segment Anything Model)は、画像内の任意物体を分割するセグメンテーションモデルである。ゼロショット転移学習により未知の物体や環境でもセグメンテーションを実現。Windows環境での実行手順、プログラムコード、実験アイデアを含む。

目次

概要

SAM(Segment Anything Model)は、画像内の任意の物体を分割するセグメンテーションのモデルである。セグメンテーションは、画像内の各画素がどの物体に属するかを識別する技術である。Meta AI Research(旧Facebook AI Research)が開発し、論文「Segment Anything」(ICCV 2023)で発表された。

SAMの特徴は、ゼロショット転移学習(事前学習済みモデルを新しいタスクに適用する手法)により未知の物体や環境でもセグメンテーションを実現する点にある。この技術は医療画像解析、自動運転の物体検出、製造業における品質検査システム等の分野で活用される。

SAMを使用したリアルタイム画像セグメンテーションプログラムを動作させ、AIの物体認識プロセスを確認する。

Python開発環境,ライブラリ類

ここでは、最低限の事前準備について説明する。機械学習や深層学習を行う場合は、NVIDIA CUDA、Visual Studio、Cursorなどを追加でインストールすると便利である。これらについては別ページ https://www.kkaneko.jp/cc/dev/aiassist.htmlで詳しく解説しているので、必要に応じて参照してください。

Python 3.12 のインストール

インストール済みの場合は実行不要。

管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する。管理者権限は、wingetの--scope machineオプションでシステム全体にソフトウェアをインストールするために必要である。

REM Python をシステム領域にインストール
winget install --scope machine --id Python.Python.3.12 -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

関連する外部ページ

Python の公式ページ: https://www.python.org/

AI エディタ Windsurf のインストール

Pythonプログラムの編集・実行には、AI エディタの利用を推奨する。ここでは,Windsurfのインストールを説明する。

管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行して、Windsurfをシステム全体にインストールする。管理者権限は、wingetの--scope machineオプションでシステム全体にソフトウェアをインストールするために必要となる。

winget install --scope machine Codeium.Windsurf -e --silent

関連する外部ページ

Windsurf の公式ページ: https://windsurf.com/

必要なライブラリのインストール

コマンドプロンプトを管理者として実行(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する:


pip install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
pip install opencv-python matplotlib numpy scikit-learn segment-anything

プログラムコード

概要

このプログラムは,各物体を個別の領域として分離する。

主要技術

参考文献


# SAMリアルタイム画像セグメンテーション
# 特徴技術名: Segment Anything Model (SAM)
# 出典: A. Kirillov et al., "Segment Anything," in Proc. IEEE/CVF International Conference on Computer Vision (ICCV), 2023, pp. 4015-4026.
# 特徴機能: ゼロショット汎用セグメンテーション - 追加学習なしで任意の物体を自動検出・分割
# 学習済みモデル: SAM ViT-Base(91Mパラメータ、375MB、汎用物体セグメンテーション、https://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth)
# 方式設計
#   - 関連利用技術:
#     * PyTorch(深層学習フレームワーク、GPU加速対応)
#     * OpenCV(画像処理、カメラ制御、表示機能)
#     * scikit-learn(特徴量正規化、MinMaxScaler)
#   - 入力と出力: 入力: 動画(ユーザは「0:動画ファイル,1:カメラ,2:サンプル動画」のメニューで選択.0:動画ファイルの場合はtkinterでファイル選択.1の場合はOpenCVでカメラが開く.2の場合はhttps://github.com/opencv/opencv/blob/master/samples/data/vtest.aviを使用)、出力: セグメンテーション結果(OpenCV画面でリアルタイム表示、各検出物体を色分け表示)
#   - 処理手順: 1.フレーム取得 2.BGR→RGB変換 3.SAM自動マスク生成 4.特徴量抽出(重心・アスペクト比) 5.特徴量正規化 6.色付けマスク生成 7.画像合成・表示
#   - 前処理: BGR→RGB色空間変換(SAMの入力形式に適合)
#   - 後処理: 特徴量正規化による色分け、重み付き画像合成(元画像60%、マスク40%)
#   - 追加処理: バッファクリア処理(grab/retrieve分離によるリアルタイム性向上)、乱数シード固定(色付け再現性確保)
#   - 調整を必要とする設定値: points_per_side(デフォルト32): グリッドポイント数、pred_iou_thresh(デフォルト0.88): マスク品質閾値
# 将来方策: pred_iou_thresh値の動的調整機能 - フレーム内の物体数に応じて閾値を自動調整し、最適な検出精度を実現
# その他の重要事項: CUDA対応、DirectShowバックエンド使用
# 前準備: pip install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
#         pip install opencv-python matplotlib numpy scikit-learn segment-anything

import cv2
import numpy as np
import torch
import tkinter as tk
from tkinter import filedialog
import urllib.request
import os
import time
from pathlib import Path
from sklearn.preprocessing import MinMaxScaler
from segment_anything import sam_model_registry, SamAutomaticMaskGenerator

# 設定定数
CHECKPOINT_NAME = 'sam_vit_b_01ec64.pth'
MODEL_TYPE = 'vit_b'
BASE_URL = 'https://dl.fbaipublicfiles.com/segment_anything/'
SEED = 42

# ファイル名定数
RESULT_FILE = 'result.txt'
SAMPLE_VIDEO_FILE = 'vtest.avi'
TIME_FORMAT = '%Y-%m-%d %H:%M:%S'

# 調整可能な設定値
POINTS_PER_SIDE = 32  # グリッドポイント数
PRED_IOU_THRESH = 0.88  # マスク品質閾値

# 画像処理設定
ORIGINAL_WEIGHT = 0.6  # 元画像の重み
MASK_WEIGHT = 0.4      # マスクの重み
DEFAULT_COLOR = 0.5    # 単一マスク時のデフォルト色値

# 乱数シード設定(再現性確保)
np.random.seed(SEED)
torch.manual_seed(SEED)

print('=== SAMリアルタイム画像セグメンテーション ===')
print('概要: Segment Anything Model (SAM)を使用した自動物体検出')
print("操作: 'q'キーで終了")
print('出力: 検出された物体を色分けして表示')
print('=====================================')

# SAMモデルダウンロード
checkpoint_path = Path(CHECKPOINT_NAME)
if not checkpoint_path.exists():
    print(f'ダウンロード中: {CHECKPOINT_NAME}')
    try:
        urllib.request.urlretrieve(f'{BASE_URL}{CHECKPOINT_NAME}', checkpoint_path)
        print(f'ダウンロード完了: {checkpoint_path}')
    except Exception as e:
        print(f'モデルのダウンロードに失敗しました: {BASE_URL}{CHECKPOINT_NAME}')
        print(f'エラー: {e}')
        exit()
else:
    print(f'既存ファイルを使用: {CHECKPOINT_NAME}')

# デバイス設定(GPU/CPUフォールバック機能)
if torch.cuda.is_available():
    try:
        # CUDAメモリアクセステスト
        test_tensor = torch.zeros(1).cuda()
        del test_tensor
        device = torch.device('cuda')
    except:
        device = torch.device('cpu')
        print('GPU検出されましたが使用できません。CPUを使用します。')
else:
    device = torch.device('cpu')
print(f'使用デバイス: {device}')

# SAMモデル初期化
sam = sam_model_registry[MODEL_TYPE](checkpoint=str(checkpoint_path))
sam.to(device=device)
mask_generator = SamAutomaticMaskGenerator(
    sam,
    points_per_side=POINTS_PER_SIDE,
    pred_iou_thresh=PRED_IOU_THRESH
)


def video_processing(frame):
    # BGR to RGB変換
    frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

    # SAM自動マスク生成
    masks = mask_generator.generate(frame_rgb)

    # マスク画像初期化
    mask_img = np.zeros((frame_rgb.shape[0], frame_rgb.shape[1], 3), dtype=np.uint8)

    if masks:
        # 特徴量抽出処理
        features_list = []
        for mask_data in masks:
            mask = mask_data['segmentation']

            # 重心計算
            M = cv2.moments(mask.astype(np.uint8))
            if M['m00'] != 0:
                cx = int(M['m10'] / M['m00'])
                cy = int(M['m01'] / M['m00'])
            else:
                cx, cy = 0, 0

            # アスペクト比計算
            x, y, w, h = cv2.boundingRect(mask.astype(np.uint8))
            aspect_ratio = float(w) / h if h > 0 else 0

            features = [cx, cy, aspect_ratio]
            features_list.append(features)

        # 特徴量正規化処理
        feat_matrix = np.array(features_list)
        if len(feat_matrix) == 1:
            norm_feat = np.array([[DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_COLOR]])
        else:
            scaler = MinMaxScaler()
            norm_feat = scaler.fit_transform(feat_matrix)

        # 色付けマスク生成処理
        for i, mask_data in enumerate(masks):
            mask = mask_data['segmentation']
            color = (norm_feat[i] * 255).astype(np.uint8)
            mask_img[mask] = color

    # 画像合成処理
    display_frame = cv2.addWeighted(frame_rgb, ORIGINAL_WEIGHT, mask_img, MASK_WEIGHT, 0)
    result_frame = cv2.cvtColor(display_frame, cv2.COLOR_RGB2BGR)

    # 検出物体数を返す
    return result_frame, len(masks) if masks else 0


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/raw/master/samples/data/vtest.avi'
    try:
        urllib.request.urlretrieve(url, SAMPLE_VIDEO_FILE)
        temp_file = SAMPLE_VIDEO_FILE
        cap = cv2.VideoCapture(SAMPLE_VIDEO_FILE)
    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

        proc_frame, n_masks = video_processing(frame)
        cv2.imshow('Video', proc_frame)

        # 1秒間隔で処理結果を表示
        cur_time = time.time()
        if cur_time - last_time >= 1.0:
            res_text = f'検出物体数: {n_masks}'
            print(res_text)
            results.append(f'{time.strftime(TIME_FORMAT)} - {res_text}')
            last_time = cur_time

        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
finally:
    cap.release()
    cv2.destroyAllWindows()
    if temp_file:
        os.remove(temp_file)

    # 結果をファイルに保存
    if results:
        with open(RESULT_FILE, 'w', encoding='utf-8') as f:
            f.write('\n'.join(results))
        print(f'{RESULT_FILE}に保存しました')

使用方法

  1. 上記のプログラムを実行
  2. 初回実行時はSAMモデル(約375MB)が自動ダウンロードされる
  3. カメラウィンドウが表示され、リアルタイムでセグメンテーション結果を確認する
  4. 画面には元画像と色分けされたマスクが合成表示される
  5. 'q'キーでプログラムを終了する

実行結果の解釈:

実験・探求のアイデア

SAMモデルの選択と比較

プログラム上部の定数を変更して異なるモデルサイズを選択できる。

ViT-Base(最小・高速、375MB)

SAM_CHECKPOINT_NAME = "sam_vit_b_01ec64.pth"
MODEL_TYPE = "vit_b"

ViT-Large(中間、1.25GB)

SAM_CHECKPOINT_NAME = "sam_vit_l_0b3195.pth"
MODEL_TYPE = "vit_l"

ViT-Huge(最大・高精度、2.56GB)

SAM_CHECKPOINT_NAME = "sam_vit_h_4b8939.pth"
MODEL_TYPE = "vit_h"

処理速度と精度の関係観察
異なるモデルサイズで同じシーンを処理し、検出精度と処理速度の関係を比較する。

セグメンテーション境界の精密度調査
複雑な境界を持つ物体(髪の毛、植物の葉など)での境界検出精度をモデル間で比較する。

特徴量による物体表現の実験

特徴量変更実験
現在の色決定要素(重心x座標、重心y座標、アスペクト比)を他の特徴量に変更して物体の表現方法を比較する:

特徴量空間の理解

AIの物体認識プロセスの探求

物体特性発見実験

環境条件による性能変化の発見

AI認識プロセスの洞察