MMPretrain による静止画像分類(ソースコードと実行結果)


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 mmengine mmcv-lite opencv-python pillow
pip install --no-build-isolation mmpretrain
MMPretrain画像分類プログラム
概要
画像を入力として受け取り、カテゴリに分類する。主要技術
ConvNeXt V2は純粋な畳み込みニューラルネットワーク(ConvNet)である[1]。この技術は、Fully Convolutional Masked Autoencoder(FCMAE)とGlobal Response Normalization(GRN)という二つの手法を組み合わせた自己教師あり学習フレームワークを使用している。FCMAEは画像の一部をマスクして隠し、残りの部分から元の画像を復元する学習を行う。
MMPretrainはOpenMMLab プロジェクトの一部として開発された画像分類および事前訓練のためのオープンソースフレームワークである[2]。ResNet、EfficientNet、Vision Transformerなど多様なバックボーンモデルをサポートし、教師あり学習と自己教師あり学習の両方に対応している。
参考文献
- [1] Woo, S., Debnath, S., Hu, R., Chen, X., Liu, Z., Kweon, I. S., & Xie, S. (2023). ConvNeXt V2: Co-designing and scaling ConvNets with masked autoencoders. In Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (pp. 16133-16142).
- [2] MMPretrain Contributors. (2023). OpenMMLab's Pre-training Toolbox and Benchmark. Retrieved from https://github.com/open-mmlab/mmpretrain
【関連する外部ページ】
- MMPretrain の GitHub のページ: https://github.com/open-mmlab/mmpretrain
- MMPretrain の公式ドキュメント: https://mmpretrain.readthedocs.io/en/latest/
- MMPretrain での訓練(公式ドキュメント): https://mmpretrain.readthedocs.io/en/latest/user_guides/train.html
- MMPretrain の公式の学習済みモデル: https://mmpretrain.readthedocs.io/en/latest/modelzoo_statistics.html
- MMPretrain の model zoo のページ: https://github.com/open-mmlab/mmpretrain/blob/master/docs/en/model_zoo.md
ソースコード
"""
- プログラム名: MMPretrain画像分類デモプログラム
- 特徴技術名: ConvNeXt V2(純粋ConvNet)
- 出典: S. Woo, S. Debnath, R. Hu, X. Chen, Z. Liu, I. S. Kweon, and S. Xie, "ConvNeXt V2: Co-designing and Scaling ConvNets with Masked Autoencoders," in Proc. IEEE/CVF Conf. Computer Vision and Pattern Recognition (CVPR), 2023, pp. 16133-16142.
- 特徴機能: Fully Convolutional Masked Autoencoder (FCMAE)とGlobal Response Normalization (GRN)による自己教師あり学習。従来のConvNetで88.9%のImageNet精度を達成し、公開データのみを使用した手法で最高性能
- 学習済みモデル:
各モデルの学習済み重みの詳細情報
- ResNet50: 25.6Mパラメータ 98MBファイルサイズ 224x224入力 ImageNet-1k 1.28M画像 90エポック訓練
- ConvNeXt-Tiny: 28.6Mパラメータ 109MBファイルサイズ 224x224入力 ImageNet-1k 1.28M画像 300エポック訓練
- ConvNeXt-Base: 88.6Mパラメータ 338MBファイルサイズ 224x224入力 ImageNet-1k 1.28M画像 300エポック訓練
- EfficientNet-B4: 19.3Mパラメータ 74MBファイルサイズ 380x380入力 ImageNet-1k 1.28M画像訓練
- ViT-Base: 86.6Mパラメータ 330MBファイルサイズ 384x384入力 ImageNet-21k 14M画像事前訓練→ImageNet-1kファインチューニング
- ConvNeXt-V2-Atto: 3.7Mパラメータ 15MBファイルサイズ 224x224入力 FCMAE自己教師あり事前訓練→ImageNet-1kファインチューニング
- ConvNeXt-V2-Huge: 650Mパラメータ 2.4GBファイルサイズ 224x224入力 ImageNet-22k 14M画像FCMAE事前訓練→ImageNet-1kファインチューニング
- TinyViT-5M: 5.4Mパラメータ 22MBファイルサイズ 224x224入力 ImageNet-1k知識蒸留訓練
- TinyViT-21M: 21Mパラメータ 85MBファイルサイズ 224x224入力 ImageNet-22k事前訓練→ImageNet-1k蒸留ファインチューニング
全モデル MMPretrainライブラリ経由で自動ダウンロード
- 方式設計
- 関連利用技術:
* MMPretrain(OpenMMLab画像分類ツールボックス、統一的なAPIと豊富な事前訓練モデルを提供)
* PyTorch(深層学習フレームワーク、動的計算グラフと柔軟なモデル定義を提供)
* MMCV(OpenMMLab基盤ライブラリ、画像処理とデータ変換機能を提供)
- 入力と出力:
入力: 1つの静止画像,カメラ(ユーザは「0:画像ファイル,1:カメラ,2:サンプル画像」のメニューで選択.0:画像ファイルの場合はtkinterでファイル選択可能.1の場合はOpenCVでカメラが開き,スペースキーで撮影.2の場合はhttps://raw.githubusercontent.com/opencv/opencv/master/samples/data/fruits.jpg とhttps://raw.githubusercontent.com/opencv/opencv/master/samples/data/messi5.jpgとhttps://raw.githubusercontent.com/opencv/opencv/master/samples/data/aero3.jpgとhttps://upload.wikimedia.org/wikipedia/commons/3/3a/Cat03.jpgからinput()で選択)
出力: 処理結果をOpenCV画面でリアルタイムに表示.OpenCV画面内に処理結果をテキストで表示.さらに,print()で処理結果を表示.プログラム終了時にprint()で表示した処理結果をresult.txtファイルに保存し,「result.txtに保存」したことをprint()で表示.プログラム開始時に,プログラムの概要,ユーザが行う必要がある操作(もしあれば)をprint()で表示
- 処理手順: 1)モデル初期化(ConvNeXt V2事前訓練モデル読み込み)、2)画像入力(ファイル/カメラ/サンプルから選択)、3)前処理(画像リサイズと正規化)、4)推論実行(FCMAE学習とGRNによる特徴抽出と分類)、5)結果出力(予測クラスと信頼度表示)
- 前処理、後処理: 前処理:画像を224x224ピクセルにリサイズ、ImageNet統計による正規化(平均値と標準偏差でピクセル値を正規化)。後処理:softmax関数による確率変換、上位予測クラスの選択
- 追加処理: なし
- 調整を必要とする設定値: なし(デモプログラムのため固定設定)
- 将来方策: デモプログラムのため特別な調整値なし
- その他の重要事項: 初回実行時はモデルの自動ダウンロードが発生し時間を要する場合がある
- 前準備: Python 3.12対応の完全な手順
pip install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
pip install mmengine mmcv-lite opencv-python pillow
pip install --no-build-isolation mmpretrain
"""
import cv2
import tkinter as tk
from tkinter import filedialog
import urllib.request
import os
import ssl
import time
from datetime import datetime
import numpy as np
import torch
from PIL import Image, ImageDraw, ImageFont
from mmpretrain import ImageClassificationInferencer
# デモ実行のためにSSL証明書検証を無効化(本番環境では削除すること)
ssl._create_default_https_context = ssl._create_unverified_context
# 定数
FONT_PATH = 'C:/Windows/Fonts/meiryo.ttc'
FONT_SIZE = 20
# GPU/CPU自動選択
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f'デバイス: {str(device)}')
# モデル一覧(環境のmmpretrain版によりID差異の可能性あり)
models = {
'0': {'name': 'ResNet50', 'model_id': 'resnet50_8xb32_in1k', 'params': '25.6M', 'size': '98MB', 'input': '224x224', 'pretrain': 'ImageNet-1k 1.28M画像 90エポック'},
'1': {'name': 'ConvNeXt-Tiny', 'model_id': 'convnext-tiny_32xb128_in1k', 'params': '28.6M', 'size': '109MB', 'input': '224x224', 'pretrain': 'ImageNet-1k 1.28M画像 300エポック'},
'2': {'name': 'ConvNeXt-Base', 'model_id': 'convnext-base_32xb128_in1k', 'params': '88.6M', 'size': '338MB', 'input': '224x224', 'pretrain': 'ImageNet-1k 1.28M画像 300エポック'},
'3': {'name': 'EfficientNet-B4', 'model_id': 'efficientnet-b4_3rdparty_8xb32_in1k', 'params': '19.3M', 'size': '74MB', 'input': '380x380', 'pretrain': 'ImageNet-1k 1.28M画像'},
'4': {'name': 'ViT-Base', 'model_id': 'vit-base-p16_64xb64_in1k-384px', 'params': '86.6M', 'size': '330MB', 'input': '384x384', 'pretrain': 'ImageNet-21k→ImageNet-1k'},
'5': {'name': 'ConvNeXt-V2-Atto', 'model_id': 'convnext-v2-atto_fcmae-pre_3rdparty_in1k', 'params': '3.7M', 'size': '15MB', 'input': '224x224', 'pretrain': 'FCMAE→ImageNet-1k'},
'6': {'name': 'ConvNeXt-V2-Huge', 'model_id': 'convnext-v2-huge_fcmae-pre_3rdparty_in1k', 'params': '650M', 'size': '2.4GB', 'input': '224x224', 'pretrain': 'ImageNet-22k FCMAE→ImageNet-1k'},
'7': {'name': 'TinyViT-5M', 'model_id': 'tinyvit-5m_3rdparty_in1k', 'params': '5.4M', 'size': '22MB', 'input': '224x224', 'pretrain': 'ImageNet-1k 知識蒸留'},
'8': {'name': 'TinyViT-21M', 'model_id': 'tinyvit-21m_3rdparty_in1k', 'params': '21M', 'size': '85MB', 'input': '224x224', 'pretrain': 'ImageNet-22k→ImageNet-1k蒸留'}
}
results_log = []
inferencer = None
selected_model = None
def load_font():
try:
return ImageFont.truetype(FONT_PATH, FONT_SIZE)
except Exception:
# フォント未設置環境のフォールバック
return ImageFont.load_default()
def draw_text_lines_bgr(img_bgr, lines, color=(0, 255, 0), anchor=(10, 10), line_gap=28):
font = load_font()
img_pil = Image.fromarray(cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(img_pil)
x, y = anchor
for line in lines:
draw.text((x, y), line, font=font, fill=color)
y += line_gap
return cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR)
def format_topk(classes, scores, k=5):
idx = np.argsort(scores)[::-1][:k]
items = []
lines = []
for rank, i in enumerate(idx, start=1):
label = classes[i] if (classes is not None and i < len(classes)) else str(i)
sc = float(scores[i])
items.append(f"{rank}) {label}:{sc:.3f}")
lines.append(f"{rank}. {label} {sc:.3f}")
return "Top-5: " + ", ".join(items), lines
def resolve_classes_from_inferencer(out):
# 1) inferencer.classes
cls = getattr(inferencer, 'classes', None)
if cls is not None:
return cls
# 2) inferencer.model.dataset_meta.get('classes')
mdl = getattr(inferencer, 'model', None)
if mdl is not None:
dm = getattr(mdl, 'dataset_meta', None)
if isinstance(dm, dict) and 'classes' in dm:
return dm['classes']
# 3) 推論出力中の'classes'
if isinstance(out, dict) and 'classes' in out:
return out['classes']
# 4) 不明な場合はNone(インデックスで代用)
return None
def image_processing(img_bgr):
assert inferencer is not None and selected_model is not None
current_time = time.time()
try:
out = inferencer(img_bgr)[0] # 1件分のdict
scores = out.get('pred_scores', None)
if scores is None:
score = float(out.get('pred_score', 0.0))
scores = np.array([score], dtype=np.float32)
else:
if hasattr(scores, 'detach'):
scores = scores.detach().cpu().numpy()
elif hasattr(scores, 'numpy'):
scores = scores.numpy()
else:
scores = np.asarray(scores)
classes = resolve_classes_from_inferencer(out)
top5_line, top5_lines = format_topk(classes, scores, k=min(5, len(scores)))
lines = [
f"Model: {selected_model['name']}",
"画像分類 Top-5"
] + top5_lines
processed = draw_text_lines_bgr(img_bgr.copy(), lines, color=(0, 255, 0), anchor=(10, 10))
result_text = f"{top5_line}"
return processed, result_text, current_time
except Exception as e:
err_msg = f"推論エラー: {e}"
processed = draw_text_lines_bgr(img_bgr.copy(), [err_msg], color=(0, 0, 255), anchor=(10, 10))
return processed, err_msg, current_time
def process_and_display_images(image_sources, source_type):
display_index = 1
for source in image_sources:
img = cv2.imread(source) if source_type == 'file' else source
if img is None:
continue
cv2.imshow(f'Image_{display_index}', img)
processed_img, result, current_time = image_processing(img)
cv2.imshow(f'画像分類 Top-5_{display_index}', processed_img)
print(datetime.fromtimestamp(current_time).strftime("%Y-%m-%d %H:%M:%S.%f")[:-3], result)
results_log.append(result)
display_index += 1
# ガイダンス表示
print("=" * 60)
print("MMPretrain 画像分類デモプログラム")
print("=" * 60)
print("概要: 事前訓練モデルを用いて静止画の画像分類(Top-5)を行う")
print("操作方法:")
print(" 1) モデル番号を選択する")
print(" 2) 入力方法を選択する(0:画像ファイル, 1:カメラ, 2:サンプル画像)")
print(" 3) カメラ選択時はスペースキーで撮影、qキーで終了")
print("注意: 初回実行時はモデルの自動ダウンロードに時間を要する場合がある")
print("=" * 60)
# モデル選択UI
print("\n利用可能なモデル:")
print("-" * 110)
print(f"{'No.':<3} {'Model':<18} {'Parameters':<12} {'Size':<8} {'Input':<10} {'学習済みモデル':<30}")
print("-" * 110)
for key, model in models.items():
print(f"{key:<3} {model['name']:<18} {model['params']:<12} {model['size']:<8} {model['input']:<10} {model['pretrain']:<30}")
print("-" * 110)
model_choice = input("モデル番号を選択 (0-8): ").strip()
if model_choice not in models:
print("無効な選択です")
raise SystemExit
selected_model = models[model_choice]
print(f"\n選択されたモデル: {selected_model['name']} ({selected_model['params']} parameters)")
# Inferencer初期化
try:
inferencer = ImageClassificationInferencer(selected_model['model_id'], device=device)
except Exception as e:
print(f"モデル初期化に失敗しました: {e}")
raise SystemExit
print("\n画像入力方法を選択してください:")
print("0: 画像ファイル")
print("1: カメラ")
print("2: サンプル画像")
choice = input("選択: ").strip()
try:
if choice == '0':
root = tk.Tk()
root.withdraw()
if not (paths := filedialog.askopenfilenames()):
raise SystemExit
process_and_display_images(paths, 'file')
cv2.waitKey(0)
elif choice == '1':
# OpenCVでのカメラ開始(推奨形)
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
if not cap.isOpened():
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
try:
while True:
ret, frame = cap.read()
if not ret:
break
cv2.imshow('Camera', frame)
key = cv2.waitKey(1) & 0xFF
if key == ord(' '):
processed_img, result, current_time = image_processing(frame)
cv2.imshow('画像分類 Top-5', processed_img)
print(datetime.fromtimestamp(current_time).strftime("%Y-%m-%d %H:%M:%S.%f")[:-3], result)
results_log.append(result)
elif key == ord('q'):
break
finally:
cap.release()
else:
urls = [
"https://raw.githubusercontent.com/opencv/opencv/master/samples/data/fruits.jpg",
"https://raw.githubusercontent.com/opencv/opencv/master/samples/data/messi5.jpg",
"https://raw.githubusercontent.com/opencv/opencv/master/samples/data/aero3.jpg",
"https://upload.wikimedia.org/wikipedia/commons/3/3a/Cat03.jpg"
]
downloaded_files = []
for i, url in enumerate(urls):
try:
urllib.request.urlretrieve(url, f"sample_{i}.jpg")
downloaded_files.append(f"sample_{i}.jpg")
except Exception:
print(f"画像のダウンロードに失敗しました: {url}")
process_and_display_images(downloaded_files, 'file')
cv2.waitKey(0)
finally:
print('\n=== プログラム終了 ===')
cv2.destroyAllWindows()
if results_log:
with open('result.txt', 'w', encoding='utf-8') as f:
f.write('=== 結果 ===\n')
f.write(f'使用デバイス: {str(device).upper()}\n')
if device.type == 'cuda':
f.write(f'GPU: {torch.cuda.get_device_name(0)}\n')
f.write('\n')
f.write('\n'.join(results_log))
print(f'\n処理結果をresult.txtに保存しました')