MiDaSによる深度推定

【概要】MiDaSは単眼カメラから深度情報を推定するAI技術である。Webカメラを用いたリアルタイム深度推定プログラムにより、深度マップの可視化と単眼深度推定を確認する。Windows環境での実行手順、プログラムコード、実験アイデアを含む。

目次

はじめに

MiDaSは単眼深度推定技術である。深度推定とは、画像から物体までの距離情報を推定する技術であり、深度マップ(各ピクセルの深度値を画像として表現したもの)として出力される。単眼深度推定は1台のカメラで深度を推定する技術で、ステレオカメラやLiDARを使用する手法とは異なる。

Ranftl et al.による論文「Towards Robust Monocular Depth Estimation: Mixing Datasets for Zero-Shot Cross-Dataset Transfer」(IEEE Transactions on Pattern Analysis and Machine Intelligence, Vol. 44, No. 3, 2022)で提案されたMiDaSの新規性は、複数データセットでの混合学習により未知環境でも深度推定を実現する点にある。自動運転、ロボットビジョン、AR/VRアプリケーションに活用できる。

Webカメラ映像からリアルタイムで深度マップを生成し、その機能を確認できる。

技術的背景

処理の流れ

MiDaSの処理は以下の3段階で構成される:

  1. 前処理:入力画像をBGR形式からRGB形式に変換し、各モデルが学習時に使用した形式に正規化する
  2. 推論:畳み込みニューラルネットワークが画像特徴から深度を推定する
  3. 後処理:bicubic補間(滑らかな深度マップを生成する画像拡大手法)による解像度調整と、可視化のための0-1範囲への正規化を行う

利用可能なモデル

MiDaSでは精度と速度の関係を考慮した3つのモデルが提供される:

事前準備

Python, Windsurfをインストールしていない場合の手順(インストール済みの場合は実行不要)。

  1. 管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する。
  2. 以下のコマンドをそれぞれ実行する(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 opencv-python numpy tkinter

プログラムコード


# プログラム名: MiDaSリアルタイム単眼深度推定
# 特徴技術名: MiDaS (Monocular Depth Estimation) v3.0
# 出典: Ranftl, R., Bochkovskiy, A., & Koltun, V. (2021). Vision Transformers for Dense Prediction. In Proceedings of the IEEE/CVF International Conference on Computer Vision (pp. 12179-12188).
# 特徴機能: ゼロショット汎化による単眼深度推定 - 複数データセットでの学習により、未知の環境でも高精度な深度マップを生成
# 学習済みモデル:
#   - DPT_Large: MiDaS v3.0大型モデル(約345Mパラメータ、高精度、Vision Transformer使用)
#   - DPT_Hybrid: MiDaS v3.0ハイブリッドモデル(約123Mパラメータ、中精度、CNN+Transformer)
#   - MiDaS_small: MiDaS v2.1小型モデル(約21.4Mパラメータ、高速、EfficientNet使用)
#   - URL: torch.hub経由で自動ダウンロード(intel-isl/MiDaS)
# 方式設計:
#   - 関連利用技術:
#     - PyTorch: 深層学習フレームワーク(モデル実行、GPU処理)
#     - OpenCV: 画像処理(カメラ入力、画像表示、カラーマップ適用)
#     - NumPy: 数値計算(深度マップの正規化処理)
#   - 入力と出力: 入力: 動画(ユーザは「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. カメラから画像フレーム取得
#     2. BGR→RGB変換
#     3. MiDaS前処理(リサイズ、正規化)
#     4. DPTモデルによる深度推定
#     5. 深度マップの補間とリサイズ
#     6. カラーマップ適用と表示
#   - 前処理、後処理:
#     - 前処理: 入力画像の正規化(平均値減算、標準偏差除算)、384x384または512x512へのリサイズ
#     - 後処理: bicubic補間による元画像サイズへの復元、深度値の正規化(0-1範囲)
#   - 追加処理: フレームスキップ処理(FRAME_SKIP=10)による計算負荷軽減、最新フレーム取得のためのバッファクリア(grab/retrieve分離)
#   - 調整を必要とする設定値:
#     - MODEL_TYPE: 使用するMiDaSモデル("DPT_Large"/"DPT_Hybrid"/"MiDaS_small")
#     - FRAME_SKIP: 深度推定実行間隔(デフォルト10フレーム)
# 将来方策: MODEL_TYPEの自動選択機能 - GPU性能をベンチマークし、30FPS達成可能な最高精度モデルを自動選択
# その他の重要事項: Windows環境専用(cv2.CAP_DSHOW使用)、CUDA対応GPU推奨、初回実行時はモデルダウンロードのため時間がかかる
# 前準備:
#   - pip install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
#   - pip install opencv-python numpy tkinter

import cv2
import tkinter as tk
from tkinter import filedialog
import os
import torch
import numpy as np
import time
import sys
import io

# Windows環境での文字エンコーディング設定
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', line_buffering=True)

# 設定パラメータ
MODEL_TYPE = 'DPT_Large'  # 使用するMiDaSモデル
FRAME_SKIP = 10  # 深度推定を実行するフレーム間隔
COLORMAP = cv2.COLORMAP_PLASMA  # 深度マップのカラーマップ
FONT_SCALE = 0.7  # テキスト表示のフォントサイズ
TEXT_COLOR = (0, 255, 0)  # テキストの色(BGR)
TEXT_THICKNESS = 2  # テキストの太さ

# プログラム開始時の説明表示
print('=== MiDaSリアルタイム単眼深度推定 ===')
print('単一のカメラ画像から深度マップを推定します')
print("操作方法: 'q'キーで終了")
print('')

# 学習済みMiDaSモデルのダウンロードと初期化
print('MiDaSモデルを読み込んでいます...')
midas = torch.hub.load('intel-isl/MiDaS', MODEL_TYPE)

# GPU利用可能性の確認
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
midas.to(device)
midas.eval()

print(f'使用デバイス: {device}')
print(f'使用モデル: {MODEL_TYPE}')

# 前処理の取得
midas_transforms = torch.hub.load('intel-isl/MiDaS', 'transforms')
if MODEL_TYPE == 'DPT_Large' or MODEL_TYPE == 'DPT_Hybrid':
    transform = midas_transforms.dpt_transform
else:
    transform = midas_transforms.small_transform

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


def video_processing(frame):
    global frame_count, depth_map, last_print_time, results

    frame_count += 1  # フレームカウントを統一してインクリメント

    # 深度推定は処理負荷軽減のため指定フレームごとに実行
    if frame_count % FRAME_SKIP == 0:
        # 画像を前処理
        img_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        input_batch = transform(img_rgb).to(device)

        # 深度推定の実行
        with torch.no_grad():
            prediction = midas(input_batch)
            prediction = torch.nn.functional.interpolate(
                prediction.unsqueeze(1),
                size=img_rgb.shape[:2],
                mode='bicubic',
                align_corners=False,
            ).squeeze()

        # 深度マップの取得
        depth_map = prediction.cpu().numpy()

    # 1秒間隔での表示
    current_time = time.time()
    if current_time - last_print_time >= 1.0 and depth_map is not None:
        depth_info = f'時刻: {current_time:.2f}, フレーム: {frame_count}, 深度範囲: {depth_map.min():.3f} - {depth_map.max():.3f}'
        print(depth_info)
        results.append(depth_info)
        last_print_time = current_time

    # 深度マップが利用できる場合の表示処理
    if depth_map is not None:
        # 深度マップを正規化してカラーマップに変換
        d_norm = (depth_map - depth_map.min()) / (depth_map.max() - depth_map.min() + 1e-8)
        d_color = cv2.applyColorMap((d_norm * 255).astype(np.uint8), COLORMAP)

        # 元画像をリサイズ
        f_resize = cv2.resize(frame, (depth_map.shape[1], depth_map.shape[0]))

        # 横に並べて表示
        combined = np.hstack([f_resize, d_color])

        # 画面中央の深度値を取得
        h, w = depth_map.shape
        center_depth = depth_map[h // 2, w // 2]

        # テキスト表示
        cv2.putText(combined, f'Center Depth: {center_depth:.3f}',
                   (10, 30), cv2.FONT_HERSHEY_SIMPLEX, FONT_SCALE, TEXT_COLOR, TEXT_THICKNESS)
        cv2.putText(combined, f'Frame: {frame_count}',
                   (10, 60), cv2.FONT_HERSHEY_SIMPLEX, FONT_SCALE, TEXT_COLOR, TEXT_THICKNESS)
        cv2.putText(combined, "Press 'q' to quit",
                   (10, h - 20), cv2.FONT_HERSHEY_SIMPLEX, FONT_SCALE, TEXT_COLOR, TEXT_THICKNESS)

        return combined
    else:
        # 深度マップが未生成の場合
        cv2.putText(frame, 'Processing depth map...',
                   (10, 30), cv2.FONT_HERSHEY_SIMPLEX, FONT_SCALE, TEXT_COLOR, TEXT_THICKNESS)
        return 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)
elif choice == '1':
    cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
    cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
elif choice == '2':
    # サンプル動画ダウンロード
    import urllib.request
    url = 'https://github.com/opencv/opencv/raw/master/samples/data/vtest.avi'
    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()

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

        result = video_processing(frame)
        cv2.imshow('Video', result)

        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.txt', 'w', encoding='utf-8') as f:
            f.write('MiDaSリアルタイム単眼深度推定 実行結果\n')
            f.write('=' * 50 + '\n')
            for result in results:
                f.write(result + '\n')
        print('\n結果をresult.txtに保存しました')

    # 最終結果出力
    print(f'\n処理完了。総フレーム数: {frame_count}フレーム')

使用方法

  1. 上記のプログラムを実行する
  2. 初回実行時はモデルのダウンロードに時間がかかる
  3. Webカメラの映像がウィンドウに表示され、左側に元画像、右側に深度マップが表示される
  4. 深度マップでは、PLASMAカラーマップにより近い距離が暖色系、遠い距離が寒色系で表現される
  5. qキーで終了する

実験・探求

モデル比較実験

MODEL_TYPEを変更して3つのモデルの性能を比較する:

パラメータ調整実験

深度推定の限界検証