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 Penayangan TensorFlow 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 membuat aplikasi Android sederhana dan membuat prediksi dengan model deteksi objek yang didownload melalui TensorFlow Serve (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 untuk 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 Menambahkan 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 didownload sebelumnya.

Menambahkan dependensi untuk OkHttp dan gRPC

  • Dalam file app/build.gradle project Anda, konfirmasikan 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'
}

Menyinkronkan 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. UI-nya cukup sederhana: ada gambar kucing yang Anda inginkan untuk mendeteksi objek dan pengguna dapat memilih cara untuk 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 Jalankan inferensi, tidak akan terjadi apa-apa. Ini karena belum dapat berkomunikasi dengan backend.

5. Men-deploy model deteksi objek dengan Penayangan TensorFlow

Deteksi objek adalah tugas ML yang sangat umum dan tujuannya adalah untuk mendeteksi objek dalam gambar, yaitu untuk 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 lengkap, 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 Serve:

  1. Download file model.
  2. Kompresi file .tar.gz yang telah 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 Penayangan TensorFlow 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 otomatis mendownload gambar Penayangan TensorFlow terlebih dahulu, yang memerlukan waktu satu menit. Setelah itu, Penayangan TensorFlow akan dimulai. Log akan terlihat seperti cuplikan kode ini:

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 Penayangan TensorFlow melalui REST

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

  • REST
  • gRPC

Mengirim permintaan dan menerima respons melalui REST

Ada tiga langkah sederhana:

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

Anda akan mencapai ini dalam MainActivity.java.

Membuat permintaan REST

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

private Request createRESTRequest() {
}

Penayangan TensorFlow mengharapkan permintaan POST yang berisi tensor gambar untuk model SSD MobileNet yang digunakan, sehingga Anda harus mengekstrak nilai RGB dari setiap piksel gambar ke dalam array lalu menggabungkan array dalam JSON, yang merupakan payload dari 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 Penayangan TensorFlow

Aplikasi ini memungkinkan pengguna memilih REST atau gRPC untuk berkomunikasi dengan TensorFlow Serve, sehingga ada dua cabang dalam pemroses 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 pemroses onClick(View view) untuk menggunakan OkHttp agar dapat mengirim permintaan ke Penayangan 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;
}

Memproses respons REST dari TensorFlow Serving

Model SSDNetNet menampilkan sejumlah hasil, yang mencakup:

  • num_detections: jumlah deteksi
  • detection_scores: skor deteksi
  • detection_classes: indeks class 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 yang diprediksi dari respons, mengidentifikasi kategori objek yang paling mungkin dan koordinat vertex kotak pembatas, dan terakhir merender kotak pembatas deteksi pada UI.

Menjalankan aplikasi

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

Perlu waktu beberapa detik sebelum aplikasi merender kotak pembatas kucing dan menampilkan 17 sebagai kategori objek, yang dipetakan ke objek cat di set data COCO.

5a1a32768dc516d6.png

7. Menghubungkan aplikasi Android dengan TensorFlow Serve melalui gRPC

Selain REST, Penayangan TensorFlow juga mendukung gRPC.

b6f4449c2c850b0e.png

gRPC adalah framework Modern Procedure Call (RPC) modern yang bersifat open source dan berperforma tinggi, yang dapat berjalan di lingkungan apa pun. Layanan ini dapat secara efisien menghubungkan layanan di dan ke seluruh pusat data dengan dukungan yang dapat dicolokkan untuk load balancing, perekaman aktivitas, pemeriksaan kesehatan, dan autentikasi. Telah diamati bahwa dalam praktiknya, gRPC lebih berperforma 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 Penayangan TensorFlow.
  • Ekstrak hasil yang diprediksi dari respons gRPC dan render UI.

Anda akan mencapai ini dalam MainActivity.java.

Opsional: Membuat kode stub klien gRPC

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

a9d0e5cb543467b4.png

Penayangan TensorFlow dan TensorFlow menentukan file .proto untuk Anda. Mulai TensorFlow dan TensorFlow Serve 2.8, file .proto berikut adalah 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 yang diprediksi 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 > Jalankan inferensi.

Perlu waktu beberapa detik sebelum aplikasi merender kotak pembatas kucing dan menampilkan 17 sebagai kategori objek, yang dipetakan ke kategori cat di set data COCO.

8 Selamat

Anda menggunakan Penayangan TensorFlow untuk menambahkan kemampuan deteksi objek ke aplikasi Anda.

Pelajari lebih lanjut