Reconnaître les points de repère en toute sécurité avec Cloud Vision en utilisant Firebase Auth et les fonctions sur Android

Pour appeler une API Google Cloud à partir de votre application, vous devez créer une API REST intermédiaire qui gère l'autorisation et protège les valeurs secrètes telles que les clés API. Vous devez ensuite écrire du code dans votre application mobile pour vous authentifier et communiquer avec ce service intermédiaire.

Une façon de créer cette API REST consiste à utiliser Firebase Authentication and Functions, qui vous offre une passerelle gérée sans serveur vers les API Google Cloud qui gère l'authentification et peut être appelée depuis votre application mobile avec des SDK prédéfinis.

Ce guide montre comment utiliser cette technique pour appeler l'API Cloud Vision depuis votre application. Cette méthode permettra à tous les utilisateurs authentifiés d'accéder aux services facturés Cloud Vision via votre projet Cloud. Déterminez donc si ce mécanisme d'authentification est suffisant pour votre cas d'utilisation avant de continuer.

Avant que tu commences

Configurez votre projet

  1. Si vous avez pas déjà, ajoutez Firebase à votre projet Android .
  2. Si vous n'avez pas encore activé les API basées sur le cloud pour votre projet, faites-le maintenant :

    1. Ouvrez la page par API Firebase ML de la console Firebase.
    2. Si vous ne l' avez pas déjà mis à jour votre projet au plan de tarification Blaze, cliquez sur Mise à jour pour le faire. (Vous serez invité à mettre à niveau uniquement si votre projet ne fait pas partie du plan Blaze.)

      Seuls les projets de niveau Blaze peuvent utiliser des API basées sur le cloud.

    3. Si API cloud computing ne sont pas déjà activé, cliquez sur Activer les API cloud computing.
  3. Configurez vos clés API Firebase existantes pour interdire l'accès à l'API Cloud Vision :
    1. Ouvrez la vérification des pouvoirs page de la console Cloud.
    2. Pour chaque clé API dans la liste, ouvrez la vue d' édition, et dans la section Restrictions clés, ajouter toutes les API disponibles , sauf l'API cloud Vision à la liste.

Déployer la fonction appelable

Ensuite, déployez la fonction Cloud que vous utiliserez pour relier votre application et l'API Cloud Vision. Les functions-samples des functions-samples référentiel contient un exemple que vous pouvez utiliser.

Par défaut, l'accès à l'API Cloud Vision via cette fonction autorisera uniquement les utilisateurs authentifiés de votre application à accéder à l'API Cloud Vision. Vous pouvez modifier la fonction pour différentes exigences.

Pour déployer la fonction :

  1. Clone ou télécharger les fonctions échantillons repo et le changement à la vision-annotate-image répertoire:
    git clone https://github.com/firebase/functions-samples
    cd vision-annotate-image
    
  2. Installer les dépendances:
    cd functions
    npm install
    cd ..
    
  3. Si vous ne disposez pas de la CLI Firebase, installez - le .
  4. Initialiser un projet Firebase dans la vision-annotate-image répertoire. Lorsque vous y êtes invité, sélectionnez votre projet dans la liste.
    firebase init
  5. Déployer la fonction:
    firebase deploy --only functions:annotateImage

Ajouter Firebase Auth à votre application

La fonction appelable déployée ci-dessus rejettera toute demande d'utilisateurs non authentifiés de votre application. Si vous ne l' avez pas déjà fait, vous devrez ajouter Firebase Auth à votre application.

Ajoutez les dépendances nécessaires à votre application

  • Ajoutez les dépendances pour les fonctions et Firebase gson Android bibliothèques à votre module (app-niveau) de fichier Gradle (généralement app / build.gradle):
    implementation 'com.google.firebase:firebase-functions:20.0.1'
    implementation 'com.google.code.gson:gson:2.8.6'
    
  • 1. Préparez l'image d'entrée

    Pour appeler Cloud Vision, l'image doit être formatée en tant que chaîne encodée en base64. Pour traiter une image à partir d'un URI de fichier enregistré :
    1. Obtenez l'image comme Bitmap objet:

      Java

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

      Kotlin+KTX

      var bitmap: Bitmap = MediaStore.Images.Media.getBitmap(contentResolver, uri)
    2. Vous pouvez éventuellement réduire l'image pour économiser de la bande passante. Voir les nuages Vision tailles d'image recommandée.

      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. Convertissez l'objet bitmap en chaîne encodée en 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. L'image représentée par le Bitmap objet doit être en position verticale, sans rotation supplémentaire nécessaire.

    2. Invoquez la fonction appelable pour reconnaître les points de repère

    Reconnaître des repères dans une image, appelez la fonction appelable, en passant une demande Nuage JSON Vision .

    1. Tout d'abord, initialisez une instance de Cloud Functions :

      Java

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

      Kotlin+KTX

      private lateinit var functions: FirebaseFunctions
      // ...
      functions = Firebase.functions
      
    2. Définissez une méthode pour appeler la fonction :

      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. Créer une demande JSON avec type 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. Enfin, invoquez la fonction :

      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. Obtenez des informations sur les points de repère reconnus

    Si l'opération de reconnaissance historique réussit, une réponse JSON de BatchAnnotateImagesResponse sera retourné dans le résultat de la tâche. Chaque objet dans le landmarkAnnotations tableau représente un point de repère qui a été reconnu dans l'image. Pour chaque point de repère, vous pouvez obtenir ses coordonnées de délimitation dans l'image d'entrée, le nom du point de repère, sa latitude et sa longitude, son ID d'entité Knowledge Graph (si disponible) et le score de confiance de la correspondance. Par exemple:

    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"]
        }
    }