MediaPipe前景・背景セグメンテーション(ソースコードと実行結果)

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 mediapipe opencv-python numpy

MediaPipe前景・背景セグメンテーションプログラム

概要

このプログラムは、動画フレームから人物を検出し、その位置をバウンディングボックスで特定する。

ソースコード


# プログラム名: MediaPipe前景・背景セグメンテーションプログラム
# 特徴技術名: MediaPipe
# 出典: MediaPipe Tasks - Google
# 特徴機能: MediaPipe Selfie Segmentationによる前景・背景分離。リアルタイムで動作する軽量な前景抽出
# 学習済みモデル: Selfie Segmentation内蔵モデル(前景・背景分離)
# 方式設計:
#   - 関連利用技術:
#     - MediaPipe: Googleが開発したマルチプラットフォーム機械学習ソリューション
#     - OpenCV: 画像処理、カメラ制御、描画処理、動画入出力管理
#   - 入力と出力: 入力: 動画(ユーザは「0:動画ファイル,1:カメラ,2:サンプル動画」のメニューで選択.0:動画ファイルの場合はtkinterでファイル選択.1の場合はOpenCVでカメラが開く.2の場合はhttps://raw.githubusercontent.com/opencv/opencv/master/samples/data/vtest.aviを使用)、出力: OpenCV画面でリアルタイム表示(検出した前景を半透明マスクで表示)、1秒間隔でprint()による処理結果表示、プログラム終了時にresult.txtファイルに保存
#   - 処理手順: 1.フレーム取得、2.MediaPipe推論実行、3.前景・背景分離、4.信頼度閾値による選別、5.半透明マスク描画
#   - 前処理、後処理: 前処理:MediaPipe内部で自動実行。後処理:信頼度による閾値フィルタリングを実施
#   - 追加処理: セグメンテーションマスクの平均信頼度計算、前景領域の面積・重心計算
#   - 調整を必要とする設定値: CONF_THRESH(セグメンテーション信頼度閾値、デフォルト0.5)- 値を上げると誤検出が減少するが検出漏れが増加
# 将来方策: CONF_THRESHの動的調整機能。フレーム毎のマスク面積を監視し、面積が閾値を超えた場合は信頼度を上げ、面積が少ない場合は下げる適応的制御の実装
# その他の重要事項: Windows環境専用設計、複数人物が存在する場合も単一の前景マスクとして処理
# 前準備:
#   - pip install mediapipe opencv-python numpy

import cv2
import tkinter as tk
from tkinter import filedialog
import os
import numpy as np
import mediapipe as mp
import warnings
import time
import urllib.request

warnings.filterwarnings('ignore')

# ===== 設定・定数管理 =====
# MediaPipe設定
mp_selfie_segmentation = mp.solutions.selfie_segmentation

# モデル選択(0, 1から選択可能)
MODEL_SIZE = '0'  # 使用するモデルサイズ(0=一般モデル, 1=横向き画像最適化モデル)

# モデル情報
MODEL_INFO = {
    '0': {
        'name': 'General Model',
        'desc': '一般用途(推奨)',
        'model_selection': 0
    },
    '1': {
        'name': 'Landscape Model',
        'desc': '横向き画像最適化',
        'model_selection': 1
    }
}

# クラス名(前景)
CLASS_NAME = 'foreground'

# 前景用の色(緑)
FOREGROUND_COLOR = (0, 255, 0)

SAMPLE_URL = 'https://raw.githubusercontent.com/opencv/opencv/master/samples/data/vtest.avi'
SAMPLE_FILE = 'vtest.avi'
RESULT_FILE = 'result.txt'

# カメラ設定
WINDOW_WIDTH = 1280  # カメラ解像度幅
WINDOW_HEIGHT = 720  # カメラ解像度高さ
FPS = 30  # フレームレート

# 検出パラメータ(調整可能)
CONF_THRESH = 0.5  # セグメンテーション信頼度閾値(0.0-1.0)

# 表示設定
PRINT_INTERVAL = 1.0  # 結果出力間隔(秒)


# プログラム概要表示
print('=== MediaPipe前景・背景セグメンテーションプログラム ===')
print('概要: リアルタイムで前景を抽出し、半透明マスクで表示します')
print('機能: MediaPipe Selfie Segmentationによる前景・背景分離')
print('操作: qキーで終了')
print('出力: 1秒間隔での処理結果表示、終了時にresult.txt保存')
print()

# システム初期化
print('システム初期化中...')
start_time = time.time()

# MediaPipeモデル初期化
segmentation = None
try:
    print(f'MediaPipe Selfie Segmentation {MODEL_INFO[MODEL_SIZE]["name"]}モデルを初期化中...')
    segmentation = mp_selfie_segmentation.SelfieSegmentation(
        model_selection=MODEL_INFO[MODEL_SIZE]["model_selection"]
    )
    print(f'MediaPipe Selfie Segmentation {MODEL_INFO[MODEL_SIZE]["name"]}モデルの初期化が完了しました')
    print(f'モデル: {MODEL_INFO[MODEL_SIZE]["name"]} ({MODEL_INFO[MODEL_SIZE]["desc"]})')
    print(f'セグメンテーション対象: 前景(人物等)')
except Exception as e:
    print('MediaPipe Selfie Segmentationモデルの初期化に失敗しました')
    print(f'エラー: {e}')
    exit()

print('CPUモード')
print('初期化完了')
print()

# グローバル変数
frame_count = 0
last_print_time = time.time()
results_log = []


def video_processing(frame):
    """フレーム処理メイン関数"""
    global frame_count, last_print_time, results_log

    frame_count += 1

    # RGB変換(MediaPipeはRGBを期待)
    rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

    # セグメンテーション実行
    results = segmentation.process(rgb_frame)

    foreground_detected = False
    confidence = 0.0
    area = 0
    centroid_x = 0
    centroid_y = 0

    if results.segmentation_mask is not None:
        # マスクを取得
        mask = results.segmentation_mask

        # 閾値処理
        binary_mask = (mask > CONF_THRESH).astype(np.uint8)

        # 前景が検出された場合
        if np.any(binary_mask):
            foreground_detected = True
            # マスクの平均信頼度を計算
            confidence = np.mean(mask[binary_mask == 1])

            # 面積計算(ピクセル数)
            area = np.sum(binary_mask)

            # 重心計算
            y_indices, x_indices = np.where(binary_mask == 1)
            if len(x_indices) > 0:
                centroid_x = int(np.mean(x_indices))
                centroid_y = int(np.mean(y_indices))

    # 1秒間隔での出力
    current_time = time.time()
    if current_time - last_print_time >= PRINT_INTERVAL:
        if foreground_detected:
            output = f'フレーム {frame_count}: 前景検出あり (信頼度: {confidence:.1%}, 面積: {area}px, 重心: ({centroid_x}, {centroid_y}))'
        else:
            output = f'フレーム {frame_count}: 前景検出なし'
        print(output)

        results_log.append(output)
        last_print_time = current_time

    # 描画処理
    output_frame = frame.copy()

    if foreground_detected and results.segmentation_mask is not None:
        mask = results.segmentation_mask

        # 半透明マスクの作成
        colored_mask = np.zeros_like(frame)
        colored_mask[:, :] = FOREGROUND_COLOR

        # マスクを適用(半透明)
        alpha = 0.4
        mask_3channel = np.stack([mask] * 3, axis=-1)
        output_frame = output_frame * (1 - mask_3channel * alpha) + colored_mask * mask_3channel * alpha
        output_frame = output_frame.astype(np.uint8)

        # ラベル表示
        label1 = f'{CLASS_NAME}'
        label2 = f'Conf:{confidence:.1%}'
        label3 = f'Area:{area}px'
        label4 = f'Centroid:({centroid_x},{centroid_y})'

        cv2.putText(output_frame, label1, (10, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.7, FOREGROUND_COLOR, 2)
        cv2.putText(output_frame, label2, (10, 115), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1)
        cv2.putText(output_frame, label3, (10, 135), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1)
        cv2.putText(output_frame, label4, (10, 155), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1)

        # 重心に十字マーク表示
        cv2.drawMarker(output_frame, (centroid_x, centroid_y), (255, 0, 0), cv2.MARKER_CROSS, 10, 2)

    # システム情報表示
    status = '前景あり' if foreground_detected else '前景なし'
    info1 = f'MediaPipe (CPU) | Frame: {frame_count} | Status: {status}'
    info2 = 'Press: q=Quit'

    cv2.putText(output_frame, info1, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
    cv2.putText(output_frame, info2, (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 0), 1)

    return output_frame


# 入力選択
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)
    if not cap.isOpened():
        print(f'動画ファイルを開けませんでした: {path}')
        exit()
elif choice == '1':
    cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
    cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, WINDOW_WIDTH)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, WINDOW_HEIGHT)
    cap.set(cv2.CAP_PROP_FPS, FPS)
    if not cap.isOpened():
        print('カメラを開けませんでした')
        exit()
elif choice == '2':
    # サンプル動画ダウンロード・処理
    try:
        urllib.request.urlretrieve(SAMPLE_URL, SAMPLE_FILE)
        temp_file = SAMPLE_FILE
        cap = cv2.VideoCapture(SAMPLE_FILE)
        if not cap.isOpened():
            print(f'サンプル動画を開けませんでした: {SAMPLE_FILE}')
            exit()
        print('サンプル動画のダウンロードが完了しました')
    except Exception as e:
        print(f'動画のダウンロードに失敗しました: {SAMPLE_URL}')
        print(f'エラー: {e}')
        exit()
else:
    print('無効な選択です')
    exit()

# 動画処理開始メッセージ
print('\n=== 動画処理開始 ===')
print('操作方法:')
print('  q キー: プログラム終了')
print()

# メイン処理
try:
    while True:
        cap.grab()
        ret, frame = cap.retrieve()
        if not ret:
            break

        processed_frame = video_processing(frame)
        cv2.imshow('MediaPipe Foreground-Background Segmentation', processed_frame)

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

    # 結果保存
    if results_log:
        with open(RESULT_FILE, 'w', encoding='utf-8') as f:
            f.write('=== MediaPipe前景・背景セグメンテーション結果 ===\n')
            f.write(f'処理フレーム数: {frame_count}\n')
            f.write(f'使用デバイス: CPU\n')
            f.write('\n')
            f.write('\n'.join(results_log))

        print(f'\n処理結果を{RESULT_FILE}に保存しました')

    if temp_file and os.path.exists(temp_file):
        os.remove(temp_file)

print('\n=== プログラム終了 ===')