YOLO26 のセマンティックセグメンテーションを PyTorch と ONNX で比較する
こんにちは、皆さん。
画像に車が写っているかだけでなく、道路や歩道がどこからどこまで続いているかを、画素単位で知りたいことがあります。
さて、今日は YOLO26n のセマンティックセグメンテーションモデルを Apple Silicon の CPU で動かし、PyTorch と ONNX Runtime の結果と速度を比較しました。
先に結果を書くと、両方の class map は 99.3129% の画素で一致しました。ONNX Runtime の全処理時間は平均27.55 ms、PyTorch は266.00 msでした。差の多くは推論本体ではなく、後処理の違いから生じています。
今回検証する内容
セマンティックセグメンテーションは、画像の各画素をクラスへ分ける処理です。物体を四角で囲む object detection と違い、道路、空、車などの形に沿った領域を得られます。
今回は Cityscapes で学習された公式 yolo26n-sem.pt を使い、次を確認します。
- Apple M1 Max の CPU で PyTorch 推論と ONNX export ができるか
- 同じ入力条件で、2つの class map がどの程度一致するか
- 前処理、推論、後処理、全処理にかかる時間
- 都市道路の生成画像を19クラスへどのように分けるか
検証に使用した元画像です。
対象の lab: kiarina/labs/2026/07/14/yolo26-semantic-segmentation
検証環境の再現
mise と uv、初回のモデル・共有画像取得にインターネット接続が必要です。
git clone --depth 1 --filter=blob:none --sparse \
https://github.com/kiarina/labs.git
cd labs
git sparse-checkout set .mise/tasks 2026/07/14/yolo26-semantic-segmentation
mise trust . && mise trust 2026/07/14/yolo26-semantic-segmentation
mise -C 2026/07/14/yolo26-semantic-segmentation run
初回は checkpoint を取得して SHA-256 を検証し、ONNX opset 18 へ変換します。実行後は PyTorch 版、ONNX 版、差分、説明付き比較の4画像が生成されます。
使用したモデルとライセンス
使用した学習済みモデルは1つです。PyTorch と ONNX は別のモデルを比較しているのではなく、同じ checkpoint を2つの実行経路で動かしています。
| モデル | 役割 | 入出力 |
|---|---|---|
yolo26n-sem.pt |
各画素を Cityscapes の19クラスへ分類する | 1x3x640x640画像 → class logits |
export した yolo26n-sem.onnx
|
同じ重みを ONNX Runtime で実行する | 1x3x640x640画像 → 1x640x640 class ID |
class logits は、各画素が各クラスらしい度合いを表す生の値です。class ID は、その中から選ばれたクラスの番号です。
データの流れを短く表すと次のようになります。
1774x887 JPEG
-> 縦横比を保って640x640へ配置(letterbox)
-> 同じcheckpointを2経路で実行
PyTorch: class logits -> 拡大 -> class ID
ONNX: 640x640 class ID -> 拡大
-> 元の1774x887 class map
-> 画素一致率、クラス別IoU、処理時間を比較
letterbox は、画像を変形せず余白を足して指定サイズへ収める方法です。IoU は2領域の重なり具合で、100%に近いほど一致しています。
- checkpoint: Ultralytics assets v8.4.0
- model size: 3,487,283 bytes
- SHA-256:
f3f293cca764de1f93044030d8d5612de9c5ffbf37c9c8ea1b69418b73038999 - Ultralytics のコードと学習済みモデル: AGPL-3.0 または Ultralytics Enterprise License
Ultralytics のライセンス説明では、学習済みモデルも標準では AGPL-3.0 の対象とされています。非公開・商用製品などへ組み込む場合は Enterprise License が必要になる場合があります。利用時点の公式条件を確認してください。本 lab は checkpoint や生成した ONNX を Git 管理せず、実行時に取得・生成します。
検証方法
入力は、車道、歩道、建物、信号、車、人、自転車などが写る固定画像1枚です。
file: tests/assets/jpg/street_scene_1774x887_287kb.jpg
resolution: 1774x887
SHA-256: d5c865f452599311fbbfd0c132bb4f8b7ade4dd88f0c8ac14ce136490ea53a2e
PyTorch と ONNX の両方を imgsz=640、rect=False、CPU に固定しました。3回 warm-up した後、10回測定しています。warm-up は、初回だけ発生する準備処理の影響を減らすための事前実行です。モデル初期化、画像読み込み、画像保存は benchmark に含めません。
このモデルが出力できるのは次の19クラスです。
road, sidewalk, building, wall, fence, pole, traffic light, traffic sign,
vegetation, terrain, sky, person, rider, car, truck, bus, train,
motorcycle, bicycle
検証結果
左が元画像、右が ONNX の class map を50%で重ねた結果です。下には実際に予測されたクラスと画素割合を載せています。
Apple M1 Max の CPU で、PyTorch と ONNX Runtime の両方を実行できました。
| backend / stage | mean | min | max | std dev |
|---|---|---|---|---|
| PyTorch preprocessing | 0.93 ms | 0.89 ms | 1.04 ms | 0.04 ms |
| PyTorch inference | 28.83 ms | 26.39 ms | 30.31 ms | 1.02 ms |
| PyTorch postprocessing | 236.07 ms | 233.19 ms | 239.70 ms | 1.67 ms |
| PyTorch wall time | 266.00 ms | 264.44 ms | 270.02 ms | 1.69 ms |
| ONNX preprocessing | 1.01 ms | 0.91 ms | 1.24 ms | 0.10 ms |
| ONNX inference | 25.67 ms | 24.32 ms | 26.91 ms | 0.88 ms |
| ONNX postprocessing | 0.72 ms | 0.60 ms | 1.11 ms | 0.15 ms |
| ONNX wall time | 27.55 ms | 26.06 ms | 28.82 ms | 0.95 ms |
ONNX の推論本体は PyTorch の約0.89倍、全処理時間は約0.10倍でした。速度差の中心は後処理です。PyTorch は logits を元画像サイズへ拡大してから class ID を選びます。今回 export した ONNX は class ID を直接出力するため、後処理が短くなりました。
記事作成時に再実行すると、PyTorch の全処理は平均290.79 ms、ONNX は29.76 msでした。速度は実行ごとに変動しますが、約10倍の差と画素一致率99.3129%は再現しました。
画素とクラス別の一致
全画素の 99.3129% が同じ class ID、0.6871% が異なる class ID でした。予測された16クラスのデータは次のとおりです。
| class | PyTorch area | ONNX area | IoU |
|---|---|---|---|
| road | 21.27% | 21.22% | 99.52% |
| sidewalk | 10.38% | 10.39% | 98.48% |
| building | 16.28% | 16.30% | 98.99% |
| wall | 3.05% | 3.05% | 97.16% |
| fence | 4.18% | 4.17% | 98.32% |
| pole | 1.62% | 1.61% | 88.15% |
| traffic light | 0.03% | 0.04% | 88.03% |
| traffic sign | 0.29% | 0.29% | 93.30% |
| vegetation | 26.52% | 26.54% | 98.85% |
| terrain | 0.02% | 0.02% | 82.92% |
| sky | 8.94% | 8.97% | 99.15% |
| person | 0.90% | 0.90% | 96.20% |
| rider | 0.21% | 0.21% | 94.61% |
| car | 5.11% | 5.12% | 98.67% |
| motorcycle | 0.74% | 0.74% | 95.68% |
| bicycle | 0.44% | 0.44% | 94.24% |
赤い部分が2経路で異なった画素です。大きな領域の内部より、物体の境界に集中しています。
最初に失敗した比較
最初は rect を固定せず、画素一致率が98.9648%でした。PyTorch は最小限の padding、固定 shape の ONNX は640x640の paddingを使い、同じ imgsz=640 でも入力条件が揃っていませんでした。
両方を rect=False にすると99.3129%へ改善しました。backend を比べるときは、モデルだけでなく前処理も揃える必要があります。
検証環境は次のとおりです。
machine: MacBook Pro (Apple M1 Max, arm64)
OS: macOS 26.5.2
Python: 3.12.10
Ultralytics: 8.4.95
PyTorch: 2.13.0
ONNX: 1.22.0
ONNX Runtime: 1.27.0
OpenCV: 5.0.0
NumPy: 2.5.1
provider: CPUExecutionProvider
結果の考察
専門的な数値は上に載せたとおりですが、簡単に読むと次の3点です。
- 大きな領域はよく一致した
道路、建物、植生、空は IoU 98%以上でした。全体の見た目もほぼ同じです。差は細い pole、traffic light、terrain や、各領域の境界で相対的に大きくなりました。
- ONNX が速い主因は出力形式だった
推論本体の差は約3 msですが、後処理は約235 ms違いました。今回は ONNX が class ID map を直接返すためです。「ONNX なら常に10倍速い」という結果ではなく、この export と後処理を含む条件での結果です。
- 都市道路は分けられたが、遠いバスは取れなかった
道路、歩道、建物、空、人、車、自転車などは妥当な位置へ割り当てられました。一方、画像奥の小さなバスは bus になりませんでした。遠くて小さい物体には限界があります。
今回の入力は正解 mask のない生成画像1枚です。そのため、99.3129%は PyTorch と ONNX の一致率であり、予測の正解率ではありません。Cityscapes validation、実写、別画像、異なる解像度、MPS、CoreML、量子化、メモリ使用量は未検証です。
検証後の感想
ONNX へ変換しても見た目をほぼ保ちつつ、CPU で1枚約30 msまで短くなったのは良い結果でした。道路や歩道の領域をローカルで連続処理する用途には使いやすそうです。
一方で、速さの大部分が後処理の違いから来ている点は重要でした。backend の名前だけで性能を判断せず、前処理と出力形式まで確認する必要があります。次は実写の道路動画で、フレーム間の安定性と CoreML の速度も比べてみたいです。


