Безопасно маркируйте изображения с помощью Cloud Vision, используя аутентификацию Firebase и функции на Android

Для вызова API Google Cloud из вашего приложения необходимо создать промежуточный REST API, который обрабатывает авторизацию и защищает секретные значения, такие как ключи API. Затем вам нужно написать код в вашем мобильном приложении для аутентификации и взаимодействия с этим промежуточным сервисом.

Один из способов создания такого REST API — использование Firebase Authentication and Functions, который предоставляет управляемый бессерверный шлюз к API Google Cloud, обрабатывающий аутентификацию и доступный для вызова из мобильного приложения с помощью предварительно настроенных SDK.

В этом руководстве показано, как использовать этот метод для вызова API Cloud Vision из вашего приложения. Этот метод позволит всем авторизованным пользователям получать доступ к платным услугам Cloud Vision через ваш облачный проект, поэтому, прежде чем продолжить, подумайте, достаточно ли этого механизма аутентификации для вашего случая.

Прежде чем начать

Настройте свой проект

  1. Если вы еще этого не сделали, добавьте Firebase в свой Android-проект .
  2. Если вы еще не включили облачные API для своего проекта, сделайте это сейчас:

    1. Откройте страницу API Firebase ML в консоли Firebase .
    2. Если вы еще не перевели свой проект на тарифный план Blaze с оплатой по факту использования , нажмите «Обновить» , чтобы сделать это. (Вам будет предложено обновить тарифный план только в том случае, если ваш проект не подключен к тарифному плану Blaze.)

      Использовать облачные API могут только проекты, использующие тарифный план Blaze.

    3. Если облачные API еще не включены, нажмите «Включить облачные API» .
  3. Настройте существующие ключи API Firebase, чтобы запретить доступ к API Cloud Vision:
    1. Откройте страницу «Учетные данные» в консоли Cloud.
    2. Для каждого ключа API в списке откройте окно редактирования и в разделе «Ограничения по ключу» добавьте в список все доступные API, кроме Cloud Vision API.

Разверните вызываемую функцию

Далее разверните облачную функцию, которую вы будете использовать для связи вашего приложения с API Cloud Vision. В репозитории functions-samples содержится пример, который вы можете использовать.

По умолчанию доступ к 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 в ваше приложение.

Добавьте необходимые зависимости в ваше приложение.

  • Добавьте зависимости для библиотек Cloud Functions for Firebase (клиент) и 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")
  • Теперь вы готовы подписывать изображения.

    1. Подготовьте входное изображение.

    Для вызова 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 , должно быть прямым, без необходимости дополнительного поворота.

    2. Вызовите вызываемую функцию для присвоения метки изображению.

    Для маркировки объектов на изображении вызовите функцию, передав ей 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
                          // ...
                      }
                  }
              });
      

    3. Получите информацию об объектах с соответствующими обозначениями.

    Если операция разметки изображения прошла успешно, в результате выполнения задачи будет возвращен 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();
    }