التعرف على الكائنات في الوقت الفعلي على نظام التشغيل iOS باستخدام YOLOv3


مرحبا بالجميع!

في هذه المقالة ، سوف نكتب برنامجًا صغيرًا لحل مشكلة اكتشاف الكائنات والتعرف عليها (اكتشاف الكائن) في الوقت الفعلي. سيتم كتابة البرنامج بلغة برمجة سويفت لمنصة iOS. لاكتشاف الكائنات ، سوف نستخدم شبكة عصبية تلافيفية مع بنية تسمى YOLOv3. في هذه المقالة ، سوف نتعلم كيفية العمل في iOS مع الشبكات العصبية باستخدام إطار عمل CoreML ، وفهم قليل لماهية شبكة YOLOv3 وكيفية استخدام ومعالجة مخرجات هذه الشبكة. سنقوم أيضًا بالتحقق من تشغيل البرنامج ومقارنة عدة صيغ من YOLOv3: YOLOv3-tiny و YOLOv3-416.

ستكون المصادر متاحة في نهاية المقالة ، بحيث يكون الجميع قادرين على اختبار تشغيل الشبكة العصبية على أجهزتهم.

كشف الكائن


بادئ ذي بدء ، سوف نفهم باختصار ما مهمة الكشف عن الكائنات (اكتشاف الكائن) في الصورة وما هي الأدوات المستخدمة لهذا اليوم. أفهم أن الكثيرين على دراية بهذا الموضوع ، لكني لا أزال أسمح لنفسي بالتحدث قليلاً عنه.

الآن ، يتم حل الكثير من المهام في مجال رؤية الكمبيوتر باستخدام الشبكات العصبية التلافيفية (الشبكات العصبية التلافيفية) ، فيما بعد CNN. بسبب بنيتها ، فإنها تستخرج الميزات من الصورة بشكل جيد. تُستخدم شبكات CNN في التصنيف ، والاعتراف ، والتجزئة ، وغيرها الكثير.

تصميمات CNN الشائعة للتعرف على الأشياء:

  • R-CNN. يمكننا أن نقول النموذج الأول لحل هذه المشكلة. يعمل مثل مصنف الصورة العادية. يتم تغذية مناطق مختلفة من الصورة بإدخال الشبكة ويتم عمل تنبؤات لها. بطيء جدًا لأنه يدير صورة واحدة عدة آلاف من المرات.
  • سريع R-CNN. تعمل نسخة محسنة وأسرع من R-CNN على مبدأ مماثل ، ولكن أولاً يتم تغذية الصورة بأكملها إلى إدخال CNN ، ثم يتم إنشاء المناطق من التمثيل الداخلي المستلم. ولكن لا يزال بطيئا جدا للمهام في الوقت الحقيقي.
  • أسرع R-CNN. يتمثل الاختلاف الرئيسي عن السابق في أنه بدلاً من خوارزمية البحث الانتقائي ، فإنه يستخدم شبكة عصبية لتحديد المناطق "لحفظها".
  • YOLO. هناك مبدأ مختلف تمامًا عن التشغيل مقارنةً بالمبادئ السابقة لا يستخدم المناطق على الإطلاق. الأسرع. سيتم مناقشة المزيد من التفاصيل حول هذا الموضوع في المقال.
  • SSD. إنه مشابه من حيث المبدأ لـ YOLO ، ولكنه يستخدم VGG16 كشبكة لاستخراج الميزات. أيضا سريع جدا ومناسبة للعمل في الوقت الحقيقي.
  • ميزة شبكات الهرم (FPN). هناك نوع آخر من الشبكات مثل Single Shot Detector ، نظرًا لميزات استخراج الميزات أفضل من SSD يتعرف على الكائنات الصغيرة.
  • RetinaNet. يستخدم مزيجًا من FPN + ResNet وبفضل وظيفة خطأ خاصة (فقد بؤري) ، يعطي دقة أعلى.

في هذه المقالة ، سوف نستخدم بنية YOLO ، أي التعديل الأخير لها ، YOLOv3.

لماذا يولو؟




YOLO أو You Only Look Once هي بنية CNN الشائعة للغاية ، والتي تُستخدم للتعرف على كائنات متعددة في صورة ما. يمكن الحصول على مزيد من المعلومات الكاملة حول هذا الموضوع على الموقع الرسمي ، ويمكنك أيضًا العثور على المنشورات التي تصف بالتفصيل النظرية والمكون الرياضي لهذه الشبكة ، بالإضافة إلى وصف عملية التدريب.

الميزة الرئيسية لهذه البنية مقارنة بالأنظمة الأخرى هي أن معظم الأنظمة تطبق CNN عدة مرات على مناطق مختلفة من الصورة ؛ في YOLO ، يتم تطبيق CNN مرة واحدة على الصورة بأكملها دفعة واحدة. تقسم الشبكة الصورة إلى نوع من الشبكة وتتوقع الصناديق المحيطية واحتمال وجود كائن مرغوب فيه لكل قسم.

تتمثل مزايا هذا النهج في أن الشبكة تنظر إلى الصورة بأكملها في الحال وتراعي السياق عند اكتشاف كائن والتعرف عليه. YOLO أسرع بـ 1000 مرة من R-CNN وحوالي 100 مرة أسرع من Fast R-CNN. في هذه المقالة ، سنطلق شبكة على جهاز محمول للمعالجة عبر الإنترنت ، لذلك فهذه هي أهم جودة بالنسبة لنا.

يمكن الاطلاع على مزيد من المعلومات حول مقارنة البنيات هنا .

YOLOv3


YOLOv3 هو نسخة متقدمة من بنية YOLO. يتكون من 106 طبقة تلافيفية ويكتشف الأجسام الصغيرة بشكل أفضل مقارنة بسابقتها YOLOv2. الميزة الرئيسية في YOLOv3 هي أن هناك ثلاث طبقات في الإخراج ، كل منها مصمم للكشف عن كائنات ذات أحجام مختلفة.
الصورة أدناه توضح هيكلها التخطيطي:



YOLOv3-tiny - إصدار قصير من بنية YOLOv3 ، يتكون من طبقات أقل (هناك طبقتان فقط للإخراج). يتنبأ بأشياء أصغر حجمًا وهو مخصص لمجموعات البيانات الصغيرة. ولكن نظرًا للهيكل المقطوع ، تشغل أوزان الشبكة مقدارًا صغيرًا من الذاكرة (حوالي 35 ميجابايت) وتنتج FPS أعلى. لذلك ، يفضل استخدام مثل هذه البنية على جهاز محمول.

نكتب برنامجًا للتعرف على الكائنات


الجزء الممتع يبدأ!

لنقم بإنشاء تطبيق يتعرف على الكائنات المختلفة في الصورة في الوقت الفعلي باستخدام كاميرا الهاتف. سيتم كتابة جميع الشفرات بلغة برمجة Swift 4.2 وسيتم تشغيلها على جهاز iOS.

في هذا البرنامج التعليمي ، سنأخذ شبكة جاهزة مع جداول تم تدريبها مسبقًا على مجموعة بيانات COCO . ويعرض 80 فصول مختلفة. لذلك ، سيكون بإمكان الخلايا العصبية لدينا التعرف على 80 كائنًا مختلفًا.

من Darknet إلى CoreML


تم تنفيذ بنية YOLOv3 الأصلية باستخدام إطار عمل Darknet .

على نظام التشغيل iOS ، بدءًا من الإصدار 11.0 ، توجد مكتبة CoreML رائعة تتيح لك تشغيل نماذج التعلم الآلي على الجهاز مباشرةً. ولكن هناك قيودًا واحدة: لا يمكن تشغيل البرنامج إلا على جهاز يعمل بنظام التشغيل iOS 11 وما فوق.

المشكلة هي أن CoreML لا يفهم سوى التنسيق المحدد لنموذج .coreml . بالنسبة إلى المكتبات الأكثر شيوعًا ، مثل Tensorflow أو Keras أو XGBoost ، يمكن التحويل مباشرةً إلى تنسيق CoreML. لكن بالنسبة إلى Darknet لا يوجد مثل هذا الاحتمال. لتحويل النموذج المحفوظ والمدرب من Darknet إلى CoreML ، يمكنك استخدام خيارات متعددة ، على سبيل المثال ، حفظ Darknet إلى ONNX ، ثم تحويله من ONNX إلى CoreML.

سوف نستخدم طريقة أكثر بساطة وسنستخدم تطبيق Keras لتطبيق YOLOv3. خوارزمية الإجراء هي: تحميل أوزان Darknet في نموذج Keras ، وحفظها بتنسيق Keras وتحويلها مباشرةً إلى CoreML من هذا.

  1. تحميل Darknet. قم بتنزيل ملفات نموذج Darknet-YOLOv3 المدربين من هنا . في هذه المقالة ، سأستخدم بنائين: YOLOv3-416 و YOLOv3-tiny. سنحتاج إلى ملفات cfg والأوزان.
  2. من Darknet إلى Keras. أولاً ، استنساخ المستودع ، انتقل إلى مجلد الريبو وقم بتشغيل الأمر:

    python convert.py yolov3.cfg yolov3.weights yolo.h5 

    حيث قام yolov3.cfg و yolov3.weights بتنزيل ملفات Darknet. نتيجة لذلك ، يجب أن يكون لدينا ملف بالملحق .h5 - هذا هو نموذج YOLOv3 المحفوظ بتنسيق Keras.
  3. من Keras إلى CoreML. بقي الخطوة الأخيرة. لتحويل النموذج إلى CoreML ، تحتاج إلى تشغيل البرنامج النصي python (يجب عليك أولاً تثبيت مكتبة coremltools لـ python):

     import coremltools coreml_model = coremltools.converters.keras.convert( 'yolo.h5', input_names='image', image_input_names='image', input_name_shape_dict={'image': [None, 416, 416, 3]}, #      image_scale=1/255.) coreml_model.input_description['image'] = 'Input image' coreml_model.save('yolo.mlmodel') 

يجب أن يتم تنفيذ الخطوات الموضحة أعلاه للطرازين YOLOv3-416 و YOLOv3-tiny.
عندما قمنا بكل هذا ، لدينا ملفان: yolo.mlmodel و yolo-tiny.mlmodel. الآن يمكنك البدء في كتابة رمز التطبيق نفسه.

إنشاء تطبيق iOS


لن أصف كل رمز التطبيق ، يمكنك رؤيته في المستودع ، وهو رابط سيتم تقديمه في نهاية المقال. اسمحوا لي أن أقول أن لدينا ثلاثة UIViewController واحد: OnlineViewController ، PhotoViewController و SettingsViewController. الأول هو إخراج الكاميرا والكشف عن الكائنات عبر الإنترنت لكل إطار. في الثانية ، يمكنك التقاط صورة أو تحديد صورة من المعرض واختبار الشبكة على هذه الصور. يحتوي الإعداد الثالث على الإعدادات ، يمكنك اختيار طراز YOLOv3-416 أو YOLOv3- الصغير ، وكذلك اختيار عتبات IoU (التقاطع على الاتحاد) وثقة الكائن (احتمال وجود كائن في قسم الصورة الحالية).

تحميل النماذج في CoreML

بعد أن قمنا بتحويل النموذج المدرب من تنسيق Darknet إلى CoreML ، لدينا ملف بالامتداد .mlmodel . في حالتي ، قمت بإنشاء ملفين: yolo.mlmodel و yolo-tiny.mlmodel ، للطرازين YOLOv3-416 و YOLOv3-tiny ، على التوالي. الآن يمكنك تحميل هذه الملفات في مشروع في Xcode.

نقوم بإنشاء فئة ModelProvider ؛ حيث سيتم تخزين النموذج الحالي وطرق استدعاء الشبكة العصبية بشكل غير متزامن للتنفيذ. يتم تحميل النموذج بهذه الطريقة:

  private func loadModel(type: YOLOType) { do { self.model = try YOLO(type: type) } catch { assertionFailure("error creating model") } } 

فئة YOLO مسؤولة مباشرةً عن تحميل ملفات .mlmodel ومعالجة مخرجات الطراز. تنزيل ملفات النماذج:

  var url: URL? = nil self.type = type switch type { case .v3_Tiny: url = Bundle.main.url(forResource: "yolo-tiny", withExtension:"mlmodelc") self.anchors = tiny_anchors case .v3_416: url = Bundle.main.url(forResource: "yolo", withExtension:"mlmodelc") self.anchors = anchors_416 } guard let modelURL = url else { throw YOLOError.modelFileNotFound } do { model = try MLModel(contentsOf: modelURL) } catch let error { print(error) throw YOLOError.modelCreationError } 

نموذج ModelProvider الكامل.
 import UIKit import CoreML protocol ModelProviderDelegate: class { func show(predictions: [YOLO.Prediction]?, stat: ModelProvider.Statistics, error: YOLOError?) } @available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *) class ModelProvider { struct Statistics { var timeForFrame: Float var fps: Float } static let shared = ModelProvider(modelType: Settings.shared.modelType) var model: YOLO! weak var delegate: ModelProviderDelegate? var predicted = 0 var timeOfFirstFrameInSecond = CACurrentMediaTime() init(modelType type: YOLOType) { loadModel(type: type) } func reloadModel(type: YOLOType) { loadModel(type: type) } private func loadModel(type: YOLOType) { do { self.model = try YOLO(type: type) } catch { assertionFailure("error creating model") } } func predict(frame: UIImage) { DispatchQueue.global().async { do { let startTime = CACurrentMediaTime() let predictions = try self.model.predict(frame: frame) let elapsed = CACurrentMediaTime() - startTime self.showResultOnMain(predictions: predictions, elapsed: Float(elapsed), error: nil) } catch let error as YOLOError { self.showResultOnMain(predictions: nil, elapsed: -1, error: error) } catch { self.showResultOnMain(predictions: nil, elapsed: -1, error: YOLOError.unknownError) } } } private func showResultOnMain(predictions: [YOLO.Prediction]?, elapsed: Float, error: YOLOError?) { if let delegate = self.delegate { DispatchQueue.main.async { let fps = self.measureFPS() delegate.show(predictions: predictions, stat: ModelProvider.Statistics(timeForFrame: elapsed, fps: fps), error: error) } } } private func measureFPS() -> Float { predicted += 1 let elapsed = CACurrentMediaTime() - timeOfFirstFrameInSecond let currentFPSDelivered = Double(predicted) / elapsed if elapsed > 1 { predicted = 0 timeOfFirstFrameInSecond = CACurrentMediaTime() } return Float(currentFPSDelivered) } } 


معالجة إخراج الشبكة العصبية

الآن دعونا نتعرف على كيفية معالجة مخرجات الشبكة العصبية والحصول على المربعات المحيطة المقابلة. في Xcode ، إذا حددت ملفًا نموذجيًا ، يمكنك رؤية ما هي عليه ومشاهدة طبقات الإخراج.


الدخول والخروج YOLOv3- صغيرة.


المدخل والخروج YOLOv3-416.

كما ترون في الصورة أعلاه ، لدينا ثلاث طبقات YOLOv3-416 واثنتان لطبقات الإخراج البالغة الصغر YOLOv3 في كل منها يتم توقع مربعات ربط للكائنات المختلفة.
في هذه الحالة ، هذه مجموعة منتظمة من الأرقام ، دعنا نتعرف على كيفية تحليلها.

يستخدم نموذج YOLOv3 ثلاث طبقات كمخرج لتقسيم الصورة إلى شبكة مختلفة ، تحتوي أحجام خلايا هذه الشبكات على القيم التالية: 8 و 16 و 32. لنفترض أن لدينا صورة بحجم 416 × 416 بكسل عند الإدخال ، ثم سيكون لمصفوفات الإخراج (الشبكات) حجم 52 × 52 و 26 × 26 و 13 × 13 (416/8 = 52 و 416/16 = 26 و 416/32 = 13). في حالة YOLOv3-tiny ، كل شيء هو نفسه ، ولكن بدلاً من ثلاث شبكات ، لدينا اثنين: 16 و 32 ، أي ، مصفوفات ذات أبعاد 26 × 26 و 13 × 13.

بعد بدء تشغيل نموذج CoreML المحمل ، نحصل على كائنين (أو ثلاثة) من فئة MLMultiArray في الإخراج. وإذا نظرت إلى خاصية شكل هذه الكائنات ، فسنرى الصورة التالية (على YOLOv3-tiny):

[1،1،255،26،26][1،1،255،13،13]


كما هو متوقع ، سيكون حجم المصفوفات 26 × 26 و 13 × 13. ولكن ماذا يعني الرقم 255؟ كما ذكرنا سابقًا ، تكون طبقات الإخراج هي مصفوفات 52 × 52 و 26 × 26 و 13 × 13. الحقيقة هي أن كل عنصر من عناصر هذه المصفوفة ليس رقمًا ، بل هو ناقل. أي أن طبقة الإخراج عبارة عن مصفوفة ثلاثية الأبعاد. هذا المتجه له البعد B x (5 + C) ، حيث B هو رقم المربع المحيط في الخلية ، C هو عدد الفئات. من أين يأتي الرقم 5؟ والسبب في ذلك هو: بالنسبة لكل box-a ، من المتوقع أن يكون هناك كائن (ثقة الكائن) - هذا هو رقم واحد ، والأربعة المتبقية هي x ، و y ، والعرض والارتفاع للمربع المتوقع a. يوضح الشكل أدناه تمثيلًا تخطيطيًا لهذا المتجه:


تمثيل تخطيطي لطبقة المخرجات (خريطة المعالم).

بالنسبة لشبكتنا المدربة على 80 فصلًا ، يتم التنبؤ بـ 3 مربعات مربعة - أ لكل خلية في شبكة التقسيم ، لكل منها - 80 درجة من الاحتمالات + ثقة الكائن + 4 أرقام مسؤولة عن موضع وحجم هذا المربع - أ. المجموع: 3 × (5 + 80) = 255.

للحصول على هذه القيم من فئة MLMultiArray ، من الأفضل استخدام مؤشر أولي لصفيف بيانات وحساب العنوان:

  let pointer = UnsafeMutablePointer<Double>(OpaquePointer(out.dataPointer)) //    if out.strides.count < 3 { throw YOLOError.strideOutOfBounds } let channelStride = out.strides[out.strides.count-3].intValue let yStride = out.strides[out.strides.count-2].intValue let xStride = out.strides[out.strides.count-1].intValue func offset(ch: Int, x: Int, y: Int) -> Int { //     return ch * channelStride + y * yStride + x * xStride } 

تحتاج الآن إلى معالجة متجه مكون من 255 عنصرًا. لكل مربع ، تحتاج إلى الحصول على توزيع الاحتمال لـ 80 فئة ، يمكنك القيام بذلك باستخدام وظيفة softmax.

ما هو softmax
وظيفة يحول المتجه  mathbbxالبعد K في متجه من نفس البعد ، حيث كل تنسيق  mathbbxiيتم تمثيل المتجه الناتج برقم حقيقي في الفاصل الزمني [0،1] ومجموع الإحداثيات هو 1.

 sigma( mathbbx)i= fracexi sumk=1Kexk

حيث K هو بعد المتجه.

وظيفة Softmax على سويفت:

  private func softmax(_ x: inout [Float]) { let len = vDSP_Length(x.count) var count = Int32(x.count) vvexpf(&x, x, &count) var sum: Float = 0 vDSP_sve(x, 1, &sum, len) vDSP_vsdiv(x, 1, &sum, &x, 1, len) } 

للحصول على إحداثيات وأحجام مربع الإحاطة a ، تحتاج إلى استخدام الصيغ:

x= sigma( hatx)+cxy= sigma( haty)+cyw=pwe hatwh=phe hath


حيث  hatx، haty، hatw، hath- إحداثيات س ص ص المتوقعة والعرض والارتفاع ، على التوالي ،  سيجما(x)هي وظيفة السيني ، و pw،ph- قيم المراسي (المراسي) لثلاثة صناديق. يتم تحديد هذه القيم أثناء التدريب ويتم تعيينها في ملف Helpers.swift:

 let anchors1: [Float] = [116,90, 156,198, 373,326] //     let anchors2: [Float] = [30,61, 62,45, 59,119] //     let anchors3: [Float] = [10,13, 16,30, 33,23] //     


تمثيل تخطيطي لحساب موضع المربع المحيط.

رمز كامل لمعالجة طبقات الإخراج.
  private func process(output out: MLMultiArray, name: String) throws -> [Prediction] { var predictions = [Prediction]() let grid = out.shape[out.shape.count-1].intValue let gridSize = YOLO.inputSize / Float(grid) let classesCount = labels.count print(out.shape) let pointer = UnsafeMutablePointer<Double>(OpaquePointer(out.dataPointer)) if out.strides.count < 3 { throw YOLOError.strideOutOfBounds } let channelStride = out.strides[out.strides.count-3].intValue let yStride = out.strides[out.strides.count-2].intValue let xStride = out.strides[out.strides.count-1].intValue func offset(ch: Int, x: Int, y: Int) -> Int { return ch * channelStride + y * yStride + x * xStride } for x in 0 ..< grid { for y in 0 ..< grid { for box_i in 0 ..< YOLO.boxesPerCell { let boxOffset = box_i * (classesCount + 5) let bbx = Float(pointer[offset(ch: boxOffset, x: x, y: y)]) let bby = Float(pointer[offset(ch: boxOffset + 1, x: x, y: y)]) let bbw = Float(pointer[offset(ch: boxOffset + 2, x: x, y: y)]) let bbh = Float(pointer[offset(ch: boxOffset + 3, x: x, y: y)]) let confidence = sigmoid(Float(pointer[offset(ch: boxOffset + 4, x: x, y: y)])) if confidence < confidenceThreshold { continue } let x_pos = (sigmoid(bbx) + Float(x)) * gridSize let y_pos = (sigmoid(bby) + Float(y)) * gridSize let width = exp(bbw) * self.anchors[name]![2 * box_i] let height = exp(bbh) * self.anchors[name]![2 * box_i + 1] for c in 0 ..< 80 { classes[c] = Float(pointer[offset(ch: boxOffset + 5 + c, x: x, y: y)]) } softmax(&classes) let (detectedClass, bestClassScore) = argmax(classes) let confidenceInClass = bestClassScore * confidence if confidenceInClass < confidenceThreshold { continue } predictions.append(Prediction(classIndex: detectedClass, score: confidenceInClass, rect: CGRect(x: CGFloat(x_pos - width / 2), y: CGFloat(y_pos - height / 2), width: CGFloat(width), height: CGFloat(height)))) } } } return predictions } 


غير الحد الأقصى قمع

بمجرد استلام إحداثيات وأحجام الصناديق المحيطية والاحتمالات المقابلة لجميع الكائنات الموجودة في الصورة ، يمكنك البدء في رسمها أعلى الصورة. ولكن هناك مشكلة واحدة! قد يحدث مثل هذا الموقف عندما يتم توقع العديد من الصناديق ذات الاحتمالات العالية إلى حد ما لكائن واحد. ماذا تفعل في هذه الحالة؟ هنا خوارزمية بسيطة إلى حد ما تسمى قمع غير أقصى يأتي لمساعدتنا.

الخوارزمية هي كما يلي:

  1. نحن نبحث عن مربع محيط به أعلى احتمال للانتماء إلى الكائن.
  2. نحن نذهب من خلال جميع المربعات المحيطة التي تنتمي أيضا إلى هذا الكائن.
  3. نقوم بحذفها إذا كان التقاطع فوق الاتحاد (IoU) مع المربع المحيط الأول أكبر من الحد المحدد.

يتم احتساب IoU باستخدام صيغة بسيطة:

 textIoU= frac textIntersectionArea textAssociationArea
حساب IOU.
  static func IOU(a: CGRect, b: CGRect) -> Float { let areaA = a.width * a.height if areaA <= 0 { return 0 } let areaB = b.width * b.height if areaB <= 0 { return 0 } let intersection = a.intersection(b) let intersectionArea = intersection.width * intersection.height return Float(intersectionArea / (areaA + areaB - intersectionArea)) } 


غير الحد الأقصى قمع.
  private func nonMaxSuppression(boxes: inout [Prediction], threshold: Float) { var i = 0 while i < boxes.count { var j = i + 1 while j < boxes.count { let iou = YOLO.IOU(a: boxes[i].rect, b: boxes[j].rect) if iou > threshold { if boxes[i].score > boxes[j].score { if boxes[i].classIndex == boxes[j].classIndex { boxes.remove(at: j) } else { j += 1 } } else { if boxes[i].classIndex == boxes[j].classIndex { boxes.remove(at: i) j = i + 1 } else { j += 1 } } } else { j += 1 } } i += 1 } } 


بعد ذلك ، يمكن اعتبار العمل مباشرة مع نتائج التنبؤ بالشبكة العصبية مكتملًا. بعد ذلك ، تحتاج إلى كتابة الوظائف والفصول للحصول على لقطات من الكاميرا ، وعرض الصورة على الشاشة وتقديم صناديق الربط المتوقعة. لن أصف كل هذه الشفرة في هذه المقالة ، لكن يمكن عرضها في المستودع.

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

اختبار البرنامج


الآن نحن اختبار التطبيق.

اسمحوا لي أن أذكرك مرة أخرى: هناك ثلاثة عناصر تحكم في ViewView في التطبيق ، واحدة لمعالجة الصور أو اللقطات ، وواحدة لمعالجة دفق الفيديو عبر الإنترنت ، والثالثة لإعداد الشبكة.

لنبدأ مع الثالث. في ذلك يمكنك اختيار واحد من نموذجين YOLOv3-tiny أو YOLOv3-416 ، واختيار حد الثقة وعتبة IoU ، كما يمكنك تمكين أو تعطيل مكافحة التعرجات عبر الإنترنت.


الآن دعنا نرى كيف تعمل الخلايا العصبية المدربة مع صور حقيقية ، لذلك نلتقط صورة من المعرض ونمررها عبر الشبكة. توضح الصورة أدناه نتائج YOLOv3-tiny بإعدادات مختلفة.


أوضاع تشغيل مختلفة من YOLOv3- صغيرة. الصورة اليسرى توضح الوضع المعتاد للعملية. في المنتصف - الحد الأدنى IoU = 1 أي كما لو أن الحد الأقصى غير الأقصى مفقود. على اليمين توجد عتبة منخفضة لثقة الكائن ، أي يتم عرض جميع مربعات الربط الممكنة.

ما يلي هو نتيجة YOLOv3-416. قد تلاحظ أنه مقارنةً بـ YOLOv3-tiny ، فإن الإطارات الناتجة أكثر صحة ، وكذلك يتم التعرف على الكائنات الأصغر في الصورة ، والتي تتوافق مع عمل طبقة الإخراج الثالثة.


معالجة الصورة باستخدام YOLOv3-416.

عندما تم تشغيل وضع التشغيل عبر الإنترنت ، تمت معالجة كل إطار وتم إجراء تنبؤ له ، وتم إجراء الاختبارات على iPhone XS ، لذلك كانت النتيجة مقبولة تمامًا لكلا خياري الشبكة. تنتج YOLOv3-tiny متوسط ​​30 - 32 إطارًا في الثانية ، YOLOv3-416 - من 23 إلى 25 إطارًا في الثانية. يكون الجهاز الذي تم اختباره مثمرًا تمامًا ، لذلك في النماذج السابقة قد تختلف النتائج ، وفي هذه الحالة يفضل استخدام YOLOv3-tiny بالطبع. نقطة مهمة أخرى: يستغرق yolo-tiny.mlmodel (YOLOv3-tiny) حوالي 35 ميغابايت ، بينما يزن yolo.mlmodel (YOLOv3 -16) حوالي 250 ميغابايت ، وهو فرق كبير جدًا.

استنتاج


نتيجة لذلك ، تمت كتابة تطبيق iOS أنه بمساعدة شبكة عصبية يمكنه التعرف على الكائنات الموجودة في الصورة. لقد رأينا كيفية العمل مع مكتبة CoreML وكيفية استخدامها لتنفيذ مختلف النماذج المدربة مسبقًا (بالمناسبة ، يمكنك أيضًا التدريب عليها). تم حل مشكلة التعرف على الكائن باستخدام شبكة YOLOv3. على iPhone XS ، هذه الشبكة (YOLOv3-tiny) قادرة على معالجة الصور على تردد ~ 30 لقطة في الثانية ، وهو ما يكفي تمامًا للتشغيل في الوقت الفعلي.

يمكن الاطلاع على رمز التطبيق الكامل على جيثب .

Source: https://habr.com/ru/post/ar460869/


All Articles