Ultralytics YOLO27: انضم إلى قائمة الانتظار.
Get Started

المعالجة المسبقة المعززة بوحدة GPU باستخدام NVIDIA DALI#

عند نشر نماذج Ultralytics YOLO في بيئة الإنتاج، غالباً ما تصبح المعالجة المسبقة عنق الزجاجة. في حين يمكن لـTensorRT تنفيذ استدلال النموذج في بضعة مللي ثوانٍ فقط، قد تستغرق المعالجة المسبقة المعتمدة على CPU (تغيير الحجم، وإضافة الحشو، والتطبيع) من 2 إلى 10 مللي ثوانٍ لكل صورة، خصوصاً عند الدقات العالية. وتعالج NVIDIA DALI (مكتبة تحميل البيانات) هذه المشكلة بنقل خط أنابيب المعالجة المسبقة بأكمله إلى GPU.

يرشدك هذا الدليل إلى بناء خطوط أنابيب DALI التي تكرر بدقة المعالجة المسبقة بأسلوب letterbox في Ultralytics YOLO، ودمجها مع model.predict()، ومعالجة تدفقات الفيديو، ونشرها من البداية إلى النهاية باستخدام Triton Inference Server.

لمن أُعدّ هذا الدليل؟

هذا الدليل موجّه إلى المهندسين الذين ينشرون نماذج YOLO في بيئات الإنتاج التي تشكّل فيها المعالجة المسبقة على CPU عنق زجاجة مثبتاً بالقياس — ويشمل ذلك عادةً عمليات النشر باستخدام TensorRT على وحدات GPU من NVIDIA، وخطوط أنابيب الفيديو عالية الإنتاجية، أو إعدادات Triton Inference Server. إذا كنت تجري استدلالاً اعتيادياً باستخدام model.predict() ولا تواجه عنق زجاجة في المعالجة المسبقة، فإن خط أنابيب CPU الافتراضي يعمل بكفاءة.

ملخص سريع
  • هل تبني خط أنابيب DALI؟ استخدم fn.resize(mode="not_larger") + fn.crop(out_of_bounds_policy="pad") + fn.crop_mirror_normalize لتكرار المعالجة المسبقة بأسلوب letterbox في YOLO على GPU.
  • هل تدمج DALI مع Ultralytics؟ مرّر مخرجات DALI بوصفها torch.Tensor إلى model.predict() — وتتجاوز Ultralytics المعالجة المسبقة للصور تلقائياً.
  • هل تنشر باستخدام Triton؟ استخدم واجهة DALI الخلفية مع مجموعة TensorRT لتنفيذ المعالجة المسبقة دون استخدام CPU.

لماذا تستخدم DALI للمعالجة المسبقة في YOLO؟#

في خط أنابيب استدلال YOLO المعتاد، تُنفّذ خطوات المعالجة المسبقة على CPU:

  1. فك الترميز للصورة (JPEG/PNG)
  2. تغيير الحجم مع الحفاظ على نسبة العرض إلى الارتفاع
  3. إضافة حشو للوصول إلى الحجم المستهدف (letterbox)
  4. تطبيع قيم البكسل من [0, 255] إلى [0, 1]
  5. تحويل التخطيط من HWC إلى CHW

باستخدام DALI، تُنفَّذ كل هذه العمليات على GPU، ما يزيل اختناق CPU. ويكون ذلك مفيدًا على نحو خاص عندما:

السيناريولماذا يُفيد DALI
استدلال سريع على GPUتجعل محركات TensorRT ذات زمن الاستدلال الأقل من مللي ثانية المعالجة المسبقة على CPU العاملَ الأكبر في التكلفة
مدخلات عالية الدقةتتطلب تدفقات الفيديو بدقة 1080p و4K عمليات تغيير حجم مكلفة
أحجام دفعات كبيرةالاستدلال من جهة الخادم لمعالجة صور كثيرة بالتوازي
عدد محدود من أنوية CPUأجهزة الحافة مثل NVIDIA Jetson، أو خوادم GPU عالية الكثافة التي تضم عددًا قليلًا من أنوية CPU لكل GPU

المتطلبات الأساسية#

Linux فقط

يدعم NVIDIA DALI نظام Linux فقط. وهو غير متاح على Windows أو macOS.

ثبّت الحزم المطلوبة:

pip install ultralytics
pip install --extra-index-url https://pypi.nvidia.com nvidia-dali-cuda130

المتطلبات:

  • GPU من NVIDIA (إصدار قدرة حوسبية 5.0+ / Maxwell أو أحدث)
  • CUDA 11.0+ أو 12.0+ أو 13.0+
  • Python 3.10-3.14
  • نظام تشغيل Linux

فهم المعالجة المسبقة في YOLO#

قبل إنشاء مسار معالجة DALI، يجدر فهم ما تفعله Ultralytics تحديدًا أثناء المعالجة المسبقة. الفئة الأساسية هي LetterBox في ultralytics/data/augment.py:

from ultralytics.data.augment import LetterBox

letterbox = LetterBox(
    new_shape=(640, 640),  # ⁨حجم الهدف⁩
    center=True,  # ⁨توسيط الصورة (إضافة حشو متساوٍ على الجانبين)⁩
    stride=32,  # ⁨محاذاة الخطوة⁩
    padding_value=114,  # ⁨حشو رمادي (114, 114, 114)⁩
)

ينفّذ مسار المعالجة المسبقة الكامل في ultralytics/engine/predictor.py الخطوات التالية:

الخطوةالعمليةدالة CPUالمكافئ في DALI
1تغيير الحجم بأسلوب letterboxcv2.resizefn.resize(mode="not_larger")
2حشو مركزيcv2.copyMakeBorderfn.crop(out_of_bounds_policy="pad")
3BGR → RGBim[..., ::-1]fn.decoders.image(output_type=types.RGB)
4HWC → CHW + تطبيع /255np.transpose + tensor / 255fn.crop_mirror_normalize(std=[255,255,255])

تحافظ عملية letterbox على نسبة العرض إلى الارتفاع من خلال:

  1. حساب مقياس التحجيم: r = min(target_h / h, target_w / w)
  2. تغيير الحجم إلى (round(w * r), round(h * r))
  3. ملء المساحة المتبقية باللون الرمادي (114) للوصول إلى الحجم المستهدف
  4. توسيط الصورة بحيث يتوزع الحشو بالتساوي على الجانبين

مسار DALI لـ YOLO#

يُحاكي مسار DALI الموصى به السلوك الافتراضي لـ LetterBox(center=True) في Ultralytics، وهو السلوك المستخدم في استدلال YOLO القياسي.

يحاكي هذا الإصدار بدقة المعالجة المسبقة الافتراضية في Ultralytics مع الحشو المركزي، بما يطابق LetterBox(center=True):

مسار DALI مع حشو مركزي (موصى به)
from nvidia import dali
from nvidia.dali import fn, types

@dali.pipeline_def(batch_size=8, num_threads=4, device_id=0)
def yolo_dali_pipeline_centered(image_dir, target_size=640):
    """DALI pipeline replicating YOLO preprocessing with centered padding.

    Matches Ultralytics LetterBox(center=True) behavior exactly.
    """
    # ⁨قراءة الصور وفك ترميزها على GPU⁩
    jpegs, _ = fn.readers.file(file_root=image_dir, random_shuffle=False, name="Reader")
    images = fn.decoders.image(jpegs, device="mixed", output_type=types.RGB)

    # ⁨تغيير الحجم مع الحفاظ على نسبة العرض إلى الارتفاع⁩
    resized = fn.resize(
        images,
        resize_x=target_size,
        resize_y=target_size,
        mode="not_larger",
        interp_type=types.INTERP_LINEAR,
        antialias=False,  # ⁨مطابقة cv2.INTER_LINEAR (دون تنعيم الحواف)⁩
    )

    # ⁨حشو مركزي باستخدام fn.crop مع out_of_bounds_policy⁩
    # ⁨عندما يكون حجم القص أكبر من حجم الصورة، توسيط fn.crop الصورة وإضافة حشو متماثل⁩
    padded = fn.crop(
        resized,
        crop=(target_size, target_size),
        out_of_bounds_policy="pad",
        fill_values=114,  # ⁨قيمة حشو YOLO⁩
    )

    # ⁨تطبيع البيانات وتحويل التخطيط⁩
    output = fn.crop_mirror_normalize(
        padded,
        dtype=types.FLOAT,
        output_layout="CHW",
        mean=[0.0, 0.0, 0.0],
        std=[255.0, 255.0, 255.0],
    )
    return output
متى تكون `fn.pad` كافية؟

إذا لم تكن بحاجة إلى تطابق تام مع LetterBox(center=True)، يمكنك تبسيط خطوة الحشو باستخدام fn.pad(...) بدلًا من fn.crop(..., out_of_bounds_policy="pad"). يضيف هذا الخيار الحشو إلى الحافتين اليمنى والسفلية فقط، وقد يكون ذلك مقبولًا في مسارات النشر المخصصة، لكنه لن يطابق تمامًا سلوك letterbox المركزي الافتراضي في Ultralytics.

لماذا نستخدم `fn.crop` للحشو المركزي؟

يضيف عامل fn.pad في DALI الحشو إلى الحافتين اليمنى والسفلية فقط. للحصول على حشو مركزي (يطابق LetterBox(center=True) في Ultralytics)، استخدم fn.crop مع out_of_bounds_policy="pad". ومع crop_pos_x=0.5 وcrop_pos_y=0.5 الافتراضيين، تُوسَّط الصورة تلقائيًا مع حشو متماثل.

اختلاف التنعيم

يفعّل fn.resize في DALI تنعيم الحواف افتراضيًا (antialias=True)، بينما لا يطبّق cv2.resize في OpenCV مع INTER_LINEAR تنعيم الحواف. اضبط دائمًا antialias=False في DALI لمطابقة مسار CPU. يؤدي إغفال ذلك إلى فروق عددية طفيفة قد تؤثر في دقة النموذج.

تشغيل المسار#

إنشاء مسار DALI وتشغيله
# Build and run the pipeline
pipe = yolo_dali_pipeline_centered(image_dir="/path/to/images", target_size=640)
pipe.build()

# Get a batch of preprocessed images
(output,) = pipe.run()

# Convert to numpy or PyTorch tensors
batch_np = output.as_cpu().as_array()  # Shape: (batch_size, 3, 640, 640)
print(f"Output shape: {batch_np.shape}, dtype: {batch_np.dtype}")
print(f"Value range: [{batch_np.min():.4f}, {batch_np.max():.4f}]")

استخدام DALI مع Predict في Ultralytics#

يمكنك تمرير موتر PyTorch خضع للمعالجة المسبقة مباشرةً إلى model.predict(). عند تمرير torch.Tensor، تتجاوز Ultralytics المعالجة المسبقة للصورة (letterbox وBGR→RGB وHWC→CHW والتطبيع /255)، ولا تنفّذ سوى نقل الجهاز وتحويل نوع البيانات قبل إرسالها إلى النموذج.

بما أن Ultralytics لا تملك أبعاد الصورة الأصلية في هذه الحالة، تُعاد إحداثيات مربعات الكشف ضمن مساحة letterbox ذات الأبعاد 640×640. لإعادتها إلى إحداثيات الصورة الأصلية، استخدم scale_boxes، الذي يتعامل مع منطق التقريب الدقيق المستخدم في LetterBox:

from ultralytics.utils.ops import scale_boxes

# ⁨مربعات: موتر بالشكل (N, 4) بتنسيق xyxy، وإحداثياتها ضمن مساحة letterbox ‏640x640⁩
# ⁨تحجيم المربعات من مساحة letterbox ‏(640, 640) إلى أبعاد الصورة الأصلية (orig_h, orig_w)⁩
boxes = scale_boxes((640, 640), boxes, (orig_h, orig_w))

ينطبق ذلك على جميع مسارات المعالجة المسبقة الخارجية — إدخال الموتر مباشرةً، وتدفقات الفيديو، والنشر باستخدام Triton.

DALI + تنبؤ Ultralytics
from nvidia.dali.plugin.pytorch import DALIGenericIterator

from ultralytics import YOLO

# Load model
model = YOLO("yolo26n.pt")

# Create DALI iterator
pipe = yolo_dali_pipeline_centered(image_dir="/path/to/images", target_size=640)
pipe.build()
dali_iter = DALIGenericIterator(pipe, ["images"], reader_name="Reader")

# Run inference with DALI-preprocessed tensors
for batch in dali_iter:
    images = batch[0]["images"]  # Already on GPU, shape (B, 3, 640, 640)
    results = model.predict(images, verbose=False)
    for result in results:
        print(f"Detected {len(result.boxes)} objects")
دون أي عبء للمعالجة المسبقة

عند تمرير torch.Tensor إلى model.predict()، تستغرق خطوة المعالجة المسبقة للصورة ~0.004ms (أي لا تكاد تستغرق وقتًا)، مقارنةً بنحو ~1-10ms عند إجراء المعالجة المسبقة على CPU. يجب أن يكون الموتر بتنسيق BCHW، ومن النوع float32 (أو float16)، ومطبّعًا إلى [0, 1]. وستتولى Ultralytics تلقائيًا نقل الجهاز وتحويل نوع البيانات.

DALI مع تدفقات الفيديو#

لمعالجة الفيديو في الوقت الفعلي، استخدم fn.external_source لإدخال الإطارات من أي مصدر — OpenCV أو GStreamer أو مكتبات الالتقاط المخصصة:

مسار DALI للمعالجة المسبقة لتدفق الفيديو
from nvidia import dali
from nvidia.dali import fn, types

@dali.pipeline_def(batch_size=1, num_threads=4, device_id=0)
def yolo_video_pipeline(target_size=640):
    """DALI pipeline for processing video frames from external source."""
    # ⁨مصدر خارجي لإدخال الإطارات من OpenCV وGStreamer وغيرهما.⁩
    frames = fn.external_source(device="cpu", name="input")
    frames = fn.reshape(frames, layout="HWC")

    # ⁨النقل إلى GPU وإجراء المعالجة المسبقة⁩
    frames_gpu = frames.gpu()
    resized = fn.resize(
        frames_gpu,
        resize_x=target_size,
        resize_y=target_size,
        mode="not_larger",
        interp_type=types.INTERP_LINEAR,
        antialias=False,
    )
    padded = fn.crop(
        resized,
        crop=(target_size, target_size),
        out_of_bounds_policy="pad",
        fill_values=114,
    )
    output = fn.crop_mirror_normalize(
        padded,
        dtype=types.FLOAT,
        output_layout="CHW",
        mean=[0.0, 0.0, 0.0],
        std=[255.0, 255.0, 255.0],
    )
    return output

خادم Triton للاستدلال مع DALI#

للنشر في بيئة الإنتاج، ادمج المعالجة المسبقة باستخدام DALI مع الاستدلال عبر TensorRT في Triton Inference Server، باستخدام نموذج تجميعي. يلغي ذلك المعالجة المسبقة على CPU بالكامل — تُمرر بايتات JPEG الخام إلى الداخل، وتخرج نتائج الكشف، مع إجراء المعالجة كلها على GPU.

بنية مستودع النماذج#

model_repository/
├── dali_preprocessing/
│   ├── 1/
│   │   └── model.dali
│   └── config.pbtxt
├── yolo_trt/
│   ├── 1/
│   │   └── model.plan
│   └── config.pbtxt
└── ensemble_dali_yolo/
    ├── 1/                  # Empty directory (required by Triton)
    └── config.pbtxt

الخطوة 1: إنشاء مسار DALI#

تسلسل مسار DALI لاستخدامه مع خلفية DALI في Triton:

تسلسل مسار DALI لـ Triton
from nvidia import dali
from nvidia.dali import fn, types

@dali.pipeline_def(batch_size=8, num_threads=4, device_id=0)
def triton_dali_pipeline():
    """DALI preprocessing pipeline for Triton deployment."""
    # ⁨الإدخال: بايتات الصورة الخام المشفّرة من Triton⁩
    images = fn.external_source(device="cpu", name="DALI_INPUT_0")
    images = fn.decoders.image(images, device="mixed", output_type=types.RGB)

    resized = fn.resize(
        images,
        resize_x=640,
        resize_y=640,
        mode="not_larger",
        interp_type=types.INTERP_LINEAR,
        antialias=False,
    )
    padded = fn.crop(
        resized,
        crop=(640, 640),
        out_of_bounds_policy="pad",
        fill_values=114,
    )
    output = fn.crop_mirror_normalize(
        padded,
        dtype=types.FLOAT,
        output_layout="CHW",
        mean=[0.0, 0.0, 0.0],
        std=[255.0, 255.0, 255.0],
    )
    return output

# ⁨تسلسل المسار في مستودع النماذج⁩
pipe = triton_dali_pipeline()
pipe.serialize(filename="model_repository/dali_preprocessing/1/model.dali")

الخطوة 2: تصدير YOLO إلى TensorRT#

تصدير نموذج YOLO إلى محرك TensorRT
from pathlib import Path

from ultralytics import YOLO

model = YOLO("yolo26n.pt")
engine_path = model.export(
    format="engine", imgsz=640, quantize=16, batch=8, dynamic=True, nms=False
)  # ⁨من دون NMS ‏(N, 300, 6)؛ TensorRT >= 8.5⁩

# ⁨تضيف Ultralytics ترويسة بيانات وصفية إلى ملفات .engine؛ أزلها حتى يتمكن Triton من تحميل خطة TensorRT الخام⁩
with open(engine_path, "rb") as f:
    meta_len = int.from_bytes(f.read(4), byteorder="little")  # ⁨طول ترويسة البيانات الوصفية بصيغة JSON⁩
    f.seek(4 + meta_len)
    plan = f.read()
Path("model_repository/yolo_trt/1").mkdir(parents=True, exist_ok=True)
Path("model_repository/yolo_trt/1/model.plan").write_bytes(plan)

الخطوة 3: إعداد Triton#

dali_preprocessing/config.pbtxt:

name: "dali_preprocessing"
backend: "dali"
max_batch_size: 8
input [
  {
    name: "DALI_INPUT_0"
    data_type: TYPE_UINT8
    dims: [ -1 ]
  }
]
output [
  {
    name: "DALI_OUTPUT_0"
    data_type: TYPE_FP32
    dims: [ 3, 640, 640 ]
  }
]

yolo_trt/config.pbtxt:

name: "yolo_trt"
platform: "tensorrt_plan"
max_batch_size: 8
input [
  {
    name: "images"
    data_type: TYPE_FP32
    dims: [ 3, 640, 640 ]
  }
]
output [
  {
    name: "output0"
    data_type: TYPE_FP32
    dims: [ 300, 6 ]
  }
]

ensemble_dali_yolo/config.pbtxt:

name: "ensemble_dali_yolo"
platform: "ensemble"
max_batch_size: 8
input [
  {
    name: "INPUT"
    data_type: TYPE_UINT8
    dims: [ -1 ]
  }
]
output [
  {
    name: "OUTPUT"
    data_type: TYPE_FP32
    dims: [ 300, 6 ]
  }
]
ensemble_scheduling {
  step [
    {
      model_name: "dali_preprocessing"
      model_version: -1
      input_map {
        key: "DALI_INPUT_0"
        value: "INPUT"
      }
      output_map {
        key: "DALI_OUTPUT_0"
        value: "preprocessed_image"
      }
    },
    {
      model_name: "yolo_trt"
      model_version: -1
      input_map {
        key: "images"
        value: "preprocessed_image"
      }
      output_map {
        key: "output0"
        value: "OUTPUT"
      }
    }
  ]
}
آلية عمل تعيينات التجميع

يربط التجميع النماذج عبر أسماء الموترات الافتراضية. تتطابق قيمة output_map، أي "preprocessed_image"، في خطوة DALI مع قيمة input_map، أي "preprocessed_image"، في خطوة TensorRT. هذه أسماء اعتباطية تربط مخرجات إحدى الخطوات بمدخلات الخطوة التالية — ولا يلزم أن تتطابق هذه الأسماء مع أسماء الموترات الداخلية لأي نموذج.

الخطوة 4: إرسال طلبات الاستدلال#

لماذا `tritonclient` بدلًا من `YOLO('http://...')`؟

توفّر Ultralytics دعمًا مدمجًا لـ Triton يتولى المعالجة المسبقة واللاحقة تلقائيًا. لكنه لا يعمل مع تجميع DALI، لأن YOLO() يرسل موترًا من النوع float32 بعد معالجته مسبقًا، بينما يتوقع التجميع بايتات JPEG خامًا. استخدم tritonclient مباشرةً مع تجميعات DALI، واستخدم التكامل المدمج لعمليات النشر القياسية التي لا تستخدم DALI.

إرسال الصور إلى تجميع Triton
import numpy as np
import tritonclient.http as httpclient

client = httpclient.InferenceServerClient(url="localhost:8000")

# Load image as raw bytes (JPEG/PNG encoded)
image_data = np.fromfile("image.jpg", dtype="uint8")
image_data = np.expand_dims(image_data, axis=0)  # Add batch dimension

# Create input
input_tensor = httpclient.InferInput("INPUT", image_data.shape, "UINT8")
input_tensor.set_data_from_numpy(image_data)

# Run inference through the ensemble
result = client.infer(model_name="ensemble_dali_yolo", inputs=[input_tensor])
detections = result.as_numpy("OUTPUT")  # Shape: (1, 300, 6) -> [x1, y1, x2, y2, conf, class_id]

# Filter by confidence (no NMS needed for the nms=False export)
detections = detections[0]  # First image
detections = detections[detections[:, 4] > 0.25]  # Confidence threshold
print(f"Detected {len(detections)} objects")
تجميع صور JPEG

عند إرسال دفعة من صور JPEG إلى Triton، أضف حشوًا إلى جميع مصفوفات البايتات المشفرة لتصبح بالطول نفسه (أي عدد البايتات الأقصى في الدفعة). يتطلب Triton تجانس أشكال الدفعات لموتر الإدخال.

المهام المدعومة#

تعمل المعالجة المسبقة باستخدام DALI مع جميع مهام YOLO التي تستخدم مسار LetterBox القياسي:

المهمةمدعومملاحظات
الكشف✅المعالجة المسبقة القياسية باستخدام letterbox
تجزئة الكائنات✅المعالجة المسبقة نفسها المستخدمة في الكشف
التجزئة الدلالية✅المعالجة المسبقة للصورة نفسها المستخدمة في الكشف
تقدير العمق✅المعالجة المسبقة للصورة نفسها المستخدمة في الكشف
التصنيف❌يستخدم تحويلات torchvision (قصًا مركزيًا)، وليس letterbox
تقدير الوضعية✅المعالجة المسبقة نفسها المستخدمة في الكشف
الكشف الموجّه (OBB)✅المعالجة المسبقة نفسها المستخدمة في الكشف

القيود#

  • Linux فقط: لا يدعم DALI نظامَي Windows أو macOS
  • يتطلب وحدة معالجة رسومات NVIDIA: لا يوجد بديل يعمل على CPU فقط
  • مسار ثابت: تُحدَّد بنية المسار عند إنشائه، ولا يمكن تغييرها ديناميكيًا
  • fn.pad يضيف الحشو إلى اليمين والأسفل فقط: استخدم fn.crop مع out_of_bounds_policy="pad" لتوسيط الحشو
  • لا يتوفر وضع rect: تنتج مسارات DALI مخرجات ذات حجم ثابت (مثل 640×640). ولا يُدعم وضع rect في auto=True الذي ينتج مخرجات متغيرة الحجم (مثل 384×640). لاحظ أنه رغم دعم TensorRT لأشكال الإدخال الديناميكية، فإن مسار DALI ثابت الحجم يتوافق طبيعيًا مع محرك ثابت الحجم لتحقيق أعلى معدل إنتاجية
  • استهلاك الذاكرة مع مثيلات متعددة: قد يؤدي استخدام instance_group مع count > 1 في Triton إلى استهلاك مرتفع للذاكرة. استخدم مجموعة المثيلات الافتراضية لنموذج DALI

الأسئلة الشائعة#

  • تعتمد الفائدة على مسار العمل لديك. عندما يكون الاستدلال على GPU سريعًا بالفعل باستخدام TensorRT، قد تصبح المعالجة المسبقة على CPU، التي تستغرق من 2 إلى 10 مللي ثانية، العامل الأكبر في التكلفة. يزيل DALI هذا الاختناق بتشغيل المعالجة المسبقة على GPU. وتظهر أكبر المكاسب مع المدخلات عالية الدقة (1080p و4K)، وأحجام الدفعات الكبيرة، والأنظمة ذات العدد المحدود من أنوية CPU لكل GPU.

  • نعم. استخدم DALIGenericIterator للحصول على مخرجات torch.Tensor المعالجة مسبقًا، ثم مررها إلى model.predict(). ومع ذلك، تكون فائدة الأداء أكبر مع نماذج TensorRT، حيث يكون الاستدلال سريعًا جدًا بالفعل وتصبح المعالجة المسبقة على CPU عنق الزجاجة.

  • يضيف fn.pad الحشو إلى الحافتين اليمنى والسفلية فقط. أما fn.crop مع out_of_bounds_policy="pad" فيوسّطان الصورة ويضيفان الحشو بالتساوي إلى جميع الجوانب، بما يطابق سلوك LetterBox(center=True) في Ultralytics.

  • متطابقة تقريبًا. اضبط antialias=False في fn.resize لمطابقة cv2.INTER_LINEAR في OpenCV. قد تحدث فروق طفيفة في الفاصلة العائمة (< 0.001) بسبب اختلاف الحساب على GPU عن CPU، لكن ليس لها تأثير قابل للقياس في الدقة في الكشف.

  • CV-CUDA مكتبة أخرى من NVIDIA لمعالجة الرؤية باستخدام تسريع GPU. وتتيح التحكم في كل عامل تشغيل على حدة (مثل OpenCV، لكن على GPU) بدلًا من نهج المسارات الذي يتبعه DALI. يدعم cvcuda.copymakeborder() في CV-CUDA تحديد الحشو لكل جانب صراحةً، ما يجعل letterbox المتمركز سهل التنفيذ. اختر DALI لسير العمل القائم على المسارات (خصوصًا مع Triton)، واختر CV-CUDA للتحكم الدقيق على مستوى عوامل التشغيل في شيفرة الاستدلال المخصصة.

التعليقات