为了从您的应用调用 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.调用callable函数识别文字
要识别图像中的文本,调用可调用函数,传递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())); } }); }
创建 JSON 请求。 Cloud Vision API 支持两种类型的文本检测:
TEXT_DETECTION
和DOCUMENT_TEXT_DETECTION
。请参阅Cloud Vision OCR 文档了解这两个用例之间的区别。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("type", JsonPrimitive("TEXT_DETECTION")) // Alternatively, for DOCUMENT_TEXT_DETECTION: // feature.add("type", JsonPrimitive("DOCUMENT_TEXT_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("type", new JsonPrimitive("TEXT_DETECTION")); // Alternatively, for DOCUMENT_TEXT_DETECTION: //feature.add("type", new JsonPrimitive("DOCUMENT_TEXT_DETECTION")); JsonArray features = new JsonArray(); features.add(feature); request.add("features", features);
Kotlin+KTX
val imageContext = JsonObject() val languageHints = JsonArray() languageHints.add("en") imageContext.add("languageHints", languageHints) request.add("imageContext", imageContext)
Java
JsonObject imageContext = new JsonObject(); JsonArray languageHints = new JsonArray(); languageHints.add("en"); imageContext.add("languageHints", languageHints); request.add("imageContext", imageContext);
最后,调用函数:
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 响应。文本注释可以在fullTextAnnotation
对象中找到。您可以在text
字段中将识别的文本作为字符串获取。例如:
Kotlin+KTX
val annotation = task.result!!.asJsonArray[0].asJsonObject["fullTextAnnotation"].asJsonObject
System.out.format("%nComplete annotation:")
System.out.format("%n%s", annotation["text"].asString)
Java
JsonObject annotation = task.getResult().getAsJsonArray().get(0).getAsJsonObject().get("fullTextAnnotation").getAsJsonObject();
System.out.format("%nComplete annotation:%n");
System.out.format("%s%n", annotation.get("text").getAsString());
您还可以获得特定于图像区域的信息。对于每个block
、 paragraph
、 word
和symbol
,您可以获得区域中识别的文本和区域的边界坐标。例如:
Kotlin+KTX
for (page in annotation["pages"].asJsonArray) {
var pageText = ""
for (block in page.asJsonObject["blocks"].asJsonArray) {
var blockText = ""
for (para in block.asJsonObject["paragraphs"].asJsonArray) {
var paraText = ""
for (word in para.asJsonObject["words"].asJsonArray) {
var wordText = ""
for (symbol in word.asJsonObject["symbols"].asJsonArray) {
wordText += symbol.asJsonObject["text"].asString
System.out.format("Symbol text: %s (confidence: %f)%n",
symbol.asJsonObject["text"].asString, symbol.asJsonObject["confidence"].asFloat)
}
System.out.format("Word text: %s (confidence: %f)%n%n", wordText,
word.asJsonObject["confidence"].asFloat)
System.out.format("Word bounding box: %s%n", word.asJsonObject["boundingBox"])
paraText = String.format("%s%s ", paraText, wordText)
}
System.out.format("%nParagraph: %n%s%n", paraText)
System.out.format("Paragraph bounding box: %s%n", para.asJsonObject["boundingBox"])
System.out.format("Paragraph Confidence: %f%n", para.asJsonObject["confidence"].asFloat)
blockText += paraText
}
pageText += blockText
}
}
Java
for (JsonElement page : annotation.get("pages").getAsJsonArray()) {
StringBuilder pageText = new StringBuilder();
for (JsonElement block : page.getAsJsonObject().get("blocks").getAsJsonArray()) {
StringBuilder blockText = new StringBuilder();
for (JsonElement para : block.getAsJsonObject().get("paragraphs").getAsJsonArray()) {
StringBuilder paraText = new StringBuilder();
for (JsonElement word : para.getAsJsonObject().get("words").getAsJsonArray()) {
StringBuilder wordText = new StringBuilder();
for (JsonElement symbol : word.getAsJsonObject().get("symbols").getAsJsonArray()) {
wordText.append(symbol.getAsJsonObject().get("text").getAsString());
System.out.format("Symbol text: %s (confidence: %f)%n", symbol.getAsJsonObject().get("text").getAsString(), symbol.getAsJsonObject().get("confidence").getAsFloat());
}
System.out.format("Word text: %s (confidence: %f)%n%n", wordText.toString(), word.getAsJsonObject().get("confidence").getAsFloat());
System.out.format("Word bounding box: %s%n", word.getAsJsonObject().get("boundingBox"));
paraText.append(wordText.toString()).append(" ");
}
System.out.format("%nParagraph:%n%s%n", paraText);
System.out.format("Paragraph bounding box: %s%n", para.getAsJsonObject().get("boundingBox"));
System.out.format("Paragraph Confidence: %f%n", para.getAsJsonObject().get("confidence").getAsFloat());
blockText.append(paraText);
}
pageText.append(blockText);
}
}