Informacje o tym ćwiczeniu (w Codelabs)
1. Zanim zaczniesz
Z tego modułu ćwiczeń dowiesz się, jak uruchomić wnioskowanie wykrywania obiektów na Androidzie za pomocą TensorFlow Serve, REST i gRPC.
Wymagania wstępne
- Podstawowa znajomość programowania na Androida z językiem Java
- Podstawowa wiedza dotycząca systemów uczących się z TensorFlow, np. szkolenia i wdrażanie
- Podstawowe informacje o terminalach i Dockerze
Czego się nauczysz
- Jak znaleźć wytrenowane modele wykrywania obiektów w TensorFlow Hub.
- Jak utworzyć prostą aplikację na Androida i prognozować za pomocą pobranego modelu wykrywania obiektów za pomocą TensorFlow RESS (gRPC) i gRPC.
- Jak renderować wynik wykrywania w interfejsie.
Czego potrzebujesz
- Najnowsza wersja Androida Studio
- Docker
- Bash
2. Konfiguracja
Aby pobrać kod tych ćwiczeń z programowania:
- Przejdź do repozytorium GitHub dla tych ćwiczeń z programowania.
- Kliknij Kod > Pobierz plik ZIP, aby pobrać cały kod do tych ćwiczeń z programowania.
- Rozpakuj pobrany plik ZIP, by rozpakować folder główny
codelabs
zawierający wszystkie potrzebne zasoby.
Na potrzeby tego ćwiczenia z programu potrzebujesz tylko plików z podkatalogu TFServing/ObjectDetectionAndroid
repozytorium, które zawiera 2 foldery:
- Folder
starter
zawiera kod początkowy, który wykorzystasz w tym ćwiczeniu z programowania. - Folder
finished
zawiera pełny kod gotowej aplikacji.
3. Dodaj zależności do projektu
Zaimportuj aplikację startową do Androida Studio
- W Android Studio kliknij kolejno File > New > Import project (Plik & Nowy, Importuj projekt) i wybierz folder
starter
z pobranego wcześniej kodu źródłowego.
Dodaj zależności dla OkHttp i gRPC
- W pliku
app/build.gradle
projektu potwierdź obecność zależności.
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'
}
Synchronizowanie projektu z plikami Gradle
- Wybierz
Sync Project with Gradle Files (Synchronizuj projekt z plikami Gradle) w menu nawigacyjnym.
4. Uruchom aplikację startową
- Uruchom emulator Androida,a następnie w menu nawigacyjnym kliknij
Uruchom „app'”.
Uruchamianie i poznawanie aplikacji
Aplikacja powinna zostać uruchomiona na urządzeniu z Androidem. Interfejs jest bardzo prosty: znajduje się w nim obraz kota, który ma zostać wykryty i który użytkownik może wybrać sposób wysyłania danych do backendu: za pomocą REST lub gRPC. Backend wykonuje wykrywanie obiektu na obrazie i zwraca wyniki wykrywania do aplikacji klienckiej, co ponownie renderuje interfejs użytkownika.
Obecnie jeśli klikniesz Uruchom wnioskowanie, nic się nie stanie. Wynika to z tego, że nie może się on jeszcze komunikować z backendem.
5. Wdrażanie modelu wykrywania obiektów za pomocą wyświetlania w TensorFlow
Wykrywanie obiektów to bardzo częste zadanie, którego celem jest wykrywanie obiektów w obrazach, czyli prognozowanie możliwych kategorii obiektów i otaczających ich pól. Oto wynik wyniku:
W TensorFlow Hub opublikowano wiele wytrenowanych modeli. Aby zobaczyć pełną listę, wejdź na stronę object_detection. W tym ćwiczeniu z programowania korzystasz z względnie prostego modelu SSD MobileNet V2 FPNLite 320 x 320, więc nie musisz używać GPU.
Aby wdrożyć model wykrywania obiektów za pomocą serwera TensorFlow:
- Pobierz plik z modelem.
- Rozpakuj pobrany plik
.tar.gz
za pomocą narzędzia do dekompresji, takiego jak 7-Zip. - Utwórz folder
ssd_mobilenet_v2_2_320
, a następnie utwórz podfolder123
. - Umieść wyodrębniony folder
variables
i pliksaved_model.pb
w podfolderze123
.
Folder ssd_mobilenet_v2_2_320
można nazywać folderem SavedModel
. 123
to przykładowy numer wersji. Jeśli chcesz, możesz wybrać inny numer.
Struktura folderów powinna wyglądać tak:
Rozpocznij udostępnianie TensorFlow
- W terminalu uruchom udostępnianie TensorFlow z Dockerem, zastępując zmienną
PATH/TO/SAVEDMODEL
ścieżką bezwzględną folderussd_mobilenet_v2_2_320
na komputerze.
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 automatycznie pobiera najpierw obraz związany z wyświetlaniem TensorFlow, co zajmuje minutę. Po tym czasie powinno rozpocząć się udostępnianie TensorFlow. Dziennik powinien wyglądać jak ten fragment kodu:
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. Łączenie aplikacji na Androida z TensorFlow Serve przez REST
Backend jest już gotowy, więc możesz wysyłać żądania klienta do usługi TensorFlow, aby wykrywać obiekty w obrazach. Istnieją dwa sposoby wysyłania żądań do obsługi TensorFlow:
- REST
- gRPC
Wysyłanie żądań i odbieranie odpowiedzi przez REST
Aby to zrobić:
- Utwórz żądanie REST.
- Wyślij żądanie REST do serwera TensorFlow Serving.
- Wyodrębnij przewidywany wynik z odpowiedzi REST i wyrenderuj interfejs użytkownika.
Osiągniesz te wyniki w kategorii MainActivity.java.
Utwórz żądanie REST
W tej chwili w pliku MainActivity.java
jest pusta funkcja createRESTRequest()
. Zaimplementujesz tę funkcję, aby utworzyć żądanie REST.
private Request createRESTRequest() {
}
Udostępnianie w TensorFlow oczekuje na żądanie POST zawierające tensor obrazu dla używanego modelu SSD MobileNet. Musisz wyodrębnić wartości RGB z każdego piksela obrazu do tablicy, a następnie umieścić ją w formacie JSON, który jest ładunkiem żądania.
- Dodaj ten kod do funkcji
createRESTRequest()
:
//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;
Wyślij żądanie REST do serwera TensorFlow
Aplikacja umożliwia użytkownikowi wybranie REST lub gRPC do komunikowania się z obsługą TensorFlow, dlatego odbiornik onClick(View view)
ma 2 gałęzie.
predictButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
if (requestRadioGroup.getCheckedRadioButtonId() == R.id.rest) {
// TODO: REST request
}
else {
}
}
}
)
- Dodaj ten kod do gałęzi REST detektora
onClick(View view)
, aby użyć polecenia OKHttp do wysłania żądania do serwera TensorFlow:
// 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;
}
Przetwórz odpowiedź REST z wyświetlania w TensorFlow
Model SSD MobileNet zwraca wiele wyników, które obejmują:
num_detections
: liczba wykryć.detection_scores
: wyniki wykrywaniadetection_classes
: indeks klasy wykrywaniadetection_boxes
: współrzędne ramki ograniczającej
Implementujesz funkcję postprocessRESTResponse()
do obsługi odpowiedzi.
private void postprocessRESTResponse(Predict.PredictResponse response) {
}
- Dodaj ten kod do funkcji
postprocessRESTResponse()
:
// 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);
Obecnie funkcja przetwarzania z wyprzedzeniem przetwarza wyodrębnione wartości z odpowiedzi, identyfikuje najbardziej prawdopodobną kategorię obiektu i współrzędne granic granic, a na koniec renderuje pole wykrywania w interfejsie.
Uruchom
- Kliknij
Uruchom „app' w menu nawigacyjnym, a następnie zaczekaj na załadowanie aplikacji.
- Wybierz REST > Uruchom wnioskowanie.
Zanim aplikacja wyrenderuje ramkę ograniczającą kota i wyświetli 17
jako kategorię obiektu, która jest mapowana na obiekt cat
w zbiorze danych COCO, może upłynąć kilka sekund.
7. Łączenie aplikacji na Androida z udostępnianiem danych w TensorFlow za pomocą gRPC
Oprócz REST, TensorFlow Serving obsługuje też gRPC.
gRPC to nowoczesna platforma RPC o wysokiej wydajności, która może działać w dowolnym środowisku. Może skutecznie łączyć usługi w centrach danych i z innych, a także korzystać z wtyczek z obsługą równoważenia obciążenia, śledzenia, kontroli stanu i uwierzytelniania. Zaobserwowano, że gRPC jest w praktyce bardziej wydajny niż REST.
Wysyłanie żądań i odbieranie odpowiedzi za pomocą gRPC
Wystarczą 4 proste kroki:
- [Opcjonalnie] Wygeneruj kod klienta klienta gRPC.
- Utwórz żądanie gRPC.
- Wyślij żądanie gRPC do wyświetlania przez TensorFlow.
- Wyodrębnij przewidywany wynik z odpowiedzi gRPC i wyrenderuj interfejs użytkownika.
Osiągniesz te wyniki w kategorii MainActivity.java.
Opcjonalnie: wygeneruj kod wewnętrzny klienta gRPC
Aby używać gRPC z udostępnianiem za pomocą TensorFlow, musisz wykonać przepływ pracy gRPC. Więcej informacji znajdziesz w dokumentacji gRPC.
Pliki TensorFlow i TensorFlow definiują pliki .proto
. Od TensorFlow 2.8 i TensorFlow 2.8 potrzebne są te pliki .proto
:
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
- Aby wygenerować wycinek, dodaj ten kod do pliku
app/build.gradle
.
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' }
}
}
}
}
Utwórz żądanie gRPC
Podobne do żądania REST tworzysz żądanie gRPC w funkcji createGRPCRequest()
.
private Request createGRPCRequest() {
}
- Dodaj ten kod do funkcji
createGRPCRequest()
:
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();
Wyślij żądanie gRPC do udostępniania w TensorFlow
Teraz możesz zakończyć działanie detektora: onClick(View view)
.
predictButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
if (requestRadioGroup.getCheckedRadioButtonId() == R.id.rest) {
}
else {
// TODO: gRPC request
}
}
}
)
- Dodaj ten kod do gałęzi gRPC:
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;
}
Przetwórz odpowiedź gRPC z udostępniania w TensorFlow
Podobnie jak w przypadku gRPC implementowanie funkcji postprocessGRPCResponse()
w celu obsługi odpowiedzi
private void postprocessGRPCResponse(Predict.PredictResponse response) {
}
- Dodaj ten kod do funkcji
postprocessGRPCResponse()
:
// 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);
Teraz funkcja postprocessing może wyodrębnić przewidywane wartości z odpowiedzi i wyrenderować ramkę wykrywania w interfejsie.
Uruchom
- Kliknij
Uruchom „app' w menu nawigacyjnym, a następnie zaczekaj na załadowanie aplikacji.
- Wybierz gRPC > Uruchom wnioskowanie.
Zanim aplikacja wyświetli ramkę ograniczającą kota i wyświetli kategorię 17
obiektu, który zostanie odwzorowany na kategorię cat
w zbiorze danych COCO, może upłynąć kilka sekund.
8. Gratulacje
Dzięki wykorzystaniu TensorFlow udało się dodać funkcje wykrywania obiektów do aplikacji.