1. Hinweis
In diesem Codelab erfahren Sie, wie Sie mit TensorFlow Serving mit REST und gRPC eine Inferenz zur Objekterkennung über eine Android-App ausführen.
Vorbereitung
- Grundkenntnisse der Android-Entwicklung mit Java
- Grundkenntnisse im Bereich maschinelles Lernen mit TensorFlow, z. B. Training und Bereitstellung
- Grundkenntnisse zu Terminals und Docker
Lerninhalte
- So finden Sie vortrainierte Objekterkennungsmodelle in TensorFlow Hub.
- Hier erfahren Sie, wie Sie eine einfache Android-App erstellen und mit dem heruntergeladenen Objekterkennungsmodell über TensorFlow Serving (REST und gRPC) Vorhersagen treffen.
- So rendern Sie das Erkennungsergebnis in der Benutzeroberfläche.
Voraussetzungen
- Die aktuelle Version von Android Studio
- Docker
- Bash
2. Einrichten
So laden Sie den Code für dieses Codelab herunter:
- Rufen Sie das GitHub-Repository für dieses Codelab auf.
- Klicken Sie auf Code > Download zip, um den gesamten Code für dieses Codelab herunterzuladen.
- Entpacken Sie die heruntergeladene ZIP-Datei, um einen
codelabs
-Stammordner mit allen benötigten Ressourcen zu entpacken.
Für dieses Codelab benötigen Sie nur die Dateien im Unterverzeichnis TFServing/ObjectDetectionAndroid
im Repository, das zwei Ordner enthält:
- Der Ordner
starter
enthält den Startcode, auf dem Sie in diesem Codelab aufbauen. - Der Ordner
finished
enthält den vollständigen Code der fertigen Beispiel-App.
3. Abhängigkeiten zum Projekt hinzufügen
Starter-App in Android Studio importieren
- Klicken Sie in Android Studio auf File > New > Import Project und wählen Sie dann den Ordner
starter
aus dem Quellcode aus, den Sie zuvor heruntergeladen haben.
Abhängigkeiten für OkHttp und gRPC hinzufügen
- Prüfen Sie in der Datei
app/build.gradle
Ihres Projekts, ob die Abhängigkeiten vorhanden sind.
dependencies {
// ...
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation 'javax.annotation:javax.annotation-api:1.3.2'
implementation 'io.grpc:grpc-okhttp:1.29.0'
implementation 'io.grpc:grpc-protobuf-lite:1.29.0'
implementation 'io.grpc:grpc-stub:1.29.0'
}
Projekt mit Gradle-Dateien synchronisieren
- Wählen Sie im Navigationsmenü
Sync Project with Gradle Files (Projekt mit Gradle-Dateien synchronisieren) aus.
4. Start-App ausführen
- Starten Sie den Android-Emulator und klicken Sie dann im Navigationsmenü auf
App ausführen.
App ausführen und ausprobieren
Die App sollte auf Ihrem Android-Gerät gestartet werden. Die Benutzeroberfläche ist recht einfach: Es gibt ein Katzenbild, auf dem Sie Objekte erkennen möchten, und der Nutzer kann auswählen, wie die Daten über REST oder gRPC an das Backend gesendet werden sollen. Das Back-End führt die Objekterkennung für das Bild aus und gibt die Erkennungsergebnisse an die Client-App zurück, die die Benutzeroberfläche noch einmal rendert.
Wenn Sie jetzt auf Run inference (Inferenz ausführen) klicken, passiert nichts. Das liegt daran, dass noch keine Kommunikation mit dem Backend möglich ist.
5. Objekterkennungsmodell mit TensorFlow Serving bereitstellen
Die Objekterkennung ist eine sehr häufige ML-Aufgabe. Ziel ist es, Objekte in Bildern zu erkennen, d. h. mögliche Kategorien der Objekte und Begrenzungsrahmen um sie herum vorherzusagen. Hier ein Beispiel für ein Erkennungsergebnis:
Google hat eine Reihe vortrainierter Modelle auf TensorFlow Hub veröffentlicht. Eine vollständige Liste finden Sie auf der Seite object_detection. In diesem Codelab verwenden Sie das relativ einfache Modell SSD MobileNet V2 FPNLite 320 × 320, sodass Sie nicht unbedingt eine GPU benötigen, um es auszuführen.
So stellen Sie das Objekterkennungsmodell mit TensorFlow Serving bereit:
- Laden Sie die Modelldatei herunter.
- Entpacken Sie die heruntergeladene Datei
.tar.gz
mit einem Dekomprimierungstool wie 7‑Zip. - Erstellen Sie einen
ssd_mobilenet_v2_2_320
-Ordner und darin einen123
-Unterordner. - Verschieben Sie den extrahierten Ordner
variables
und die Dateisaved_model.pb
in den Unterordner123
.
Sie können auf den Ordner ssd_mobilenet_v2_2_320
als Ordner SavedModel
verweisen. 123
ist eine Beispielversionsnummer. Sie können auch eine andere Nummer auswählen.
Die Ordnerstruktur sollte so aussehen:
TensorFlow Serving starten
- Starten Sie TensorFlow Serving mit Docker in Ihrem Terminal, ersetzen Sie aber den Platzhalter
PATH/TO/SAVEDMODEL
durch den absoluten Pfad des Ordnersssd_mobilenet_v2_2_320
auf Ihrem Computer.
docker pull tensorflow/serving docker run -it --rm -p 8500:8500 -p 8501:8501 -v "PATH/TO/SAVEDMODEL:/models/ssd_mobilenet_v2_2" -e MODEL_NAME=ssd_mobilenet_v2_2 tensorflow/serving
Docker lädt zuerst automatisch das TensorFlow Serving-Image herunter. Das dauert etwa eine Minute. Danach sollte TensorFlow Serving gestartet werden. Das Log sollte so aussehen:
2022-02-25 06:01:12.513231: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:206] Restoring SavedModel bundle. 2022-02-25 06:01:12.585012: I external/org_tensorflow/tensorflow/core/platform/profile_utils/cpu_utils.cc:114] CPU Frequency: 3000000000 Hz 2022-02-25 06:01:13.395083: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:190] Running initialization op on SavedModel bundle at path: /models/ssd_mobilenet_v2_2/123 2022-02-25 06:01:13.837562: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:277] SavedModel load for tags { serve }; Status: success: OK. Took 1928700 microseconds. 2022-02-25 06:01:13.877848: I tensorflow_serving/servables/tensorflow/saved_model_warmup_util.cc:59] No warmup data file found at /models/ssd_mobilenet_v2_2/123/assets.extra/tf_serving_warmup_requests 2022-02-25 06:01:13.929844: I tensorflow_serving/core/loader_harness.cc:87] Successfully loaded servable version {name: ssd_mobilenet_v2_2 version: 123} 2022-02-25 06:01:13.985848: I tensorflow_serving/model_servers/server_core.cc:486] Finished adding/updating models 2022-02-25 06:01:13.985987: I tensorflow_serving/model_servers/server.cc:367] Profiler service is enabled 2022-02-25 06:01:13.988994: I tensorflow_serving/model_servers/server.cc:393] Running gRPC ModelServer at 0.0.0.0:8500 ... [warn] getaddrinfo: address family for nodename not supported 2022-02-25 06:01:14.033872: I tensorflow_serving/model_servers/server.cc:414] Exporting HTTP/REST API at:localhost:8501 ... [evhttp_server.cc : 245] NET_LOG: Entering the event loop ...
6. Android-App über REST mit TensorFlow Serving verbinden
Das Backend ist jetzt bereit. Sie können Clientanfragen an TensorFlow Serving senden, um Objekte in Bildern zu erkennen. Es gibt zwei Möglichkeiten, Anfragen an TensorFlow Serving zu senden:
- REST
- gRPC
Anfragen senden und Antworten über REST empfangen
So gehts:
- Erstellen Sie die REST-Anfrage.
- Senden Sie die REST-Anfrage an TensorFlow Serving.
- Extrahieren Sie das vorhergesagte Ergebnis aus der REST-Antwort und rendern Sie die Benutzeroberfläche.
Sie erreichen diese Ziele in MainActivity.java.
.
REST-Anfrage erstellen
Derzeit ist in der Datei MainActivity.java
eine leere createRESTRequest()
-Funktion vorhanden. Sie implementieren diese Funktion, um eine REST-Anfrage zu erstellen.
private Request createRESTRequest() {
}
TensorFlow Serving erwartet eine POST-Anfrage, die den Bildtensor für das verwendete SSD MobileNet-Modell enthält. Sie müssen also die RGB-Werte aus jedem Pixel des Bildes in ein Array extrahieren und das Array dann in ein JSON-Objekt einfügen, das die Nutzlast der Anfrage ist.
- Fügen Sie diesen Code in die Funktion
createRESTRequest()
ein:
//Create the REST request.
int[] inputImg = new int[INPUT_IMG_HEIGHT * INPUT_IMG_WIDTH];
int[][][][] inputImgRGB = new int[1][INPUT_IMG_HEIGHT][INPUT_IMG_WIDTH][3];
inputImgBitmap.getPixels(inputImg, 0, INPUT_IMG_WIDTH, 0, 0, INPUT_IMG_WIDTH, INPUT_IMG_HEIGHT);
int pixel;
for (int i = 0; i < INPUT_IMG_HEIGHT; i++) {
for (int j = 0; j < INPUT_IMG_WIDTH; j++) {
// Extract RBG values from each pixel; alpha is ignored
pixel = inputImg[i * INPUT_IMG_WIDTH + j];
inputImgRGB[0][i][j][0] = ((pixel >> 16) & 0xff);
inputImgRGB[0][i][j][1] = ((pixel >> 8) & 0xff);
inputImgRGB[0][i][j][2] = ((pixel) & 0xff);
}
}
RequestBody requestBody =
RequestBody.create("{\"instances\": " + Arrays.deepToString(inputImgRGB) + "}", JSON);
Request request =
new Request.Builder()
.url("http://" + SERVER + ":" + REST_PORT + "/v1/models/" + MODEL_NAME + ":predict")
.post(requestBody)
.build();
return request;
REST-Anfrage an TensorFlow Serving senden
In der App kann der Nutzer REST oder gRPC für die Kommunikation mit TensorFlow Serving auswählen. Daher gibt es zwei Zweige im onClick(View view)
-Listener.
predictButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
if (requestRadioGroup.getCheckedRadioButtonId() == R.id.rest) {
// TODO: REST request
}
else {
}
}
}
)
- Fügen Sie diesen Code dem REST-Zweig des
onClick(View view)
-Listeners hinzu, um OkHttp zum Senden der Anfrage an TensorFlow Serving zu verwenden:
// Send the REST request.
Request request = createRESTRequest();
try {
client =
new OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.callTimeout(20, TimeUnit.SECONDS)
.build();
Response response = client.newCall(request).execute();
JSONObject responseObject = new JSONObject(response.body().string());
postprocessRESTResponse(responseObject);
} catch (IOException | JSONException e) {
Log.e(TAG, e.getMessage());
responseTextView.setText(e.getMessage());
return;
}
REST-Antwort von TensorFlow Serving verarbeiten
Das SSD MobileNet-Modell gibt eine Reihe von Ergebnissen zurück, darunter:
num_detections
: die Anzahl der erkannten Ereignissedetection_scores
: Erkennungsergebnissedetection_classes
: Der Index der Erkennungsklassedetection_boxes
: die Koordinaten des Begrenzungsrahmens
Sie implementieren die Funktion postprocessRESTResponse()
, um die Antwort zu verarbeiten.
private void postprocessRESTResponse(Predict.PredictResponse response) {
}
- Fügen Sie diesen Code in die Funktion
postprocessRESTResponse()
ein:
// Process the REST response.
JSONArray predictionsArray = responseObject.getJSONArray("predictions");
//You only send one image, so you directly extract the first element.
JSONObject predictions = predictionsArray.getJSONObject(0);
// Argmax
int maxIndex = 0;
JSONArray detectionScores = predictions.getJSONArray("detection_scores");
for (int j = 0; j < predictions.getInt("num_detections"); j++) {
maxIndex =
detectionScores.getDouble(j) > detectionScores.getDouble(maxIndex + 1) ? j : maxIndex;
}
int detectionClass = predictions.getJSONArray("detection_classes").getInt(maxIndex);
JSONArray boundingBox = predictions.getJSONArray("detection_boxes").getJSONArray(maxIndex);
double ymin = boundingBox.getDouble(0);
double xmin = boundingBox.getDouble(1);
double ymax = boundingBox.getDouble(2);
double xmax = boundingBox.getDouble(3);
displayResult(detectionClass, (float) ymin, (float) xmin, (float) ymax, (float) xmax);
Die Nachbearbeitungsfunktion extrahiert nun die vorhergesagten Werte aus der Antwort, ermittelt die wahrscheinlichste Kategorie des Objekts und die Koordinaten der Eckpunkte des umgebenden Rechtecks und rendert schließlich das umgebende Rechteck für die Erkennung in der Benutzeroberfläche.
Ausführen
- Klicken Sie im Navigationsmenü auf
App ausführen und warten Sie, bis die App geladen ist.
- Wählen Sie REST > Run inference (REST > Inferenz ausführen) aus.
Es dauert einige Sekunden, bis die App den Begrenzungsrahmen der Katze rendert und 17
als Kategorie des Objekts anzeigt. Dies entspricht dem cat
-Objekt im COCO-Dataset.
7. Android-App über gRPC mit TensorFlow Serving verbinden
Neben REST unterstützt TensorFlow Serving auch gRPC.
gRPC ist ein modernes Open-Source-Framework für hochleistungsfähige Remote-Prozeduraufrufe (RPCs), das in jeder Umgebung ausgeführt werden kann. Damit lassen sich Dienste in und zwischen Rechenzentren effizient verbinden. Außerdem bietet es Plug-in-Unterstützung für Load-Balancing, Tracing, Systemdiagnosen und Authentifizierung. In der Praxis hat sich gezeigt, dass gRPC leistungsfähiger als REST ist.
Anfragen senden und Antworten mit gRPC empfangen
Dazu sind nur vier einfache Schritte erforderlich:
- Optional: Generieren Sie den gRPC-Client-Stub-Code.
- Erstellen Sie die gRPC-Anfrage.
- Senden Sie die gRPC-Anfrage an TensorFlow Serving.
- Extrahieren Sie das vorhergesagte Ergebnis aus der gRPC-Antwort und rendern Sie die Benutzeroberfläche.
Sie erreichen diese Ziele in MainActivity.java.
.
Optional: gRPC-Client-Stubcode generieren
Wenn Sie gRPC mit TensorFlow Serving verwenden möchten, müssen Sie dem gRPC-Workflow folgen. Weitere Informationen finden Sie in der gRPC-Dokumentation.
TensorFlow Serving und TensorFlow definieren die .proto
-Dateien für Sie. Ab TensorFlow und TensorFlow Serving 2.8 sind die folgenden .proto
-Dateien erforderlich:
tensorflow/core/example/example.proto
tensorflow/core/example/feature.proto
tensorflow/core/protobuf/struct.proto
tensorflow/core/protobuf/saved_object_graph.proto
tensorflow/core/protobuf/saver.proto
tensorflow/core/protobuf/trackable_object_graph.proto
tensorflow/core/protobuf/meta_graph.proto
tensorflow/core/framework/node_def.proto
tensorflow/core/framework/attr_value.proto
tensorflow/core/framework/function.proto
tensorflow/core/framework/types.proto
tensorflow/core/framework/tensor_shape.proto
tensorflow/core/framework/full_type.proto
tensorflow/core/framework/versions.proto
tensorflow/core/framework/op_def.proto
tensorflow/core/framework/graph.proto
tensorflow/core/framework/tensor.proto
tensorflow/core/framework/resource_handle.proto
tensorflow/core/framework/variable.proto
tensorflow_serving/apis/inference.proto
tensorflow_serving/apis/classification.proto
tensorflow_serving/apis/predict.proto
tensorflow_serving/apis/regression.proto
tensorflow_serving/apis/get_model_metadata.proto
tensorflow_serving/apis/input.proto
tensorflow_serving/apis/prediction_service.proto
tensorflow_serving/apis/model.proto
- Fügen Sie der Datei
app/build.gradle
diesen Code hinzu, um den Stub zu generieren.
apply plugin: 'com.google.protobuf'
protobuf {
protoc { artifact = 'com.google.protobuf:protoc:3.11.0' }
plugins {
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.29.0'
}
}
generateProtoTasks {
all().each { task ->
task.builtins {
java { option 'lite' }
}
task.plugins {
grpc { option 'lite' }
}
}
}
}
gRPC-Anfrage erstellen
Ähnlich wie bei der REST-Anfrage erstellen Sie die gRPC-Anfrage in der Funktion createGRPCRequest()
.
private Request createGRPCRequest() {
}
- Fügen Sie diesen Code in die Funktion
createGRPCRequest()
ein:
if (stub == null) {
channel = ManagedChannelBuilder.forAddress(SERVER, GRPC_PORT).usePlaintext().build();
stub = PredictionServiceGrpc.newBlockingStub(channel);
}
Model.ModelSpec.Builder modelSpecBuilder = Model.ModelSpec.newBuilder();
modelSpecBuilder.setName(MODEL_NAME);
modelSpecBuilder.setVersion(Int64Value.of(MODEL_VERSION));
modelSpecBuilder.setSignatureName(SIGNATURE_NAME);
Predict.PredictRequest.Builder builder = Predict.PredictRequest.newBuilder();
builder.setModelSpec(modelSpecBuilder);
TensorProto.Builder tensorProtoBuilder = TensorProto.newBuilder();
tensorProtoBuilder.setDtype(DataType.DT_UINT8);
TensorShapeProto.Builder tensorShapeBuilder = TensorShapeProto.newBuilder();
tensorShapeBuilder.addDim(TensorShapeProto.Dim.newBuilder().setSize(1));
tensorShapeBuilder.addDim(TensorShapeProto.Dim.newBuilder().setSize(INPUT_IMG_HEIGHT));
tensorShapeBuilder.addDim(TensorShapeProto.Dim.newBuilder().setSize(INPUT_IMG_WIDTH));
tensorShapeBuilder.addDim(TensorShapeProto.Dim.newBuilder().setSize(3));
tensorProtoBuilder.setTensorShape(tensorShapeBuilder.build());
int[] inputImg = new int[INPUT_IMG_HEIGHT * INPUT_IMG_WIDTH];
inputImgBitmap.getPixels(inputImg, 0, INPUT_IMG_WIDTH, 0, 0, INPUT_IMG_WIDTH, INPUT_IMG_HEIGHT);
int pixel;
for (int i = 0; i < INPUT_IMG_HEIGHT; i++) {
for (int j = 0; j < INPUT_IMG_WIDTH; j++) {
// Extract RBG values from each pixel; alpha is ignored.
pixel = inputImg[i * INPUT_IMG_WIDTH + j];
tensorProtoBuilder.addIntVal((pixel >> 16) & 0xff);
tensorProtoBuilder.addIntVal((pixel >> 8) & 0xff);
tensorProtoBuilder.addIntVal((pixel) & 0xff);
}
}
TensorProto tensorProto = tensorProtoBuilder.build();
builder.putInputs("input_tensor", tensorProto);
builder.addOutputFilter("num_detections");
builder.addOutputFilter("detection_boxes");
builder.addOutputFilter("detection_classes");
builder.addOutputFilter("detection_scores");
return builder.build();
gRPC-Anfrage an TensorFlow Serving senden
Jetzt können Sie den onClick(View view)
-Listener fertigstellen.
predictButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
if (requestRadioGroup.getCheckedRadioButtonId() == R.id.rest) {
}
else {
// TODO: gRPC request
}
}
}
)
- Fügen Sie diesen Code dem gRPC-Branch hinzu:
try {
Predict.PredictRequest request = createGRPCRequest();
Predict.PredictResponse response = stub.predict(request);
postprocessGRPCResponse(response);
} catch (Exception e) {
Log.e(TAG, e.getMessage());
responseTextView.setText(e.getMessage());
return;
}
gRPC-Antwort von TensorFlow Serving verarbeiten
Ähnlich wie bei gRPC implementieren Sie die Funktion postprocessGRPCResponse()
, um die Antwort zu verarbeiten.
private void postprocessGRPCResponse(Predict.PredictResponse response) {
}
- Fügen Sie diesen Code in die Funktion
postprocessGRPCResponse()
ein:
// Process the response.
float numDetections = response.getOutputsMap().get("num_detections").getFloatValList().get(0);
List<Float> detectionScores = response.getOutputsMap().get("detection_scores").getFloatValList();
int maxIndex = 0;
for (int j = 0; j < numDetections; j++) {
maxIndex = detectionScores.get(j) > detectionScores.get(maxIndex + 1) ? j : maxIndex;
}
Float detectionClass = response.getOutputsMap().get("detection_classes").getFloatValList().get(maxIndex);
List<Float> boundingBoxValues = response.getOutputsMap().get("detection_boxes").getFloatValList();
float ymin = boundingBoxValues.get(maxIndex * 4);
float xmin = boundingBoxValues.get(maxIndex * 4 + 1);
float ymax = boundingBoxValues.get(maxIndex * 4 + 2);
float xmax = boundingBoxValues.get(maxIndex * 4 + 3);
displayResult(detectionClass.intValue(), ymin, xmin, ymax, xmax);
Die Nachbearbeitungsfunktion kann jetzt Vorhersagewerte aus der Antwort extrahieren und das Begrenzungsrechteck für die Erkennung in der Benutzeroberfläche rendern.
Ausführen
- Klicken Sie im Navigationsmenü auf
App ausführen und warten Sie, bis die App geladen ist.
- Wählen Sie gRPC > Run inference (gRPC > Inferenz ausführen) aus.
Es dauert einige Sekunden, bis die App den Begrenzungsrahmen der Katze rendert und 17
als Kategorie des Objekts anzeigt. Diese Kategorie wird im COCO-Dataset der Kategorie cat
zugeordnet.
8. Glückwunsch
Sie haben TensorFlow Serving verwendet, um Ihrer App Funktionen zur Objekterkennung hinzuzufügen.