Membuat aplikasi Android untuk mendeteksi objek dalam gambar

1. Sebelum memulai

Dalam codelab ini, Anda akan mempelajari cara menjalankan inferensi deteksi objek dari aplikasi Android menggunakan TensorFlow Serving dengan REST dan gRPC.

Prasyarat

  • Pengetahuan dasar tentang pengembangan Android dengan Java
  • Pengetahuan dasar tentang machine learning dengan TensorFlow, seperti pelatihan dan deployment
  • Pengetahuan dasar tentang terminal dan Docker

Yang akan Anda pelajari

  • Cara menemukan model deteksi objek terlatih di TensorFlow Hub.
  • Cara mem-build aplikasi Android sederhana dan membuat prediksi dengan model deteksi objek yang didownload melalui TensorFlow Serving (REST dan gRPC).
  • Cara merender hasil deteksi di UI.

Yang Anda butuhkan

2. Memulai persiapan

Untuk mendownload kode codelab ini:

  1. Buka repositori GitHub untuk codelab ini.
  2. Klik Code > Download zip guna mendownload semua kode untuk codelab ini.

a72f2bb4caa9a96.png

  1. Ekstrak file zip yang didownload untuk mengekstrak folder root codelabs dengan semua resource yang Anda butuhkan.

Untuk codelab ini, Anda hanya memerlukan file dalam subdirektori TFServing/ObjectDetectionAndroid di repositori, yang berisi dua folder:

  • Folder starter berisi kode awal yang Anda buat untuk codelab ini.
  • Folder finished berisi kode yang sudah selesai untuk aplikasi contoh yang telah selesai.

3. Tambahkan dependensi ke project

Mengimpor aplikasi awal ke Android Studio

  • Di Android Studio, klik File > New > Import project, lalu pilih folder starter dari kode sumber yang Anda download sebelumnya.

Tambahkan dependensi untuk OkHttp dan gRPC

  • Di file app/build.gradle project Anda, konfirmasi keberadaan dependensi.
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'
}

Sinkronkan project Anda dengan file Gradle

  • Pilih 541e90b497a7fef7.png Sync Project with Gradle Files dari menu navigasi.

4. Menjalankan aplikasi awal

Menjalankan dan menjelajahi aplikasi

Aplikasi akan diluncurkan di perangkat Android Anda. UI-nya cukup sederhana: ada gambar kucing yang ingin Anda deteksi objeknya dan pengguna dapat memilih cara mengirim data ke backend, dengan REST atau gRPC. Backend melakukan deteksi objek pada gambar dan menampilkan hasil deteksi ke aplikasi klien, yang merender UI lagi.

24eab579530e9645.png

Saat ini, jika Anda mengklik Run inference, tidak akan ada yang terjadi. Hal ini karena komunikasi dengan backend belum dapat dilakukan.

5. Men-deploy model deteksi objek dengan TensorFlow Serving

Deteksi objek adalah tugas ML yang sangat umum dan tujuannya adalah mendeteksi objek dalam gambar, yaitu memprediksi kemungkinan kategori objek dan kotak pembatas di sekitarnya. Berikut adalah contoh hasil deteksi:

a68f9308fb2fc17b.png

Google telah memublikasikan sejumlah model terlatih di TensorFlow Hub. Untuk melihat daftar lengkapnya, buka halaman object_detection. Anda menggunakan model SSD MobileNet V2 FPNLite 320x320 yang relatif ringan untuk codelab ini sehingga Anda tidak perlu menggunakan GPU untuk menjalankannya.

Untuk men-deploy model deteksi objek dengan TensorFlow Serving:

  1. Download file model.
  2. Buka kompresi file .tar.gz yang didownload dengan alat dekompresi, seperti 7-Zip.
  3. Buat folder ssd_mobilenet_v2_2_320, lalu buat subfolder 123 di dalamnya.
  4. Masukkan folder variables yang diekstrak dan file saved_model.pb ke dalam subfolder 123.

Anda dapat merujuk ke folder ssd_mobilenet_v2_2_320 sebagai folder SavedModel. 123 adalah contoh nomor versi. Jika mau, Anda dapat memilih nomor lain.

Struktur folder akan terlihat seperti gambar ini:

42c8150a42033767.png

Memulai TensorFlow Serving

  • Di terminal Anda, mulai TensorFlow Serving dengan Docker, tetapi ganti placeholder PATH/TO/SAVEDMODEL dengan jalur absolut folder ssd_mobilenet_v2_2_320 di komputer Anda.
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 akan otomatis mendownload image TensorFlow Serving terlebih dahulu, yang memerlukan waktu sebentar. Setelah itu, TensorFlow Serving akan dimulai. Log akan terlihat seperti cuplikan kode berikut:

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. Menghubungkan aplikasi Android dengan TensorFlow Serving melalui REST

Backend sudah siap, sehingga Anda dapat mengirim permintaan klien ke TensorFlow Serving untuk mendeteksi objek dalam gambar. Ada dua cara untuk mengirim permintaan ke TensorFlow Serving:

  • REST
  • gRPC

Mengirim permintaan dan menerima respons melalui REST

Ada tiga langkah sederhana:

  • Buat permintaan REST.
  • Kirim permintaan REST ke TensorFlow Serving.
  • Ekstrak hasil prediksi dari respons REST dan render UI-nya.

Anda akan mencapainya dalam MainActivity.java.

Buat permintaan REST

Saat ini, ada fungsi createRESTRequest() kosong dalam file MainActivity.java. Anda mengimplementasikan fungsi ini untuk membuat permintaan REST.

private Request createRESTRequest() {
}

TensorFlow Serving mengharapkan permintaan POST yang berisi tensor gambar untuk model SSD MobileNet yang Anda gunakan, jadi Anda perlu mengekstrak nilai RGB dari setiap piksel gambar ke dalam array, lalu membungkus array dalam JSON, yang merupakan payload permintaan.

  • Tambahkan kode ini ke fungsi 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;    

Mengirim permintaan REST ke TensorFlow Serving

Aplikasi ini memungkinkan pengguna memilih REST atau gRPC untuk berkomunikasi dengan TensorFlow Serving, sehingga ada dua cabang di listener onClick(View view).

predictButton.setOnClickListener(
    new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            if (requestRadioGroup.getCheckedRadioButtonId() == R.id.rest) {
                // TODO: REST request
            }
            else {

            }
        }
    }
)
  • Tambahkan kode ini ke cabang REST dari pemroses onClick(View view) untuk menggunakan OkHttp guna mengirim permintaan ke TensorFlow Serving:
// 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;
}

Memproses respons REST dari TensorFlow Serving

Model SSD MobileNet menampilkan sejumlah hasil, yang mencakup:

  • num_detections: jumlah deteksi
  • detection_scores: skor deteksi
  • detection_classes: indeks kelas deteksi
  • detection_boxes: koordinat kotak pembatas

Anda menerapkan fungsi postprocessRESTResponse() untuk menangani respons.

private void postprocessRESTResponse(Predict.PredictResponse response) {

}
  • Tambahkan kode ini ke fungsi 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);

Sekarang, fungsi pascapemrosesan mengekstrak nilai prediksi dari respons, mengidentifikasi kategori objek yang paling mungkin dan koordinat verteks kotak pembatas, dan terakhir merender kotak pembatas deteksi di UI.

Menjalankan aplikasi

  1. Klik execute.png Run 'app' di menu navigasi, lalu tunggu aplikasi dimuat.
  2. Pilih REST > Run inference.

Aplikasi memerlukan waktu beberapa detik sebelum merender kotak pembatas kucing dan menampilkan 17 sebagai kategori objek, yang dipetakan ke objek cat dalam set data COCO.

5a1a32768dc516d6.png

7. Menghubungkan aplikasi Android dengan TensorFlow Serving melalui gRPC

Selain REST, TensorFlow Serving juga mendukung gRPC.

b6f4449c2c850b0e.png

gRPC adalah framework Remote Procedure Call (RPC) modern, open source, dan berperforma tinggi yang dapat berjalan di lingkungan apa pun. Framework ini dapat menghubungkan layanan di dalam dan di seluruh pusat data secara efisien dengan dukungan yang dapat dicocokkan untuk load balancing, tracing, health checking, dan autentikasi. Berdasarkan pengamatan dalam praktiknya, performa gRPC lebih tinggi daripada REST.

Mengirim permintaan dan menerima respons dengan gRPC

Ada empat langkah sederhana:

  • [Opsional] Buat kode stub klien gRPC.
  • Buat permintaan gRPC.
  • Kirim permintaan gRPC ke TensorFlow Serving.
  • Ekstrak hasil yang diprediksi dari respons gRPC dan render UI-nya.

Anda akan mencapainya dalam MainActivity.java.

Opsional: Membuat kode stub klien gRPC

Untuk menggunakan gRPC dengan TensorFlow Serving, Anda harus mengikuti alur kerja gRPC. Untuk mempelajari detailnya lebih lanjut, baca dokumentasi gRPC.

a9d0e5cb543467b4.png

TensorFlow Serving dan TensorFlow menentukan file .proto untuk Anda. Mulai dari TensorFlow dan TensorFlow Serve 2.8, file .proto berikut adalah file yang diperlukan:

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
  • Untuk membuat stub, tambahkan kode ini ke file 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' }
            }
        }
    }
}

Membuat permintaan gRPC

Serupa dengan permintaan REST, Anda membuat permintaan gRPC di fungsi createGRPCRequest().

private Request createGRPCRequest() {

}
  • Tambahkan kode ini ke fungsi 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();

Mengirim permintaan gRPC ke TensorFlow Serving

Sekarang Anda dapat menyelesaikan pemroses onClick(View view).

predictButton.setOnClickListener(
    new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            if (requestRadioGroup.getCheckedRadioButtonId() == R.id.rest) {

            }
            else {
                // TODO: gRPC request
            }
        }
    }
)
  • Tambahkan kode ini ke cabang 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;
}

Memproses respons gRPC dari TensorFlow Serving

Mirip dengan gRPC, Anda menerapkan fungsi postprocessGRPCResponse() untuk menangani respons.

private void postprocessGRPCResponse(Predict.PredictResponse response) {

}
  • Tambahkan kode ini ke fungsi 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);

Sekarang, fungsi pascapemrosesan dapat mengekstrak nilai prediksi dari respons dan merender kotak pembatas deteksi di UI.

Menjalankan aplikasi

  1. Klik execute.png Run 'app' di menu navigasi, lalu tunggu aplikasi dimuat.
  2. Pilih gRPC > Run inference.

Aplikasi memerlukan waktu beberapa detik sebelum merender kotak pembatas kucing dan menampilkan 17 sebagai kategori objek, yang dipetakan ke kategori cat dalam set data COCO.

8. Selamat

Anda telah menggunakan TensorFlow Serving untuk menambahkan kemampuan deteksi objek ke aplikasi Anda.

Pelajari lebih lanjut