DexiNedエッジ検出(ソースコードと実行結果)

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 numpy

DexiNedエッジ検出プログラム

概要

本プログラムは、動画フレームからエッジを検出し、グラデーションを保持したエッジマップを生成する。従来の手法では困難であった複雑な環境下でも、事前学習なしでエッジ検出を実現する。

主要技術

参考文献

[1] Soria, X., Sappa, A., Humanante, P., & Akbarinia, A. (2023). Dense extreme inception network for edge detection. Pattern Recognition, 139, 109461. https://doi.org/10.1016/j.patcog.2023.109461


# DexiNedエッジ検出プログラム
# 特徴技術名: DexiNed (Dense Extreme Inception Network for Edge Detection) - 従来のエッジ検出手法よりも詳細かつ正確なエッジを検出できるディープラーニングベースのエッジ検出モデル
# 出典: Soria, X., Sappa, A., Humanante, P., & Akbarinia, A. (2023). Dense extreme inception network for edge detection. Pattern Recognition, 139, 109461. https://doi.org/10.1016/j.patcog.2023.109461
# 特徴機能: 二値化を必要としない高品質なエッジマップ生成 - 事前学習なしで一度の訓練で高精度なエッジ検出が可能であり、グラデーションを持つ詳細なエッジマップを直接生成できる。様々なデータセットに対する一般化能力が高く、再学習なしで異なるドメインに適用可能
# 学習済みモデル: DexiNed_BIPED_10.pth (BIPED) および DexiNed_MDBD.pth (MDBD) - 複雑な環境下でも高精度にエッジを検出可能。Korniaライブラリ経由で自動的にダウンロードされる
# 方式設計
#   - 関連利用技術:
#     - PyTorch: ディープラーニングフレームワーク、モデルの実行と推論に使用
#     - Kornia: PyTorchベースのコンピュータビジョンライブラリ、DexiNedモデルの提供と画像処理に使用
#     - OpenCV: 画像・動画処理ライブラリ、入力の取得と結果の表示に使用
#     - tkinter: Pythonの標準GUIライブラリ、ファイル選択ダイアログに使用
#   - 入力と出力: 入力: 動画(ユーザは「0:動画ファイル,1:カメラ,2:サンプル動画」のメニューで選択.0:動画ファイルの場合はtkinterでファイル選択.1の場合はOpenCVでカメラが開く.2の場合はhttps://github.com/opencv/opencv/raw/master/samples/data/vtest.aviを使用)
#     出力: OpenCV画面でエッジマップを重ね合わせた画像をリアルタイム表示。画面内にエッジ密度などの処理結果をテキストで表示。1秒間隔でprint()による処理結果の表示。処理結果はプログラム終了時にresult.txtに保存
#   - 処理手順:
#     1. 入力方法の選択と入力ソースの初期化
#     2. 動画フレームの取得
#     3. BGRからRGBへの変換と画像の正規化
#     4. DexiNedモデルによるエッジマップの生成
#     5. エッジマップのカラー変換と元画像への重ね合わせ(適応的なブレンディングを使用)
#     6. 処理結果の表示と記録
#     7. 結果の保存
#   - 前処理、後処理:
#     - 前処理: 入力画像をBGRからRGBに変換し、PyTorchテンソルに変換。0-255から0-1の範囲に正規化。バッチ次元の追加
#     - 後処理: エッジマップをグレースケールからJETカラーマップに変換。エッジの強さに応じた適応的なアルファブレンディングで元画像と重ね合わせて視覚化
#   - 追加処理:
#     - エッジ密度の計算: エッジマップの平均値からエッジの密度を計算し、画像上に表示
#     - 時間情報の追加: 現在時刻を画像上に表示して処理時間の参考情報を提供
#     - 適応的なブレンディング: エッジの強さに応じて元画像との重ね合わせ比率を変化させ、視認性を向上
#   - 調整を必要とする設定値:
#     - alpha: 元画像の重み (0.7) - 重ね合わせ時の元画像の透明度を決定
#     - beta: エッジマップの重み (0.6) - 重ね合わせ時のエッジマップの透明度を決定
#     - dataset: 使用するデータセット ('biped'または'mdbd') - エッジ検出の特性に影響
# 将来方策: alpha、betaの値を調整可能にするためのスライダーUIの実装 - ユーザーがリアルタイムに値を調整しながら最適な視覚化効果を得られるようにする
# その他の重要事項:
#   - DexiNedはRGB (カラー画像)を入力として使用するエッジ検出モデルです
#   - エッジマップは二値化せず、グラデーションを保持したままカラーマップ変換を行います
#   - GPU環境がある場合は自動的にGPUを使用して処理速度が向上します
#   - 適応的なブレンディングにより、エッジの強い部分をより明確に表示します
# 前準備:
# pip install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
# pip install kornia opencv-python

import cv2
import tkinter as tk
from tkinter import filedialog
import urllib.request
import os
import numpy as np
import torch
import kornia
import time

# 設定パラメータ
ALPHA = 0.7  # 元画像の重み(値が大きいほど元画像が濃く表示される)
BETA = 0.6   # エッジマップの重み(値が大きいほどエッジが濃く表示される)
COLORMAP = cv2.COLORMAP_JET  # エッジマップの色付け方法(cv2.COLORMAPから選択)
FONT_SIZE = 0.7  # テキスト表示のフォントサイズ
FONT_COLOR = (0, 255, 0)  # テキスト表示の色(BGR形式)
FONT_THICKNESS = 2  # テキスト表示の太さ
DATASET = 'biped'  # 使用するデータセット('biped'または'mdbd')
USE_ADAPTIVE_BLENDING = True  # 適応的なアルファブレンディングを使用するか

# ファイルとURL
SAMPLE_VIDEO_URL = 'https://github.com/opencv/opencv/raw/master/samples/data/vtest.avi?raw=true'
RESULT_FILE = 'result.txt'  # 結果を保存するファイル名

# 結果を保存するリスト
results = []

def initialize_model(dataset=DATASET):
    """
    最新のKornia APIを使用してDexiNedモデルを初期化
    dataset: 使用するデータセット('biped'または'mdbd')
    """
    print('DexiNedモデルを初期化中...')
    try:
        model = None

        # 方法1: 最新バージョン (kornia >= 0.7.0)
        try:
            # DexiNedモデルを直接初期化
            model = kornia.models.edge_detection.DexiNed(pretrained=True).eval()
            print(f'DexiNedモデルを初期化しました (最新API)')
        except (AttributeError, ImportError) as e:
            print(f'最新APIでの初期化に失敗しました: {e}')

            # 方法2: 中間バージョン
            try:
                # DexiNedBuilderを使用した初期化(モデル名は'DexiNed'のみ)
                model = kornia.models.edge_detection.dexined.DexiNedBuilder.build(
                    model_name='DexiNed',
                    pretrained=True
                ).eval()
                print(f'DexiNedモデルを初期化しました (Builder API)')
            except (AttributeError, ImportError) as e:
                print(f'Builder APIでの初期化に失敗しました: {e}')

                # 方法3: 旧バージョン
                try:
                    model = kornia.filters.DexiNed(pretrained=True).eval()
                    print('DexiNedモデルを初期化しました (旧バージョンAPI)')
                except (AttributeError, ImportError) as e:
                    print(f'旧バージョンAPIでの初期化に失敗しました: {e}')

                    # 方法4: さらに古いバージョン
                    try:
                        from kornia.contrib import EdgeDetector
                        model = EdgeDetector.dexined(pretrained=True).eval()
                        print('DexiNedモデルを初期化しました (contrib API)')
                    except (AttributeError, ImportError) as e:
                        print(f'contrib APIでの初期化に失敗しました: {e}')

        # 初期化失敗
        if model is None:
            raise ImportError('Kornia内でDexiNedモデルが見つかりません。Korniaのバージョンを確認してください。\n'
                            'pip install kornia==0.7.0 を試してください。')

        if torch.cuda.is_available():
            model = model.cuda()
            print('GPUを使用します')
        else:
            print('CPUを使用します')

        print('DexiNedモデルの初期化が完了しました')
        return model
    except Exception as e:
        print(f'モデルの初期化に失敗しました: {e}')
        print('Korniaのバージョンを確認してください:')
        print(f'現在のKorniaバージョン: {kornia.__version__}')
        exit()

def preprocess_image(img):
    """
    画像を前処理してモデル入力用のテンソルに変換
    DexiNedはRGB形式の入力を想定しているため、BGR→RGB変換を行う
    """
    # BGRからRGBに変換(DexiNedはRGB形式の入力を想定)
    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

    # テンソルに変換して正規化(0-255から0-1の範囲に)
    img_tensor = kornia.image_to_tensor(img_rgb).float() / 255.0
    img_tensor = img_tensor.unsqueeze(0)  # バッチ次元を追加

    # GPUが利用可能ならGPUに転送
    if torch.cuda.is_available():
        img_tensor = img_tensor.cuda()

    return img_tensor

def video_processing(frame):
    """
    DexiNedモデルで動画フレームのエッジを検出し、視覚化
    エッジの強さに応じた適応的なブレンディングを使用して視認性を向上
    """
    # フレームが空の場合はそのまま返す
    if frame is None:
        return frame

    # 画像の前処理
    img_tensor = preprocess_image(frame)

    # モデルによるエッジ検出
    with torch.no_grad():  # 勾配計算を無効化して推論
        out = model(img_tensor)

        # 最終結果(fused output)を使用
        edge_map = out[-1].cpu().numpy().squeeze()

        # GPUメモリの解放
        del out
        del img_tensor
        if torch.cuda.is_available():
            torch.cuda.empty_cache()

    # グレースケールをカラーマップに変換
    edge_colored = cv2.applyColorMap((edge_map * 255).astype(np.uint8), COLORMAP)

    if USE_ADAPTIVE_BLENDING:
        # エッジの強さに応じた適応的なアルファブレンディング
        # エッジが強い部分ほど不透明に表示
        edge_strength = edge_map.copy()
        edge_strength_normalized = cv2.normalize(edge_strength, None, 0, 1, cv2.NORM_MINMAX)
        alpha_mask = ALPHA + (1.0 - ALPHA) * edge_strength_normalized
        alpha_mask = np.expand_dims(alpha_mask, axis=2)
        alpha_mask = np.repeat(alpha_mask, 3, axis=2)  # 3チャンネル(BGR)に拡張

        # エッジマップを元の画像に重ね合わせ
        # numpy配列の直接演算を使用して適応的なブレンディングを実現
        result_frame = (frame * alpha_mask + edge_colored * BETA * (1 - alpha_mask)).astype(np.uint8)
    else:
        # 従来の固定重みによるブレンディング
        result_frame = cv2.addWeighted(frame, ALPHA, edge_colored, BETA, 0)

    # 処理結果の情報を取得
    edge_percentage = np.mean(edge_map) * 100
    result_info = f'エッジ密度: {edge_percentage:.2f}%'

    # 結果を記録
    results.append(result_info)

    # 処理結果の情報を画像に追加
    cv2.putText(result_frame, result_info, (10, 30),
                cv2.FONT_HERSHEY_SIMPLEX, FONT_SIZE, FONT_COLOR, FONT_THICKNESS)

    # 現在時刻を追加
    timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
    cv2.putText(result_frame, timestamp, (10, 60),
                cv2.FONT_HERSHEY_SIMPLEX, FONT_SIZE, FONT_COLOR, FONT_THICKNESS)

    # 使用しているデータセットを表示
    dataset_info = f'データセット: {DATASET}'
    cv2.putText(result_frame, dataset_info, (10, 90),
                cv2.FONT_HERSHEY_SIMPLEX, FONT_SIZE, FONT_COLOR, FONT_THICKNESS)

    # 1秒に1回、処理結果を表示
    current_time = time.time()
    if not hasattr(video_processing, 'last_print_time') or current_time - video_processing.last_print_time >= 1.0:
        print(result_info)
        video_processing.last_print_time = current_time

    return result_frame

# プログラムの概要と操作方法を表示
print('DexiNedエッジ検出プログラムを開始します')
print('このプログラムはDexiNedモデルを使用して動画からエッジマップを生成します')
print('エッジマップは二値化せず、グラデーションを保持したまま表示されます')
print('表示される処理結果画像は、元画像とエッジマップを重ね合わせたものです')
print(f'使用データセット: {DATASET}')
print('qキーで終了します')

# DexiNedモデルを初期化
model = initialize_model(DATASET)

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)  # バッファサイズを最小に設定
    if not cap.isOpened():
        print('カメラの初期化に失敗しました。カメラが接続されているか確認してください。')
        exit()
elif choice == '2':
    # サンプル動画ダウンロード・処理
    filename = 'vtest.avi'
    try:
        urllib.request.urlretrieve(SAMPLE_VIDEO_URL, filename)
        temp_file = filename
        cap = cv2.VideoCapture(filename)
        if not cap.isOpened():
            print(f'動画ファイルの読み込みに失敗しました: {filename}')
            exit()
    except Exception as e:
        print(f'動画のダウンロードに失敗しました: {SAMPLE_VIDEO_URL}')
        print(f'エラー: {e}')
        exit()
else:
    print('無効な選択です')
    exit()

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

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

        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:
        for result in results:
            f.write(f'{result}\n')
    print(f'処理結果を{RESULT_FILE}に保存しました')