앱에서 Google Cloud API를 호출하려면 승인을 처리하고 API 키와 같은 보안 비밀 값을 보호하는 중간 REST API를 만들어야 합니다. 그런 다음 모바일 앱에서 코드를 작성하여 이 중간 서비스에 인증하고 통신해야 합니다.
이 REST API를 만드는 한 가지 방법은 Firebase 인증 및 Firebase Functions를 사용하는 것입니다. 이 방법을 사용하면 인증을 처리하고 사전 빌드된 SDK를 사용하여 모바일 앱에서 호출할 수 있는 Google Cloud API에 대한 관리형 서버리스 게이트웨이가 제공됩니다.
이 가이드에서는 이 기법을 사용하여 앱에서 Cloud Vision API를 호출하는 방법을 설명합니다. 이 방법을 사용하면 인증된 모든 사용자가 Cloud 프로젝트를 통해 Cloud Vision 청구 서비스에 액세스할 수 있으므로 계속하기 전에 이 인증 메커니즘이 현재 사용 사례에 충분한지 고려해야 합니다.
시작하기 전에
프로젝트 구성
- 아직 추가하지 않았으면 Android 프로젝트에 Firebase를 추가합니다.
-
프로젝트에 클라우드 기반 API를 아직 사용 설정하지 않았으면 지금 설정하세요.
- Firebase Console의 Firebase ML API 페이지를 엽니다.
-
프로젝트를 Blaze 요금제로 아직 업그레이드하지 않은 경우 업그레이드를 클릭하여 업그레이드하세요. 프로젝트가 Blaze 요금제가 아닌 경우에만 업그레이드하라는 메시지가 표시됩니다.
Blaze 수준 프로젝트만 클라우드 기반 API를 사용할 수 있습니다.
- 클라우드 기반 API가 아직 사용 설정되지 않은 경우 클라우드 기반 API 사용 설정을 클릭합니다.
- Cloud Vision API에 대한 액세스를 허용하지 않도록 기존 Firebase API 키를 구성합니다.
- Cloud 콘솔의 사용자 인증 정보 페이지를 엽니다.
- 목록에 있는 API 키마다 편집 화면을 열고 키 제한 섹션에서 Cloud Vision API를 제외한 모든 사용 가능한 API를 목록에 추가합니다.
호출 가능 함수 배포
다음으로 앱과 Cloud Vision API를 연결하는 데 사용할 Cloud 함수를 배포합니다. functions-samples
저장소에는 사용할 수 있는 예시가 포함되어 있습니다.
기본적으로 이 함수를 통해 Cloud Vision API에 액세스하면 앱의 인증된 사용자만 Cloud Vision API에 액세스할 수 있습니다. 다양한 요구사항에 맞게 함수를 수정할 수 있습니다.
함수 배포 단계는 다음과 같습니다.
- functions-samples repo를 클론하거나 다운로드하고
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 인증 추가
위에서 배포한 호출 가능 함수는 인증되지 않은 앱 사용자의 모든 요청을 거부합니다. 아직 추가하지 않았다면 Firebase 인증을 앱에 추가해야 합니다.
앱에 필요한 종속 항목 추가
<project>/<app-module>/build.gradle.kts
또는
<project>/<app-module>/build.gradle
).
implementation("com.google.firebase:firebase-functions:21.0.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. 호출 가능 함수를 호출하여 이미지 라벨 지정
이미지의 객체에 라벨을 지정하려면 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())); } }); }
유형을
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();
}