YOLOv8を用いたタバコ葉病害AI検出システムの構築

YOLOv8を用いたタバコ葉病害AI検出システムの構築

スマート農業分野では、作物の健康状態を効率的に監視し、病害虫の早期発見を行うことが収穫量と品質の維持に不可欠です。本稿では、最新の物体検出モデルであるYOLOv8を用いて、ドローンで撮影されたタバコ葉の画像を解析し、病害を自動的に検出するAIシステムの構築手順について解説します。環境構築からモデル訓練、推論、評価までの包括的なワークフローを提供します。

ドローン空撮タバコ葉病害検出データセット

本プロジェクトで使用するデータセットは、実際の農場環境を模倣したドローン空撮画像から構成されています。このデータセットは、タバコ葉の健康状態を評価するために特別に設計されており、以下の特徴を持ちます。

  • 画像数: 1,964枚の高解像度航空写真
  • データソース: DJI製ドローンによる実環境撮影
  • 前処理: 画像のトリミング、回転、フリップ、輝度調整などのデータ拡張を実施
  • 品質: 微細な病変も識別可能な高精細画像
  • アノテーション: 専門家による手動ラベリング(YOLO形式 .txt
  • ファイル構造: images/, labels/, dataset_config.yaml を含む
  • 互換性: YOLOv5, YOLOv8などのモデルで即座に利用可能

クラス定義

クラスID クラス名(英語) クラス名(日本語) 説明
0 healthy 健康な葉 病害の兆候が見られない健全なタバコ葉
1 diseased 病害の葉 病原体や害虫による損傷、症状が確認されるタバコ葉(例: モザイク病、赤星病、虫食いなど)

データセットの利点

  • 実用性: ドローン空撮データにより、実際の農業監視シナリオに即した応用が可能。
  • 高精度ラベリング: 専門家による手作業での厳密なアノテーションが、モデルの学習精度を向上。
  • 即時利用可能: YOLO形式の完全なデータセット構造を提供し、追加の前処理なしで訓練を開始できます。
  • 多様なデータ: データ拡張により、モデルの汎化性能が高まり、様々な条件下での検出に対応します。

プロジェクト推奨ディレクトリ構造

効率的な開発のために、以下のディレクトリ構造を推奨します。これにより、データ、モデル、スクリプト、出力が明確に分離されます。

tobacco_disease_detector/
│
├── data/
│   └── tobacco_leaf_data/
│       ├── images/
│       │   ├── train/
│       │   ├── val/
│       │   └── test/
│       ├── labels/
│       │   ├── train/
│       │   ├── val/
│       │   └── test/
│       └── dataset_config.yaml # データセット設定ファイル
│
├── models/
│   └── yolov8n.pt              # YOLOv8公式プリトレーニング済み重み
│
├── output/                     # 訓練、推論、評価結果の保存先
│   ├── training_logs/
│   └── inference_results/
│   └── evaluation_results/
│
├── scripts/
│   ├── environment_setup.py    # 環境構築スクリプト
│   ├── train_model.py          # モデル訓練スクリプト
│   ├── predict_detections.py   # 推論実行スクリプト
│   ├── evaluate_model.py       # モデル評価スクリプト
│   └── run_full_workflow.py    # 全ワークフロー実行スクリプト
│
└── README.md

開発環境の構築

1. CUDAドライバーのインストール(GPU利用の場合)

NVIDIA製GPUを使用する場合、CUDA Toolkitのインストールが必要です。YOLOv8はGPUを利用することで高速な処理が可能です。

  • NVIDIA公式ウェブサイトから、お使いのGPUとOSに対応するCUDAドライバーおよびToolkitをダウンロードし、インストールしてください。
  • インストール後、ターミナルで nvidia-smi を実行し、GPU情報が表示されれば正常にインストールされています。

2. Anacondaのインストール

Pythonのバージョン管理とパッケージ管理にはAnacondaまたはMinicondaの使用を強く推奨します。

  • Anacondaの公式ダウンロードページ(https://www.anaconda.com/products/distribution)からインストーラーをダウンロードし、指示に従ってインストールします。
  • インストール後、ターミナルで conda --version を実行し、バージョン情報が表示されれば成功です。

3. Python仮想環境の作成と依存関係のインストール

以下のPythonスクリプトは、必要な仮想環境の作成と、PyTorch、Ultralyticsなどのライブラリのインストールを自動化します。

scripts/environment_setup.py

# scripts/environment_setup.py
import subprocess
import sys

def create_and_setup_environment():
    """
    Conda仮想環境を作成し、必要なPythonパッケージをインストールする。
    """
    env_name = "tobacco_yolo_env"
    
    # 仮想環境を作成
    print(f"仮想環境 '{env_name}' を作成中...")
    try:
        subprocess.run(
            ["conda", "create", "-n", env_name, "python=3.10", "-y"],
            check=True, capture_output=True, text=True
        )
        print(f"Conda stdout:\n{subprocess.run(['conda', 'info', '--envs'], capture_output=True, text=True).stdout}")
    except subprocess.CalledProcessError as e:
        if "already exists" in e.stderr:
            print(f"仮想環境 '{env_name}' は既に存在します。既存の環境を使用します。")
        else:
            print(f"仮想環境作成中にエラーが発生しました: {e.stderr}")
            sys.exit(1)
    print(f"仮想環境 '{env_name}' の準備ができました。")

    # PyTorchとUltralyticsをインストール (conda経由で推奨)
    print("PyTorchとUltralytics、その他依存パッケージをインストール中...")
    
    # PyTorch with CUDA 11.8
    # 'conda run -n {env_name}' を使用して、指定された環境内でコマンドを実行します。
    pytorch_install_cmd = [
        "conda", "run", "-n", env_name,
        "conda", "install", "pytorch", "torchvision", "torchaudio", "pytorch-cuda=11.8",
        "-c", "pytorch", "-c", "nvidia", "-y"
    ]
    
    # その他のパッケージをpipでインストール
    pip_install_cmd = [
        "conda", "run", "-n", env_name,
        "pip", "install", "ultralytics", "opencv-python", "matplotlib", "pillow"
    ]

    try:
        print("PyTorchをインストール中...")
        subprocess.run(pytorch_install_cmd, check=True, capture_output=True, text=True)
        print("その他のパッケージをインストール中...")
        subprocess.run(pip_install_cmd, check=True, capture_output=True, text=True)
    except subprocess.CalledProcessError as e:
        print(f"パッケージインストール中にエラーが発生しました: {e.cmd}")
        print(f"Stderr: {e.stderr}")
        print(f"Stdout: {e.stdout}")
        sys.exit(1)
            
    print("環境設定が完了しました。")
    print(f"以下のコマンドで仮想環境をアクティベートしてください: conda activate {env_name}")

if __name__ == "__main__":
    create_and_setup_environment()

上記スクリプトを保存し、実行します。

python scripts/environment_setup.py

インストール完了後、以下のコマンドで作成した仮想環境をアクティベートします。

conda activate tobacco_yolo_env

注意: PyTorchとCUDAのバージョンは、ご自身のGPUおよびYOLOv8の互換性要件に合わせて適宜調整してください。ここではCUDA 11.8をターゲットとしています。

データセット設定ファイル (`dataset_config.yaml`)

YOLOv8モデルがデータセットの位置とクラス情報を認識するために、設定ファイルが必要です。このファイルは通常、以下のような内容になります。

# data/tobacco_leaf_data/dataset_config.yaml
train: ../data/tobacco_leaf_data/images/train # 訓練画像のパス
val: ../data/tobacco_leaf_data/images/val   # 検証画像のパス
test: ../data/tobacco_leaf_data/images/test # テスト画像のパス

nc: 2                       # クラス数 (healthy, diseasedの2クラス)
names: ['healthy', 'diseased'] # クラス名のリスト

train, val, test の各パスがデータセットディレクトリからの相対パスであることを確認してください。nc は検出対象の総クラス数を、names は各クラスの名称を定義します。

モデル訓練スクリプト (`scripts/train_model.py`)

以下のスクリプトは、YOLOv8nモデルをタバコ葉病害データセットで訓練するプロセスを実行します。

# scripts/train_model.py
from ultralytics import YOLO
import os

def train_tobacco_detector():
    """
    YOLOv8モデルをカスタムデータセットで訓練する。
    """
    # プリトレーニング済みモデルのロード (YOLOv8nは軽量モデル)
    # 必要に応じて 'yolov8s.pt', 'yolov8m.pt' などに変更可能
    model_path = 'models/yolov8n.pt'
    if not os.path.exists(model_path):
        print(f"情報: プリトレーニング済みモデル '{model_path}' が見つかりません。")
        print("Ultralyticsは初回訓練時に自動ダウンロードを試みます。")
        # UltralyticsはYOLO('yolov8n.pt')でモデルを自動的にダウンロードします
    
    yolo_detector = YOLO(model_path)

    # 訓練の開始
    print("モデル訓練を開始します...")
    training_results = yolo_detector.train(
        data='data/tobacco_leaf_data/dataset_config.yaml', # データセット設定ファイルのパス
        epochs=100,                                   # 訓練エポック数
        imgsz=640,                                    # 入力画像サイズ
        batch=16,                                     # バッチサイズ (GPUメモリに合わせて調整)
        name='tobacco_yolov8n_detection',             # 訓練実行の識別名
        project='output/training_logs',               # 訓練結果の保存ディレクトリ
        device='cuda',                                # 使用デバイス ('cuda'または'cpu')
        workers=8,                                    # データローダーのワーカー数
        optimizer='AdamW',                            # オプティマイザ
        lr0=0.001,                                    # 初期学習率
        weight_decay=0.0005,                          # 重み減衰
        momentum=0.937,                               # モーメンタム
        patience=30,                                  # 早期停止の待機エポック数
        amp=True,                                     # 混合精度訓練の有効化
        close_mosaic=20,                              # Mosaicデータ拡張を停止するエポック数
        cache='disk'                                  # 画像をディスクにキャッシュしてデータ読み込みを高速化
    )

    print("訓練が完了しました。")
    print(training_results)

if __name__ == "__main__":
    train_tobacco_detector()

このスクリプトは、以下のコマンドで実行できます。

conda activate tobacco_yolo_env
python scripts/train_model.py

訓練が完了すると、最適なモデルの重みは output/training_logs/tobacco_yolov8n_detection/weights/best.pt に保存されます。

推論実行スクリプト (`scripts/predict_detections.py`)

訓練済みのモデルを使用して、新しい画像や動画、リアルタイムのカメラフィードから病害を検出する方法を示します。

# scripts/predict_detections.py
from ultralytics import YOLO
import cv2
import os

# 訓練済みモデルのパス
TRAINED_MODEL_PATH = 'output/training_logs/tobacco_yolov8n_detection/weights/best.pt'
# 推論結果を保存するベースディレクトリ
OUTPUT_BASE_DIR = 'output/inference_results'
# 検出の最小信頼度閾値
CONFIDENCE_THRESHOLD = 0.35

def load_detector_model(model_path):
    """訓練済みYOLOモデルをロードする。"""
    if not os.path.exists(model_path):
        raise FileNotFoundError(f"エラー: 訓練済みモデルが見つかりません: {model_path}")
    print(f"モデル '{model_path}' をロードしました。")
    return YOLO(model_path)

def process_single_image(model, image_path, output_name='single_image_test'):
    """単一の画像に対して物体検出を実行し、結果を保存する。"""
    print(f"単一画像 '{image_path}' の検出を開始...")
    results = model.predict(
        source=image_path,
        conf=CONFIDENCE_THRESHOLD,
        save=True,                      # 結果画像を保存
        project=OUTPUT_BASE_DIR,
        name=output_name,
        show=False                      # 検出結果のウィンドウを非表示にする
    )
    print(f"検出完了。結果は {os.path.join(OUTPUT_BASE_DIR, output_name)}/ に保存されました。")

def process_image_directory(model, directory_path, output_name='batch_images_test'):
    """指定されたディレクトリ内の全画像に対して物体検出を実行する。"""
    print(f"ディレクトリ '{directory_path}' 内の画像をバッチ処理中...")
    results = model.predict(
        source=directory_path,
        conf=CONFIDENCE_THRESHOLD,
        save=True,
        project=OUTPUT_BASE_DIR,
        name=output_name,
        show=False
    )
    print(f"バッチ検出完了。結果は {os.path.join(OUTPUT_BASE_DIR, output_name)}/ に保存されました。")

def process_live_camera_feed(model):
    """ウェブカメラからのリアルタイム検出を実行する。"""
    print("カメラフィードからのリアルタイム検出を開始します。'q'を押して終了してください。")
    results_generator = model.predict(
        source=0,                       # 0はデフォルトのウェブカメラ
        conf=CONFIDENCE_THRESHOLD,
        show=True,                      # リアルタイムで結果を表示
        stream=True,                    # ジェネレーターとして結果をストリーム
    )
    for _ in results_generator:
        # 結果は show=True で自動表示されるため、ここでは特別な処理は不要
        pass 

def process_video_file(model, video_path, output_name='video_processing_test'):
    """動画ファイルに対して物体検出を実行し、結果を保存する。"""
    print(f"動画ファイル '{video_path}' の処理を開始...")
    results = model.predict(
        source=video_path,
        conf=CONFIDENCE_THRESHOLD,
        save=True,
        project=OUTPUT_BASE_DIR,
        name=output_name,
        show=False
    )
    print(f"動画処理完了。結果は {os.path.join(OUTPUT_BASE_DIR, output_name)}/ に保存されました。")

if __name__ == "__main__":
    try:
        yolo_model = load_detector_model(TRAINED_MODEL_PATH)
        
        # 例: テストディレクトリ内の画像を処理
        test_images_dir = 'data/tobacco_leaf_data/images/test'
        print(f"テスト画像のパス: {os.path.abspath(test_images_dir)}")
        if os.path.isdir(test_images_dir):
            process_image_directory(yolo_model, test_images_dir)
        else:
            print(f"エラー: テスト画像ディレクトリ '{test_images_dir}' が見つかりません。")

        # その他の推論オプション(コメント解除して実行)
        # process_single_image(yolo_model, 'data/tobacco_leaf_data/images/test/some_test_image.jpg')
        # process_live_camera_feed(yolo_model)
        # process_video_file(yolo_model, 'path/to/your/test_video.mp4')

    except FileNotFoundError as e:
        print(e)
    except Exception as e:
        print(f"推論実行中に予期せぬエラーが発生しました: {e}")

スクリプトの実行例:

conda activate tobacco_yolo_env
python scripts/predict_detections.py

モデル評価スクリプト (`scripts/evaluate_model.py`)

訓練済みのモデルが検証データセット上でどの程度の性能を発揮するかを評価します。mAP (mean Average Precision)、Precision (精度)、Recall (再現率) などの主要な指標が計算されます。

# scripts/evaluate_model.py
from ultralytics import YOLO
import os

def evaluate_tobacco_detector():
    """
    訓練済みYOLOv8モデルを検証データセットで評価する。
    """
    # 訓練済みモデルのパス
    final_model_path = 'output/training_logs/tobacco_yolov8n_detection/weights/best.pt'
    if not os.path.exists(final_model_path):
        raise FileNotFoundError(f"エラー: 評価対象の訓練済みモデルが見つかりません: {final_model_path}")

    # モデルのロード
    trained_detector = YOLO(final_model_path)

    # 検証データセットでの評価を実行
    print("モデルの評価を開始します...")
    evaluation_metrics = trained_detector.val(
        data='data/tobacco_leaf_data/dataset_config.yaml', # データセット設定ファイル
        imgsz=640,                                    # 入力画像サイズ
        batch=16,                                     # バッチサイズ
        conf=0.001,                                   # 評価用の低い信頼度閾値 (mAP計算用)
        iou=0.6,                                      # IoU閾値
        device='cuda',                                # 使用デバイス
        project='output/evaluation_results',          # 評価結果の保存先
        name='tobacco_yolov8n_evaluation'
    )

    # 評価結果の表示
    print("\n--- モデル評価結果 ---")
    print(f"mAP50 (平均適合率@IoU=0.5): {evaluation_metrics.box.map50:.4f}")
    print(f"mAP50-95 (平均適合率@IoU=0.5〜0.95): {evaluation_metrics.box.map:.4f}")
    print(f"Precision (精度): {evaluation_metrics.box.p:.4f}")
    print(f"Recall (再現率): {evaluation_metrics.box.r:.4f}")
    
    # F1スコアはPrecisionとRecallから計算
    if evaluation_metrics.box.p > 0 and evaluation_metrics.box.r > 0:
        f1_score = 2 * (evaluation_metrics.box.p * evaluation_metrics.box.r) / (evaluation_metrics.box.p + evaluation_metrics.box.r)
        print(f"F1 Score: {f1_score:.4f}")
    else:
        print("F1 Score: PrecisionまたはRecallが0のため計算できません。")
    
    print(f"\n詳細な評価結果は {evaluation_metrics.save_dir} に保存されました。")

if __name__ == "__main__":
    try:
        evaluate_tobacco_detector()
    except FileNotFoundError as e:
        print(e)
    except Exception as e:
        print(f"モデル評価中に予期せぬエラーが発生しました: {e}")

このスクリプトは、以下のコマンドで実行できます。

conda activate tobacco_yolo_env
python scripts/evaluate_model.py

評価結果のグラフや詳細は、output/evaluation_results/tobacco_yolov8n_evaluation/ ディレクトリに生成されます。

訓練結果の可視化

モデル訓練が完了すると、output/training_logs/tobacco_yolov8n_detection/ ディレクトリには、訓練プロセスを視覚的に分析するための様々なグラフが生成されます。

  • results.png: 訓練損失、検証損失、mAP曲線など、訓練中の主要メトリクスの推移
  • confusion_matrix.png: 各クラス間の検出精度を示す混同行列
  • PR_curve.png: 各クラスの適合率-再現率曲線
  • F1_curve.png: F1スコア曲線

これらのグラフは、モデルの性能特性を理解し、過学習や未学習の兆候を特定するのに役立ちます。

よくある問題と解決策

問題 解決策
GPUメモリ不足エラー batch サイズを減らす (例: 8または4)。より軽量なモデル (yolov8n.pt) を使用する。
訓練速度が遅い cache='disk' または cache='ram' を訓練オプションに追加してデータロードを高速化する。ワーカー数 workers を調整する。
モデルが過学習する epochs を減らす。データ拡張 (augment=True) を強化する。早期停止 (patience) の設定を調整する。
検出精度が低い アノテーションの品質を再確認する。訓練エポック数を増やす。conf および iou 閾値を調整する。データセットのサイズや多様性を増やす。
環境構築エラー environment_setup.py スクリプトがすべてのコマンドを正常に実行したか確認する。特にPyTorchとCUDAのバージョンが互換性を持っているか確認する。

包括的なワークフロー実行スクリプト

訓練、評価、推論の各ステップを順次実行するための便利なスクリプトです。

# run_full_workflow.py
import subprocess
import sys

def execute_script(script_path):
    """指定されたPythonスクリプトを実行するヘルパー関数。"""
    print(f"\n--- {script_path} を実行中 ---")
    try:
        # 環境がアクティベートされている前提
        result = subprocess.run(["python", script_path], check=True, capture_output=True, text=True)
        print(result.stdout)
        if result.stderr:
            print(f"STDERR for {script_path}:\n{result.stderr}")
    except subprocess.CalledProcessError as e:
        print(f"エラー: {script_path} の実行中に問題が発生しました。")
        print(f"Stderr: {e.stderr}")
        print(f"Stdout: {e.stdout}")
        sys.exit(1)
    except FileNotFoundError:
        print(f"エラー: スクリプト '{script_path}' が見つかりません。パスを確認してください。")
        sys.exit(1)

if __name__ == "__main__":
    # このスクリプトを実行する前に、必ずConda仮想環境をアクティベートしてください。
    # 例: `conda activate tobacco_yolo_env`
    
    print("AI検出システム構築ワークフローを開始します。")
    print("事前にターミナルで `conda activate tobacco_yolo_env` を実行してください。")

    # 訓練スクリプトの実行
    execute_script("scripts/train_model.py")
    
    # 評価スクリプトの実行
    execute_script("scripts/evaluate_model.py")
    
    # 推論スクリプトの実行
    execute_script("scripts/predict_detections.py")
    
    print("\nすべてのワークフローが正常に完了しました。")

このスクリプトを実行する前に、必ず仮想環境をアクティベートしてください。

conda activate tobacco_yolo_env
python scripts/run_full_workflow.py

タグ: YOLOv8 物体検出 深層学習 農業AI ドローン

8月6日 05:39 投稿