Ultralytics YOLO27: ウェイトリストに登録する。
Get Started
レッスン中級

回転オブジェクト向けの回転バウンディングボックス

四隅で表すOBBにラベルを付け、DOTA8でyolo26n-obb.ptを学習し、角度の規約に惑わされずにxywhr予測を安定した向きの軸に変換します。

港の船、ベルトコンベヤー上の荷物、上空から見た駐車場の車には、共通の問題があります。回転して密集したオブジェクトを軸に平行なボックスで囲むと、互いに重なり、背景も多く含まれるため、NMSが実際のオブジェクトを削除し、検出数が少なくなります。回転バウンディングボックスは各オブジェクトを回転した長方形で囲むため、密集したシーンでも正確に数えられ、角度も得られます。ただし、角度にはほとんどの初回統合で混乱を招く規約があります。このレッスンの港の画像では、最初の船の回転角は−15°ですが、船体の向きは75°です。

成果

dota8.yamlでyolo26n-obb.ptを学習し、result.obbからクラスごとの検出数を数え、各予測のxywhrを長辺の軸([0, 180)度)に変換します。

短期集中コース
すでに使い方をご存じの方は、こちらをご覧ください。
  1. ラベルの行: クラス、続いて正規化された四隅のx1 y1 … x4 y4を周囲に沿った順序で記述します。

  2. yolo obb train data=dota8.yaml model=yolo26n-obb.pt epochs=1 imgsz=320.

  3. result.obb.xywhr(ラジアン単位の回転角)または.xyxyxyxy(四隅)を読み取ります。OBBの結果ではresult.boxesは設定されません。

  4. 向きを長辺に合わせて正規化します。w < hの場合は90°を加え、その後、角度を180°で割った余りを取ります。

実践的

回転が重要な場合#

空撮画像の係留ヨット3隻をぴったり囲む、Ultralytics YOLOの回転バウンディングボックス。それぞれにShipのラベルと信頼度が付いています

ポーズ推定とOBBでは、検出よりもOBBを選ぶべき状況を説明しています。実用的な判断基準は、隣接するオブジェクトを囲む軸に平行なバウンディングボックスの重なりがオブジェクト同士の重なりを上回る場合、NMSが実際の検出を抑制し、検出数が少なくなることです。典型例は航空画像です。そのため、事前学習済みのyolo26n-obb.ptはimgsz=1024のDOTAで学習されています。

軸に平行なボックス方向付きボックス
45°の船に合わせる背景が約半分を占めるぴったりフィット
密集した回転オブジェクトの列ボックスが重なり、NMSが実際のオブジェクトを削除するボックスがきれいに分離する
角度を出力いいえはい。軸として出力します(180°を法とする)
結果フィールドresult.boxesresult.obb

xyxyxyxyラベル形式#

OBBラベルの行は、クラスインデックスに続けて、正規化された四隅を周囲に沿った順序で記述します。実際のDOTA8の行(クラス1はship):

1 0.813477 0.523437 0.814453 0.504882 0.87207 0.504882 0.87207 0.524414

4つの点として読み取ります。(0.813, 0.523) → (0.814, 0.505) → (0.872, 0.505) → (0.872, 0.524)。この船はほぼ水平で、画像に対する幅は約0.059、高さは約0.019です。内部では、Ultralyticsは最小面積の長方形を当てはめて、各隅の集合を中心、幅、高さ、回転角(xywhr)に変換します。そのため、アノテーションツールで作られた四角形がわずかに長方形でなくても学習できます。OBBデータセットガイドでは形式を説明しており、ultralytics.data.converterのconvert_dota_to_yolo_obbは元のDOTAアノテーションを変換します。

Ultralytics Platformのアノテーションエディター。DOTA8の航空画像上で、テニスコート、小型車、プールを回転ボックスで囲んでいます

DOTA8で学習する#

DOTA8は、パイプラインのテスト用に構築された小規模な回転ボックスデータセットです。

航空画像タイルのDOTA8学習モザイク。係留されたボート、テニスコート、駐車場、駐機場の飛行機が並び、それぞれの物体に回転ボックスとクラスインデックスが付いています

from ultralytics import YOLO

model = YOLO("yolo26n-obb.pt")
model.train(data="dota8.yaml", epochs=1, imgsz=320, device="cpu")
metrics = model.val()
print(f"OBB mAP50: {metrics.box.map50:.3f}  mAP50-95: {metrics.box.map:.3f}")
OBB mAP50: 0.995  mAP50-95: 0.832

OBBのメトリクスはmetrics.boxにありますが、その計算に使われるIoUは回転した矩形同士で算出されます。

DOTA8はスモークテスト用

DOTA8は事前学習データの一部であるため、スコアが高くなります。パイプラインが動作することは確認できますが、独自の画像については何も示しません。

予測を読み取り、角度を正規化する#

import math
from collections import Counter

from ultralytics import YOLO

def long_side_axis(w, h, r):
    """Angle of the box's long side in degrees, in [0, 180), whichever side the model called width."""
    deg = math.degrees(r) + (90 if w < h else 0)
    return deg % 180

model = YOLO("yolo26n-obb.pt")  # trained on DOTAv1, 15 aerial classes
result = model("https://ultralytics.com/images/boats.jpg", verbose=False)[0]
obb = result.obb.cpu()

print("xywhr:", tuple(obb.xywhr.shape), " xyxyxyxy:", tuple(obb.xyxyxyxy.shape))
print(Counter(result.names[int(c)] for c in obb.cls.tolist()))
for (cx, cy, w, h, r), conf in list(zip(obb.xywhr.tolist(), obb.conf.tolist()))[:5]:
    print(f"{conf:.2f} center=({cx:.0f},{cy:.0f}) size={w:.0f}x{h:.0f} raw={math.degrees(r):4.0f} deg  axis={long_side_axis(w, h, r):4.0f} deg")
xywhr: (173, 5)  xyxyxyxy: (173, 4, 2)
Counter({'ship': 168, 'harbor': 5})
0.87 center=(619,850) size=33x108 raw= -15 deg  axis=  75 deg
0.86 center=(1650,937) size=116x33 raw=  21 deg  axis=  21 deg
0.86 center=(1105,571) size=124x35 raw=  21 deg  axis=  21 deg
0.86 center=(1596,1037) size=129x35 raw=  25 deg  axis=  25 deg
0.86 center=(825,940) size=34x99 raw= -15 deg  axis=  75 deg

各行の説明:

  • xywhr: (173, 5) — 173件の検出結果があり、それぞれの中心x、中心y、幅、高さはピクセル単位で、回転角はラジアン単位です。xyxyxyxyには、同じボックスが4つの頂点として格納されており、cv2.polylinesにそのまま渡せます。
  • Counterはresult.obb.clsから数を集計します。船が168隻、港が5か所です。OBBモデルではresult.boxesがNoneにあたるため、検出用に書いたカウントコードではフィールドを切り替える必要があります。
  • rawとaxis。 1隻目の船は33 × 108、角度は−15°です。そのwは短辺なので、船体の向きは−15° + 90° = 75°です。2隻目は116 × 33、角度は21°で、すでに長辺が先になっています。生の角度を読むと、1隻目の軸は90°ずれてしまいます。正規化すれば、すべての船の角度が同じ意味を持ち、船同士やフレーム間で比較できます。

このルールは、インストールされたソースコードに基づいています。予測された回転角は[−π/4, 3π/4) — およそ−45°から135° — の範囲にあり、同じ矩形を(w, h, θ)または(h, w, θ + 90°)として表せます。wが長辺だと決めつけないでください。

小さな物体には縮小ではなくタイルを使う#

航空画像の物体は小さいため、学習時のimgszより小さなサイズで検証またはデプロイすると、再現率が急速に低下します。モデルの入力サイズより大きいsatelliteタイルは縮小せず、重なりを持たせて分割してください。そうしないと、30ピクセルの車が5ピクセルのぼやけた塊になってしまいます。SAHIのタイル推論ガイドで手法を説明しています。検出モデルを使って高速道路のフレーム全体を推論すると、遠くにある多くの車両を見逃します。

画像全体を推論した高速道路のフレーム。小さく遠くにある車両の多くを見逃しています

タイル推論では、これらの車両を検出できます。

SAHIのタイル推論を適用した同じ高速道路のフレーム。小さく遠くにある車両をより多く検出しています

よくある落とし穴#

前後の方向は分からない

症状: 「heading」がフレーム間で180°反転する、または船が後ろ向きに進んでいるように見えます。原因: 180°回転した長方形は同じ長方形なので、OBBが示すのは軸であり、方向ではありません。対処: 船やフォークリフトの向きを知る必要がある場合は、船首またはフォーク先端にposeキーポイントを追加するか、切り抜き画像に対する分類器を追加してください。

生の検証曲線と検出実行の比較

症状: 類似したデータで検出を実行した場合と、OBBの適合率-再現率曲線が異なって見えます。 原因: OBBの検証ではデフォルトでconf=0.01が使用されますが、他のタスクでは0.001が使用されます。 対処: mAP値を比較するか、一致する適合率-再現率曲線が必要な場合は、confを明示的に設定します。

試してみる

obb.pyを実行し、港の画像にある各船の長辺の軸を計算して、最も多い軸を10°単位に丸め、その軸から30°を超えてずれている船の数を数えるスクリプトを書いてください。175°と5°の差はわずか10°であることを忘れないでください。

完了条件
以下のすべてを満たすと、レッスンは完了です。
  • スクリプトは船の数、最も多い軸、その軸から30°を超えてずれている船の数を出力します。

  • 軸の比較では180°で折り返すため、175°と5°の差は10°として数えられます。

  • OBBでは船首と船尾を区別できない理由と、船首の向きが必要な場合に何を追加するかを説明できます。

解答を表示
import math
from collections import Counter

from ultralytics import YOLO

result = YOLO("yolo26n-obb.pt")("https://ultralytics.com/images/boats.jpg", verbose=False)[0]
axes = []
for (cx, cy, w, h, r), cls in zip(result.obb.xywhr.cpu().tolist(), result.obb.cls.tolist()):
    if result.names[int(cls)] == "ship":
        axes.append((math.degrees(r) + (90 if w < h else 0)) % 180)  # long-side axis in [0, 180)

def axis_gap(a, b):
    """Smallest difference between two axes; 175 and 5 degrees are only 10 apart."""
    d = abs(a - b) % 180
    return min(d, 180 - d)

common = Counter(round(a / 10) * 10 % 180 for a in axes).most_common(1)[0][0]
off = [a for a in axes if axis_gap(a, common) > 30]
print(f"{len(axes)} ships, most common axis ~{common} deg, {len(off)} more than 30 deg off it")
168 ships, most common axis ~20 deg, 32 more than 30 deg off it

ほとんどのボートは約20°で平行に並んでいます。軸から外れた32隻は、港の監視ルールで確認対象としてフラグ付けされる外れ値です。矩形は180°回転しても対称なため、OBBでは船首と船尾を区別できません。船首にポーズキーポイントを設定すれば、正確な向きを取得できます。

次のステップ

次は位置特定を完全に省き、画像全体とクロップを対象とする分類器を学習します。