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

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

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

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

قبل از شروع

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

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

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

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

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

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

سپس، 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» را ندارید، آن را نصب کنید.
  4. پروژه Firebase را در فهرست راهنمای vision-annotate-image مقداردهی اولیه کنید. وقتی درخواست شد، پروژه خود را در فهرست انتخاب کنید.
    firebase init
  5. استقرار تابع:
    firebase deploy --only functions:annotateImage

افزودن «احراز هویت Firebase» به برنامه

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

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

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

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

    برای فراخوانی Cloud Vision، تصویر باید به‌صورت رشته کدبندی‌شده با base64 قالب‌بندی شود. برای پردازش تصویر از نشانی وب فایل ذخیره‌شده:
    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);
        }
    }