Catch up on everything announced at Firebase Summit, and learn how Firebase can help you accelerate app development and run your app with confidence. Learn More

Reconhecer pontos de referência com segurança com o Cloud Vision usando o Firebase Auth and Functions no Android

Mantenha tudo organizado com as coleções Salve e categorize o conteúdo com base nas suas preferências.

Para chamar uma API do Google Cloud do seu aplicativo, você precisa criar uma API REST intermediária que processe a autorização e proteja valores secretos, como chaves de API. Em seguida, você precisa escrever código em seu aplicativo móvel para autenticar e se comunicar com esse serviço intermediário.

Uma maneira de criar essa API REST é usando o Firebase Authentication and Functions, que oferece um gateway gerenciado e sem servidor para as APIs do Google Cloud que lidam com a autenticação e podem ser chamados de seu aplicativo para dispositivos móveis com SDKs pré-criados.

Este guia demonstra como usar essa técnica para chamar a API Cloud Vision do seu aplicativo. Esse método permitirá que todos os usuários autenticados acessem os serviços faturados do Cloud Vision por meio do seu projeto do Cloud. Portanto, considere se esse mecanismo de autenticação é suficiente para seu caso de uso antes de continuar.

Antes de você começar

Configure seu projeto

  1. Se ainda não o fez, adicione o Firebase ao seu projeto Android .
  2. Se você ainda não ativou as APIs baseadas em nuvem para seu projeto, faça isso agora:

    1. Abra a página de APIs do Firebase ML do console do Firebase.
    2. Se você ainda não atualizou seu projeto para o plano de preços Blaze, clique em Atualizar para fazer isso. (Você será solicitado a atualizar somente se seu projeto não estiver no plano Blaze.)

      Somente projetos de nível Blaze podem usar APIs baseadas em nuvem.

    3. Se as APIs baseadas em nuvem ainda não estiverem habilitadas, clique em Habilitar APIs baseadas em nuvem .
  3. Configure suas chaves de API do Firebase existentes para impedir o acesso à API Cloud Vision:
    1. Abra a página Credenciais do console do Cloud.
    2. Para cada chave de API na lista, abra a visualização de edição e, na seção Restrições de chave, adicione à lista todas as APIs disponíveis, exceto a API Cloud Vision.

Implante a função que pode ser chamada

Em seguida, implante a Função do Cloud que você usará para fazer a ponte entre seu aplicativo e a API Cloud Vision. O repositório functions-samples contém um exemplo que você pode usar.

Por padrão, acessar a API do Cloud Vision por meio dessa função permitirá que apenas usuários autenticados do seu aplicativo acessem a API do Cloud Vision. Você pode modificar a função para diferentes requisitos.

Para implantar a função:

  1. Clone ou baixe o repositório functions-samples e mude para o diretório vision-annotate-image :
    git clone https://github.com/firebase/functions-samples
    cd vision-annotate-image
    
  2. Instalar dependências:
    cd functions
    npm install
    cd ..
    
  3. Se você não tiver a Firebase CLI, instale-a .
  4. Inicialize um projeto do Firebase no diretório vision-annotate-image . Quando solicitado, selecione seu projeto na lista.
    firebase init
  5. Implante a função:
    firebase deploy --only functions:annotateImage

Adicionar o Firebase Auth ao seu aplicativo

A função chamável implantada acima rejeitará qualquer solicitação de usuários não autenticados do seu aplicativo. Caso ainda não tenha feito isso, você precisará adicionar o Firebase Auth ao seu aplicativo.

Adicione as dependências necessárias ao seu aplicativo

  • Adicione as dependências das bibliotecas do Firebase Functions e gson Android ao arquivo Gradle do módulo (nível do aplicativo) (geralmente app/build.gradle):
    implementation 'com.google.firebase:firebase-functions:20.2.1'
    implementation 'com.google.code.gson:gson:2.8.6'
    
  • 1. Prepare a imagem de entrada

    Para chamar o Cloud Vision, a imagem deve ser formatada como uma string codificada em base64. Para processar uma imagem de um URI de arquivo salvo:
    1. Obtenha a imagem como um objeto Bitmap :

      Java

      Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), uri);

      Kotlin+KTX

      var bitmap: Bitmap = MediaStore.Images.Media.getBitmap(contentResolver, uri)
    2. Opcionalmente, reduza a imagem para economizar largura de banda. Consulte os tamanhos de imagem recomendados pelo Cloud Vision.

      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+KTX

      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

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

      Kotlin+KTX

      // Scale down bitmap size
      bitmap = scaleBitmapDown(bitmap, 640)
    3. Converta o objeto de bitmap em uma string codificada em base64:

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

      Kotlin+KTX

      // 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)
    4. A imagem representada pelo objeto Bitmap deve estar na vertical, sem necessidade de rotação adicional.

    2. Invoque a função que pode ser chamada para reconhecer pontos de referência

    Para reconhecer pontos de referência em uma imagem, invoque a função que pode ser chamada, passando uma solicitação JSON Cloud Vision .

    1. Primeiro, inicialize uma instância do Cloud Functions:

      Java

      private FirebaseFunctions mFunctions;
      // ...
      mFunctions = FirebaseFunctions.getInstance();
      

      Kotlin+KTX

      private lateinit var functions: FirebaseFunctions
      // ...
      functions = Firebase.functions
      
    2. Defina um método para invocar a função:

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

      Kotlin+KTX

      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))
                  }
      }
      
    3. Crie uma solicitação JSON com o tipo LANDMARK_DETECTION :

      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("LANDMARK_DETECTION"));
      JsonArray features = new JsonArray();
      features.add(feature);
      request.add("features", features);
      

      Kotlin+KTX

      // 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("LANDMARK_DETECTION"))
      val features = JsonArray()
      features.add(feature)
      request.add("features", features)
      
    4. Por fim, invoque a função:

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

      Kotlin+KTX

      annotateImage(request.toString())
              .addOnCompleteListener { task ->
                  if (!task.isSuccessful) {
                      // Task failed with an exception
                      // ...
                  } else {
                      // Task completed successfully
                      // ...
                  }
              }
      

    3. Obtenha informações sobre os pontos de referência reconhecidos

    Se a operação de reconhecimento de ponto de referência for bem-sucedida, uma resposta JSON de BatchAnnotateImagesResponse será retornada no resultado da tarefa. Cada objeto na matriz landmarkAnnotations representa um ponto de referência que foi reconhecido na imagem. Para cada ponto de referência, você pode obter suas coordenadas delimitadoras na imagem de entrada, o nome do ponto de referência, sua latitude e longitude, sua ID de entidade do Gráfico de conhecimento (se disponível) e a pontuação de confiança da correspondência. Por exemplo:

    Java

    for (JsonElement label : task.getResult().getAsJsonArray().get(0).getAsJsonObject().get("landmarkAnnotations").getAsJsonArray()) {
        JsonObject labelObj = label.getAsJsonObject();
        String landmarkName = labelObj.get("description").getAsString();
        String entityId = labelObj.get("mid").getAsString();
        float score = labelObj.get("score").getAsFloat();
        JsonObject bounds = labelObj.get("boundingPoly").getAsJsonObject();
        // Multiple locations are possible, e.g., the location of the depicted
        // landmark and the location the picture was taken.
        for (JsonElement loc : labelObj.get("locations").getAsJsonArray()) {
            JsonObject latLng = loc.getAsJsonObject().get("latLng").getAsJsonObject();
            double latitude = latLng.get("latitude").getAsDouble();
            double longitude = latLng.get("longitude").getAsDouble();
        }
    }
    

    Kotlin+KTX

    for (label in task.result!!.asJsonArray[0].asJsonObject["landmarkAnnotations"].asJsonArray) {
        val labelObj = label.asJsonObject
        val landmarkName = labelObj["description"]
        val entityId = labelObj["mid"]
        val score = labelObj["score"]
        val bounds = labelObj["boundingPoly"]
        // Multiple locations are possible, e.g., the location of the depicted
        // landmark and the location the picture was taken.
        for(loc in labelObj["locations"].asJsonArray) {
            val latitude = loc.asJsonObject["latLng"].asJsonObject["latitude"]
            val longitude = loc.asJsonObject["latLng"].asJsonObject["longitude"]
        }
    }