یک برنامه اندروید برای شناسایی اشیاء درون تصاویر ایجاد کنید

1. قبل از شروع

در این کد لبه، شما یاد می گیرید که چگونه یک استنتاج تشخیص شی را از یک برنامه اندروید با استفاده از سرویس TensorFlow با REST و gRPC اجرا کنید.

پیش نیازها

  • دانش اولیه توسعه اندروید با جاوا
  • دانش اولیه یادگیری ماشین با TensorFlow، مانند آموزش و استقرار
  • دانش اولیه ترمینال ها و داکر

چیزی که یاد خواهید گرفت

  • چگونه مدل های تشخیص اشیاء از پیش آموزش دیده را در TensorFlow Hub پیدا کنیم.
  • چگونه یک برنامه اندرویدی ساده بسازیم و با مدل تشخیص شی دانلود شده از طریق سرویس TensorFlow (REST و gRPC) پیش بینی کنیم.
  • نحوه نمایش نتیجه تشخیص در رابط کاربری

آنچه شما نیاز دارید

2. راه اندازی شوید

برای دانلود کد این کد لبه:

  1. به مخزن GitHub برای این Codelab بروید.
  2. روی Code > Download zip کلیک کنید تا همه کدهای این کد لبه را دانلود کنید.

a72f2bb4caa9a96.png

  1. فایل فشرده دانلود شده را از حالت فشرده خارج کنید تا بسته بندی پوشه ریشه codelabs با تمام منابع مورد نیاز شما باز شود.

برای این کد لبه، شما فقط به فایل های موجود در زیر شاخه TFServing/ObjectDetectionAndroid در مخزن نیاز دارید که شامل دو پوشه است:

  • پوشه starter حاوی کد شروعی است که برای این Codelab بر اساس آن ساخته اید.
  • پوشه finished شده حاوی کد تکمیل شده برای برنامه نمونه تمام شده است.

3. وابستگی ها را به پروژه اضافه کنید

برنامه شروع را به Android Studio وارد کنید

  • در Android Studio، روی File > New > Import project کلیک کنید و سپس پوشه starter را از کد منبعی که قبلا دانلود کرده اید انتخاب کنید.

وابستگی های OkHttp و gRPC را اضافه کنید

  • در فایل app/build.gradle پروژه خود، وجود وابستگی ها را تایید کنید.
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'
}

پروژه خود را با فایل های Gradle همگام سازی کنید

  • انتخاب کنید 541e90b497a7fef7.png پروژه را با Gradle Files از منوی پیمایش همگام سازی کنید.

4. برنامه استارتر را اجرا کنید

برنامه را اجرا و کاوش کنید

برنامه باید روی دستگاه اندرویدی شما راه اندازی شود. رابط کاربری بسیار ساده است: یک تصویر گربه وجود دارد که در آن می‌خواهید اشیاء را شناسایی کنید و کاربر می‌تواند راه ارسال داده‌ها را با REST یا gRPC انتخاب کند. Backend تشخیص شی را روی تصویر انجام می دهد و نتایج تشخیص را به برنامه مشتری برمی گرداند، که UI را دوباره رندر می کند.

24eab579530e9645.png

در حال حاضر، اگر روی Run inference کلیک کنید، هیچ اتفاقی نمی‌افتد. این به این دلیل است که هنوز نمی تواند با backend ارتباط برقرار کند.

5. یک مدل تشخیص شی را با سرویس TensorFlow اجرا کنید

تشخیص اشیا یک کار بسیار رایج ML است و هدف آن شناسایی اشیاء درون تصاویر است، یعنی پیش‌بینی دسته‌های احتمالی اشیاء و جعبه‌های محدود اطراف آنها. در اینجا یک مثال از یک نتیجه تشخیص آورده شده است:

a68f9308fb2fc17b.png

گوگل تعدادی مدل از پیش آموزش دیده را در TensorFlow Hub منتشر کرده است. برای مشاهده لیست کامل، به صفحه object_detection مراجعه کنید. شما از مدل نسبتا سبک SSD MobileNet V2 FPNLite 320x320 برای این کد لبه استفاده می کنید تا لزوماً برای اجرای آن نیازی به استفاده از GPU نداشته باشید.

برای استقرار مدل تشخیص شی با سرویس TensorFlow:

  1. فایل مدل را دانلود کنید.
  2. فایل .tar.gz دانلود شده را با یک ابزار رفع فشرده سازی مانند 7-Zip از حالت فشرده خارج کنید.
  3. یک پوشه ssd_mobilenet_v2_2_320 ایجاد کنید و سپس یک زیر پوشه 123 داخل آن ایجاد کنید.
  4. پوشه variables استخراج شده و فایل saved_model.pb را در زیر پوشه 123 قرار دهید.

می توانید به پوشه ssd_mobilenet_v2_2_320 به عنوان پوشه SavedModel کنید. 123 یک نمونه نسخه شماره است. در صورت تمایل می توانید شماره دیگری را انتخاب کنید.

ساختار پوشه باید مانند تصویر زیر باشد:

42c8150a42033767.png

سرویس TensorFlow را شروع کنید

  • در ترمینال خود، سرویس TensorFlow را با Docker شروع کنید، اما PATH/TO/SAVEDMODEL را با مسیر مطلق پوشه ssd_mobilenet_v2_2_320 در رایانه خود جایگزین کنید.
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 به طور خودکار ابتدا تصویر TensorFlow Serving را دانلود می کند که یک دقیقه طول می کشد. پس از آن، سرویس TensorFlow باید شروع شود. گزارش باید مانند این قطعه کد باشد:

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. برنامه اندروید را با سرویس TensorFlow از طریق REST متصل کنید

Backend اکنون آماده است، بنابراین می توانید درخواست های مشتری را به TensorFlow Serving ارسال کنید تا اشیاء درون تصاویر را شناسایی کنید. دو راه برای ارسال درخواست به سرویس TensorFlow وجود دارد:

  • باقی مانده
  • gRPC

ارسال درخواست و دریافت پاسخ از طریق REST

سه مرحله ساده وجود دارد:

  • درخواست REST را ایجاد کنید.
  • درخواست REST را به سرویس TensorFlow ارسال کنید.
  • نتیجه پیش‌بینی‌شده را از پاسخ REST استخراج کنید و UI را رندر کنید.

شما به این موارد در MainActivity.java.

درخواست REST را ایجاد کنید

در حال حاضر، در فایل MainActivity.java ، یک createRESTRequest() خالی وجود دارد. شما این تابع را برای ایجاد یک درخواست REST پیاده سازی می کنید.

private Request createRESTRequest() {
}

TensorFlow Serving انتظار دارد یک درخواست POST که حاوی تانسور تصویر برای مدل SSD MobileNet است که استفاده می‌کنید، بنابراین باید مقادیر RGB را از هر پیکسل تصویر در یک آرایه استخراج کنید و سپس آرایه را در یک JSON بپیچید که همان بار است. از درخواست

  • این کد را به تابع 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;    

درخواست REST را به سرویس TensorFlow ارسال کنید

این برنامه به کاربر اجازه می‌دهد تا REST یا gRPC را برای برقراری ارتباط با سرویس TensorFlow انتخاب کند، بنابراین دو شاخه در شنونده onClick(View view) وجود دارد.

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

            }
        }
    }
)
  • این کد را به شاخه REST شنونده onClick(View view) اضافه کنید تا از OkHttp برای ارسال درخواست به 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;
}

پاسخ REST را از سرویس TensorFlow پردازش کنید

مدل SSD MobileNet تعدادی از نتایج را برمی‌گرداند که عبارتند از:

  • num_detections : تعداد شناسایی‌ها
  • detection_scores : نمرات تشخیص
  • detection_classes : شاخص کلاس تشخیص
  • detection_boxes : مختصات جعبه مرزی

شما تابع postprocessRESTResponse() را برای مدیریت پاسخ پیاده سازی می کنید.

private void postprocessRESTResponse(Predict.PredictResponse response) {

}
  • این کد را به تابع 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);

اکنون تابع پس پردازش مقادیر پیش‌بینی‌شده را از پاسخ استخراج می‌کند، محتمل‌ترین دسته شی و مختصات راس‌های جعبه مرزی را شناسایی می‌کند، و در نهایت جعبه مرزی تشخیص را در UI ارائه می‌کند.

آن را اجرا کنید

  1. کلیک execute.png برنامه را در منوی ناوبری اجرا کنید و سپس منتظر بمانید تا برنامه بارگیری شود.
  2. REST > اجرای استنتاج را انتخاب کنید.

چند ثانیه طول می کشد تا برنامه کادر محدود گربه را نمایش دهد و عدد 17 را به عنوان دسته شی نشان دهد که به شی cat در مجموعه داده COCO نگاشت می شود.

5a1a32768dc516d6.png

7. برنامه اندروید را با سرویس TensorFlow از طریق gRPC متصل کنید

علاوه بر REST، سرویس TensorFlow از gRPC نیز پشتیبانی می کند.

b6f4449c2c850b0e.png

gRPC یک چارچوب مدرن، متن باز و با کارایی بالا Remote Procedure Call (RPC) است که می تواند در هر محیطی اجرا شود. این می تواند به طور موثر خدمات را در مراکز داده و در سراسر آنها با پشتیبانی قابل اتصال برای تعادل بار، ردیابی، بررسی سلامت و احراز هویت متصل کند. مشاهده شده است که gRPC در عمل عملکرد بیشتری نسبت به REST دارد.

ارسال درخواست و دریافت پاسخ با gRPC

چهار مرحله ساده وجود دارد:

  • [اختیاری] کد خرد مشتری gRPC را ایجاد کنید.
  • درخواست gRPC را ایجاد کنید.
  • درخواست gRPC را به سرویس TensorFlow ارسال کنید.
  • نتیجه پیش‌بینی‌شده را از پاسخ gRPC استخراج کنید و UI را رندر کنید.

شما به این موارد در MainActivity.java.

اختیاری: کد خرد مشتری gRPC را ایجاد کنید

برای استفاده از gRPC با سرویس TensorFlow، باید گردش کار gRPC را دنبال کنید. برای کسب اطلاعات بیشتر در مورد جزئیات، به مستندات gRPC مراجعه کنید.

a9d0e5cb543467b4.png

TensorFlow Serving و .proto فایل های .proto را برای شما تعریف می کنند. در مورد TensorFlow و TensorFlow Serving 2.8، این فایل‌های .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
  • برای تولید خرد، این کد را به فایل 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' }
            }
        }
    }
}

درخواست gRPC را ایجاد کنید

مشابه درخواست REST، شما درخواست gRPC را در تابع createGRPCRequest() ایجاد می کنید.

private Request createGRPCRequest() {

}
  • این کد را به 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();

درخواست gRPC را به سرویس TensorFlow ارسال کنید

اکنون می توانید شنونده onClick(View view) را تمام کنید.

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

            }
            else {
                // TODO: gRPC request
            }
        }
    }
)
  • این کد را به شاخه 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;
}

پاسخ gRPC از سرویس TensorFlow را پردازش کنید

مشابه gRPC، تابع postprocessGRPCResponse postprocessGRPCResponse() را برای مدیریت پاسخ پیاده سازی می کنید.

private void postprocessGRPCResponse(Predict.PredictResponse response) {

}
  • این کد را به تابع 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);

اکنون تابع پس پردازش می‌تواند مقادیر پیش‌بینی‌شده را از پاسخ استخراج کرده و کادر تشخیص مرزی را در UI ارائه کند.

آن را اجرا کنید

  1. کلیک execute.png برنامه را در منوی ناوبری اجرا کنید و سپس منتظر بمانید تا برنامه بارگیری شود.
  2. gRPC > اجرای استنتاج را انتخاب کنید.

چند ثانیه طول می‌کشد تا برنامه کادر محدود گربه را نمایش دهد و عدد 17 را به‌عنوان دسته شی نشان دهد که به دسته cat در مجموعه داده COCO نگاشت می‌شود.

8. تبریک می گویم

شما از TensorFlow Serving برای افزودن قابلیت‌های تشخیص شی به برنامه خود استفاده کردید!

بیشتر بدانید