為了從您的應用程式呼叫 Google Cloud API,您需要建立一個中間 REST API 來處理授權並保護 API 金鑰等秘密值。然後,您需要在行動應用程式中編寫程式碼以對此中間服務進行身份驗證並與其進行通訊。
建立此REST API 的一種方法是使用Firebase 驗證和功能,它為您提供了一個通往Google Cloud API 的託管無伺服器網關,該網關處理身份驗證,並可以使用預先建置的SDK 從行動應用程式進行調用。
本指南示範如何使用此技術從您的應用程式呼叫 Cloud Vision API。此方法將允許所有經過驗證的使用者透過您的雲端專案存取 Cloud Vision 計費服務,因此在繼續之前請考慮此身份驗證機制是否足以滿足您的用例。
在你開始之前
配置您的項目
- 如果您尚未將 Firebase 新增至您的 Android 專案中,請將其新增至您的 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。您可以根據不同的需求修改該功能。
部署該功能:
- 複製或下載函數樣本儲存庫並變更為
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:20.4.0") 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 請求,並將Type設為
LABEL_DETECTION
: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();
}