YOLOv8-OBBによる回転検出モデルの学習からC#デプロイまでの実装ガイド

環境セットアップと準備

YOLOv8の回転バウンディングボックス(OBB)機能を使用するには、Ultralyticsの公式リポジトリからソースコードを取得します。リポジトリにはOBBタスク用のモジュールが統合されています。環境構築については、以前のバージョンのYOLO環境があればそれを流用できますが、新規に構築する場合は以下のコマンドで必要なライブラリをインストールします。CUDA環境を整えておくと学習速度が大幅に向上します。

pip install ultralytics -i https://pypi.tuna.tsinghua.edu.cn/simple

データセットのアノテーション

回転検出用のデータセットを作成するには、回転した矩形をアノテーションできるツールが必要です。roLabelImgのようなツールを使用して、画像に対して回転ボックスの座標と角度をXML形式で保存します。アノテーション時には、ショートカットキー(z, x, c, vなど)を使用して角度を微調整できます。このプロセスにより、画像ごとにオブジェクトのクラスと回転矩形(cx, cy, w, h, angle)を含んだXMLファイルが生成されます。

アノテーションデータの形式変換

roLabelImgが出力するXML形式は、そのままではYOLOv8-OBBで使用できません。一度DOTA形式(4点座標)のテキストファイルに変換し、その後、YOLO用の正規化された座標形式へ変換する必要があります。以下は、roLabelImgのXML(robndboxまたはbndbox)を解析し、DOTA形式のTXTファイル(x1 y1 x2 y2 x3 y3 x4 y4 class_name)に変換するスクリプトの例です。

import os
import xml.etree.ElementTree as ET
import math

# 自身のクラスリストを定義
CLASS_NAMES = ['target_object']

def calculate_poly_points(cx, cy, w, h, angle_rad):
    """中心座標とサイズ、角度から4点の座標を計算する"""
    cos_a = math.cos(angle_rad)
    sin_a = math.sin(angle_rad)
    
    dx = w / 2.0
    dy = h / 2.0
    
    # 反時計回りの回転を考慮して4隅の座標を計算
    # x0, y0 (top-left-ish relative to center before rotation)
    # x1, y1 (top-right)
    # x2, y2 (bottom-right)
    # x3, y3 (bottom-left)
    
    p0x = -dx * cos_a - (-dy) * sin_a + cx
    p0y = -dx * sin_a + (-dy) * cos_a + cy
    
    p1x = dx * cos_a - (-dy) * sin_a + cx
    p1y = dx * sin_a + (-dy) * cos_a + cy
    
    p2x = dx * cos_a - dy * sin_a + cx
    p2y = dx * sin_a + dy * cos_a + cy
    
    p3x = -dx * cos_a - dy * sin_a + cx
    p3y = -dx * sin_a + dy * cos_a + cy
    
    return [p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y]

def parse_and_convert(xml_path, output_txt_path):
    tree = ET.parse(xml_path)
    root = tree.getroot()
    size = root.find('size')
    img_w = float(size.find('width').text)
    img_h = float(size.find('height').text)
    
    annotations = []
    for obj in root.findall('object'):
        cls_name = obj.find('name').text
        if cls_name not in CLASS_NAMES:
            continue
            
        robndbox = obj.find('robndbox')
        if robndbox is not None:
            # 回転ボックスの場合
            cx = float(robndbox.find('cx').text)
            cy = float(robndbox.find('cy').text)
            w = float(robndbox.find('w').text)
            h = float(robndbox.find('h').text)
            angle_deg = float(robndbox.find('angle').text)
            # ラジアンに変換 (符号はライブラリの実装に注意)
            angle_rad = math.radians(angle_deg)
            points = calculate_poly_points(cx, cy, w, h, angle_rad)
        else:
            # 通常の水平ボックスの場合(便宜上4点として扱う)
            bndbox = obj.find('bndbox')
            xmin = float(bndbox.find('xmin').text)
            ymin = float(bndbox.find('ymin').text)
            xmax = float(bndbox.find('xmax').text)
            ymax = float(bndbox.find('ymax').text)
            points = [xmin, ymin, xmax, ymin, xmax, ymax, xmin, ymax]
        
        # 画像境界外に出るのを防ぐ簡易的なクリッピング
        points = [max(0, min(p, img_w if i%2==0 else img_h)) for i, p in enumerate(points)]
        
        annotations.append(f"{' '.join(map(str, points))} {cls_name}\n")
        
    with open(output_txt_path, 'w') as f:
        f.writelines(annotations)

# ディレクトリ内のXMLを一括処理
source_dir = 'path/to/xml_files'
dest_dir = 'path/to/dota_txt_files'
os.makedirs(dest_dir, exist_ok=True)

for filename in os.listdir(source_dir):
    if filename.endswith('.xml'):
        xml_file = os.path.join(source_dir, filename)
        txt_file = os.path.join(dest_dir, filename.replace('.xml', '.txt'))
        parse_and_convert(xml_file, txt_file)

このスクリプトで生成したTXTファイルは、まだDOTA形式です。YOLOv8-OBBで学習するには、Ultralyticsが提供している変換ユーティリティを使用して、座標を正規化されたYOLO形式(クラスID x_center y_center width height angle)に変換します。

from ultralytics.data.converter import convert_dota_to_yolo_obb

# DOTA形式のファイルがあるルートディレクトリを指定
# 中身は train_original, val_original などのフォルダ構造になっていると仮定
dataset_root = 'path/to/dataset_root'
convert_dota_to_yolo_obb(dataset_root)

モデルの学習

データセットの準備ができたら、data.yamlファイル(クラス名とパスを記述)を作成し、学習を開始します。以下は、学習プロセスを記述したPythonスクリプトの例です。

from ultralytics import YOLO

def train_custom_obb():
    # モデルアーキテクチャの設定ファイルと学習済み重み
    model_config = 'yolov8s-obb.yaml'
    pretrained_weights = 'yolov8s-obb.pt'
    
    # モデルのロード
    model = YOLO(model_config).load(pretrained_weights)

    # 学習の実行
    model.train(
        data='data/custom_dataset.yaml',
        epochs=100,
        imgsz=640,
        batch=8,
        workers=4,
        name='yolov8_obb_custom',
        device='0'  # GPU ID
    )

if __name__ == '__main__':
    train_custom_obb()

推論とモデル変換

学習が完了したら、best.ptモデルを使用して推論を行います。

from ultralytics import YOLO

def run_inference():
    # 学習済みモデルの読み込み
    model = YOLO('runs/obb/train/weights/best.pt')

    # 推論の実行と結果の保存
    results = model(
        source='path/to/test_images',
        conf=0.25,
        iou=0.7,
        save=True,
        name='inference_result'
    )

if __name__ == '__main__':
    run_inference()

次に、このモデルをC#などの他の環境で使用するためにONNX形式へエクスポートします。

from ultralytics import YOLO

def export_to_onnx():
    model = YOLO('runs/obb/train/weights/best.pt')
    
    # ONNX形式へエクスポート
    model.export(
        format='onnx',
        imgsz=640,
        opset=12,
        simplify=True
    )

if __name__ == '__main__':
    export_to_onnx()

C#によるモデルのデプロイ

エクスポートされたONNXモデルを使用してC#アプリケーション内で推論を行うには、Microsoft.ML.OnnxRuntime NuGetパッケージを使用します。デプロイの際は、入力画像の前処理(リサイズ、正規化)と、出力テンソルの後処理(NMS、回転矩形の復元)を実装する必要があります。

実装アプローチとして、推論ロジックをカプセル化したクラスライブラリ(DLL)を作成し、それをメインアプリケーションから参照する設計が推奨されます。これにより、UIロジックと推論ロジックを分離できます。DLL内では、モデルの入力サイズ(640x640など)に合わせて画像を前処理し、モデルが出力するボックス座標、信頼度、クラスIDを解析して、元の画像サイズにスケールバックする処理を記述します。

タグ: YOLOv8 OBB ObjectDetection ONNX csharp

8月4日 23:29 投稿