如要從應用程式呼叫 Google Cloud API,您需要建立中介 REST API,以便處理授權並保護 API 金鑰等機密值。接著,您需要在行動應用程式中編寫程式碼,以便驗證並與這項中介服務通訊。
您可以使用 Firebase 驗證和功能,透過受管理的無伺服器閘道存取 Google Cloud API,以便處理驗證作業,並透過預先建構的 SDK 從行動應用程式呼叫。
本指南將說明如何使用這項技術,從應用程式呼叫 Cloud Vision API。這項方法可讓所有已驗證的使用者透過您的 Cloud 專案存取 Cloud Vision 收費服務,因此請先考量這項驗證機制是否適用於您的用途,再繼續操作。
事前準備
設定專案
- 如果您尚未將 Firebase 新增至 Android 專案,請新增 Firebase。
-
如果您尚未為專案啟用雲端 API,請立即啟用:
- 開啟 Firebase 控制台的 Firebase ML API 頁面。
-
如果您尚未將專案升級至 Blaze 定價方案,請按一下「Upgrade」進行升級 (只有在專案未採用 Blaze 方案時,系統才會提示您升級)。
只有 Blaze 級別專案可以使用雲端 API。
- 如果您尚未啟用雲端 API,請按一下「啟用雲端 API」。
- 設定現有的 Firebase API 金鑰,禁止存取 Cloud Vision API:
部署可呼叫的函式
接著,部署 Cloud Function,以便用來連結應用程式和 Cloud Vision API。functions-samples
存放區包含您可以使用的範例。
根據預設,透過這個函式存取 Cloud Vision API 時,只有已驗證的應用程式使用者才能存取 Cloud Vision API。您可以視需要修改函式。
如何部署函式:
- 複製或下載 functions-samples 存放區,然後變更為
Node-1st-gen/vision-annotate-image
目錄:git clone https://github.com/firebase/functions-samples
cd Node-1st-gen/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。
為應用程式新增必要的依附元件
<project>/<app-module>/build.gradle.kts
或 <project>/<app-module>/build.gradle
):implementation("com.google.firebase:firebase-functions:21.1.0") implementation("com.google.code.gson:gson:2.8.6")
您現在可以開始辨識圖片中的文字。
1. 準備輸入圖片
如要呼叫 Cloud Vision,圖片必須採用 Base64 編碼字串格式。如要從已儲存的檔案 URI 處理圖片,請按照下列步驟操作:- 取得圖片做為
Bitmap
物件:Kotlin
var bitmap: Bitmap = MediaStore.Images.Media.getBitmap(contentResolver, uri)
Java
Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), uri);
- 您可以選擇縮小圖片,以節省頻寬。請參閱
Cloud Vision 建議的圖片大小。
Kotlin
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
// Scale down bitmap size bitmap = scaleBitmapDown(bitmap, 640)
Java
// Scale down bitmap size bitmap = scaleBitmapDown(bitmap, 640);
- 將位圖物件轉換為採用 Base64 編碼的字串:
Kotlin
// 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
private lateinit var functions: FirebaseFunctions // ... functions = Firebase.functions
Java
private FirebaseFunctions mFunctions; // ... mFunctions = FirebaseFunctions.getInstance();
定義用於叫用函式的做法:
Kotlin
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
// 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
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
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
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
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);
}
}