Para llamar a una API de Google Cloud desde tu app, debes crear una API de REST intermedia que maneje la autorización y proteja los valores secretos, como las claves de API. Luego, debes escribir código en tu app para dispositivos móviles a fin de autenticarte en este servicio intermedio y comunicarte con él.
Una forma de crear esta API de REST es usar Firebase Authentication y Functions, lo que te brinda una puerta de enlace administrada y sin servidores a las APIs de Google Cloud que controla la autenticación y se puede llamar desde tu app para dispositivos móviles con SDK precompilados.
En esta guía, se muestra cómo usar esta técnica para llamar a la API de Cloud Vision desde tu app. Este método permitirá que todos los usuarios autenticados accedan a los servicios facturados de Cloud Vision a través de tu proyecto de Cloud, por lo que debes considerar si este mecanismo de autenticación es suficiente para tu caso de uso antes de continuar.
Antes de comenzar
Configura tu proyecto
- Si aún no lo has hecho, agrega Firebase a tu proyecto de Android.
-
Si aún no habilitaste las APIs de Cloud en tu proyecto, hazlo de la siguiente manera:
- Abre la Firebase ML Página de APIs de Firebase console.
-
Si todavía no actualizaste tu proyecto a un plan de precios Blaze, haz clic en Actualizar para hacerlo (se te pedirá que realices la actualización únicamente si tu proyecto no está en el plan Blaze).
Solo los proyectos con un plan Blaze pueden usar las APIs de Cloud.
- Si las APIs de Cloud no están habilitadas, haz clic en Habilitar las APIs de Cloud.
- Configura tus claves de API de Firebase existentes para inhabilitar el acceso a la API de Cloud Vision siguiendo los pasos que se indican a continuación:
- Abre la página Credenciales de la consola de Cloud.
- Para cada clave de API de la lista, abre la vista de edición y, en la sección Restricciones de claves, agrega a la lista todas las APIs disponibles excepto la de Cloud Vision.
Implementa la función que admite llamadas
A continuación, implementa la Cloud Function que usarás para conectar tu app y la API de Cloud Vision. El repositorio functions-samples
contiene un ejemplo
que puedes usar.
De forma predeterminada, el acceso a la API de Cloud Vision a través de esta función permitirá que solo los usuarios autenticados de tu app accedan a la API de Cloud Vision. Puedes modificar la función para diferentes requisitos.
Sigue estos pasos para implementar la función:
- Clona o descarga functions-samples repo
y cambia al directorio
Node-1st-gen/vision-annotate-image
:git clone https://github.com/firebase/functions-samples
cd Node-1st-gen/vision-annotate-image
- Instala las dependencias:
cd functions
npm install
cd ..
- Si no tienes Firebase CLI, instálalo.
- Inicializa un proyecto de Firebase en el directorio
vision-annotate-image
. Cuando se te solicite, selecciona tu proyecto en la lista.firebase init
- Sigue estos pasos para implementar la función:
firebase deploy --only functions:annotateImage
Agrega Firebase Auth a tu app
La función que admite llamadas implementada anteriormente rechazará todas las solicitudes de usuarios no autenticados de tu app. Si aún no lo has hecho, tendrás que agregar Firebase Auth a tu app.
Agrega las dependencias necesarias a tu app
<project>/<app-module>/build.gradle.kts
o <project>/<app-module>/build.gradle
):
implementation("com.google.firebase:firebase-functions:21.1.0") implementation("com.google.code.gson:gson:2.8.6")
Ya estás listo para comenzar a reconocer texto en imágenes.
1. Prepara la imagen de entrada
Para llamar a Cloud Vision, la imagen debe tener el formato de una string codificada en base64. Para procesar una imagen desde un URI de archivo guardado, realiza los siguientes pasos:- Obtén la imagen como un objeto
Bitmap
:Kotlin+KTX
var bitmap: Bitmap = MediaStore.Images.Media.getBitmap(contentResolver, uri)
Java
Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), uri);
- Opcionalmente, puedes reducir la escala de la imagen para ahorrar ancho de banda. Consulta los
tamaños de imagen recomendados de 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);
- Convierte el objeto de mapa de bits en una string codificada en base64 de la siguiente manera:
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);
La imagen que representa el objeto
Bitmap
debe estar en posición
vertical, sin que sea necesario rotarla.
2. Invoca la función que admite llamadas para reconocer texto
Para reconocer texto en una imagen, invoca la función que admite llamadas pasando una solicitud de JSON de Cloud Vision.
Primero, inicializa una instancia de Cloud Functions:
Kotlin+KTX
private lateinit var functions: FirebaseFunctions // ... functions = Firebase.functions
Java
private FirebaseFunctions mFunctions; // ... mFunctions = FirebaseFunctions.getInstance();
Define un método para invocar la función:
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())); } }); }
Crea la solicitud JSON. La API de Cloud Vision es compatible con dos tipos de detección de texto:
TEXT_DETECTION
yDOCUMENT_TEXT_DETECTION
. Consulta la documentación de OCR de Cloud Vision para ver la diferencia entre los dos casos de uso.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);
También puedes proporcionar sugerencias de idioma para ayudar a detectarlos (consulta los idiomas compatibles):
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);
Por último, invoca la función:
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. Extrae texto de bloques de texto reconocido
Si la operación de reconocimiento de texto se ejecuta correctamente, se mostrará una respuesta JSON de BatchAnnotateImagesResponse en el resultado de la tarea. Las anotaciones de texto se pueden encontrar en el objetofullTextAnnotation
.
Puedes obtener el texto reconocido como una string en el campo text
. Por ejemplo:
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());
También puedes obtener información específica de las regiones de la imagen. Para cada block
, paragraph
, word
y symbol
puedes obtener el texto reconocido en la región y las coordenadas que limitan la región. Por ejemplo:
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);
}
}