获取我们在 Firebase 峰会上发布的所有信息,了解 Firebase 可如何帮助您加快应用开发速度并满怀信心地运行应用。了解详情

在 Android 上使用 Firebase Auth 和 Functions 使用 Cloud Vision 安全地标记图像

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

为了从您的应用调用 Google Cloud API,您需要创建一个中间 REST API 来处理授权并保护秘密值(例如 API 密钥)。然后,您需要在您的移动应用程序中编写代码以验证此中间服务并与之通信。

创建此 REST API 的一种方法是使用 Firebase Authentication and Functions,它为您提供了一个托管的无服务器网关,用于处理身份验证的 Google Cloud API,并且可以使用预构建的 SDK 从您的移动应用程序调用。

本指南演示了如何使用此技术从您的应用程序调用 Cloud Vision API。此方法将允许所有经过身份验证的用户通过您的 Cloud 项目访问 Cloud Vision 计费服务,因此在继续之前考虑此身份验证机制是否足以满足您的用例。

在你开始之前

配置您的项目

  1. 如果您还没有,请将 Firebase 添加到您的 Android 项目中。
  2. 如果您尚未为您的项目启用基于云的 API,请立即启用:

    1. 打开 Firebase 控制台的Firebase ML API 页面
    2. 如果您尚未将项目升级到 Blaze 定价计划,请单击升级进行升级。 (仅当您的项目不在 Blaze 计划中时,系统才会提示您升级。)

      只有 Blaze 级项目可以使用基于云的 API。

    3. 如果尚未启用基于云的 API,请单击启用基于云的 API
  3. 配置您现有的 Firebase API 密钥以禁止访问 Cloud Vision API:
    1. 打开云控制台的凭据页面。
    2. 对于列表中的每个 API 密钥,打开编辑视图,然后在密钥限制部分中,将Cloud Vision API 之外的所有可用 API 添加到列表中。

部署可调用函数

接下来,部署将用于桥接应用程序和 Cloud Vision API 的 Cloud Function。 functions-samples存储库包含一个您可以使用的示例。

默认情况下,通过此函数访问 Cloud Vision API 将仅允许您的应用程序的经过身份验证的用户访问 Cloud Vision API。您可以根据不同的要求修改该功能。

要部署函数:

  1. 克隆或下载functions-samples 存储库并切换到vision-annotate-image目录:
    git clone https://github.com/firebase/functions-samples
    cd vision-annotate-image
    
  2. 安装依赖项:
    cd functions
    npm install
    cd ..
    
  3. 如果您没有 Firebase CLI,请安装它
  4. vision-annotate-image目录中初始化一个 Firebase 项目。出现提示时,在列表中选择您的项目。
    firebase init
  5. 部署函数:
    firebase deploy --only functions:annotateImage

将 Firebase Auth 添加到您的应用

上面部署的可调用函数将拒绝来自您应用的未经身份验证的用户的任何请求。如果您还没有这样做,则需要将 Firebase Auth 添加到您的应用中。

向您的应用程序添加必要的依赖项

  • 将 Firebase 函数和 gson Android 库的依赖项添加到您的模块(应用程序级)Gradle 文件(通常是 app/build.gradle):
    implementation 'com.google.firebase:firebase-functions:20.2.2'
    implementation 'com.google.code.gson:gson:2.8.6'
    
  • 现在您已准备好标记图像。

    1.准备输入图像

    为了调用 Cloud Vision,必须将图像格式化为 base64 编码的字符串。要处理来自已保存文件 URI 的图像:
    1. 获取图像作为Bitmap对象:

      Kotlin+KTX

      var bitmap: Bitmap = MediaStore.Images.Media.getBitmap(contentResolver, uri)

      Java

      Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), uri);
    2. 或者,缩小图像以节省带宽。请参阅Cloud Vision 推荐的图像尺寸。

      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

      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

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

      Java

      // Scale down bitmap size
      bitmap = scaleBitmapDown(bitmap, 640);
    3. 将位图对象转换为 base64 编码的字符串:

      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)

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

      private lateinit var functions: FirebaseFunctions
      // ...
      functions = Firebase.functions
      

      Java

      private FirebaseFunctions mFunctions;
      // ...
      mFunctions = FirebaseFunctions.getInstance();
      
    2. 定义调用函数的方法:

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

      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. 创建Type设置为LABEL_DETECTION的 JSON 请求:

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

      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.获取有关标记对象的信息

    如果图像标记操作成功,将在任务结果中返回BatchAnnotateImagesResponse的 JSON 响应。 labelAnnotations数组中的每个对象代表图像中标记的内容。对于每个标签,您可以获得标签的文本描述、其知识图谱实体 ID (如果可用)以及匹配的置信度分数。例如:

    Kotlin+KTX

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