ONNX'e Dışa Aktar (ve Eşdeğerliği Doğrula)
Taşınabilir bir ONNX modeli dışa aktar, ardından dağıtıma almadan önce kutular, güven değerleri ve mAP bakımından PyTorch ile eşleştiğini doğrula.
Güven değerlerini 0.1 kaydıran bir dışa aktarım hata vermez. Dağıttığın her cihazda tespitleri sessizce conf eşiğinin öte yanına taşır; sayılar yanlış çıkana kadar da kimse fark etmez. ONNX, dağıtım için taşınabilir bir başlangıç noktasıdır; bu kursta beş dakikalık eşdeğerlik kontrolü en düşük maliyetli güvencedir.
Uygulamalı
Dışa aktarma#
from ultralytics import YOLO
model = YOLO("yolo26n.pt") # veya runs/detect/pills_v1/weights/best.pt
model.export(format="onnx", dynamic=True) # ağırlıkların yanına yolo26n.onnx yazarGünlük, seçilen opset değerini (opset 18, onnx 1.23 ile) ve onnxslim'in grafiği basitleştirdiğini gösteriyor. Dışa Aktarma modu içindeki davranışı değiştiren seçenekler:
| Seçenek | Varsayılan | Ne zaman değiştirilmeli |
|---|---|---|
imgsz | 640 | Üretim farklı bir boyutta çalışıyor; sabit dışa aktarım bu boyuta kilitlenir |
dynamic | False | Çıkarım sırasında toplu iş boyutu veya görsel şekli değişiyor |
batch | 1 | Çıkarım sırasında toplu işleme yapıyorsun (dinamik dışa aktarımda en büyük toplu iş) |
opset | desteklenen en güncel değer | Daha eski bir çalışma zamanı modeli reddediyor; değeri düşür |
quantize | None (FP32) | FP16 için 16; kalibrasyon data ile INT8 için 8 |
nms | None | True, grafiğe NMS ekler; False, YOLO26'nın NMS kullanmayan başlığını seçer |
Varsayılan nms=None ile dışa aktarım ham tahminleri döndürür ve Ultralytics sarmalayıcısı tıpkı PyTorch'ta olduğu gibi NMS çalıştırır.
Bilinen Bir Görüntüde Eşdeğerliği Kontrol Et#
Aynı görüntüyü iki modelden geçir ve her şeyi karşılaştır:
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")Her iki sonucu da güven değerine göre sıralamak aynı nesneleri eşleştirir; böylece son iki satır her tespiti eşleştiği tespitle karşılaştırır. Şimdi bunu biri gösterildiği gibi, diğeri dynamic=True kaldırılmış olarak iki kez çalıştır:
| Dışa aktarma | Kutular | Aynı sınıflar | Maks. güven farkı | Maks. kutu farkı |
|---|---|---|---|---|
dynamic=True | 5'e karşı 5 | evet | 0.0000 | 0.00 px |
| sabit (varsayılan) | 5'e karşı 5 | evet | 0.0977 | 10.11 px |
Sabit dışa aktarım bozuk değil. PyTorch, dikey bus.jpg görselini en küçük, adım boyutuna hizalanmış 640×480 (yükseklik × genişlik) şekline göre mektup kutusu yöntemiyle boyutlandırırken sabit 640×640 grafiğin görseli gri şeritlerle kareye tamamlaması gerekir. Farklı dolgu, tahminleri biraz değiştirir. Aynı koşullarda karşılaştırmak için ya dynamic=True ile dışa aktar ya da PyTorch tarafında rect=False kullanarak sabit dışa aktarım gibi tam kareye dolgu yap (bkz. sabit şekil ve minimum dikdörtgen); rect=False ile sabit dışa aktarım da 0.0000 farkla eşleşir.
Her İki Dosyayı da Doğrula#
Tek bir görsel büyük hataları yakalar; doğrulama ise ince farkları ortaya çıkarır. Ön işleme eşleşmesi için her ikisinde de aynı batch değerini kullan:
yolo val model=yolo26n.pt data=coco8.yaml batch=1
yolo val model=yolo26n.onnx data=coco8.yaml batch=1Her ikisi de burada mAP50-95 0.654 değerini bildiriyor. COCO8 modelden çok iş hattını doğrular; kendi doğrulama kümen üzerinde 0.5 puanı aşan farkları incele. Tüm kontrol:
ONNX Runtime'ı Doğrudan Çağır#
Üretim hizmetleri çoğu zaman Ultralytics sarmalayıcısını kullanmak yerine çıkarım motorunu doğrudan çağırır. Bu durumda ön işleme senin sorumluluğundadır ve Ultralytics ile tam olarak eşleşmelidir. nms=False ile YOLO26 dışa aktarımı son tespitleri döndürür; dolayısıyla NMS koduna gerek yoktur:
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]Bir otobüs (sınıf 5) ve dört kişi (sınıf 0); YOLO(path)(img) ile piksel düzeyinde eşleşiyor. Otobüsün güven değeri burada 0.93, varsayılan dışa aktarımla ise 0.88; çünkü nms=False farklı bir başlık seçiyor. Aynı nms değeriyle dışa aktarılan dosyaları karşılaştır.
Yaygın Hatalar#
Belirti: Kutular aynı, ancak güven değerleri 0.1'e kadar farklı; fark kare olmayan görüntülerde en büyük. Çözüm: Pariteyi dynamic=True ile veya PyTorch tarafında rect=False ile kontrol et ya da statik dışa aktarımı doğrulama mAP değerine göre değerlendir.
Belirti: Kendi ONNX Runtime kodun, YOLO("model.onnx") değerine kıyasla daha az veya yeri kaymış kutu buluyor. Çözüm: ort_raw.py ile birebir aynı şekilde letterbox uygula (görüntüyü esnetme), 114 ile kenar boşluğu ekle, BGR'yi RGB'ye dönüştür, 255'e böl ve kutulardaki kenar boşluğu ile ölçeklemeyi geri al.
Belirti: Şekiller eşleşse bile tüm güven değerleri biraz farklı. Çözüm: Yalnızca bir nms=False (NMS içermeyen) dışa aktarımını başka bir nms=False dışa aktarımıyla karşılaştır. nms=True kullanırken grafiğe gömülü conf ve iou değerlerinin başka yerlerde kullandıklarınla eşleştiğini de kontrol et.
Çözümü göster
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 pointsyolo26n için bus.jpg üzerinde hazırlanan eşdeğerlik raporunda şunlar yazıyor: statik dışa aktarmada en yüksek güven farkı 0,0977 (letterbox şekli, rect=False ile giderildi), dinamik dışa aktarmada 0,0000, mAP50-95 farkı 0,00 puan. Dinamik dışa aktarımı kullanıma al.