Ultralytics YOLO27: ウェイトリストに登録する。
Get Started
本番環境でのUltralytics YOLO·ランタイムを選ぶ·レッスン 2/9
レッスン中級

ONNXにエクスポート(および同等性を検証)

移植可能なONNXモデルをエクスポートし、出荷前にボックス、信頼度、mAPをPyTorchと比較して一致することを確認します。

エクスポート後に信頼度が0.1ずれても、クラッシュするわけではありません。デプロイ先のすべてのデバイスで検出結果がconfのしきい値をひそかにまたぎ、カウントが間違って初めて気づくことになります。ONNXはデプロイ向けの移植可能な出発点です。このコースでは、5分間の同等性チェックが最も安価な保険になります。

成果

ONNXにエクスポートし、数値を使ってボックス、信頼度、mAPがPyTorchと一致することを検証し、差異があれば説明してください。

短期集中コース
すでに使い方をご存じの方は、こちらをご覧ください。
  1. yolo export model=yolo26n.pt format=onnx dynamic=True.

  2. 1枚の画像を両方のファイルに入力し、件数だけでなく、ボックス数、クラス、信頼度、座標を比較してください。

  3. 両方に同じbatchを指定してyolo valを実行してください。mAP50-95の差は0.5ポイント以内である必要があります。

  4. 差異がありますか?入力形状(固定か動的か)、独自の前処理、エクスポートしたヘッド(nms)を確認してください。

実践的

エクスポート#

from ultralytics import YOLO

model = YOLO("yolo26n.pt")  # or runs/detect/pills_v1/weights/best.pt
model.export(format="onnx", dynamic=True)  # 重みと同じフォルダーにyolo26n.onnxを書き出します

ログには、選択されたopset(onnx 1.23でのopset 18)と、onnxslimによってグラフが簡略化されたことが表示されています。エクスポートモードで変更できる動作に関わるオプションは次のとおりです。

オプションデフォルト変更する場合
imgsz640本番環境で別のサイズを使用する場合、静的エクスポートではサイズが固定されます
dynamicFalse推論時にバッチサイズまたは画像形状が変わる場合
batch1推論時にバッチ処理を行う場合(動的エクスポートの最大バッチサイズ)
opsetサポートされている最新バージョン古いランタイムでモデルが拒否される場合は、値を下げます
quantizeNone(FP32)FP16には16、INT8にはキャリブレーション用のdataを指定して8を使用します
nmsNoneTrueはグラフにNMSを組み込みます。FalseはYOLO26のNMSフリーヘッドを選択します

デフォルトのnms=Noneでは、エクスポート結果として生の予測が返され、PyTorchと同様にUltralyticsラッパーがNMSを実行します。

既知の画像で同等性を確認する#

同じ画像を両方のモデルに入力し、すべてを比較してください。

from ultralytics import YOLO

pt = YOLO("yolo26n.pt")
onnx = YOLO(pt.export(format="onnx", dynamic=True))

img = "https://ultralytics.com/images/bus.jpg"
a = pt(img, verbose=False)[0].boxes
b = onnx(img, verbose=False)[0].boxes

ia, ib = a.conf.argsort(descending=True), b.conf.argsort(descending=True)
print(f"boxes:         PyTorch {len(a)}  ONNX {len(b)}")
print(f"same classes:  {a.cls[ia].tolist() == b.cls[ib].tolist()}")
print(f"max conf diff: {(a.conf[ia] - b.conf[ib]).abs().max():.4f}")
print(f"max xyxy diff: {(a.xyxy[ia] - b.xyxy[ib]).abs().max():.2f} px")

両方の検出結果を信頼度順に並べると同じ物体が対応するため、最後の2行で各検出結果と対応する結果を比較できます。次に、表示どおりに実行する場合と、dynamic=Trueを削除する場合の2回実行してください。

エクスポートボックスクラスが一致信頼度の最大差ボックスの最大差
dynamic=True5対5はい0.00000.00 px
静的(デフォルト)5対5はい0.097710.11 px

静的エクスポートに問題があるわけではありません。PyTorchでは、縦長のbus.jpgはストライドに合わせた最小サイズの640×480(高さ×幅)にレターボックス化されます。一方、静的な640×640のグラフでは、灰色の帯で正方形になるようパディングする必要があります。パディングが異なると、予測にわずかな差が生じます。同じ条件で比較するには、dynamic=Trueを指定してエクスポートするか、PyTorch側にrect=Falseを指定して静的エクスポートと同様に正方形全体にパディングしてください(固定形状と最小矩形の比較を参照)。rect=Falseを指定すると、静的エクスポートも0.0000の差で一致します。

両方のファイルを検証する#

1枚の画像で大きなエラーを検出できますが、細かな差異を見つけるには検証が必要です。前処理を一致させるため、両方に同じbatchを使用してください。

yolo val model=yolo26n.pt data=coco8.yaml batch=1
yolo val model=yolo26n.onnx data=coco8.yaml batch=1

ここでは両方ともmAP50-95 0.654を報告しています。COCO8で確認できるのはパイプラインであり、モデルではありません。独自の検証セットで0.5ポイントを超える差があれば調査してください。チェック全体は次のとおりです。

ONNX Runtimeを直接呼び出す#

本番サービスでは、Ultralyticsラッパーを使わず、推論エンジンを直接呼び出すことがよくあります。その場合、前処理は自分で行い、Ultralyticsと完全に一致させる必要があります。nms=Falseを指定すると、YOLO26のエクスポート結果は最終検出結果を返すため、NMSのコードは不要です。

import cv2
import numpy as np
import onnxruntime as ort

from ultralytics import YOLO
from ultralytics.utils.downloads import safe_download

path = YOLO("yolo26n.pt").export(format="onnx", nms=False)  # NMS-free head: output (1, 300, 6)
session = ort.InferenceSession(path, providers=["CPUExecutionProvider"])  # put CUDAExecutionProvider first on GPU
name = session.get_inputs()[0].name

# Preprocess like Ultralytics: letterbox to 640x640 with gray (114) padding, BGR->RGB, 0-1, NCHW
im = cv2.imread(safe_download("https://ultralytics.com/images/bus.jpg"))
h, w = im.shape[:2]
r = 640 / max(h, w)
nh, nw = round(h * r), round(w * r)
top, left = (640 - nh) // 2, (640 - nw) // 2
canvas = np.full((640, 640, 3), 114, np.uint8)
canvas[top : top + nh, left : left + nw] = cv2.resize(im, (nw, nh), interpolation=cv2.INTER_LINEAR)
x = canvas[..., ::-1].transpose(2, 0, 1)[None].astype(np.float32) / 255

(out,) = session.run(None, {name: x})
det = out[0][out[0][:, 4] > 0.25]  # rows: x1, y1, x2, y2, score, class
det[:, [0, 2]] = ((det[:, [0, 2]] - left) / r).clip(0, w)  # undo padding and scale
det[:, [1, 3]] = ((det[:, [1, 3]] - top) / r).clip(0, h)
for x1, y1, x2, y2, score, cls in det:
    print(f"class {int(cls):2d}  score {score:.2f}  box [{x1:.0f}, {y1:.0f}, {x2:.0f}, {y2:.0f}]")
class  5  score 0.93  box [6, 229, 807, 749]
class  0  score 0.92  box [47, 399, 237, 902]
class  0  score 0.90  box [223, 405, 345, 863]
class  0  score 0.86  box [669, 391, 810, 880]
class  0  score 0.52  box [0, 554, 65, 874]

バス1台(クラス5)と人4人(クラス0)が検出され、YOLO(path)(img)とピクセル単位で一致しています。バスの信頼度はここでは0.93ですが、デフォルトのエクスポートでは0.88です。これはnms=Falseによって別のヘッドが選択されるためです。同じnmsの値でエクスポートしたファイル同士を比較してください。

よくある落とし穴#

静的エクスポートと長方形PyTorch推論の比較

症状: ボックスは同じですが、確信度に最大0.1の差があり、特に正方形でない画像で差が大きくなります。 対処: dynamic=Trueを使って、またはPyTorch側でrect=Falseを使って一致度を確認するか、検証mAPで静的エクスポートを評価します。

Ultralyticsと異なる手書きの前処理

症状: 独自のONNX Runtimeコードでは、YOLO("model.onnx")より検出ボックスが少なくなる、または位置がずれます。 対処: ort_raw.pyとまったく同じように、レターボックス処理を行い(引き伸ばさない)、114でパディングし、BGRからRGBに変換して255で割ります。その後、ボックスのパディングとスケールを元に戻します。

異なるヘッドの混在

症状: 形状が一致していても、すべての確信度が少しずつ異なります。 対処: nms=False(NMSなし)のエクスポートは、別のnms=Falseエクスポートとのみ比較します。nms=Trueを使用する場合は、グラフに組み込まれたconfとiouが、他の場所で使用するものと一致することも確認します。

試してみる

モデル(またはyolo26n.pt)をONNXに、静的形式とdynamic=Trueの2種類でエクスポートし、それぞれに対してparity.pyを実行してください。次に、同じbatchを使用して、.ptと動的な.onnxに対してyolo valを実行し、各エクスポートの信頼度の最大差とmAP50-95の差を記載した3行の同等性レポートを作成してください。

完了条件
以下のすべてを満たすと、レッスンは完了です。
  • .ptと並べて.onnxファイルを用意し、静的および動的エクスポートの両方について同等性の出力を確認できます。

  • 静的エクスポートで信頼度に差が出る理由を説明し、その差を解消するdynamic=Trueまたはrect=Falseの修正方法を示せます。

  • ONNXファイルの検証mAP50-95がPyTorchモデルから0.5ポイント以内であるか、その差が説明されています。

解答を表示
from ultralytics import YOLO

if __name__ == "__main__":  # validation starts worker processes on macOS and Windows
    weights = "yolo26n.pt"  # or runs/detect/pills_v1/weights/best.pt
    onnx_path = YOLO(weights).export(format="onnx", dynamic=True)

    maps = {}
    for w in (weights, onnx_path):
        maps[w] = YOLO(w).val(data="coco8.yaml", batch=1, plots=False).box.map  # or my_dataset/data.yaml
        print(f"{w}: mAP50-95 = {maps[w]:.4f}")
    print(f"gap = {abs(maps[weights] - maps[onnx_path]) * 100:.2f} points")
yolo26n.pt: mAP50-95 = 0.6540
yolo26n.onnx: mAP50-95 = 0.6540
gap = 0.00 points

yolo26nのbus.jpgに関するパリティレポートでは、静的エクスポートの最大信頼度差は0.0977(レターボックス形状。rect=Falseで解消)、動的エクスポートは0.0000、mAP50-95の差は0.00ポイントと記されています。動的エクスポートをリリースしてください。

次のステップ

NVIDIAハードウェアでは、次のステップはTensorRTです。FP16とINT8は、わずかな精度低下と引き換えに大幅な高速化を実現します。