برچسب گذاری ایمن تصاویر با Cloud Vision با استفاده از Firebase Auth و توابع در Android

برای فراخوانی یک API گوگل کلود از برنامه خود، باید یک API REST واسط ایجاد کنید که مجوزها را مدیریت کرده و از مقادیر مخفی مانند کلیدهای API محافظت کند. سپس باید در برنامه تلفن همراه خود کدی بنویسید تا احراز هویت شده و با این سرویس واسط ارتباط برقرار کند.

یک راه برای ایجاد این REST API استفاده از Firebase Authentication and Functions است که یک دروازه مدیریت‌شده و بدون سرور به APIهای Google Cloud در اختیار شما قرار می‌دهد که احراز هویت را مدیریت می‌کند و می‌توان آن را از طریق برنامه تلفن همراه شما با SDKهای از پیش ساخته شده فراخوانی کرد.

این راهنما نحوه استفاده از این تکنیک را برای فراخوانی API Cloud Vision از برنامه شما نشان می‌دهد. این روش به همه کاربران احراز هویت شده اجازه می‌دهد تا از طریق پروژه Cloud شما به سرویس‌های دارای صورتحساب Cloud Vision دسترسی پیدا کنند، بنابراین قبل از ادامه، در نظر بگیرید که آیا این مکانیسم احراز هویت برای مورد استفاده شما کافی است یا خیر.

قبل از اینکه شروع کنی

پروژه خود را پیکربندی کنید

  1. اگر هنوز Firebase را به پروژه اندروید خود اضافه نکرده‌اید، آن را اضافه کنید.
  2. اگر هنوز APIهای مبتنی بر ابر را برای پروژه خود فعال نکرده‌اید، اکنون این کار را انجام دهید:

    1. صفحه Firebase ML APIs را در کنسول Firebase باز کنید.
    2. اگر هنوز پروژه خود را به طرح قیمت‌گذاری پرداخت در محل Blaze ارتقا نداده‌اید، برای انجام این کار روی ارتقا کلیک کنید. (فقط در صورتی که پروژه شما در طرح قیمت‌گذاری Blaze نباشد، از شما خواسته می‌شود که آن را ارتقا دهید.)

      فقط پروژه‌های موجود در طرح قیمت‌گذاری Blaze می‌توانند از APIهای مبتنی بر ابر استفاده کنند.

    3. اگر APIهای مبتنی بر ابر از قبل فعال نشده‌اند، روی فعال کردن APIهای مبتنی بر ابر کلیک کنید.
  3. کلیدهای API فایربیس موجود خود را پیکربندی کنید تا دسترسی به API کلود ویژن را غیرفعال کنید:
    1. صفحه اعتبارنامه‌ها (Credentials) کنسول Cloud را باز کنید.
    2. برای هر کلید API موجود در لیست، نمای ویرایش را باز کنید و در بخش محدودیت‌های کلید، تمام APIهای موجود به جز API Cloud Vision را به لیست اضافه کنید.

تابع قابل فراخوانی را مستقر کنید

در مرحله بعد، تابع ابری (Cloud Function) را که برای ایجاد پل بین برنامه خود و API Cloud Vision استفاده خواهید کرد، مستقر کنید. مخزن functions-samples شامل مثالی است که می‌توانید از آن استفاده کنید.

به طور پیش‌فرض، دسترسی به API Cloud Vision از طریق این تابع، فقط به کاربران احراز هویت شده برنامه شما اجازه دسترسی به API Cloud Vision را می‌دهد. می‌توانید این تابع را برای الزامات مختلف تغییر دهید.

برای استقرار تابع:

  1. مخزن functions-samples را کلون یا دانلود کنید و به دایرکتوری 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 Auth به برنامه شما

تابع فراخوانی‌شده‌ی فوق، هرگونه درخواستی از کاربران احراز هویت نشده‌ی برنامه‌ی شما را رد می‌کند. اگر قبلاً این کار را انجام نداده‌اید، باید Firebase Auth را به برنامه‌ی خود اضافه کنید.

وابستگی‌های لازم را به برنامه خود اضافه کنید

  • وابستگی‌های مربوط به توابع ابری برای کتابخانه‌های Firebase (client) و gson Android را به فایل Gradle ماژول (سطح برنامه) خود اضافه کنید (معمولاً <project>/<app-module>/build.gradle.kts یا <project>/<app-module>/build.gradle ):
    implementation("com.google.firebase:firebase-functions:22.1.0")
    implementation("com.google.code.gson:gson:2.8.6")
  • حالا آماده‌اید تا تصاویر را برچسب‌گذاری کنید.

    ۱. تصویر ورودی را آماده کنید

    برای فراخوانی Cloud Vision، تصویر باید به صورت یک رشته کدگذاری شده با base64 فرمت شود. برای پردازش تصویر از یک فایل ذخیره شده، URI:
    1. تصویر را به عنوان یک شیء Bitmap دریافت کنید:

      Kotlin

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

      Java

      Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), uri);
    2. در صورت تمایل، برای صرفه‌جویی در پهنای باند، مقیاس تصویر را کاهش دهید. به اندازه‌های تصویر توصیه‌شده توسط Cloud Vision مراجعه کنید.

      Kotlin

      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

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

      Java

      // Scale down bitmap size
      bitmap = scaleBitmapDown(bitmap, 640);
    3. شیء بیت‌مپ را به یک رشته کدگذاری شده با base64 تبدیل کنید:

      Kotlin

      // 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 باید عمودی باشد و نیازی به چرخش اضافی نباشد.

    ۲. تابع قابل فراخوانی را برای برچسب‌گذاری تصویر فراخوانی کنید.

    برای برچسب‌گذاری اشیاء در یک تصویر، تابع قابل فراخوانی را با ارسال یک درخواست JSON Cloud Vision فراخوانی کنید.

    1. ابتدا، یک نمونه از توابع ابری را مقداردهی اولیه کنید:

      Kotlin

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

      Java

      private FirebaseFunctions mFunctions;
      // ...
      mFunctions = FirebaseFunctions.getInstance();
      
    2. یک متد برای فراخوانی تابع تعریف کنید:

      Kotlin

      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 را با نوع تنظیم شده روی LABEL_DETECTION ایجاد کنید:

      Kotlin

      // 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("LABEL_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("LABEL_DETECTION"));
      JsonArray features = new JsonArray();
      features.add(feature);
      request.add("features", features);
      
    4. در نهایت، تابع را فراخوانی کنید:

      Kotlin

      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
                          // ...
                      }
                  }
              });
      

    ۳. اطلاعات مربوط به اشیاء برچسب‌گذاری شده را دریافت کنید

    اگر عملیات برچسب‌گذاری تصویر با موفقیت انجام شود، یک پاسخ JSON از نوع BatchAnnotateImagesResponse در نتیجه‌ی وظیفه بازگردانده می‌شود. هر شیء در آرایه‌ی labelAnnotations نشان‌دهنده‌ی چیزی است که در تصویر برچسب‌گذاری شده است. برای هر برچسب، می‌توانید توضیحات متنی برچسب، شناسه‌ی موجودیت نمودار دانش آن (در صورت وجود) و امتیاز اطمینان تطابق را دریافت کنید. برای مثال:

    Kotlin

    for (label in task.result!!.asJsonArray[0].asJsonObject["labelAnnotations"].asJsonArray) {
        val labelObj = label.asJsonObject
        val text = labelObj["description"]
        val entityId = labelObj["mid"]
        val confidence = labelObj["score"]
    }
    

    Java

    for (JsonElement label : task.getResult().getAsJsonArray().get(0).getAsJsonObject().get("labelAnnotations").getAsJsonArray()) {
        JsonObject labelObj = label.getAsJsonObject();
        String text = labelObj.get("description").getAsString();
        String entityId = labelObj.get("mid").getAsString();
        float score = labelObj.get("score").getAsFloat();
    }