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

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

یکی از راه‌های ایجاد این REST API استفاده از Firebase Authentication و Functions است که به شما یک درگاه مدیریت‌شده و بدون سرور به Google Cloud APIs می‌دهد که اصالت‌سنجی را مدیریت می‌کند و می‌تواند از برنامه تلفن همراه شما با کیت‌های توسعه نرم‌افزار ازپیش‌ساخته‌شده فراخوانی شود.

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

قبل از شروع

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

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

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

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

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

استقرار تابع قابل‌فراخوانی

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

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

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

  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 (کارخواه) و کتابخانه‌های gson Android را به فایل Gradle (سطح برنامه) (معمولاً <project>/<app-module>/build.gradle.kts یا <project>/<app-module>/build.gradle) اضافه کنید:
    implementation("com.google.firebase:firebase-functions:23.0.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. ابتدا، نمونه‌ای از Cloud Functions را مقداردهی اولیه کنید:

      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 نشان‌دهنده چیزی است که در تصویر برچسب‌گذاری شده است. برای هر برچسب، می‌توانید توضیحات نوشتاری برچسب، شناسه نهاد Knowledge Graph آن (درصورت وجود)، و امتیاز اطمینان مطابقت را دریافت کنید. برای مثال:

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