إنشاء تطبيق متوافق مع Android لرصد العناصر داخل الصور

1. قبل البدء

في هذا الدرس التطبيقي حول الترميز، ستتعرّف على كيفية تنفيذ استنتاج رصد الكائنات من أحد تطبيقات Android باستخدام TensorFlow serving مع REST وgRPC.

المتطلّبات الأساسية

  • معرفة أساسية بتطوير تطبيقات Android باستخدام لغة البرمجة Java
  • معرفة أساسية بتعلُّم الآلة مع TensorFlow، مثل التدريب والنشر
  • معرفة أساسية حول محطات الدفع وقاعدة الإرساء

ما ستتعرَّف عليه

  • كيفية العثور على نماذج رصد الكائنات المعدّة مسبقًا في TensorFlow Hub
  • كيفية إنشاء تطبيق بسيط يعمل بنظام التشغيل Android وتوقّع التوقّعات باستخدام نموذج اكتشاف العنصر الذي تم تنزيله من خلال TensorFlow العرض (REST وgRPC).
  • كيفية عرض نتيجة الاكتشاف في واجهة المستخدم.

الأشياء التي تحتاج إليها

2. الإعداد

لتنزيل الرمز في هذا الدرس التطبيقي حول الترميز:

  1. انتقِل إلى مستودع GitHub لهذا الدرس التطبيقي حول الترميز.
  2. انقر على رمز > تنزيل ملف zip لتنزيل كل رموز هذا الدرس التطبيقي حول الترميز.

a72f2bb4caa9a96.png

  1. يمكنك فك ضغط ملف ZIP الذي تم تنزيله لفك ضغط مجلد الجذر الذي يتضمّن codelabs مع جميع الموارد التي تحتاجها.

بالنسبة إلى هذا الدرس التطبيقي حول الترميز، تحتاج فقط إلى الملفات في دليل TFServing/ObjectDetectionAndroid الفرعي في المستودع، الذي يحتوي على مجلدَين:

  • يتضمّن المجلد starter رمز التفعيل الذي تستند إليه في هذا الدرس التطبيقي حول الترميز.
  • يحتوي مجلد finished على الرمز المكتمل لنموذج التطبيق المكتمل.

3- إضافة تبعيات إلى المشروع

استيراد تطبيق إجراء التفعيل إلى "استوديو Android"

  • في "استوديو Android"، انقر على File > جديد > استيراد المشروع ثم اختَر المجلد 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 من قائمة التنقل.

4. تشغيل تطبيق إجراء التفعيل

تشغيل التطبيق واستكشافه

من المفترض أن يتم تشغيل التطبيق على جهاز Android. واجهة المستخدم سهلة الاستخدام، فهي توفّر صورة للقطط تريد من خلالها اكتشاف الأشياء ويمكن للمستخدم اختيار الطريقة لإرسال البيانات إلى الخلفية، وذلك باستخدام REST أو gRPC. وتُجري الواجهة الخلفية اكتشاف العنصر على الصورة وتعرض نتائج الاكتشاف إلى تطبيق العميل، والتي تعرض واجهة المستخدم مرة أخرى.

24eab579530e9645.png

الآن، إذا نقرت على تشغيل الاستنتاج، لن يحدث أي شيء. ويرجع هذا إلى عدم إمكانية التواصل مع الخلفية.

5. نشر نموذج اكتشاف العنصر باستخدام TensorFlow serving

اكتشاف الكائنات هو مهمة شائعة جدًا في تعلِّم الآلة وهدفها هو اكتشاف الكائنات داخل الصور، وتحديدًا للتنبؤ بالفئات المحتملة من العناصر والمربّعات المحيطة بها. في ما يلي مثال على نتيجة اكتشاف:

a68f9308fb2fc17b.png

نشرت Google عددًا من النماذج المدرَّبة مسبقًا على TensorFlow Hub. للاطّلاع على القائمة الكاملة، انتقِل إلى الصفحة object_detection. تستخدم النموذج SSD MobileNet V2 FPNLite 320x320 خفيف الوزن لهذا الدرس التطبيقي لكي لا تحتاج بالضرورة إلى استخدام وحدة معالجة رسومات لتشغيله.

لنشر نموذج اكتشاف العنصر باستخدام 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 أولاً، الأمر الذي يستغرق دقيقة واحدة. بعد ذلك، من المفترض أن يبدأ عرض 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- ربط تطبيق Android بخدمة TensorFlow العرض من خلال REST

أصبحت الخلفية جاهزة الآن، لذا يمكنك إرسال طلبات العميل إلى TensorFlow العرض لاكتشاف العناصر داخل الصور. تتوفّر طريقتان لإرسال الطلبات إلى TensorFlow Driving:

  • استراحة
  • gRPC

إرسال طلبات وتلقّي ردود من خلال REST

هناك ثلاث خطوات بسيطة:

  • إنشاء طلب REST.
  • يُرجى إرسال طلب REST إلى TensorFlow serving.
  • استخرِج النتيجة المتوقعة من استجابة REST واعرض واجهة المستخدم.

ستعمل على تحقيق ذلك في MainActivity.java.

إنشاء طلب REST

هناك الآن دالة createRESTRequest() فارغة في الملف MainActivity.java. نفّذ هذه الدالة لإنشاء طلب REST.

private Request createRESTRequest() {
}

تتوقع خدمة TensorFlow عرض طلب POST يحتوي على موتّر الصورة لنموذج SSD للأجهزة الجوّالة الذي تستخدمه، لذا تحتاج إلى استخراج قيم 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 العرض:
// 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 العرض

يعرض نموذج SDS للأجهزة الجوّالة عددًا من النتائج، ومن بينها:

  • 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);

والآن، تستخرج وظيفة ما بعد المعالجة القيم المتوقعة من الاستجابة، وتعرّف على الفئة الأكثر احتمالاً للعنصر وإحداثيات الخطوط الحدّية، وتعرض في النهاية مربع وضع الرصد على واجهة المستخدم.

التشغيل

  1. انقر على تنفيذ تشغيل التطبيق&#39؛ في قائمة التنقل ثم انتظر حتى يتم تحميل التطبيق.
  2. اختَر REST > Run theاستنتاج.

يستغرق الأمر بضع ثوانٍ قبل أن يعرض التطبيق المربّع المحاذي للقطّ ويعرض 17 كفئة الكائن الذي يتم ربطه بالكائن cat في مجموعة بيانات COCO.

5a1a32768dc516d6.png

7- ربط تطبيق Android بخدمة TensorFlow من خلال خدمة gRPC

بالإضافة إلى REST، تتوافق خدمة TensorFlow أيضًا مع gRPC.

b6f4449c2c850b0e.png

gRPC هو إطار عمل حديث ومفتوح المصدر وعالي الأداء للإجراءات عن بُعد يمكن تشغيله في أي بيئة. وتستطيع هذه الخدمة توصيل الخدمات بكفاءة في مراكز البيانات وفي مناطق أخرى مع دعم قابل للتوصيل لموازنة الحِمل وتتبُّع الحالة الصحية والمصادقة. لقد لاحظنا أن gRPC هو أكثر أداءً من REST من الناحية العملية.

إرسال الطلبات وتلقّي الردود باستخدام gRPC

هناك أربع خطوات بسيطة:

  • [اختياري] أنشئ رمز كائن gRPC للعميل.
  • إنشاء طلب gRPC.
  • أرسِل طلب gRPC إلى TensorFlow العرض.
  • استخرِج النتيجة المتوقعة من استجابة gRPC وعرض واجهة المستخدم.

ستعمل على تحقيق ذلك في MainActivity.java.

اختياري: إنشاء رمز كائن gRPC للعميل

لاستخدام gRPC مع عرض TensorFlow، عليك اتّباع طريقة عمل gRPC. لمعرفة المزيد من التفاصيل، يُرجى الاطّلاع على وثائق gRPC.

a9d0e5cb543467b4.png

يشكّل TensorFlow للعرض وTensorFlow ملفات .proto بالنيابة عنك. اعتبارًا من TensorFlow وTensorFlow العرض 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() لمعالجة الاستجابة.

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);

يمكن لدالة ما بعد المعالجة استخراج القيم المتوقعة من الاستجابة، وعرض مربع تحديد الرصد في واجهة المستخدم.

التشغيل

  1. انقر على تنفيذ تشغيل التطبيق&#39؛ في قائمة التنقل ثم انتظر حتى يتم تحميل التطبيق.
  2. اختَر gRPC > لتشغيل الاستنتاج.

يستغرق الأمر بضع ثوانٍ قبل أن يعرض التطبيق المربع المحيط بالقط ويعرض 17 كفئة الكائن، والتي يتم ربطها بالفئة cat في مجموعة بيانات COCO.

8- تهانينا

لقد استخدمت منصة TensorFlow للعرض لإضافة إمكانات رصد العناصر إلى تطبيقك.

مزيد من المعلومات