Edge für iOS bereitstellen ­­­­­– Anleitung

Umfang

In dieser Anleitung laden Sie ein exportiertes, benutzerdefiniertes TensorFlow Lite-Modell von AutoML Vision Edge herunter. Anschließend führen Sie eine vorab entwickelte iOS-App aus, die anhand des Modells Bilder von Blumen erkennt.

Screenshot vom Endprodukt auf Mobilgerät
Bildnachweis: Felipe Venâncio, "from my mother's garden" (CC BY 2.0, in App angezeigtes Bild)

Ziele

In dieser umfassenden Schritt-für-Schritt-Anleitung für den Einstieg verwenden Sie Code für die folgende Aufgabe:

  • Vortrainiertes Modell mit dem TFLite-Interpreter in einer iOs-App ausführen

Hinweise

TensorFlow installieren

Bevor Sie mit der Anleitung beginnen, müssen Sie verschiedene Softwarekomponenten installieren:

Wenn Sie eine funktionierende Python-Installation haben, führen Sie die folgenden Befehle aus, um diese Software herunterzuladen:

pip install --upgrade  "tensorflow==1.7.*"
pip install PILLOW

Git-Repository klonen

Mit dem folgenden Befehl können Sie das Git-Repository über die Befehlszeile klonen:

git clone https://github.com/googlecodelabs/tensorflow-for-poets-2

Rufen Sie das Verzeichnis des lokalen Klons des Repositorys (Verzeichnis tensorflow-for-poets-2) auf. Aus diesem Verzeichnis führen Sie alle folgenden Codebeispiele aus:

cd tensorflow-for-poets-2

iOS-App einrichten

Für die Demo-iOS-App sind mehrere zusätzliche Tools erforderlich:

  1. Xcode
  2. Xcode-Befehlszeilentools
  3. CocoaPods

Xcode herunterladen

Verwenden Sie diesen Link, um Xcode auf Ihrem Computer herunterzuladen.

Xcode-Befehlszeilentools installieren

Führen Sie den folgenden Befehl aus, um die Xcode-Befehlszeilentools zu installieren:

xcode-select --install

Cocoapods installieren

CocoaPods verwendet Ruby, das standardmäßig unter macOS installiert ist.

Führen Sie den folgenden Befehl aus, um CocoaPods zu installieren:

sudo gem install cocoapods
Install TFLite Cocoapod

Der Rest dieses Codelabs muss direkt in macOS ausgeführt werden. Schließen Sie das Docker-Plug-in jetzt. Mit Strg + D verlassen Sie Docker.

Verwenden Sie den folgenden Befehl, um TensorFlow Lite zu installieren und die Datei .xcworkspace mit CocoaPods zu erstellen:

pod install --project-directory=ios/tflite/

Öffnen Sie das Projekt mit Xcode. Sie können das Projekt entweder über die Befehlszeile oder über die Benutzeroberfläche öffnen.

Führen Sie den folgenden Befehl aus, um das Projekt über die Befehlszeile zu öffnen:

open ios/tflite/tflite_photos_example.xcworkspace

Um das Projekt über die Benutzeroberfläche zu öffnen, starten Sie Xcode und klicken Sie auf die Schaltfläche "Open another Project".

Xcode-Benutzeroberfläche

Öffnen Sie nach dem Öffnen des Projekts die Datei .xcworkspace und nicht die Datei .xcproject.

Ursprüngliche App ausführen

Die App ist ein einfaches Beispiel für die Ausführung eines Bilderkennungsmodells im iOS-Simulator. Die App liest aus der Fotogalerie, da der Simulator die Kameraeingabe nicht unterstützt.

Bevor Sie Ihr benutzerdefiniertes Modell einfügen, testen Sie die Basisversion der App. Diese Version verwendet das MobileNet-Standardmodell, das mithilfe von 1.000 ImageNet-Kategorien vortrainiert wurde.

Wählen Sie zum Starten der App im Simulator rechts oben im Xcode-Fenster die Schaltfläche Play Xcode-Wiedergabesymbol (Wiedergabe) aus.

Mit der Schaltfläche Next Photo (Nächstes Foto) werden die Fotos auf dem Gerät nacheinander angezeigt.

Sie können Fotos zur Fotogalerie des Geräts hinzufügen, indem Sie diese per Drag-and-drop in das Simulatorfenster ziehen.

Das Ergebnis sollte Annotationen wie auf dem folgenden Bild enthalten:

Screenshot: Testlauf der App

Angepasste App ausführen

Bei der ursprünglichen App-Einrichtung werden Bilder mithilfe des MobileNet-Standardmodells in eine der 1.000 ImageNet-Klassen eingeteilt.

Ändern Sie die App so, dass sie das neu trainierte Modell mit benutzerdefinierten Bildkategorien für Objekte verwendet:

Modelldateien zum Projekt hinzufügen

Das Demoprojekt ist so konfiguriert, dass im Verzeichnis android/tflite/app/src/main/assets/ nach den Dateien graph.lite und labels.txt gesucht wird.

Führen Sie den folgenden Befehl aus, um diese beiden Dateien durch Ihre Versionen zu ersetzen:

cp tf_files/optimized_graph.lite ios/tflite/data/graph.lite
cp tf_files/retrained_labels.txt ios/tflite/data/labels.txt

App ausführen

Wählen Sie für einen Neustart der App im Simulator rechts oben im Xcode-Fenster die Schaltfläche Play Xcode-Wiedergabesymbol (Wiedergabe) aus.

Fügen Sie zum Testen der Änderungen die Bilddateien aus dem Verzeichnis flower_photos/ hinzu und rufen Sie Vorhersagen ab.

Die Ergebnisse sollten in etwa so aussehen:

Screenshot vom Endprodukt auf Mobilgerät
Bildnachweis: Felipe Venâncio, "from my mother's garden" (CC BY 2.0, in der App angezeigtes Bild)

Beachten Sie, dass die Standardbilder keine Blumen zeigen.

Fügen Sie zum Testen des Modells die zuvor heruntergeladenen Trainingsdatenbilder hinzu oder laden Sie Bilder aus einer Google-Suche herunter, um Vorhersagen zu treffen.

Funktionsweise

Sehen Sie sich, nachdem Sie die App ausgeführt haben, den TensorFlow Lite-spezifischen Code an.

TensorFlowLite-Pod

Diese App verwendet einen vorkompilierten TFLite CocoaPod. Die Pod-Datei enthält CocoaPod im Projekt:

Podfile

platform :ios, '8.0'
inhibit_all_warnings!

target 'tflite_photos_example'
       pod 'TensorFlowLite'

Die Code-Schnittstelle zu TFLite ist in der Datei CameraExampleViewController.mm enthalten.

Einrichtung

Der erste relevante Block (nach den erforderlichen Importen) ist die Methode viewDidLoad:

CameraExampleViewController.mm

#include "tensorflow/contrib/lite/kernels/register.h"
#include "tensorflow/contrib/lite/model.h"
#include "tensorflow/contrib/lite/string_util.h"
#include "tensorflow/contrib/lite/tools/mutable_op_resolver.h"

...

- (void)viewDidLoad {
  [super viewDidLoad];
  labelLayers = [[NSMutableArray alloc] init];

  NSString* graph_path = FilePathForResourceName(model_file_name, model_file_type);
  model = tflite::FlatBufferModel::BuildFromFile([graph_path UTF8String]);
  if (!model) {
    LOG(FATAL) << "Failed to mmap model " << graph_path;
  }
  LOG(INFO) << "Loaded model " << graph_path;
  model->error_reporter();
  LOG(INFO) << "resolved reporter";

  ...

Die wichtigste Zeile in dieser ersten Hälfte der Methode ist die Zeile model = tflite::FlatBufferModel::BuildFromFile([graph_path UTF8String]);. Dieser Code erstellt ein FlatBufferModel aus der Grafikdatei.

Ein FlatBuffer ist eine speicherzuordnende Datenstruktur. Dies ist eines der wichtigsten Features von TFLite, da es dem System eine bessere Verwaltung des vom Modell verwendeten Arbeitsspeichers ermöglicht. Das System kann Teile des Modells transparent nach Bedarf zum Speicher hinzufügen oder daraus entfernen.

Im zweiten Teil der Methode wird ein Interpreter für das Modell erstellt, der Op-Implementierungen an die Grafikdatenstruktur anhängt, die Sie zuvor geladen haben:

CameraExampleViewController.mm

- (void)viewDidLoad {
  ...

  tflite::ops::builtin::BuiltinOpResolver resolver;
  LoadLabels(labels_file_name, labels_file_type, &labels);

  tflite::InterpreterBuilder(*model, resolver)(&interpreter);
  if (!interpreter) {
    LOG(FATAL) << "Failed to construct interpreter";
  }
  if (interpreter->AllocateTensors() != kTfLiteOk) {
    LOG(FATAL) << "Failed to allocate tensors!";
  }

  [self attachPreviewLayer];
}

Wenn Sie mit TensorFlow in Python vertraut sind, entspricht dies in etwa der Erstellung einer tf.Session().

Modell ausführen

Die Methode UpdatePhoto verarbeitet alle Details zum Abrufen des nächsten Fotos, Aktualisieren des Vorschaufensters und Ausführen des Modells auf dem Foto.

CameraExampleViewController.mm

- (void)UpdatePhoto{
  PHAsset* asset;
  if (photos==nil || photos_index >= photos.count){
    [self updatePhotosLibrary];
    photos_index=0;
  }
  if (photos.count){
    asset = photos[photos_index];
    photos_index += 1;
    input_image = [self convertImageFromAsset:asset
                                   targetSize:CGSizeMake(wanted_input_width, wanted_input_height)
                                         mode:PHImageContentModeAspectFill];
    display_image = [self convertImageFromAsset:asset
                                     targetSize:CGSizeMake(asset.pixelWidth,asset.pixelHeight)
                                           mode:PHImageContentModeAspectFit];
    [self DrawImage];
  }

  if (input_image != nil){
    image_data image = [self CGImageToPixels:input_image.CGImage];
    [self inputImageToModel:image];
    [self runModel];
  }
}

Die letzten drei Zeilen sind für uns relevant.

Mit der Methode CGImageToPixels wird das von der iOS-Fotogalerie zurückgegebene CGImage in eine einfache Struktur umgewandelt, die die Breite, die Höhe, die Kanäle und die Pixeldaten enthält.

CameraExampleViewController.h

typedef struct {
  int width;
  int height;
  int channels;
  std::vector<uint8_t> data;
} image_data;

Mit der Methode inputImageToModel wird das Bild in den Arbeitsspeicher des Interpreters eingefügt. Dazu gehört auch die Größenanpassung des Bildes und die Anpassung der Pixelwerte an die Erwartungen des Modells.

CameraExampleViewController.mm

- (void)inputImageToModel:(image_data)image{
  float* out = interpreter->typed_input_tensor<float>(0);

  const float input_mean = 127.5f;
  const float input_std = 127.5f;
  assert(image.channels >= wanted_input_channels);
  uint8_t* in = image.data.data();

  for (int y = 0; y < wanted_input_height; ++y) {
    const int in_y = (y * image.height) / wanted_input_height;
    uint8_t* in_row = in + (in_y * image.width * image.channels);
    float* out_row = out + (y * wanted_input_width * wanted_input_channels);
    for (int x = 0; x < wanted_input_width; ++x) {
      const int in_x = (x * image.width) / wanted_input_width;
      uint8_t* in_pixel = in_row + (in_x * image.channels);
      float* out_pixel = out_row + (x * wanted_input_channels);
      for (int c = 0; c < wanted_input_channels; ++c) {
        out_pixel[c] = (in_pixel[c] - input_mean) / input_std;
      }
    }
  }
}

Da das Modell nur eine einzige Eingabe hat, fordert die Zeile float* out = interpreter->typed_input_tensor<float>(0); beim Interpreter einen Zeiger auf den Speicher für Eingabe 0 an. Der Rest der Methode verarbeitet die Zeigerarithmetik und die Pixelskalierung, um die Daten in dieses Eingabe-Array zu kopieren.

Abschließend führt die Methode runModel das Modell aus:

CameraExampleViewController.mm

- (void)runModel {
  double startTimestamp = [[NSDate new] timeIntervalSince1970];
  if (interpreter->Invoke() != kTfLiteOk) {
    LOG(FATAL) << "Failed to invoke!";
  }
  double endTimestamp = [[NSDate new] timeIntervalSince1970];
  total_latency += (endTimestamp - startTimestamp);
  total_count += 1;
  NSLog(@"Time: %.4lf, avg: %.4lf, count: %d", endTimestamp - startTimestamp,
        total_latency / total_count,  total_count);

  ...

}

Anschließend werden die Ergebnisse von runModel zurückgelesen. Zu diesem Zweck wird beim Interpreter ein Zeiger auf die Daten des Ausgabe-Arrays angefordert. Die Ausgabe ist ein einfaches Array aus Gleitkommazahlen. Die Methode GetTopN verarbeitet die Extrahierung der obersten fünf Ergebnisse (mithilfe einer Prioritätswarteschlange).

CameraExampleViewController.mm

- (void)runModel {
  ...

  const int output_size = (int)labels.size();
  const int kNumResults = 5;
  const float kThreshold = 0.1f;

  std::vector<std::pair<float, int>> top_results;

  float* output = interpreter->typed_output_tensor<float>(0);
  GetTopN(output, output_size, kNumResults, kThreshold, &top_results);

  ...
}

In den nächsten paar Zeilen werden die fünf ersten (probability, class_id)-Paare einfach in (probability, label)-Paare umgewandelt und dann asynchron an die Methode setPredictionValues übergeben, die den Bildschirmbericht aktualisiert:

CameraExampleViewController.mm

- (void)runModel {
  ...

  std::vector<std::pair<float, std::string>> newValues;
  for (const auto& result : top_results) {
    std::pair<float, std::string> item;
    item.first = result.first;
    item.second = labels[result.second];

    newValues.push_back(item);
  }
  dispatch_async(dispatch_get_main_queue(), ^(void) {
    [self setPredictionValues:newValues];
  });
}

Nächste Schritte

Sie haben jetzt eine Schritt-für-Schritt-Anleitung für eine iOS-App zur Klassifizierung von Blumen mithilfe eines Edge-Modells durchgearbeitet. Sie haben ein trainiertes Edge TensorFlow Lite-Modell verwendet, um eine App zur Bildklassifizierung zu testen, bevor Sie Änderungen daran vorgenommen und Beispielannotationen erhalten haben. Anschließend haben Sie TensorFlow Lite-spezifischen Code untersucht, um die zugrunde liegende Funktionalität nachzuvollziehen.

Die folgenden Informationen helfen Ihnen dabei, sich weiter mit TensorFlow-Modellen und AutoML Vision Edge vertraut zu machen: