شناسایی ایمن نوشتار در تصاویر با 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 را ایجاد کنید. ‫Cloud Vision API از دو نوع تشخیص نوشتار پشتیبانی می‌کند: TEXT_DETECTION و DOCUMENT_TEXT_DETECTION. برای اطلاع از تفاوت بین این دو مورد استفاده، اسناد Cloud Vision OCR را ببینید.

      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("type", JsonPrimitive("TEXT_DETECTION"))
      // Alternatively, for DOCUMENT_TEXT_DETECTION:
      // feature.add("type", JsonPrimitive("DOCUMENT_TEXT_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("type", new JsonPrimitive("TEXT_DETECTION"));
      // Alternatively, for DOCUMENT_TEXT_DETECTION:
      //feature.add("type", new JsonPrimitive("DOCUMENT_TEXT_DETECTION"));
      JsonArray features = new JsonArray();
      features.add(feature);
      request.add("features", features);
      

      درصورت تمایل، نکات زبانی ارائه دهید تا به تشخیص زبان کمک کنید (زبان‌های پشتیبانی‌شده را ببینید):

      Kotlin

      val imageContext = JsonObject()
      val languageHints = JsonArray()
      languageHints.add("en")
      imageContext.add("languageHints", languageHints)
      request.add("imageContext", imageContext)
      

      Java

      JsonObject imageContext = new JsonObject();
      JsonArray languageHints = new JsonArray();
      languageHints.add("en");
      imageContext.add("languageHints", languageHints);
      request.add("imageContext", imageContext);
      
    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 در نتیجه تکلیف برگردانده خواهد شد. حاشیه‌نویسی‌های نوشتاری را می‌توانید در شیء fullTextAnnotation پیدا کنید.

    می‌توانید نوشتار تشخیص‌داده‌شده را به‌صورت رشته در فیلد text دریافت کنید. برای مثال:

    Kotlin

    val annotation = task.result!!.asJsonArray[0].asJsonObject["fullTextAnnotation"].asJsonObject
    System.out.format("%nComplete annotation:")
    System.out.format("%n%s", annotation["text"].asString)
    

    Java

    JsonObject annotation = task.getResult().getAsJsonArray().get(0).getAsJsonObject().get("fullTextAnnotation").getAsJsonObject();
    System.out.format("%nComplete annotation:%n");
    System.out.format("%s%n", annotation.get("text").getAsString());
    

    همچنین می‌توانید اطلاعات مختص به بخش‌های تصویر را دریافت کنید. برای هر block، paragraph، word، و symbol، می‌توانید نوشتار تشخیص‌داده‌شده در منطقه و مختصات مرزی منطقه را دریافت کنید. برای مثال:

    Kotlin

    for (page in annotation["pages"].asJsonArray) {
        var pageText = ""
        for (block in page.asJsonObject["blocks"].asJsonArray) {
            var blockText = ""
            for (para in block.asJsonObject["paragraphs"].asJsonArray) {
                var paraText = ""
                for (word in para.asJsonObject["words"].asJsonArray) {
                    var wordText = ""
                    for (symbol in word.asJsonObject["symbols"].asJsonArray) {
                        wordText += symbol.asJsonObject["text"].asString
                        System.out.format(
                            "Symbol text: %s (confidence: %f)%n",
                            symbol.asJsonObject["text"].asString,
                            symbol.asJsonObject["confidence"].asFloat,
                        )
                    }
                    System.out.format(
                        "Word text: %s (confidence: %f)%n%n",
                        wordText,
                        word.asJsonObject["confidence"].asFloat,
                    )
                    System.out.format("Word bounding box: %s%n", word.asJsonObject["boundingBox"])
                    paraText = String.format("%s%s ", paraText, wordText)
                }
                System.out.format("%nParagraph: %n%s%n", paraText)
                System.out.format("Paragraph bounding box: %s%n", para.asJsonObject["boundingBox"])
                System.out.format("Paragraph Confidence: %f%n", para.asJsonObject["confidence"].asFloat)
                blockText += paraText
            }
            pageText += blockText
        }
    }
    

    Java

    for (JsonElement page : annotation.get("pages").getAsJsonArray()) {
        StringBuilder pageText = new StringBuilder();
        for (JsonElement block : page.getAsJsonObject().get("blocks").getAsJsonArray()) {
            StringBuilder blockText = new StringBuilder();
            for (JsonElement para : block.getAsJsonObject().get("paragraphs").getAsJsonArray()) {
                StringBuilder paraText = new StringBuilder();
                for (JsonElement word : para.getAsJsonObject().get("words").getAsJsonArray()) {
                    StringBuilder wordText = new StringBuilder();
                    for (JsonElement symbol : word.getAsJsonObject().get("symbols").getAsJsonArray()) {
                        wordText.append(symbol.getAsJsonObject().get("text").getAsString());
                        System.out.format("Symbol text: %s (confidence: %f)%n", symbol.getAsJsonObject().get("text").getAsString(), symbol.getAsJsonObject().get("confidence").getAsFloat());
                    }
                    System.out.format("Word text: %s (confidence: %f)%n%n", wordText.toString(), word.getAsJsonObject().get("confidence").getAsFloat());
                    System.out.format("Word bounding box: %s%n", word.getAsJsonObject().get("boundingBox"));
                    paraText.append(wordText.toString()).append(" ");
                }
                System.out.format("%nParagraph:%n%s%n", paraText);
                System.out.format("Paragraph bounding box: %s%n", para.getAsJsonObject().get("boundingBox"));
                System.out.format("Paragraph Confidence: %f%n", para.getAsJsonObject().get("confidence").getAsFloat());
                blockText.append(paraText);
            }
            pageText.append(blockText);
        }
    }