為了從您的應用程序調用 Google Cloud API,您需要創建一個中間 REST API 來處理授權並保護 API 密鑰等秘密值。然後,您需要在您的移動應用程序中編寫代碼來驗證此中間服務並與之通信。
創建此 REST API 的一種方法是使用 Firebase 身份驗證和函數,它為您提供了一個託管的、無服務器的 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 Functions 函數。 functions-samples
存儲庫包含一個您可以使用的示例。
默認情況下,通過此函數訪問 Cloud Vision API 將只允許您應用的經過身份驗證的用戶訪問 Cloud Vision API。您可以根據不同的要求修改功能。
部署功能:
- 克隆或下載functions-samples repo並更改到
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 身份驗證添加到您的應用
上面部署的可調用函數將拒絕來自您應用程序的未經身份驗證的用戶的任何請求。如果您尚未這樣做,則需要將 Firebase Auth 添加到您的應用中。
為您的應用添加必要的依賴項
implementation 'com.google.firebase:firebase-functions:20.1.0' implementation 'com.google.code.gson:gson:2.8.6'
現在您已準備好標記圖像。
1.準備輸入圖像
為了調用 Cloud Vision,圖像必須格式化為 base64 編碼的字符串。從保存的文件 URI 處理圖像:- 將圖像作為
Bitmap
對象獲取:Java
Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), uri);
Kotlin+KTX
var bitmap: Bitmap = MediaStore.Images.Media.getBitmap(contentResolver, uri)
- 或者,縮小圖像以節省帶寬。請參閱Cloud Vision 推薦的圖像尺寸。
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
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
// Scale down bitmap size bitmap = scaleBitmapDown(bitmap, 640);
Kotlin+KTX
// Scale down bitmap size bitmap = scaleBitmapDown(bitmap, 640)
- 將位圖對象轉換為 base64 編碼字符串:
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);
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)
Bitmap
對象表示的圖像必須是直立的,不需要額外的旋轉。2.調用callable函數給圖片打標籤
要標記圖像中的對象,請調用傳遞JSON Cloud Vision 請求的可調用函數。首先,初始化一個 Cloud Functions 實例:
Java
private FirebaseFunctions mFunctions; // ... mFunctions = FirebaseFunctions.getInstance();
Kotlin+KTX
private lateinit var functions: FirebaseFunctions // ... functions = Firebase.functions
定義調用函數的方法:
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())); } }); }
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)) } }
創建類型設置為
LABEL_DETECTION
的 JSON 請求: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
// 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
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 // ... } } });
Kotlin+KTX
annotateImage(request.toString()) .addOnCompleteListener { task -> if (!task.isSuccessful) { // Task failed with an exception // ... } else { // Task completed successfully // ... } }
3. 獲取標記對象的信息
如果圖片標註操作成功,任務結果中會返回一個BatchAnnotateImagesResponse的 JSON 響應。labelAnnotations
數組中的每個對像都代表圖像中標記的內容。對於每個標籤,您可以獲得標籤的文本描述、其知識圖實體 ID (如果可用)和匹配的置信度分數。例如: 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();
}
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"]
}