TripoSR による単一画像からの3次元モデル推定

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

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/

Gitのインストール

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


REM Git をシステム領域にインストール
winget install --scope machine --id Git.Git -e --silent
REM Git のパス設定
set "GIT_PATH=C:\Program Files\Git\cmd"
if exist "%GIT_PATH%" (
    echo "%PATH%" | find /i "%GIT_PATH%" >nul
    if errorlevel 1 setx PATH "%PATH%;%GIT_PATH%" /M >nul
)

CMakeのインストール

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


REM CMake をシステム領域にインストール
winget install --scope machine --id Kitware.CMake -e --silent
REM CMake のパス設定
set "GMAKE_PATH=C:\Program Files\CMake\bin"
if exist "%GMAKE_PATH%" (
    echo "%PATH%" | find /i "%GMAKE_PATH%" >nul
    if errorlevel 1 setx PATH "%PATH%;%GMAKE_PATH%" /M >nul
)

Visual Studio 2022 Build Toolsとランタイムのインストール

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


REM Visual Studio 2022 Build Toolsとランタイムのインストール
winget install --scope machine Microsoft.VisualStudio.2022.BuildTools Microsoft.VCRedist.2015+.x64
set VS_INSTALLER="C:\Program Files (x86)\Microsoft Visual Studio\Installer\setup.exe"
set VS_PATH="C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools"
REM C++開発ワークロードのインストール
%VS_INSTALLER% modify --installPath %VS_PATH% ^
--add Microsoft.VisualStudio.Workload.VCTools ^
--add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 ^
--add Microsoft.VisualStudio.Component.Windows11SDK.22621 ^
--includeRecommended --quiet --norestart

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

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


pip install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
pip install rembg pillow opencv-python trimesh numpy transformers huggingface-hub

cd %USERPROFILE%
git clone https://github.com/VAST-AI-Research/TripoSR.git
cd TripoSR
pip install -r requirements.txt
cd ..

icacls "C:\Program Files\Python312\Lib\site-packages\pymatting" /grant %USERNAME%:(OI)(CI)F /T
icacls "C:\Program Files\Python312\Lib\site-packages\numba" /grant %USERNAME%:(OI)(CI)F /T
mkdir "%USERPROFILE%\.cache\huggingface\hub" 2>nul
icacls "%USERPROFILE%\.cache\huggingface\hub" /grant %USERNAME%:(OI)(CI)F /T
icacls "C:\Program Files\Python312\Lib\site-packages" /grant %USERNAME%:(OI)(CI)M /T

TripoSR による単一画像からの3次元モデル推定プログラム


# プログラム名: TripoSR による単一画像からの3次元モデル推定プログラム
# 特徴技術名: TripoSR(単一画像から3Dモデルを推定する手法)
# 出典: Tochilkin, D., et al. (2024). TripoSR: Fast 3D Object Reconstruction from a Single Image. arXiv preprint arXiv:2403.02151.
# 特徴機能: 高速フィードフォワード3D生成により、単一画像から0.5秒以内で高品質な3Dメッシュを生成する能力
# 学習済みモデル: HuggingFaceで提供されるstabilityai/TripoSRモデル(model.ckpt、1.68GB)。Large Reconstruction Model(LRM)アーキテクチャをベースとし、DINOv1ビジョントランスフォーマーで初期化されたイメージエンコーダを使用。URL: https://huggingface.co/stabilityai/TripoSR
#
# === Windowsでのキャッシュディレクトリ権限設定 ===
#
# 問題1: pymatting/numbaのキャッシュ権限エラーの解決
# 管理者権限のコマンドプロンプトで以下を実行:
#   icacls "C:\Program Files\Python312\Lib\site-packages\pymatting" /grant %USERNAME%:(OI)(CI)F /T
#   icacls "C:\Program Files\Python312\Lib\site-packages\numba" /grant %USERNAME%:(OI)(CI)F /T
#
# 問題2: HuggingFaceのキャッシュディレクトリ権限
# HuggingFaceのモデルキャッシュはデフォルトで C:\Users\ユーザー名\.cache\huggingface\hub に保存されます。
#   mkdir "%USERPROFILE%\.cache\huggingface\hub" 2>nul
#   icacls "%USERPROFILE%\.cache\huggingface\hub" /grant %USERNAME%:(OI)(CI)F /T
#
# 方式設計:
#   関連利用技術:
#     - PIL(画像処理)
#     - NumPy(数値計算)
#     - PyTorch(機械学習フレームワーク)
#     - rembg(背景除去)
#     - trimesh(3Dメッシュ処理)
#     - OpenCV(画像入出力・表示)
#     - tkinter(ファイル選択)
#     - tsr(TripoSRシステム)
#   入力と出力:
#     - 入力: 画像(ユーザは「0:画像ファイル,1:カメラ,2:サンプル画像」のメニューで選択.0:画像ファイルの場合はtkinterで複数ファイル選択可能.1の場合はOpenCVでカメラが開き,スペースキーで撮影(複数回可能).2の場合はhttps://github.com/opencv/opencv/raw/master/samples/data/fruits.jpg とhttps://github.com/opencv/opencv/raw/master/samples/data/messi5.jpg とhttps://github.com/opencv/opencv/raw/master/samples/data/aero3.jpgを使用)
#     - 出力: 3DメッシュファイルOBJ形式、OpenCV画面での結果表示、result.txtファイルへの処理結果保存
#   処理手順:
#     1. 入力画像の読み込み・前処理
#     2. 背景除去処理
#     3. TripoSRモデルによる3D再構成
#     4. メッシュデータの生成・保存
#     5. 結果の可視化
#   前処理、後処理:
#     - 前処理: 背景除去(rembg)、画像リサイズ・正規化
#     - 後処理: メッシュの最適化、テクスチャマッピング
#   追加処理: 画像品質向上のための自動コントラスト調整、メッシュの頂点数最適化
#   調整を必要とする設定値: resolution(メッシュ解像度、32-320の範囲)、threshold(背景除去閾値)
#
# 将来方策: 複数画像からの統合3Dモデル生成、リアルタイム処理への拡張
# その他の重要事項: CUDA対応によるGPU加速、メモリ使用量の最適化
#
# 前準備:
# 1. TripoSRのインストール:
#    git clone https://github.com/VAST-AI-Research/TripoSR.git
#    cd TripoSR
#    pip install -r requirements.txt
#    cd ..
#    そして、TripoSRフォルダをPythonのsite-packagesにコピーするか、
#    このスクリプトと同じディレクトリに配置してください
#
# 2. その他の依存関係:
#    pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
#    pip install rembg pillow opencv-python trimesh numpy transformers huggingface-hub
#
# rembg/numbaで問題が発生する場合:
# 1. 以下のコマンドで権限を修正(管理者権限のコマンドプロンプトで実行):
#    icacls "C:\Program Files\Python312\Lib\site-packages" /grant %USERNAME%:(OI)(CI)M /T
# 2. または、rembgをアンインストールして背景除去なしで実行:
#    pip uninstall rembg pymatting numba llvmlite -y

import os

# Numbaを無効化(必ずインポート前に設定)
os.environ['NUMBA_DISABLE_JIT'] = '1'  # Numbaの無効化
os.environ['NUMBA_DISABLE_CUDA'] = '1'  # NumbaのCUDA機能を無効化
os.environ['NUMBA_CACHE_DIR'] = r'C:\Users\user\AppData\Local\Temp\numba_cache'  # キャッシュディレクトリを変更

# 環境変数の設定(Windowsでキャッシュディレクトリを変更する場合)
# コメントを外して使用してください
# os.environ['HUGGINGFACE_HUB_CACHE'] = r'D:\AI_Models\huggingface\hub'

import cv2
import tkinter as tk
from tkinter import filedialog
import urllib.request
import numpy as np
from PIL import Image
import torch
import trimesh
import tempfile

# rembgのインポートを条件付きに
try:
    import rembg
    REMBG_AVAILABLE = True
except ImportError:
    print('警告: rembgがインストールされていないか、エラーが発生しました。背景除去機能は使用できません。')
    REMBG_AVAILABLE = False

# TripoSRシステムのインポート
import sys
import os

# TripoSRがローカルにある場合のパスを追加
if os.path.exists('./TripoSR'):
    sys.path.insert(0, './TripoSR')
elif os.path.exists('../TripoSR'):
    sys.path.insert(0, '../TripoSR')

try:
    from tsr.system import TSR
    from tsr.utils import remove_background, resize_foreground
    TRIPOSR_AVAILABLE = True
except ImportError:
    print('エラー: TripoSRがインストールされていません。')
    print('インストール方法:')
    print('1. git clone https://github.com/VAST-AI-Research/TripoSR.git')
    print('2. cd TripoSR')
    print('3. pip install -r requirements.txt')
    print('4. このスクリプトと同じディレクトリにTripoSRフォルダを配置')
    print('プログラムを終了します。')
    exit(1)

# デバイスの設定
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(f'使用デバイス: {device}')

# TripoSRモデルのセットアップ
model = None
rembg_session = None

def setup_triposr_model():
    global model, rembg_session
    try:
        print('TripoSRモデルを初期化中...')
        model = TSR.from_pretrained(
            "stabilityai/TripoSR",
            config_name="config.yaml",
            weight_name="model.ckpt"
        )
        # チャンクサイズを調整(メモリ使用量とスピードのバランス)
        model.renderer.set_chunk_size(8192)
        model.to(device)
        print('TripoSRモデルの初期化完了')

        # 背景除去セッションの初期化
        if REMBG_AVAILABLE:
            rembg_session = rembg.new_session()
        else:
            rembg_session = None
        return True
    except Exception as e:
        print(f'エラー: TripoSRモデルの初期化に失敗しました: {e}')
        print('インターネット接続を確認してください。')
        print('プログラムを終了します。')
        exit(1)

# 背景除去処理(TripoSR互換)
def remove_background_custom(image, session=None):
    try:
        if not REMBG_AVAILABLE:
            print('警告: rembgが利用できません。背景除去をスキップします。')
            if isinstance(image, np.ndarray):
                return image.astype(np.float32) / 255.0
            else:
                return np.array(image).astype(np.float32) / 255.0

        if session is None:
            session = rembg.new_session()

        if isinstance(image, np.ndarray):
            image_pil = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
        else:
            image_pil = image

        # 背景除去
        result = rembg.remove(image_pil, session=session)

        # アルファチャンネルを使用して背景を白に設定
        result_np = np.array(result).astype(np.float32) / 255.0
        if result_np.shape[2] == 4:  # RGBA
            rgb = result_np[:, :, :3]
            alpha = result_np[:, :, 3:4]
            result_rgb = rgb * alpha + (1 - alpha)
            result_np = result_rgb

        return result_np
    except Exception as e:
        print(f'背景除去に失敗しました: {e}')
        if isinstance(image, np.ndarray):
            return image.astype(np.float32) / 255.0
        else:
            return np.array(image).astype(np.float32) / 255.0

# TripoSRを使用した3D再構成
def reconstruct_3d_triposr(image, mc_resolution=256, bake_texture=False, texture_resolution=1024):
    try:
        if model is None:
            return None, None

        # 画像の前処理
        if isinstance(image, np.ndarray):
            if image.dtype == np.uint8:
                image = image.astype(np.float32) / 255.0
            # BGRからRGBに変換
            if len(image.shape) == 3 and image.shape[2] == 3:
                image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

        # モデルに入力
        with torch.no_grad():
            scene_codes = model([image], device=device)

        # メッシュの抽出
        meshes = model.extract_mesh(scene_codes, vertex_colors=not bake_texture, resolution=mc_resolution)

        texture_data = None
        if bake_texture and meshes:
            try:
                from tsr.bake_texture import bake_texture as bake_tex
                texture_data = bake_tex(meshes[0], model, scene_codes[0], texture_resolution)
            except Exception as e:
                print(f'テクスチャベイキングに失敗しました: {e}')
                meshes = model.extract_mesh(scene_codes, vertex_colors=True, resolution=mc_resolution)

        return meshes[0] if meshes else None, texture_data

    except Exception as e:
        print(f'TripoSR 3D再構成に失敗しました: {e}')
        return None, None

# 簡易3Dプレビュー表示
def preview_3d_mesh(mesh, window_name="3D Preview"):
    if mesh is None:
        return

    print('3Dプレビュー: マウスドラッグで回転、ESCで終了')

    # メッシュの頂点を取得
    vertices = np.array(mesh.vertices)
    faces = np.array(mesh.faces)

    # 中心を原点に
    center = vertices.mean(axis=0)
    vertices = vertices - center

    # スケール調整
    max_dist = np.max(np.abs(vertices))
    vertices = vertices / max_dist * 200

    # 回転パラメータ
    rot_x, rot_y = 0, 0
    mouse_down = False
    prev_x, prev_y = 0, 0

    def mouse_callback(event, x, y, flags, param):
        nonlocal rot_x, rot_y, mouse_down, prev_x, prev_y

        if event == cv2.EVENT_LBUTTONDOWN:
            mouse_down = True
            prev_x, prev_y = x, y
        elif event == cv2.EVENT_LBUTTONUP:
            mouse_down = False
        elif event == cv2.EVENT_MOUSEMOVE and mouse_down:
            rot_y += (x - prev_x) * 0.5
            rot_x += (y - prev_y) * 0.5
            prev_x, prev_y = x, y

    cv2.namedWindow(window_name)
    cv2.setMouseCallback(window_name, mouse_callback)

    while True:
        # 画像をクリア
        img = np.ones((600, 800, 3), dtype=np.uint8) * 255

        # 回転行列
        rx = np.radians(rot_x)
        ry = np.radians(rot_y)

        # Y軸回転
        rot_y_mat = np.array([
            [np.cos(ry), 0, np.sin(ry)],
            [0, 1, 0],
            [-np.sin(ry), 0, np.cos(ry)]
        ])

        # X軸回転
        rot_x_mat = np.array([
            [1, 0, 0],
            [0, np.cos(rx), -np.sin(rx)],
            [0, np.sin(rx), np.cos(rx)]
        ])

        # 頂点を回転
        rotated = vertices @ rot_y_mat @ rot_x_mat

        # 投影(簡易的な平行投影)
        projected = rotated[:, :2] + [400, 300]
        projected = projected.astype(int)

        # ワイヤーフレーム描画
        for face in faces:
            pts = projected[face]
            for i in range(3):
                cv2.line(img, tuple(pts[i]), tuple(pts[(i+1)%3]), (0, 0, 0), 1)

        cv2.putText(img, "Mouse drag to rotate, ESC to exit", (10, 30),
                   cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 128, 0), 2)

        cv2.imshow(window_name, img)

        key = cv2.waitKey(30) & 0xFF
        if key == 27:  # ESC
            break

    cv2.destroyWindow(window_name)

# 画像処理メイン関数
def image_processing(img, resolution=256, use_texture=False, texture_resolution=1024):
    try:
        print('3D再構成処理を開始します...')

        # 背景除去
        no_bg_img = remove_background_custom(img, rembg_session)

        # 前景のリサイズ(TripoSR互換)
        if TRIPOSR_AVAILABLE and hasattr(TSR, 'resize_foreground'):
            # float32画像をPIL Imageに変換
            if no_bg_img.dtype == np.float32:
                pil_img = Image.fromarray((no_bg_img * 255).astype(np.uint8))
            else:
                pil_img = Image.fromarray(no_bg_img)
            no_bg_img = resize_foreground(pil_img, 0.85)
            no_bg_img = np.array(no_bg_img).astype(np.float32) / 255.0

        # 3D再構成
        mesh, texture_data = reconstruct_3d_triposr(no_bg_img, resolution, use_texture, texture_resolution)

        if mesh is not None:
            # メッシュの保存
            timestamp = len([f for f in os.listdir('.') if f.startswith('output_3d_model_')]) if os.path.exists('.') else 0
            output_filename = f'output_3d_model_{timestamp}.obj'

            try:
                # テクスチャ付きで保存
                if use_texture and texture_data is not None:
                    output_mtl = f'output_3d_model_{timestamp}.mtl'
                    output_tex = f'output_3d_model_{timestamp}_texture.png'

                    # MTLファイルの作成
                    with open(output_mtl, 'w') as f:
                        f.write(f'newmtl material_0\n')
                        f.write(f'Ka 1.0 1.0 1.0\n')
                        f.write(f'Kd 1.0 1.0 1.0\n')
                        f.write(f'Ks 0.0 0.0 0.0\n')
                        f.write(f'map_Kd {os.path.basename(output_tex)}\n')

                    # テクスチャ画像の保存
                    if 'texture' in texture_data:
                        Image.fromarray(texture_data['texture']).save(output_tex)

                    # UV座標付きでメッシュを保存
                    if 'mesh' in texture_data:
                        texture_data['mesh'].export(output_filename)
                    else:
                        mesh.export(output_filename)

                    result_text = f'3Dモデルを生成しました: {output_filename} (テクスチャ付き)'
                else:
                    # 頂点カラーで保存
                    mesh.export(output_filename)
                    result_text = f'3Dモデルを生成しました: {output_filename} (頂点カラー)'

                result_text += f' (頂点数: {len(mesh.vertices)}, 面数: {len(mesh.faces)})'
                print(result_text)

                # 結果をファイルに保存
                with open('result.txt', 'a', encoding='utf-8') as f:
                    f.write(result_text + '\n')

                # 画像に結果テキストを描画
                if no_bg_img.dtype == np.float32:
                    result_img = (no_bg_img * 255).astype(np.uint8)
                else:
                    result_img = no_bg_img.copy()

                # BGRに変換
                if len(result_img.shape) == 3 and result_img.shape[2] == 3:
                    result_img = cv2.cvtColor(result_img, cv2.COLOR_RGB2BGR)

                cv2.putText(result_img, f'3D Model: {output_filename}', (10, 30),
                           cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
                cv2.putText(result_img, f'Vertices: {len(mesh.vertices)}', (10, 60),
                           cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
                cv2.putText(result_img, f'Faces: {len(mesh.faces)}', (10, 90),
                           cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)

                # 最後のメッシュを保存(プレビュー用)
                show_processed_image.last_mesh = mesh

                return result_img
            except Exception as e:
                print(f'メッシュの保存に失敗しました: {e}')
                if no_bg_img.dtype == np.float32:
                    return (no_bg_img * 255).astype(np.uint8)
                else:
                    return no_bg_img
        else:
            if no_bg_img.dtype == np.float32:
                return (no_bg_img * 255).astype(np.uint8)
            else:
                return no_bg_img

    except Exception as e:
        print(f'画像処理中にエラーが発生しました: {e}')
        return img

def show_processed_image(img, window_name, resolution=256, use_texture=False, texture_resolution=1024, preview_3d=False):
    if img is None:
        print('画像の読み込みに失敗しました')
        return
    processed_img = image_processing(img, resolution, use_texture, texture_resolution)
    cv2.imshow(window_name, processed_img)
    cv2.waitKey(0)

    # 3Dプレビュー表示
    if preview_3d and hasattr(show_processed_image, 'last_mesh'):
        preview_3d_mesh(show_processed_image.last_mesh)

# プログラム開始時の説明
print('TripoSR 3D Object Reconstruction')
print('単一画像から3Dモデルを生成します')
print('操作方法:')
print('- カメラモード: スペースキーで撮影、qキーで終了')
print('- 画像表示中: 任意のキーで次へ')
print()

# モデルセットアップ
print('モデルの初期化中...')
setup_triposr_model()

print()
print('0: 画像ファイル')
print('1: カメラ')
print('2: サンプル画像')

choice = input('選択: ')

# メッシュ解像度の選択
print()
print('メッシュ解像度を選択してください:')
print('1: 低解像度 (64) - 高速')
print('2: 中解像度 (128) - バランス')
print('3: 高解像度 (256) - 高品質(デフォルト)')
print('4: 最高解像度 (320) - 最高品質・低速')

res_choice = input('選択 (1-4, デフォルト: 3): ').strip()
resolution_map = {'1': 64, '2': 128, '3': 256, '4': 320}
resolution = resolution_map.get(res_choice, 256)
print(f'選択された解像度: {resolution}')

# テクスチャ生成の選択
print()
print('テクスチャを生成しますか?')
print('1: いいえ(頂点カラーのみ) - 高速')
print('2: はい(テクスチャマップ生成) - 高品質・低速')

tex_choice = input('選択 (1-2, デフォルト: 1): ').strip()
use_texture = (tex_choice == '2')
texture_resolution = 1024

if use_texture:
    print('テクスチャ解像度:')
    print('1: 512x512')
    print('2: 1024x1024(デフォルト)')
    print('3: 2048x2048')
    tex_res_choice = input('選択 (1-3, デフォルト: 2): ').strip()
    tex_res_map = {'1': 512, '2': 1024, '3': 2048}
    texture_resolution = tex_res_map.get(tex_res_choice, 1024)

# 3Dプレビューの選択
print()
preview_3d = input('3Dプレビューを表示しますか? (y/N): ').strip().lower() == 'y'

print()

if choice == '0':
    root = tk.Tk()
    root.withdraw()
    paths = filedialog.askopenfilenames(
        filetypes=[('Image files', '*.jpg *.jpeg *.png *.bmp *.tiff')]
    )
    if not paths:
        exit()
    for path in paths:
        img = cv2.imread(path)
        if img is not None:
            show_processed_image(img, 'Image', resolution, use_texture, texture_resolution, preview_3d)
        else:
            print(f'画像の読み込みに失敗しました: {path}')

elif choice == '1':
    cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
    if not cap.isOpened():
        print('カメラを開けませんでした')
        exit()

    try:
        print('カメラを起動しました。スペースキーで撮影、qキーで終了')
        while True:
            ret, frame = cap.read()
            if not ret:
                break
            cv2.imshow('Camera', frame)
            key = cv2.waitKey(1) & 0xFF
            if key == ord(' '):
                show_processed_image(frame, 'Image', resolution, use_texture, texture_resolution, preview_3d)
            elif key == ord('q'):
                break
    finally:
        cap.release()

elif choice == '2':
    urls = [
        'https://github.com/opencv/opencv/raw/master/samples/data/fruits.jpg',
        'https://github.com/opencv/opencv/raw/master/samples/data/messi5.jpg',
        'https://github.com/opencv/opencv/raw/master/samples/data/aero3.jpg'
    ]
    downloaded_files = []
    for i, url in enumerate(urls):
        filename = f'sample_{i}.jpg'
        try:
            print(f'サンプル画像をダウンロード中: {url}')
            urllib.request.urlretrieve(url, filename)
            downloaded_files.append(filename)
            img = cv2.imread(filename)
            if img is not None:
                show_processed_image(img, 'Sample Image', resolution, use_texture, texture_resolution, preview_3d)
            else:
                print(f'画像の読み込みに失敗しました: {filename}')
        except Exception as e:
            print(f'画像のダウンロードに失敗しました: {url}')
            print(f'エラー: {e}')
            continue

    # ダウンロードしたファイルを削除
    for filename in downloaded_files:
        try:
            os.remove(filename)
        except OSError:
            pass

else:
    print('無効な選択です')
    exit()

cv2.destroyAllWindows()

# 結果ファイルの保存確認
if os.path.exists('result.txt'):
    print('result.txtに結果を保存しました')
else:
    print('処理結果がありませんでした')