تعرف على المعالم بشكل آمن باستخدام Cloud Vision باستخدام Firebase Auth والوظائف على Android

لاستدعاء Google Cloud API من تطبيقك، تحتاج إلى إنشاء واجهة REST API وسيطة تتعامل مع التفويض وتحمي القيم السرية مثل مفاتيح واجهة برمجة التطبيقات. ستحتاج بعد ذلك إلى كتابة التعليمات البرمجية في تطبيق الهاتف المحمول الخاص بك للمصادقة على هذه الخدمة الوسيطة والتواصل معها.

تتمثل إحدى طرق إنشاء REST API في استخدام مصادقة Firebase ووظائفها، والتي تمنحك بوابة مُدارة بدون خادم إلى Google Cloud APIs التي تتعامل مع المصادقة ويمكن استدعاؤها من تطبيق الهاتف المحمول الخاص بك باستخدام حزم SDK المُنشأة مسبقًا.

يوضح هذا الدليل كيفية استخدام هذه التقنية لاستدعاء Cloud Vision API من تطبيقك. ستسمح هذه الطريقة لجميع المستخدمين المعتمدين بالوصول إلى خدمات Cloud Vision التي تتم فوترتها من خلال مشروع Cloud الخاص بك، لذا فكر فيما إذا كانت آلية المصادقة هذه كافية لحالة الاستخدام الخاصة بك قبل المتابعة.

قبل ان تبدأ

تكوين المشروع الخاص بك

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

    1. افتح صفحة Firebase ML APIs لوحدة تحكم Firebase.
    2. إذا لم تكن قد قمت بالفعل بترقية مشروعك إلى خطة تسعير Blaze، فانقر فوق ترقية للقيام بذلك. (سيُطلب منك الترقية فقط إذا لم يكن مشروعك مدرجًا في خطة Blaze.)

      يمكن فقط للمشاريع على مستوى Blaze استخدام واجهات برمجة التطبيقات المستندة إلى السحابة.

    3. إذا لم تكن واجهات برمجة التطبيقات المستندة إلى السحابة ممكّنة بالفعل، فانقر على تمكين واجهات برمجة التطبيقات المستندة إلى السحابة .
  3. قم بتكوين مفاتيح Firebase API الموجودة لديك لمنع الوصول إلى Cloud Vision API:
    1. افتح صفحة بيانات الاعتماد الخاصة بوحدة التحكم السحابية.
    2. بالنسبة لكل مفتاح API في القائمة، افتح طريقة عرض التحرير، وفي قسم قيود المفاتيح، أضف جميع واجهات برمجة التطبيقات المتاحة باستثناء Cloud Vision API إلى القائمة.

نشر الدالة القابلة للاستدعاء

بعد ذلك، قم بنشر وظيفة السحابة التي ستستخدمها لربط تطبيقك وCloud Vision API. يحتوي مستودع functions-samples على مثال يمكنك استخدامه.

افتراضيًا، سيسمح الوصول إلى Cloud Vision API من خلال هذه الوظيفة فقط للمستخدمين المعتمدين لتطبيقك بالوصول إلى Cloud Vision API. يمكنك تعديل الوظيفة لمتطلبات مختلفة.

لنشر الوظيفة:

  1. انسخ أو قم بتنزيل مستودع عينات الوظائف وقم بالتغيير إلى دليل Node-1st-gen/vision-annotate-image :
    git clone https://github.com/firebase/functions-samples
    cd Node-1st-gen/vision-annotate-image
    
  2. تثبيت التبعيات:
    cd functions
    npm install
    cd ..
    
  3. إذا لم يكن لديك Firebase CLI، فقم بتثبيته .
  4. قم بتهيئة مشروع Firebase في دليل vision-annotate-image . عندما يُطلب منك ذلك، حدد مشروعك في القائمة.
    firebase init
  5. نشر الوظيفة:
    firebase deploy --only functions:annotateImage

أضف مصادقة Firebase إلى تطبيقك

سترفض الوظيفة القابلة للاستدعاء المنشورة أعلاه أي طلب من المستخدمين غير المصادق عليهم لتطبيقك. إذا لم تكن قد قمت بذلك بالفعل، فستحتاج إلى إضافة Firebase Auth إلى تطبيقك.

أضف التبعيات الضرورية إلى تطبيقك

  • أضف تبعيات Cloud Functions for Firebase (العميل) ومكتبات gson Android إلى ملف Gradle الخاص بالوحدة النمطية (على مستوى التطبيق) (عادةً <project>/<app-module>/build.gradle.kts أو <project>/<app-module>/build.gradle ):
    implementation("com.google.firebase:firebase-functions:20.4.0")
    implementation("com.google.code.gson:gson:2.8.6")
  • 1. قم بإعداد صورة الإدخال

    من أجل الاتصال بـ Cloud Vision، يجب تنسيق الصورة كسلسلة بترميز base64. لمعالجة صورة من ملف محفوظ URI:
    1. الحصول على الصورة ككائن Bitmap :

      Kotlin+KTX

      var bitmap: Bitmap = MediaStore.Images.Media.getBitmap(contentResolver, uri)
      

      Java

      Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), uri);
    2. اختياريًا، قم بتصغير الصورة لحفظها في النطاق الترددي. راجع أحجام الصور الموصى بها من Cloud Vision.

      Kotlin+KTX

      private fun scaleBitmapDown(bitmap: Bitmap, maxDimension: Int): Bitmap {
          val originalWidth = bitmap.width
          val originalHeight = bitmap.height
          var resizedWidth = maxDimension
          var resizedHeight = maxDimension
          if (originalHeight > originalWidth) {
              resizedHeight = maxDimension
              resizedWidth =
                  (resizedHeight * originalWidth.toFloat() / originalHeight.toFloat()).toInt()
          } else if (originalWidth > originalHeight) {
              resizedWidth = maxDimension
              resizedHeight =
                  (resizedWidth * originalHeight.toFloat() / originalWidth.toFloat()).toInt()
          } else if (originalHeight == originalWidth) {
              resizedHeight = maxDimension
              resizedWidth = maxDimension
          }
          return Bitmap.createScaledBitmap(bitmap, resizedWidth, resizedHeight, false)
      }

      Java

      private Bitmap scaleBitmapDown(Bitmap bitmap, int maxDimension) {
          int originalWidth = bitmap.getWidth();
          int originalHeight = bitmap.getHeight();
          int resizedWidth = maxDimension;
          int resizedHeight = maxDimension;
      
          if (originalHeight > originalWidth) {
              resizedHeight = maxDimension;
              resizedWidth = (int) (resizedHeight * (float) originalWidth / (float) originalHeight);
          } else if (originalWidth > originalHeight) {
              resizedWidth = maxDimension;
              resizedHeight = (int) (resizedWidth * (float) originalHeight / (float) originalWidth);
          } else if (originalHeight == originalWidth) {
              resizedHeight = maxDimension;
              resizedWidth = maxDimension;
          }
          return Bitmap.createScaledBitmap(bitmap, resizedWidth, resizedHeight, false);
      }

      Kotlin+KTX

      // Scale down bitmap size
      bitmap = scaleBitmapDown(bitmap, 640)

      Java

      // Scale down bitmap size
      bitmap = scaleBitmapDown(bitmap, 640);
    3. تحويل كائن الصورة النقطية إلى سلسلة مشفرة base64:

      Kotlin+KTX

      // Convert bitmap to base64 encoded string
      val byteArrayOutputStream = ByteArrayOutputStream()
      bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream)
      val imageBytes: ByteArray = byteArrayOutputStream.toByteArray()
      val base64encoded = Base64.encodeToString(imageBytes, Base64.NO_WRAP)

      Java

      // Convert bitmap to base64 encoded string
      ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
      bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream);
      byte[] imageBytes = byteArrayOutputStream.toByteArray();
      String base64encoded = Base64.encodeToString(imageBytes, Base64.NO_WRAP);
    4. يجب أن تكون الصورة التي يمثلها كائن Bitmap في وضع مستقيم، دون الحاجة إلى تدوير إضافي.

    2. قم باستدعاء الوظيفة القابلة للاستدعاء للتعرف على المعالم

    للتعرف على المعالم في صورة ما، قم باستدعاء الوظيفة القابلة للاستدعاء، وتمرير طلب JSON Cloud Vision .

    1. أولاً، قم بتهيئة مثيل Cloud Functions:

      Kotlin+KTX

      private lateinit var functions: FirebaseFunctions
      // ...
      functions = Firebase.functions
      

      Java

      private FirebaseFunctions mFunctions;
      // ...
      mFunctions = FirebaseFunctions.getInstance();
      
    2. تحديد طريقة لاستدعاء الوظيفة:

      Kotlin+KTX

      private fun annotateImage(requestJson: String): Task<JsonElement> {
          return functions
              .getHttpsCallable("annotateImage")
              .call(requestJson)
              .continueWith { task ->
                  // This continuation runs on either success or failure, but if the task
                  // has failed then result will throw an Exception which will be
                  // propagated down.
                  val result = task.result?.data
                  JsonParser.parseString(Gson().toJson(result))
              }
      }
      

      Java

      private Task<JsonElement> annotateImage(String requestJson) {
          return mFunctions
                  .getHttpsCallable("annotateImage")
                  .call(requestJson)
                  .continueWith(new Continuation<HttpsCallableResult, JsonElement>() {
                      @Override
                      public JsonElement then(@NonNull Task<HttpsCallableResult> task) {
                          // This continuation runs on either success or failure, but if the task
                          // has failed then getResult() will throw an Exception which will be
                          // propagated down.
                          return JsonParser.parseString(new Gson().toJson(task.getResult().getData()));
                      }
                  });
      }
      
    3. أنشئ طلب JSON بالنوع LANDMARK_DETECTION :

      Kotlin+KTX

      // Create json request to cloud vision
      val request = JsonObject()
      // Add image to request
      val image = JsonObject()
      image.add("content", JsonPrimitive(base64encoded))
      request.add("image", image)
      // Add features to the request
      val feature = JsonObject()
      feature.add("maxResults", JsonPrimitive(5))
      feature.add("type", JsonPrimitive("LANDMARK_DETECTION"))
      val features = JsonArray()
      features.add(feature)
      request.add("features", features)
      

      Java

      // Create json request to cloud vision
      JsonObject request = new JsonObject();
      // Add image to request
      JsonObject image = new JsonObject();
      image.add("content", new JsonPrimitive(base64encoded));
      request.add("image", image);
      //Add features to the request
      JsonObject feature = new JsonObject();
      feature.add("maxResults", new JsonPrimitive(5));
      feature.add("type", new JsonPrimitive("LANDMARK_DETECTION"));
      JsonArray features = new JsonArray();
      features.add(feature);
      request.add("features", features);
      
    4. أخيرًا، قم باستدعاء الدالة:

      Kotlin+KTX

      annotateImage(request.toString())
          .addOnCompleteListener { task ->
              if (!task.isSuccessful) {
                  // Task failed with an exception
                  // ...
              } else {
                  // Task completed successfully
                  // ...
              }
          }
      

      Java

      annotateImage(request.toString())
              .addOnCompleteListener(new OnCompleteListener<JsonElement>() {
                  @Override
                  public void onComplete(@NonNull Task<JsonElement> task) {
                      if (!task.isSuccessful()) {
                          // Task failed with an exception
                          // ...
                      } else {
                          // Task completed successfully
                          // ...
                      }
                  }
              });
      

    3. الحصول على معلومات حول المعالم المعترف بها

    إذا نجحت عملية التعرف على المعالم، فسيتم إرجاع استجابة JSON الخاصة بـ BatchAnnotateImagesResponse في نتيجة المهمة. يمثل كل كائن في مصفوفة landmarkAnnotations معلمًا تم التعرف عليه في الصورة. بالنسبة لكل معلم، يمكنك الحصول على إحداثياته ​​المحيطة في الصورة المدخلة، واسم المعلم، وخط الطول والعرض، ومعرف كيان الرسم البياني المعرفي الخاص به (إذا كان متاحًا)، ودرجة الثقة للمطابقة. على سبيل المثال:

    Kotlin+KTX

    for (label in task.result!!.asJsonArray[0].asJsonObject["landmarkAnnotations"].asJsonArray) {
        val labelObj = label.asJsonObject
        val landmarkName = labelObj["description"]
        val entityId = labelObj["mid"]
        val score = labelObj["score"]
        val bounds = labelObj["boundingPoly"]
        // Multiple locations are possible, e.g., the location of the depicted
        // landmark and the location the picture was taken.
        for (loc in labelObj["locations"].asJsonArray) {
            val latitude = loc.asJsonObject["latLng"].asJsonObject["latitude"]
            val longitude = loc.asJsonObject["latLng"].asJsonObject["longitude"]
        }
    }
    

    Java

    for (JsonElement label : task.getResult().getAsJsonArray().get(0).getAsJsonObject().get("landmarkAnnotations").getAsJsonArray()) {
        JsonObject labelObj = label.getAsJsonObject();
        String landmarkName = labelObj.get("description").getAsString();
        String entityId = labelObj.get("mid").getAsString();
        float score = labelObj.get("score").getAsFloat();
        JsonObject bounds = labelObj.get("boundingPoly").getAsJsonObject();
        // Multiple locations are possible, e.g., the location of the depicted
        // landmark and the location the picture was taken.
        for (JsonElement loc : labelObj.get("locations").getAsJsonArray()) {
            JsonObject latLng = loc.getAsJsonObject().get("latLng").getAsJsonObject();
            double latitude = latLng.get("latitude").getAsDouble();
            double longitude = latLng.get("longitude").getAsDouble();
        }
    }