YOLOv7: حقيبة المجانيات القابلة للتدريب (Trainable Bag-of-Freebies)

يُعد YOLOv7، الذي تم إصداره في يوليو 2022، تقدمًا كبيرًا في اكتشاف الكائنات في الوقت الفعلي في وقت إصداره. حقق 56.8% AP على وحدة معالجة الرسومات V100، مما وضع معايير جديدة عند طرحه. تفوق YOLOv7 على مكتشفات الكائنات المعاصرة مثل YOLOR وYOLOX وScaled-YOLOv4 وYOLOv5 من حيث السرعة والدقة. تم تدريب النموذج على مجموعة بيانات MS COCO من الصفر دون استخدام أي مجموعات بيانات أخرى أو أوزان مدربة مسبقًا. يتوفر الكود المصدري لـ YOLOv7 على GitHub. لاحظ أن النماذج الأحدث مثل YOLO11 و YOLO26 قد حققت منذ ذلك الحين دقة أعلى مع كفاءة محسنة.

مقارنة YOLOv7 مع مكتشفات الكائنات الحديثة

مقارنة بين مكتشفات الكائنات الحديثة (SOTA)

من النتائج في جدول مقارنة YOLO، نعلم أن الطريقة المقترحة لديها أفضل توازن بين السرعة والدقة بشكل شامل. إذا قارنا YOLOv7-tiny-SiLU مع YOLOv5-N (r6.1)، فإن طريقتنا أسرع بـ 127 إطارًا في الثانية وأكثر دقة بنسبة 10.7% في AP. بالإضافة إلى ذلك، يتمتع YOLOv7 بـ 51.4% AP بمعدل إطارات 161 إطارًا في الثانية، بينما PPYOLOE-L بنفس AP لديه معدل إطارات 78 إطارًا في الثانية فقط. من حيث استخدام المعلمات، YOLOv7 أقل بنسبة 41% من PPYOLOE-L.

إذا قارنا YOLOv7-X بسرعة استنتاج 114 إطارًا في الثانية بـ YOLOv5-L (r6.1) بسرعة استنتاج 99 إطارًا في الثانية، يمكن لـ YOLOv7-X تحسين AP بنسبة 3.9%. إذا تمت مقارنة YOLOv7-X بـ YOLOv5-X (r6.1) بنفس النطاق، فإن سرعة استنتاج YOLOv7-X أسرع بـ 31 إطارًا في الثانية. بالإضافة إلى ذلك، من حيث كمية المعلمات والحسابات، يقلل YOLOv7-X من المعلمات بنسبة 22% والحسابات بنسبة 8% مقارنة بـ YOLOv5-X (r6.1)، ولكنه يحسن AP بنسبة 2.2% (المصدر).

الأداء
النموذجالمعلمات
(M)
FLOPs
(G)
الحجم
(بكسل)
FPSAPtest / val
50-95
APtest
50
APtest
75
APtest
S
APtest
M
APtest
L
YOLOX-S9.026.864010240.5% / 40.5%-----
YOLOX-M25.373.86408147.2% / 46.9%-----
YOLOX-L54.2155.66406950.1% / 49.7%-----
YOLOX-X99.1281.96405851.5% / 51.1%-----
PPYOLOE-S7.917.464020843.1% / 42.7%60.5%46.6%23.2%46.4%56.9%
PPYOLOE-M23.449.964012348.9% / 48.6%66.5%53.0%28.6%52.9%63.8%
PPYOLOE-L52.2110.16407851.4% / 50.9%68.9%55.6%31.4%55.3%66.1%
PPYOLOE-X98.4206.66404552.2% / 51.9%69.9%56.5%33.3%56.3%66.4%
YOLOv5-N (r6.1)1.94.5640159- / 28.0%-----
YOLOv5-S (r6.1)7.216.5640156- / 37.4%-----
YOLOv5-M (r6.1)21.249.0640122- / 45.4%-----
YOLOv5-L (r6.1)46.5109.164099- / 49.0%-----
YOLOv5-X (r6.1)86.7205.764083- / 50.7%-----
YOLOR-CSP52.9120.464010651.1% / 50.8%69.6%55.7%31.7%55.3%64.7%
YOLOR-CSP-X96.9226.86408753.0% / 52.7%71.4%57.9%33.7%57.1%66.8%
YOLOv7-tiny-SiLU6.213.864028638.7% / 38.7%56.7%41.7%18.8%42.4%51.9%
YOLOv736.9104.764016151.4% / 51.2%69.7%55.9%31.8%55.5%65.0%
YOLOv7-X71.3189.964011453.1% / 52.9%71.2%57.8%33.8%57.1%67.4%
YOLOv5-N6 (r6.1)3.218.41280123/ 36.0%-----
YOLOv5-S6 (r6.1)12.667.21280122/ 44.8%-----
YOLOv5-M6 (r6.1)35.7200.0128090/ 51.3%-----
YOLOv5-L6 (r6.1)76.8445.6128063/ 53.7%-----
YOLOv5-X6 (r6.1)140.7839.2128038/ 55.0%-----
YOLOR-P637.2325.612807653.9% / 53.5%71.4%58.9%36.1%57.7%65.6%
YOLOR-W679.8453.212806655.2% / 54.8%72.7%60.5%37.7%59.1%67.1%
YOLOR-E6115.8683.212804555.8% / 55.7%73.4%61.1%38.4%59.7%67.7%
YOLOR-D6151.7935.612803456.5% / 56.1%74.1%61.9%38.9%60.4%68.7%
YOLOv7-W670.4360.012808454.9% / 54.6%72.6%60.1%37.3%58.7%67.1%
YOLOv7-E697.2515.212805656.0% / 55.9%73.5%61.2%38.0%59.9%68.4%
YOLOv7-D6154.7806.812804456.6% / 56.3%74.0%61.8%38.8%60.1%69.5%
YOLOv7-E6E151.7843.212803656.8% / 56.8%74.4%62.1%39.3%60.5%69.0%

نظرة عامة

يعد اكتشاف الأشياء في الوقت الفعلي مكونًا مهمًا في العديد من أنظمة رؤية الحاسوب، بما في ذلك تتبع الكائنات المتعدد، والقيادة الذاتية، والروبوتات، وتحليل الصور الطبية. في السنوات الأخيرة، ركز تطوير اكتشاف الأشياء في الوقت الفعلي على تصميم بنيات فعالة وتحسين سرعة الاستدلال لمختلف وحدات المعالجة المركزية (CPUs)، ووحدات معالجة الرسومات (GPUs)، ووحدات المعالجة العصبية (NPUs). يدعم YOLOv7 كلاً من أجهزة GPU المحمولة وأجهزة GPU، بدءاً من الحافة وحتى السحابة.

على عكس كاشفات الأشياء التقليدية في الوقت الفعلي التي تركز على تحسين البنية، يقدم YOLOv7 تركيزاً على تحسين عملية التدريب. يشمل ذلك وحدات وطرق تحسين مصممة لتحسين دقة اكتشاف الأشياء دون زيادة تكلفة الاستدلال، وهو مفهوم يُعرف باسم "حقيبة الهدايا القابلة للتدريب" (trainable bag-of-freebies).

الميزات الرئيسية

يقدم YOLOv7 العديد من الميزات الرئيسية:

  1. إعادة تحديد معلمات النموذج (Model Re-parameterization): يقترح YOLOv7 نموذجاً مخططاً ومعاد تحديد معلماته، وهي استراتيجية قابلة للتطبيق على الطبقات في شبكات مختلفة مع مفهوم مسار انتشار التدرج.

  2. تعيين التسميات الديناميكي (Dynamic Label Assignment): يطرح تدريب النموذج بطبقات إخراج متعددة قضية جديدة: "كيفية تعيين أهداف ديناميكية لمخرجات الفروع المختلفة؟". لحل هذه المشكلة، يقدم YOLOv7 طريقة جديدة لتعيين التسميات تسمى تعيين التسميات الموجه بالقيادة من الخشن إلى الدقيق.

  3. التحجيم الممتد والمركب (Extended and Compound Scaling): يقترح YOLOv7 طرق "توسيع" و"تحجيم مركب" لكاشف الأشياء في الوقت الفعلي والتي يمكنها استخدام المعلمات والحسابات بفعالية.

  4. الكفاءة: يمكن للطريقة المقترحة بواسطة YOLOv7 تقليل حوالي 40% من المعلمات و50% من الحسابات الخاصة بأحدث كاشف للأشياء في الوقت الفعلي، وتتميز بسرعة استدلال أكبر ودقة اكتشاف أعلى.

أمثلة الاستخدام

لا تقوم Ultralytics بنشر أوزان مدربة مسبقاً لـ yolov7.pt أو ملفات ultralytics/cfg/models/v7/ بتنسيق YAML، كما أن التدريب والاستدلال الأصلي بنظام PyTorch لـ YOLOv7 غير مدعوم من قبل حزمة Ultralytics Python. ومع ذلك، يمكنك جلب نقطة فحص (checkpoint) لـ YOLOv7 مدربة في مستودع YOLOv7 الرئيسي إلى Ultralytics عن طريق تصديرها إلى ONNX أو TensorRT، كما هو موضح أدناه.

تصدير ONNX

لاستخدام نموذج YOLOv7 ONNX مع Ultralytics:

  1. (اختياري) قم بتثبيت Ultralytics وتصدير نموذج ONNX ليتم تثبيت التبعيات المطلوبة تلقائياً:

    pip install ultralytics
    yolo export model=yolo26n.pt format=onnx
  2. قم بتصدير نموذج YOLOv7 المطلوب باستخدام المُصدّر في مستودع YOLOv7:

    git clone https://github.com/WongKinYiu/yolov7
    cd yolov7
    python export.py --weights yolov7-tiny.pt --grid --end2end --simplify --topk-all 100 --iou-thres 0.65 --conf-thres 0.35 --img-size 640 640 --max-wh 640
  3. قم بتعديل رسم بياني لنموذج ONNX ليكون متوافقاً مع Ultralytics باستخدام البرنامج النصي التالي:

    import numpy as np
    import onnx
    from onnx import helper, numpy_helper
    
    # Load the ONNX model
    model_path = "yolov7/yolov7-tiny.onnx"  # Replace with your model path
    model = onnx.load(model_path)
    graph = model.graph
    
    # Fix input shape to batch size 1
    input_shape = graph.input[0].type.tensor_type.shape
    input_shape.dim[0].dim_value = 1
    
    # Define the output of the original model
    original_output_name = graph.output[0].name
    
    # Create slicing nodes
    sliced_output_name = f"{original_output_name}_sliced"
    
    # Define initializers for slicing (remove the first value)
    start = numpy_helper.from_array(np.array([1], dtype=np.int64), name="slice_start")
    end = numpy_helper.from_array(np.array([7], dtype=np.int64), name="slice_end")
    axes = numpy_helper.from_array(np.array([1], dtype=np.int64), name="slice_axes")
    steps = numpy_helper.from_array(np.array([1], dtype=np.int64), name="slice_steps")
    
    graph.initializer.extend([start, end, axes, steps])
    
    slice_node = helper.make_node(
        "Slice",
        inputs=[original_output_name, "slice_start", "slice_end", "slice_axes", "slice_steps"],
        outputs=[sliced_output_name],
        name="SliceNode",
    )
    graph.node.append(slice_node)
    
    # Define segment slicing
    seg1_start = numpy_helper.from_array(np.array([0], dtype=np.int64), name="seg1_start")
    seg1_end = numpy_helper.from_array(np.array([4], dtype=np.int64), name="seg1_end")
    seg2_start = numpy_helper.from_array(np.array([4], dtype=np.int64), name="seg2_start")
    seg2_end = numpy_helper.from_array(np.array([5], dtype=np.int64), name="seg2_end")
    seg3_start = numpy_helper.from_array(np.array([5], dtype=np.int64), name="seg3_start")
    seg3_end = numpy_helper.from_array(np.array([6], dtype=np.int64), name="seg3_end")
    
    graph.initializer.extend([seg1_start, seg1_end, seg2_start, seg2_end, seg3_start, seg3_end])
    
    # Create intermediate tensors for segments
    segment_1_name = f"{sliced_output_name}_segment1"
    segment_2_name = f"{sliced_output_name}_segment2"
    segment_3_name = f"{sliced_output_name}_segment3"
    
    # Add segment slicing nodes
    graph.node.extend(
        [
            helper.make_node(
                "Slice",
                inputs=[sliced_output_name, "seg1_start", "seg1_end", "slice_axes", "slice_steps"],
                outputs=[segment_1_name],
                name="SliceSegment1",
            ),
            helper.make_node(
                "Slice",
                inputs=[sliced_output_name, "seg2_start", "seg2_end", "slice_axes", "slice_steps"],
                outputs=[segment_2_name],
                name="SliceSegment2",
            ),
            helper.make_node(
                "Slice",
                inputs=[sliced_output_name, "seg3_start", "seg3_end", "slice_axes", "slice_steps"],
                outputs=[segment_3_name],
                name="SliceSegment3",
            ),
        ]
    )
    
    # Concatenate the segments
    concat_output_name = f"{sliced_output_name}_concat"
    concat_node = helper.make_node(
        "Concat",
        inputs=[segment_1_name, segment_3_name, segment_2_name],
        outputs=[concat_output_name],
        axis=1,
        name="ConcatSwapped",
    )
    graph.node.append(concat_node)
    
    # Reshape to [1, -1, 6]
    reshape_shape = numpy_helper.from_array(np.array([1, -1, 6], dtype=np.int64), name="reshape_shape")
    graph.initializer.append(reshape_shape)
    
    final_output_name = f"{concat_output_name}_batched"
    reshape_node = helper.make_node(
        "Reshape",
        inputs=[concat_output_name, "reshape_shape"],
        outputs=[final_output_name],
        name="AddBatchDimension",
    )
    graph.node.append(reshape_node)
    
    # Get the shape of the reshaped tensor
    shape_node_name = f"{final_output_name}_shape"
    shape_node = helper.make_node(
        "Shape",
        inputs=[final_output_name],
        outputs=[shape_node_name],
        name="GetShapeDim",
    )
    graph.node.append(shape_node)
    
    # Extract the second dimension
    dim_1_index = numpy_helper.from_array(np.array([1], dtype=np.int64), name="dim_1_index")
    graph.initializer.append(dim_1_index)
    
    second_dim_name = f"{final_output_name}_dim1"
    gather_node = helper.make_node(
        "Gather",
        inputs=[shape_node_name, "dim_1_index"],
        outputs=[second_dim_name],
        name="GatherSecondDim",
    )
    graph.node.append(gather_node)
    
    # Subtract from 100 to determine how many values to pad
    target_size = numpy_helper.from_array(np.array([100], dtype=np.int64), name="target_size")
    graph.initializer.append(target_size)
    
    pad_size_name = f"{second_dim_name}_padsize"
    sub_node = helper.make_node(
        "Sub",
        inputs=["target_size", second_dim_name],
        outputs=[pad_size_name],
        name="CalculatePadSize",
    )
    graph.node.append(sub_node)
    
    # Build the [2, 3] pad array:
    # 1st row -> [0, 0, 0] (no padding at the start of any dim)
    # 2nd row -> [0, pad_size, 0] (pad only at the end of the second dim)
    pad_starts = numpy_helper.from_array(np.array([0, 0, 0], dtype=np.int64), name="pad_starts")
    graph.initializer.append(pad_starts)
    
    zero_scalar = numpy_helper.from_array(np.array([0], dtype=np.int64), name="zero_scalar")
    graph.initializer.append(zero_scalar)
    
    pad_ends_name = "pad_ends"
    concat_pad_ends_node = helper.make_node(
        "Concat",
        inputs=["zero_scalar", pad_size_name, "zero_scalar"],
        outputs=[pad_ends_name],
        axis=0,
        name="ConcatPadEnds",
    )
    graph.node.append(concat_pad_ends_node)
    
    pad_values_name = "pad_values"
    concat_pad_node = helper.make_node(
        "Concat",
        inputs=["pad_starts", pad_ends_name],
        outputs=[pad_values_name],
        axis=0,
        name="ConcatPadStartsEnds",
    )
    graph.node.append(concat_pad_node)
    
    # Create Pad operator to pad with zeros
    pad_output_name = f"{final_output_name}_padded"
    pad_constant_value = numpy_helper.from_array(
        np.array([0.0], dtype=np.float32),
        name="pad_constant_value",
    )
    graph.initializer.append(pad_constant_value)
    
    pad_node = helper.make_node(
        "Pad",
        inputs=[final_output_name, pad_values_name, "pad_constant_value"],
        outputs=[pad_output_name],
        mode="constant",
        name="PadToFixedSize",
    )
    graph.node.append(pad_node)
    
    # Update the graph's final output to [1, 100, 6]
    new_output_type = onnx.helper.make_tensor_type_proto(
        elem_type=graph.output[0].type.tensor_type.elem_type, shape=[1, 100, 6]
    )
    new_output = onnx.helper.make_value_info(name=pad_output_name, type_proto=new_output_type)
    
    # Replace the old output with the new one
    graph.output.pop()
    graph.output.extend([new_output])
    
    # Save the modified model
    onnx.save(model, "yolov7-ultralytics.onnx")
  4. يمكنك بعد ذلك تحميل نموذج ONNX المعدل وتشغيل الاستدلال به في Ultralytics بشكل طبيعي:

    from ultralytics import ASSETS, YOLO
    
    model = YOLO("yolov7-ultralytics.onnx", task="detect")
    
    results = model(ASSETS / "bus.jpg")

تصدير TensorRT

  1. اتبع الخطوات 1-2 في قسم تصدير ONNX.

  2. قم بتثبيت حزمة TensorRT بلغة Python:

    pip install tensorrt
  3. قم بتشغيل البرنامج النصي التالي لتحويل نموذج ONNX المعدل إلى محرك TensorRT:

    from ultralytics.utils.export import export_engine
    
    export_engine("yolov7-ultralytics.onnx", half=True)
  4. قم بتحميل وتشغيل النموذج في Ultralytics:

    from ultralytics import ASSETS, YOLO
    
    model = YOLO("yolov7-ultralytics.engine", task="detect")
    
    results = model(ASSETS / "bus.jpg")

الاقتباسات والشكر

نود أن نعرب عن تقديرنا لمؤلفي YOLOv7 لمساهماتهم الكبيرة في مجال اكتشاف الأشياء في الوقت الفعلي:

اقتباس
@article{wang2022yolov7,
  title={YOLOv7: Trainable bag-of-freebies sets new state-of-the-art for real-time object detectors},
  author={Wang, Chien-Yao and Bochkovskiy, Alexey and Liao, Hong-Yuan Mark},
  journal={arXiv preprint arXiv:2207.02696},
  year={2022}
}

يمكن العثور على ورقة YOLOv7 الأصلية على arXiv. وقد أتاح المؤلفون عملهم للجمهور، ويمكن الوصول إلى قاعدة الأكواد على GitHub. نحن نقدر جهودهم في تطوير هذا المجال وإتاحة أعمالهم للمجتمع الأوسع.

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

ما هو YOLOv7 ولماذا يُعتبر اختراقاً في اكتشاف الأشياء في الوقت الفعلي؟

كان YOLOv7، الذي تم إصداره في يوليو 2022، نموذجاً هاماً لاكتشاف الأشياء في الوقت الفعلي حقق سرعة ودقة ممتازتين في وقت إصداره. لقد تفوق على النماذج المعاصرة مثل YOLOX وYOLOv5 وPPYOLOE في كل من استخدام المعلمات وسرعة الاستدلال. تشمل الميزات المميزة لـ YOLOv7 إعادة تحديد معلمات النموذج وتعيين التسميات الديناميكي، والتي تعمل على تحسين أدائه دون زيادة تكاليف الاستدلال. لمزيد من التفاصيل الفنية حول بنيته ومقاييس المقارنة مع كاشفات الأشياء الأخرى المتطورة، راجع ورقة YOLOv7 البحثية.

كيف يعمل YOLOv7 على التحسين مقارنة بنماذج YOLO السابقة مثل YOLOv4 وYOLOv5؟

يقدم YOLOv7 العديد من الابتكارات، بما في ذلك إعادة تحديد معلمات النموذج وتعيين التسميات الديناميكي، والتي تعزز عملية التدريب وتحسن دقة الاستدلال. مقارنة بـ YOLOv5، يعزز YOLOv7 السرعة والدقة بشكل كبير. على سبيل المثال، يعمل YOLOv7-X على تحسين الدقة بنسبة 2.2% وتقليل المعلمات بنسبة 22% مقارنة بـ YOLOv5-X. يمكن العثور على مقارنات تفصيلية في جدول الأداء مقارنة YOLOv7 مع كاشفات الأشياء المتطورة (SOTA).

هل يمكنني استخدام YOLOv7 مع أدوات ومنصات Ultralytics؟

اعتباراً من الآن، تدعم Ultralytics فقط استدلال YOLOv7 عبر ONNX وTensorRT. لتشغيل إصدار YOLOv7 المصدر عبر ONNX وTensorRT مع Ultralytics، راجع قسم أمثلة الاستخدام.

كيف يمكنني تدريب نموذج YOLOv7 مخصص باستخدام مجموعة البيانات الخاصة بي؟

لتثبيت وتدريب نموذج YOLOv7 مخصص، اتبع الخطوات التالية:

  1. انسخ مستودع YOLOv7:

    git clone https://github.com/WongKinYiu/yolov7
  2. انتقل إلى الدليل المنسوخ وقم بتثبيت التبعيات:

    cd yolov7
    pip install -r requirements.txt
  3. قم بإعداد مجموعة البيانات الخاصة بك وقم بتهيئة معلمات النموذج وفقاً لـ تعليمات الاستخدام الواردة في المستودع. لمزيد من التوجيه، تفضل بزيارة مستودع YOLOv7 على GitHub للحصول على أحدث المعلومات والتحديثات.

  4. بعد التدريب، يمكنك تصدير النموذج إلى ONNX أو TensorRT للاستخدام في Ultralytics كما هو موضح في أمثلة الاستخدام.

ما هي الميزات الرئيسية والتحسينات التي تم تقديمها في YOLOv7؟

يقدم YOLOv7 العديد من الميزات الرئيسية التي تحدث ثورة في اكتشاف الأشياء في الوقت الفعلي:

  • إعادة تحديد معلمات النموذج (Model Re-parameterization): يعزز أداء النموذج من خلال تحسين مسارات انتشار التدرج.
  • تعيين التسميات الديناميكي (Dynamic Label Assignment): يستخدم طريقة موجهة بالقيادة من الخشن إلى الدقيق لتعيين أهداف ديناميكية للمخرجات عبر فروع مختلفة، مما يحسن الدقة.
  • التحجيم الممتد والمركب (Extended and Compound Scaling): يستخدم المعلمات والحسابات بكفاءة لتوسيع نطاق النموذج لمختلف التطبيقات في الوقت الفعلي.
  • الكفاءة: يقلل عدد المعلمات بنسبة 40% والحسابات بنسبة 50% مقارنة بالنماذج المتطورة الأخرى مع تحقيق سرعات استدلال أسرع.

لمزيد من التفاصيل حول هذه الميزات، راجع قسم نظرة عامة على YOLOv7.

التعليقات