앱에서 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
)에 Firebase용 Cloud Functions(클라이언트) 및 gson Android 라이브러리의 종속 항목을 추가합니다.
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+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())); } }); }
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);
}
}