Echtzeit-Objekterkennung unter iOS mit YOLOv3


Hallo allerseits!

In diesem Artikel schreiben wir ein kleines Programm zur Lösung des Problems der Objekterkennung und -erkennung (Objekterkennung) in Echtzeit. Das Programm wird in der Programmiersprache Swift für die iOS-Plattform geschrieben. Um Objekte zu erkennen, verwenden wir ein Faltungsnetzwerk mit einer Architektur namens YOLOv3. In diesem Artikel erfahren Sie, wie Sie mit dem CoreML-Framework in iOS mit neuronalen Netzen arbeiten, wie Sie das YOLOv3-Netzwerk verstehen und wie Sie die Ausgaben dieses Netzwerks verwenden und verarbeiten. Wir werden auch die Funktionsweise des Programms überprüfen und verschiedene Varianten von YOLOv3 vergleichen: YOLOv3-tiny und YOLOv3-416.

Quellen werden am Ende des Artikels verfügbar sein, sodass jeder den Betrieb des neuronalen Netzwerks auf seinem Gerät testen kann.

Objekterkennung


Zunächst werden wir kurz verstehen, welche Aufgabe das Erkennen von Objekten (Objekterkennung) im Bild hat und welche Werkzeuge heute dafür verwendet werden. Ich verstehe, dass viele mit diesem Thema ziemlich vertraut sind, aber ich erlaube mir trotzdem, ein wenig darüber zu erzählen.

Nun werden viele Aufgaben im Bereich der Bildverarbeitung mit Hilfe von Faltungs-Neuronalen Netzen (Convolutional Neural Networks), im Folgenden CNN, gelöst. Aufgrund ihrer Struktur extrahieren sie gut Merkmale aus dem Bild. CNNs werden bei der Klassifizierung, Erkennung, Segmentierung und vielen anderen verwendet.

Beliebte CNN-Architekturen zur Objekterkennung:

  • R-CNN. Wir können das erste Modell sagen, das dieses Problem löst. Funktioniert wie ein normaler Bildklassifizierer. Verschiedene Bereiche des Bildes werden dem Netzwerkeingang zugeführt und Vorhersagen für sie gemacht. Sehr langsam, da ein einzelnes Bild mehrere tausend Mal ausgeführt wird.
  • Schnelles R-CNN. Eine verbesserte und schnellere Version von R-CNN arbeitet nach einem ähnlichen Prinzip, aber zuerst wird das gesamte Bild der CNN-Eingabe zugeführt, dann werden Regionen aus der empfangenen internen Darstellung erzeugt. Aber immer noch ziemlich langsam für Echtzeitaufgaben.
  • Schnelleres R-CNN. Der Hauptunterschied zu den vorherigen besteht darin, dass anstelle des selektiven Suchalgorithmus ein neuronales Netzwerk verwendet wird, um Regionen auszuwählen, um sie zu „merken“.
  • YOLO. Ein völlig anderes Funktionsprinzip als die vorherigen verwendet überhaupt keine Regionen. Am schnellsten. Weitere Details dazu werden im Artikel besprochen.
  • SSD Es ähnelt im Prinzip YOLO, verwendet jedoch VGG16 als Netzwerk zum Extrahieren von Funktionen. Auch ziemlich schnell und für Echtzeitarbeit geeignet.
  • Feature Pyramid Networks (FPN). Ein anderer Netzwerktyp wie der Single Shot Detector ist aufgrund der Merkmale der Merkmalsextraktion besser als die SSD kleine Objekte erkennt.
  • RetinaNet. Es verwendet eine Kombination aus FPN + ResNet und bietet dank einer speziellen Fehlerfunktion (Fokusverlust) eine höhere Genauigkeit.

In diesem Artikel verwenden wir die YOLO-Architektur, nämlich die neueste Modifikation, YOLOv3.

Warum YOLO?




YOLO oder You Only Look Once ist die sehr beliebte Architektur von CNN, mit der mehrere Objekte in einem Bild erkannt werden. Ausführlichere Informationen dazu finden Sie auf der offiziellen Website . An derselben Stelle finden Sie Veröffentlichungen, in denen die Theorie und die mathematische Komponente dieses Netzwerks sowie der Prozess seiner Ausbildung ausführlich beschrieben werden.

Das Hauptmerkmal dieser Architektur im Vergleich zu anderen ist, dass die meisten Systeme CNN mehrmals auf verschiedene Bereiche des Bildes anwenden, in YOLO wird CNN einmal auf das gesamte Bild gleichzeitig angewendet. Das Netzwerk unterteilt das Bild in eine Art Raster und sagt Begrenzungsrahmen und die Wahrscheinlichkeit voraus, dass für jeden Abschnitt ein gewünschtes Objekt vorhanden ist.

Der Vorteil dieses Ansatzes besteht darin, dass das Netzwerk das gesamte Bild auf einmal betrachtet und den Kontext bei der Erkennung und Erkennung eines Objekts berücksichtigt. YOLO ist außerdem 1000-mal schneller als R-CNN und etwa 100-mal schneller als Fast R-CNN. In diesem Artikel werden wir ein Netzwerk auf einem mobilen Gerät für die Online-Verarbeitung starten. Dies ist also die wichtigste Qualität für uns.

Weitere Informationen zum Vergleichen von Architekturen finden Sie hier .

YOLOv3


YOLOv3 ist eine erweiterte Version der YOLO-Architektur. Es besteht aus 106 Faltungsschichten und erkennt kleine Objekte besser als sein Vorgänger YOLOv2. Das Hauptmerkmal von YOLOv3 ist, dass sich am Ausgang drei Ebenen befinden, von denen jede Objekte unterschiedlicher Größe erkennt.
Das Bild unten zeigt den schematischen Aufbau:



YOLOv3-tiny - Eine beschnittene Version der YOLOv3-Architektur besteht aus weniger Ebenen (es gibt nur 2 Ausgabeebenen). Es sagt kleinere Objekte schlechter voraus und ist für kleine Datensätze gedacht. Aufgrund der abgeschnittenen Struktur belegen die Netzwerkgewichte jedoch wenig Speicher (~ 35 MB) und erzeugen höhere FPS. Daher ist eine solche Architektur für die Verwendung auf einem mobilen Gerät vorzuziehen.

Wir schreiben ein Programm zur Objekterkennung


Der lustige Teil beginnt!

Erstellen wir eine Anwendung, die mithilfe der Kamera des Telefons verschiedene Objekte im Bild in Echtzeit erkennt. Der gesamte Code wird in der Programmiersprache Swift 4.2 geschrieben und auf einem iOS-Gerät ausgeführt.

In diesem Tutorial nehmen wir ein fertiges Netzwerk mit Skalen, die auf einem COCO- Datensatz vorab trainiert wurden. Es präsentiert 80 verschiedene Klassen. Daher kann unser Neuron 80 verschiedene Objekte erkennen.

Von Darknet zu CoreML


Die ursprüngliche YOLOv3-Architektur wird mithilfe des Darknet- Frameworks implementiert.

Unter iOS gibt es ab Version 11.0 eine wunderbare CoreML- Bibliothek, mit der Sie Modelle für maschinelles Lernen direkt auf dem Gerät ausführen können. Es gibt jedoch eine Einschränkung: Das Programm kann nur auf einem Gerät mit iOS 11 und höher ausgeführt werden.

Das Problem ist, dass CoreML nur das spezifische Format des .coreml- Modells versteht. Für die meisten gängigen Bibliotheken wie Tensorflow, Keras oder XGBoost ist die direkte Konvertierung in das CoreML-Format möglich. Aber für Darknet gibt es keine solche Möglichkeit. Um das gespeicherte und trainierte Modell von Darknet in CoreML zu konvertieren, können Sie verschiedene Optionen verwenden, z. B. Darknet in ONNX speichern und dann von ONNX in CoreML konvertieren.

Wir werden einen einfacheren Weg verwenden und die Keras-Implementierung von YOLOv3 verwenden. Der Aktionsalgorithmus lautet wie folgt: Laden Sie die Darknet-Gewichte in das Keras-Modell, speichern Sie sie im Keras-Format und konvertieren Sie sie direkt in CoreML.

  1. Laden Sie Darknet herunter. Laden Sie hier die Dateien des trainierten Darknet-YOLOv3-Modells herunter. In diesem Artikel werde ich zwei Architekturen verwenden: YOLOv3-416 und YOLOv3-tiny. Wir benötigen sowohl cfg- als auch weight-Dateien.
  2. Von Darknet nach Keras. Klonen Sie zuerst das Repository , wechseln Sie in den Repo-Ordner und führen Sie den folgenden Befehl aus:

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

    Dabei haben yolov3.cfg und yolov3.weights Darknet-Dateien heruntergeladen. Daher sollten wir eine Datei mit der Erweiterung .h5 haben - dies ist das gespeicherte YOLOv3-Modell im Keras-Format.
  3. Von Keras zu CoreML. Der letzte Schritt blieb. Um das Modell in CoreML zu konvertieren, müssen Sie das Python-Skript ausführen (Sie müssen zuerst die Coremltools-Bibliothek für Python installieren):

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

Die oben beschriebenen Schritte müssen für die beiden Modelle YOLOv3-416 und YOLOv3-tiny ausgeführt werden.
Als wir das alles gemacht haben, haben wir zwei Dateien: yolo.mlmodel und yolo-tiny.mlmodel. Jetzt können Sie den Code der Anwendung selbst schreiben.

Erstellen einer iOS-App


Ich werde nicht den gesamten Anwendungscode beschreiben. Sie können ihn im Repository sehen. Ein Link dazu wird am Ende des Artikels angegeben. Lassen Sie mich nur sagen, dass wir drei UIViewController-a haben: OnlineViewController, PhotoViewController und SettingsViewController. Die erste ist die Ausgabe der Kamera und die Online-Erkennung von Objekten für jedes Bild. Im zweiten Schritt können Sie ein Foto aufnehmen oder ein Bild aus der Galerie auswählen und das Netzwerk anhand dieser Bilder testen. Die dritte enthält die Einstellungen. Sie können das YOLOv3-416- oder YOLOv3-winzige Modell sowie die Schwellenwerte IoU (Schnittpunkt über Vereinigung) und Objektvertrauen (die Wahrscheinlichkeit, dass sich ein Objekt im aktuellen Bildabschnitt befindet) auswählen.

Laden von Modellen in CoreML

Nachdem wir das trainierte Modell vom Darknet-Format in CoreML konvertiert haben, haben wir eine Datei mit der Erweiterung .mlmodel . In meinem Fall habe ich zwei Dateien erstellt: yolo.mlmodel und yolo-tiny.mlmodel für die Modelle YOLOv3-416 und YOLOv3-tiny. Jetzt können Sie diese Dateien in ein Projekt in Xcode laden.

Wir erstellen die ModelProvider-Klasse, in der das aktuelle Modell und die Methoden zum asynchronen Aufrufen des neuronalen Netzwerks zur Ausführung gespeichert werden. Das Modell wird folgendermaßen geladen:

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

Die YOLO-Klasse ist direkt für das Laden von .mlmodel- Dateien und die Verarbeitung von Modellausgaben verantwortlich. Modelldateien herunterladen:

  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 } 

Vollständiger ModelProvider-Code.
 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) } } 


Ausgangsverarbeitung für neuronale Netze

Lassen Sie uns nun herausfinden, wie die Ausgaben des neuronalen Netzwerks verarbeitet und die entsprechenden Begrenzungsrahmen abgerufen werden. Wenn Sie in Xcode eine Modelldatei auswählen, können Sie sehen, was diese sind, und die Ausgabeebenen anzeigen.


Ein- und Ausstieg YOLOv3-tiny.


Ein- und Ausgang YOLOv3-416.

Wie Sie im obigen Bild sehen können, haben wir drei für YOLOv3-416 und zwei für YOLOv3-winzige Ausgabeebenen, in denen jeweils Begrenzungsrahmen für verschiedene Objekte vorhergesagt werden.
In diesem Fall handelt es sich um ein reguläres Array von Zahlen. Lassen Sie uns herausfinden, wie es analysiert wird.

Das YOLOv3-Modell verwendet drei Ebenen als Ausgabe, um das Bild in ein anderes Gitter zu unterteilen. Die Zellengrößen dieser Gitter haben die folgenden Werte: 8, 16 und 32. Angenommen, wir haben ein Bild mit einer Größe von 416 x 416 Pixel am Eingang, dann haben die Ausgabematrizen (Gitter) eine Größe von 52 x 52 26 × 26 und 13 × 13 (416/8 = 52, 416/16 = 26 und 416/32 = 13). Im Fall von YOLOv3-tiny ist alles gleich, aber anstelle von drei Gittern haben wir zwei: 16 und 32, dh Matrizen mit den Abmessungen 26x26 und 13x13.

Nach dem Starten des geladenen CoreML-Modells erhalten wir zwei (oder drei) Objekte der MLMultiArray-Klasse in der Ausgabe. Wenn Sie sich die Formeigenschaft dieser Objekte ansehen, sehen Sie das folgende Bild (für YOLOv3-tiny):

[1,1,255,26,26][1,1,255,13,13]


Wie erwartet beträgt die Dimension der Matrizen 26x26 und 13x13. Aber was bedeutet die Zahl 255? Wie bereits erwähnt, sind die Ausgabeebenen 52 x 52, 26 x 26 und 13 x 13 Matrizen. Tatsache ist, dass jedes Element dieser Matrix keine Zahl ist, sondern ein Vektor. Das heißt, die Ausgabeschicht ist eine dreidimensionale Matrix. Dieser Vektor hat die Dimension B x (5 + C), wobei B die Anzahl der Begrenzungsrahmen in der Zelle ist, C die Anzahl der Klassen. Woher kommt die Nummer 5? Der Grund ist folgender: Für jede Box-a ist die Wahrscheinlichkeit, dass ein Objekt (Objektvertrauen) vorhergesagt wird, eine Zahl, und die verbleibenden vier sind x, y, Breite und Höhe für die vorhergesagte Box-a. Die folgende Abbildung zeigt eine schematische Darstellung dieses Vektors:


Schematische Darstellung der Ausgabeebene (Feature Map).

Für unser in 80 Klassen trainiertes Netzwerk werden für jede Zelle des Partitionsgitters 3 Begrenzungsrahmen a vorhergesagt - 80 Klassenwahrscheinlichkeiten + Objektvertrauen + 4 Zahlen, die für die Position und Größe dieses Felds a verantwortlich sind. Gesamt: 3 x (5 + 80) = 255.

Um diese Werte aus der MLMultiArray-Klasse abzurufen, ist es besser, einen Rohzeiger auf ein Datenarray und eine Adressarithmetik zu verwenden:

  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 } 

Jetzt müssen Sie einen Vektor mit 255 Elementen verarbeiten. Für jede Box benötigen Sie eine Wahrscheinlichkeitsverteilung für 80 Klassen. Dies können Sie mit der Softmax-Funktion tun.

Was ist Softmax?
Funktion konvertiert einen Vektor  mathbbxDimension K in einen Vektor der gleichen Dimension, wobei jede Koordinate  mathbbxiDer resultierende Vektor wird durch eine reelle Zahl im Intervall [0,1] dargestellt und die Summe der Koordinaten ist 1.

\ sigma (\ mathbb {x}) _ i = \ frac {e ^ {x_i}} {\ sum_ {k = 1} ^ {K} {e ^ {x_k}}

wobei K die Dimension des Vektors ist.

Softmax-Funktion bei Swift:

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

Um die Koordinaten und Größen des Begrenzungsrahmens a zu erhalten, müssen Sie die folgenden Formeln verwenden:

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


wo  hatx, haty, hatw, hath- vorhergesagte x-, y-Koordinaten, Breite bzw. Höhe  sigma(x)Ist die Sigmoidfunktion und pw,ph- Werte von Ankern (Ankern) für drei Kisten. Diese Werte werden während des Trainings ermittelt und in der Datei Helpers.swift festgelegt:

 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] //     


Schematische Darstellung der Berechnung der Position eines Begrenzungsrahmens.

Vollständiger Code für die Verarbeitung von Ausgabeebenen.
  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 } 


Nicht maximale Unterdrückung

Sobald Sie die Koordinaten und Größen der Begrenzungsrahmen und die entsprechenden Wahrscheinlichkeiten für alle im Bild gefundenen Objekte erhalten haben, können Sie beginnen, sie über das Bild zu zeichnen. Aber es gibt ein Problem! Eine solche Situation kann auftreten, wenn für ein Objekt mehrere Boxen mit ziemlich hohen Wahrscheinlichkeiten vorhergesagt werden. Was ist in diesem Fall zu tun? Hier hilft uns ein ziemlich einfacher Algorithmus namens Nicht maximale Unterdrückung.

Der Algorithmus ist wie folgt:

  1. Wir suchen einen Begrenzungsrahmen mit der höchsten Wahrscheinlichkeit, zum Objekt zu gehören.
  2. Wir durchlaufen alle Begrenzungsrahmen, die ebenfalls zu diesem Objekt gehören.
  3. Wir löschen sie, wenn Intersection over Union (IoU) mit dem ersten Begrenzungsrahmen größer als der angegebene Schwellenwert ist.

IoU wird nach einer einfachen Formel berechnet:

 textIoU= frac textSchnittpunkt textAssoziationsbereich
Berechnung der 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)) } 


Nicht maximale Unterdrückung.
  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 } } 


Danach kann die direkte Arbeit mit den Ergebnissen der Vorhersage des neuronalen Netzwerks als abgeschlossen betrachtet werden. Als Nächstes müssen Sie Funktionen und Klassen schreiben, um das Filmmaterial von der Kamera abzurufen, das Bild auf dem Bildschirm anzuzeigen und die vorhergesagten Begrenzungsrahmen zu rendern. Ich werde diesen Code nicht in diesem Artikel beschreiben, aber er kann im Repository angezeigt werden.

Erwähnenswert ist auch, dass ich bei der Verarbeitung von Online-Bildern eine kleine Glättung der Begrenzungsrahmen hinzugefügt habe. In diesem Fall handelt es sich um die übliche Mittelung der Position und Größe des vorhergesagten Quadrats über die letzten 30 Bilder.

Programm testen


Jetzt testen wir die Anwendung.

Ich möchte Sie noch einmal daran erinnern: Die Anwendung enthält drei ViewController, einen zum Verarbeiten von Fotos oder Schnappschüssen, einen zum Verarbeiten eines Online-Videostreams und einen dritten zum Einrichten des Netzwerks.

Beginnen wir mit dem dritten. Darin können Sie eines von zwei Modellen YOLOv3-tiny oder YOLOv3-416 auswählen, den Vertrauensschwellenwert und den IoU-Schwellenwert auswählen und das Online-Anti-Aliasing aktivieren oder deaktivieren.


Nun wollen wir sehen, wie das trainierte Neuron mit realen Bildern arbeitet. Dazu machen wir ein Foto aus der Galerie und leiten es durch das Netzwerk. Das Bild unten zeigt die Ergebnisse von YOLOv3-tiny mit unterschiedlichen Einstellungen.


Verschiedene Betriebsarten von YOLOv3-tiny. Das linke Bild zeigt die übliche Betriebsart. In der Mitte - der Schwellenwert IoU = 1, d.h. als ob die Nicht-Max-Unterdrückung fehlt. Rechts ist eine niedrige Schwelle des Objektvertrauens, d.h. Alle möglichen Begrenzungsrahmen werden angezeigt.

Das Folgende ist das Ergebnis von YOLOv3-416. Möglicherweise stellen Sie fest, dass im Vergleich zu YOLOv3-tiny die resultierenden Frames korrekter sind und kleinere Objekte im Bild erkannt werden, was der Arbeit der dritten Ausgabeebene entspricht.


Bildverarbeitung mit YOLOv3-416.

Wenn der Online-Betriebsmodus aktiviert war, wurde jeder Frame verarbeitet und eine Vorhersage dafür gemacht. Es wurden Tests auf dem iPhone XS durchgeführt, sodass das Ergebnis für beide Netzwerkoptionen durchaus akzeptabel war. YOLOv3-tiny erzeugt durchschnittlich 30 - 32 fps, YOLOv3-416 - 23 bis 25 fps. Das Gerät, auf dem es getestet wurde, ist sehr produktiv, daher können die Ergebnisse bei früheren Modellen abweichen. In diesem Fall ist es natürlich vorzuziehen, YOLOv3-tiny zu verwenden. Ein weiterer wichtiger Punkt: yolo-tiny.mlmodel (YOLOv3-tiny) benötigt ungefähr 35 MB, während yolo.mlmodel (YOLOv3 -16) ungefähr 250 MB wiegt, was einen sehr signifikanten Unterschied darstellt.

Fazit


Als Ergebnis wurde eine iOS-Anwendung geschrieben, die mit Hilfe eines neuronalen Netzwerks Objekte im Bild erkennen kann. Wir haben gesehen, wie man mit der CoreML-Bibliothek arbeitet und wie man damit verschiedene vorab trainierte Modelle ausführt (man kann übrigens auch damit trainieren). Das Objekterkennungsproblem wurde mithilfe des YOLOv3-Netzwerks gelöst. Auf dem iPhone XS kann dieses Netzwerk (YOLOv3-tiny) Bilder mit einer Frequenz von ~ 30 Bildern pro Sekunde verarbeiten, was für den Echtzeitbetrieb völlig ausreicht.

Der vollständige Anwendungscode kann auf GitHub angezeigt werden.

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


All Articles