Ultralytics YOLO27: انضم إلى قائمة الانتظار.
Get Started
Ultralytics YOLO في بيئة الإنتاج·اختر بيئة تشغيل·الدرس 2/9
الدرسمتوسط

التصدير إلى ONNX (والتحقق من التطابق)

صدّر نموذج ONNX قابلًا للنقل، ثم أثبت تطابقه مع PyTorch في الصناديق الوصفية ودرجات الثقة وmAP قبل شحن أي شيء.

التصدير الذي يغيّر درجات الثقة بمقدار 0.1 لا يتسبب في انهيار. بل يغيّر بهدوء نتائج الكشف التي تتجاوز عتبة conf على كل جهاز تشحن إليه النموذج، ولا يلاحظ أحد ذلك إلا بعد أن تصبح الأعداد خاطئة. يُعدّ ONNX نقطة بداية قابلة للنقل للنشر، والتحقق من التطابق خلال خمس دقائق هو أقل وسائل الحماية تكلفةً في هذه الدورة.

النتيجة

صدّر إلى ONNX، وأثبت بالأرقام تطابقه مع PyTorch في الصناديق الوصفية ودرجات الثقة وmAP، واشرح أي تفاوت تجده.

المسار السريع
إذا كنت تعرف الأساسيات بالفعل، فإليك النسخة المختصرة.
  1. yolo export model=yolo26n.pt format=onnx dynamic=True.

  2. مرّر صورة واحدة عبر الملفين؛ وقارن عدد الصناديق الوصفية والفئات ودرجات الثقة والإحداثيات، لا العدد وحده.

  3. شغّل yolo val على كليهما باستخدام batch نفسه؛ وينبغي ألا يتجاوز 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 الذي اختاره (opset 18 مع onnx 1.23) وأن onnxslim بسّط الرسم البياني. الخيارات التي تغيّر السلوك، من وضع التصدير:

الخيارافتراضيمتى تغيّره
imgsz640يعمل الإنتاج بحجم آخر؛ ويُثبَّت التصدير الثابت على ذلك الحجم
dynamicFalseيختلف حجم الدفعة أو شكل الصورة أثناء الاستدلال
batch1تستخدم تجميع الدفعات أثناء الاستدلال (أقصى حجم دفعة للتصدير الديناميكي)
opsetأحدث إصدار مدعوميرفض إصدار أقدم من بيئة التشغيل النموذج؛ خفّض الإصدار
quantizeNone (FP32)16 لـ FP16؛ و8 لـ INT8 مع data للمعايرة
nmsNoneيضمّن True NMS في الرسم البياني؛ ويختار False الرأس الخالي من NMS في YOLO26

باستخدام nms=None الافتراضي، يعيد التصدير التنبؤات الخام، ويتولى غلاف Ultralytics تنفيذ NMS، تمامًا كما في PyTorch.

تحقّق من التطابق على صورة معروفة#

مرّر الصورة نفسها عبر النموذجين وقارن كل شيء:

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")

ترتيب المجموعتين حسب درجة الثقة يطابق الكائنات نفسها، لذا تقارن السطور الأخيرة كل كشف بنظيره. شغّل البرنامج الآن مرتين: مرة كما هو موضح، ومرة بعد حذف dynamic=True:

التصديرالصناديق الوصفيةالفئات متطابقةأقصى فرق في الثقةأقصى فرق في الصندوق الوصفي
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.

تحقّق من كلا الملفين#

تكشف صورة واحدة الأخطاء الكبيرة؛ أما التحقق فيكشف الأخطاء الدقيقة. استخدم 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]

حافلة واحدة (الفئة 5) وأربعة أشخاص (الفئة 0)، مع تطابق YOLO(path)(img) حتى مستوى البكسل. تبلغ درجة الحافلة 0.93 هنا و0.88 مع التصدير الافتراضي، لأن nms=False يختار رأسًا مختلفًا؛ قارن الملفات التي صُدّرت باستخدام قيمة nms نفسها.

مواطن الخلل#

مقارنة تصدير ثابت باستدلال PyTorch مستطيل الأبعاد

العَرَض: الصناديق متطابقة، لكن درجات الثقة تختلف بما يصل إلى 0.1، ويكون الاختلاف أكبر في الصور غير المربعة. الحل: تحقّق من التكافؤ باستخدام dynamic=True، أو باستخدام rect=False في جانب PyTorch، أو قيّم التصدير الثابت وفق mAP التحقق.

معالجة مسبقة مكتوبة يدويًا تختلف عن معالجة Ultralytics

العَرَض: يعثر كود ONNX Runtime الخاص بك على صناديق أقل أو مزاحة مقارنةً بـ YOLO("model.onnx"). الحل: استخدم letterbox (ولا تمدد الصورة)، وأضف حشوًا بقيمة 114، وحوّل BGR إلى RGB، واقسم على 255، ثم أزل الحشو وأعد تحجيم الصناديق، تمامًا كما يفعل ort_raw.py.

خلط الرؤوس

العَرَض: تختلف جميع درجات الثقة قليلًا، حتى مع تطابق الأشكال. الحل: قارن تصدير nms=False (من دون NMS) بتصدير آخر من nms=False فقط. وعند استخدام nms=True، تحقق أيضًا من تطابق conf وiou المضمّنين في الرسم البياني مع ما تستخدمه في مواضع أخرى.

جرّبه

صدّر نموذجك (أو yolo26n.pt) إلى ONNX مرتين، مرة بتصدير ثابت ومرة باستخدام dynamic=True، وشغّل parity.py على كل منهما. ثم شغّل yolo val على .pt و.onnx الديناميكي، باستخدام batch نفسه، واكتب تقرير تطابق من ثلاثة أسطر: أقصى فرق في الثقة لكل تصدير، وفارق mAP50-95.

متى يُعدّ مكتملًا
تكون قد أتممت الدرس عند تحقق جميع الشروط التالية.
  • لديك ملف .onnx بجوار .pt، ونتائج طباعة للتحقق من التطابق لكل من التصدير الثابت والديناميكي.

  • يمكنك تفسير فرق الثقة في التصدير الثابت، وإظهار أن استخدام dynamic=True أو rect=False يزيله.

  • يقع mAP50-95 للتحقق على ملف ONNX ضمن 0.5 نقطة من نموذج PyTorch، أو يجري تفسير الفارق.

عرض الحل
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 (شكل letterbox، أزاله rect=False)، والتصدير الديناميكي 0.0000، وفارق mAP50-95 يساوي 0.00 نقطة. اعتمد التصدير الديناميكي.

ما الخطوة التالية

على أجهزة NVIDIA، تتمثل الخطوة التالية في TensorRT، حيث تحقق FP16 وINT8 مقايضة بسيطة في الدقة مقابل زيادة كبيرة في السرعة.

سجّل الدخول لحفظ التقدم