Resimlerdeki nesneleri algılamak için Android uygulaması oluşturma

1. Başlamadan önce

Bu codelab'de, REST ve gRPC ile TensorFlow Serving kullanarak bir Android uygulamasından nesne algılama çıkarımı çalıştırmayı öğreneceksiniz.

Ön koşullar

  • Java ile Android geliştirme hakkında temel bilgiler
  • Eğitim ve dağıtım gibi TensorFlow ile makine öğrenimi hakkında temel bilgiler
  • Terminaller ve Docker hakkında temel bilgiler

Neler öğreneceksiniz?

  • TensorFlow Hub'da önceden eğitilmiş nesne algılama modellerini bulma
  • Basit bir Android uygulaması oluşturma ve TensorFlow Serving (REST ve gRPC) aracılığıyla indirilen nesne algılama modeliyle tahmin yapma
  • Algılama sonucunu kullanıcı arayüzünde nasıl oluşturacağınız.

İhtiyacınız olanlar

2. Hazırlanın

Bu codelab'in kodunu indirmek için:

  1. Bu codelab'in GitHub deposuna gidin.
  2. Bu codelab'in tüm kodunu indirmek için Code > Download zip'i (Kod > Zip dosyasını indir) tıklayın.

a72f2bb4caa9a96.png

  1. İndirilen ZIP dosyasını açarak ihtiyacınız olan tüm kaynakların bulunduğu bir codelabs kök klasörünü çıkarın.

Bu codelab için depodaki TFServing/ObjectDetectionAndroid alt dizinindeki dosyalar yeterlidir. Bu dizinde iki klasör bulunur:

  • starter klasörü, bu codelab'de temel alacağınız başlangıç kodunu içerir.
  • finished klasöründe, tamamlanmış örnek uygulamanın kodu yer alır.

3. Bağımlılıkları projeye ekleyin

Başlangıç uygulamasını Android Studio'ya aktarma

  • Android Studio'da File > New > Import project'i (Dosya > Yeni > Projeyi içe aktar) tıklayın ve daha önce indirdiğiniz kaynak kodundan starter klasörünü seçin.

OkHttp ve gRPC için bağımlılıkları ekleyin

  • Projenizin app/build.gradle dosyasında bağımlılıkların bulunduğunu doğrulayın.
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'
}

Projenizi Gradle dosyalarıyla senkronize etme

  • Gezinme menüsünden 541e90b497a7fef7.png Sync Project with Gradle Files'ı (Projeyi Gradle dosyalarıyla senkronize et) seçin.

4. Başlangıç uygulamasını çalıştırma

Uygulamayı çalıştırıp keşfetme

Uygulama, Android cihazınızda başlatılmalıdır. Kullanıcı arayüzü oldukça basittir: Nesneleri algılamak istediğiniz bir kedi resmi vardır ve kullanıcı, verileri arka uca gönderme yöntemini REST veya gRPC ile seçebilir. Arka uç, resimde nesne algılama işlemi gerçekleştirir ve algılama sonuçlarını istemci uygulamasına döndürür. İstemci uygulaması da kullanıcı arayüzünü tekrar oluşturur.

24eab579530e9645.png

Şu anda Run inference'ı (Çıkarım çalıştır) tıkladığınızda hiçbir şey olmaz. Bunun nedeni, arka uçla henüz iletişim kurulamamasıdır.

5. TensorFlow Serving ile nesne algılama modeli dağıtma

Nesne algılama, çok yaygın bir makine öğrenimi görevidir. Amacı, görüntülerdeki nesneleri algılamak, yani nesnelerin olası kategorilerini ve etraflarındaki sınırlayıcı kutuları tahmin etmektir. Algılama sonucuna bir örnek:

a68f9308fb2fc17b.png

Google, TensorFlow Hub'da önceden eğitilmiş çeşitli modeller yayınlamıştır. Listenin tamamını görmek için object_detection sayfasını ziyaret edin. Bu codelab'de, çalıştırmak için GPU kullanmanız gerekmeyecek şekilde nispeten hafif olan SSD MobileNet V2 FPNLite 320x320 modelini kullanacaksınız.

Nesne algılama modelini TensorFlow Serving ile dağıtmak için:

  1. Model dosyasını indirin.
  2. İndirilen .tar.gz dosyasını 7-Zip gibi bir sıkıştırma açma aracıyla açın.
  3. ssd_mobilenet_v2_2_320 klasörü oluşturun ve bu klasörün içinde 123 alt klasörünü oluşturun.
  4. Ayıklanan variables klasörünü ve saved_model.pb dosyasını 123 alt klasörüne yerleştirin.

ssd_mobilenet_v2_2_320 klasörünü SavedModel klasörü olarak adlandırabilirsiniz. 123, örnek bir sürüm numarasıdır. İsterseniz başka bir numara seçebilirsiniz.

Klasör yapısı aşağıdaki resimdeki gibi olmalıdır:

42c8150a42033767.png

TensorFlow Serving'i başlatma

  • Terminalinizde Docker ile TensorFlow Serving'i başlatın ancak PATH/TO/SAVEDMODEL yer tutucusunu bilgisayarınızdaki ssd_mobilenet_v2_2_320 klasörünün mutlak yoluyla değiştirin.
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, önce TensorFlow Serving görüntüsünü otomatik olarak indirir. Bu işlem bir dakika sürer. Ardından TensorFlow Serving başlatılmalıdır. Günlük, aşağıdaki kod snippet'i gibi görünmelidir:

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 uygulamasını REST üzerinden TensorFlow Serving'e bağlama

Arka uç artık hazır. Bu nedenle, görüntülerdeki nesneleri algılamak için TensorFlow Serving'e istemci istekleri gönderebilirsiniz. TensorFlow Serving'e istek göndermenin iki yolu vardır:

  • REST
  • gRPC

REST üzerinden istek gönderme ve yanıt alma

Üç basit adım vardır:

  • REST isteğini oluşturun.
  • REST isteğini TensorFlow Serving'e gönderin.
  • Tahmin edilen sonucu REST yanıtından çıkarın ve kullanıcı arayüzünü oluşturun.

Bu hedeflere MainActivity.java. içinde ulaşacaksınız.

REST isteğini oluşturma

Şu anda MainActivity.java dosyasında boş bir createRESTRequest() işlevi var. REST isteği oluşturmak için bu işlevi uygularsınız.

private Request createRESTRequest() {
}

TensorFlow Serving, kullandığınız SSD MobileNet modeli için görüntü tensörünü içeren bir POST isteği bekler. Bu nedenle, görüntünün her pikselinden RGB değerlerini bir diziye çıkarmanız ve ardından diziyi isteğin yükü olan bir JSON'a sarmalamanız gerekir.

  • Bu kodu createRESTRequest() işlevine ekleyin:
//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 isteğini TensorFlow Serving'e gönderme

Uygulama, kullanıcının TensorFlow Serving ile iletişim kurmak için REST veya gRPC'yi seçmesine olanak tanır. Bu nedenle, onClick(View view) dinleyicisinde iki dal vardır.

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

            }
        }
    }
)
  • İsteği TensorFlow Serving'e göndermek için OkHttp'yi kullanmak üzere bu kodu onClick(View view) dinleyicisinin REST dalına ekleyin:
// 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;
}

TensorFlow Serving'den gelen REST yanıtını işleme

SSD MobileNet modeli, aşağıdakiler de dahil olmak üzere bir dizi sonuç döndürür:

  • num_detections: algılama sayısı
  • detection_scores: algılama puanları
  • detection_classes: algılama sınıfı dizini
  • detection_boxes: sınırlayıcı kutu koordinatları

Yanıtı işlemek için postprocessRESTResponse() işlevini uygularsınız.

private void postprocessRESTResponse(Predict.PredictResponse response) {

}
  • Bu kodu postprocessRESTResponse() işlevine ekleyin:
// 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);

Şimdi, son işlem işlevi yanıttan tahmin edilen değerleri çıkarır, nesnenin en olası kategorisini ve sınırlayıcı kutu köşelerinin koordinatlarını belirler ve son olarak algılama sınırlayıcı kutusunu kullanıcı arayüzünde oluşturur.

Çalıştırın

  1. Gezinme menüsünde execute.png "Uygulama"yı çalıştır'ı tıklayın ve uygulamanın yüklenmesini bekleyin.
  2. REST > Run inference'ı (REST > Çıkarım çalıştırma) seçin.

Uygulamanın kedinin sınırlayıcı kutusunu oluşturması ve nesne kategorisi olarak 17 simgesini göstermesi birkaç saniye sürer. Bu simge, COCO veri kümesindeki cat nesnesine karşılık gelir.

5a1a32768dc516d6.png

7. Android uygulamasını gRPC aracılığıyla TensorFlow Serving'e bağlama

TensorFlow Serving, REST'in yanı sıra gRPC'yi de destekler.

b6f4449c2c850b0e.png

gRPC, herhangi bir ortamda çalışabilen modern, açık kaynaklı ve yüksek performanslı bir Uzak Prosedür Çağrısı (RPC) çerçevesidir. Yük dengeleme, izleme, sağlık kontrolü ve kimlik doğrulama için takılabilir destekle veri merkezlerindeki ve veri merkezleri arasındaki hizmetleri verimli bir şekilde bağlayabilir. gRPC'nin pratikte REST'ten daha iyi performans gösterdiği gözlemlenmiştir.

gRPC ile istek gönderme ve yanıt alma

Dört basit adım vardır:

  • [İsteğe bağlı] gRPC istemci taslağı kodunu oluşturun.
  • gRPC isteğini oluşturun.
  • gRPC isteğini TensorFlow Serving'e gönderin.
  • Tahmin edilen sonucu gRPC yanıtından çıkarın ve kullanıcı arayüzünü oluşturun.

Bu hedeflere MainActivity.java. içinde ulaşacaksınız.

İsteğe bağlı: gRPC istemci taslağı kodunu oluşturma

TensorFlow Serving ile gRPC'yi kullanmak için gRPC iş akışını uygulamanız gerekir. Ayrıntılar hakkında daha fazla bilgi edinmek için gRPC belgelerine bakın.

a9d0e5cb543467b4.png

TensorFlow Serving ve TensorFlow, .proto dosyalarını sizin için tanımlar. TensorFlow ve TensorFlow Serving 2.8'den itibaren şu .proto dosyaları gereklidir:

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
  • Sap oluşturmak için bu kodu app/build.gradle dosyasına ekleyin.
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 isteğini oluşturma

REST isteğine benzer şekilde, gRPC isteğini createGRPCRequest() işlevinde oluşturursunuz.

private Request createGRPCRequest() {

}
  • Bu kodu createGRPCRequest() işlevine ekleyin:
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 isteğini TensorFlow Serving'e gönderme

Artık onClick(View view) dinleyicisini tamamlayabilirsiniz.

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

            }
            else {
                // TODO: gRPC request
            }
        }
    }
)
  • Bu kodu gRPC dalına ekleyin:
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;
}

TensorFlow Serving'den gelen gRPC yanıtını işleme

gRPC'ye benzer şekilde, yanıtı işlemek için postprocessGRPCResponse() işlevini uygularsınız.

private void postprocessGRPCResponse(Predict.PredictResponse response) {

}
  • Bu kodu postprocessGRPCResponse() işlevine ekleyin:
// 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);

Artık son işleme işlevi, yanıttan tahmin edilen değerleri çıkarabilir ve algılama sınırlayıcı kutusunu kullanıcı arayüzünde oluşturabilir.

Çalıştırın

  1. Gezinme menüsünde execute.png "Uygulama"yı çalıştır'ı tıklayın ve uygulamanın yüklenmesini bekleyin.
  2. gRPC > Çıkarım çalıştırma'yı seçin.

Uygulamanın kedinin sınırlayıcı kutusunu oluşturması ve nesnenin kategorisi olarak 17 simgesini göstermesi birkaç saniye sürer. Bu simge, COCO veri kümesindeki cat kategorisine karşılık gelir.

8. Tebrikler

Uygulamanıza nesne algılama özellikleri eklemek için TensorFlow Serving'i kullandınız.

Daha fazla bilgi