为了从您的应用调用 Google Cloud API,您需要创建一个中间 REST API 来处理授权并保护秘密值(例如 API 密钥)。然后,您需要在您的移动应用程序中编写代码以验证此中间服务并与之通信。
创建此 REST API 的一种方法是使用 Firebase Authentication and Functions,它为您提供了一个托管的无服务器网关,用于处理身份验证的 Google Cloud API,并且可以使用预构建的 SDK 从您的移动应用程序调用。
本指南演示了如何使用此技术从您的应用程序调用 Cloud Vision API。此方法将允许所有经过身份验证的用户通过您的 Cloud 项目访问 Cloud Vision 计费服务,因此在继续之前考虑此身份验证机制是否足以满足您的用例。
在你开始之前
配置您的项目
- 如果您还没有,请将 Firebase 添加到您的 Android 项目中。
如果您尚未为您的项目启用基于云的 API,请立即启用:
- 打开 Firebase 控制台的Firebase ML API 页面。
如果您尚未将项目升级到 Blaze 定价计划,请单击升级进行升级。 (仅当您的项目不在 Blaze 计划中时,系统才会提示您升级。)
只有 Blaze 级项目可以使用基于云的 API。
- 如果尚未启用基于云的 API,请单击启用基于云的 API 。
- 配置您现有的 Firebase API 密钥以禁止访问 Cloud Vision API:
- 打开云控制台的凭据页面。
- 对于列表中的每个 API 密钥,打开编辑视图,然后在密钥限制部分中,将除Cloud Vision API 之外的所有可用 API 添加到列表中。
部署可调用函数
接下来,部署将用于桥接应用程序和 Cloud Vision API 的 Cloud Function。 functions-samples
存储库包含一个您可以使用的示例。
默认情况下,通过此函数访问 Cloud Vision API 将仅允许您的应用程序的经过身份验证的用户访问 Cloud Vision API。您可以根据不同的要求修改该功能。
要部署函数:
- 克隆或下载functions-samples 存储库并切换到
vision-annotate-image
目录:git clone https://github.com/firebase/functions-samples
cd vision-annotate-image
- 安装依赖项:
cd functions
npm install
cd ..
- 如果您没有 Firebase CLI,请安装它。
- 在
vision-annotate-image
目录中初始化一个 Firebase 项目。出现提示时,在列表中选择您的项目。firebase init
- 部署函数:
firebase deploy --only functions:annotateImage
将 Firebase Auth 添加到您的应用
上面部署的可调用函数将拒绝来自您应用的未经身份验证的用户的任何请求。如果您还没有这样做,则需要将 Firebase Auth 添加到您的应用中。
向您的应用程序添加必要的依赖项
implementation 'com.google.firebase:firebase-functions:20.2.2' implementation 'com.google.code.gson:gson:2.8.6'
现在您已准备好标记图像。
1.准备输入图像
为了调用 Cloud Vision,必须将图像格式化为 base64 编码的字符串。要处理来自已保存文件 URI 的图像:- 获取图像作为
Bitmap
对象:Kotlin+KTX
var bitmap: Bitmap = MediaStore.Images.Media.getBitmap(contentResolver, uri)
Java
Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), uri);
- 或者,缩小图像以节省带宽。请参阅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);
- 将位图对象转换为 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);
Bitmap
对象表示的图像必须是直立的,不需要额外的旋转。2.调用可调用函数来标记图像
要标记图像中的对象,请调用传递JSON Cloud Vision 请求的可调用函数。首先,初始化 Cloud Functions 的一个实例:
Kotlin+KTX
private lateinit var functions: FirebaseFunctions // ... functions = Firebase.functions
Java
private FirebaseFunctions mFunctions; // ... mFunctions = FirebaseFunctions.getInstance();
定义调用函数的方法:
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())); } }); }
创建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);
最后,调用函数:
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();
}