1. Descripción general
En el primer lab de código, subirás imágenes a un bucket. Esto generará un evento de creación de archivos que controlará una función. La función llamará a la API de Vision para analizar la imagen y guardar los resultados en un almacén de datos.

Qué aprenderás
- Cloud Storage
- Cloud Functions
- API de Cloud Vision
- Cloud Firestore
2. Configuración y requisitos
Configuración del entorno de autoaprendizaje
- Accede a Google Cloud Console y crea un proyecto nuevo o reutiliza uno existente. Si aún no tienes una cuenta de Gmail o de Google Workspace, debes crear una.



- El Nombre del proyecto es el nombre visible de los participantes de este proyecto. Es una cadena de caracteres que no se utiliza en las APIs de Google. Puedes actualizarla en cualquier momento.
- El ID del proyecto debe ser único en todos los proyectos de Google Cloud y es inmutable (no se puede cambiar después de configurarlo). La consola de Cloud genera automáticamente una cadena única. Por lo general, no importa cuál sea. En la mayoría de los codelabs, deberás hacer referencia al ID del proyecto (suele identificarse como
PROJECT_ID). Si no te gusta el ID que se generó, podrías generar otro aleatorio. También puedes probar uno propio y ver si está disponible. No se puede cambiar después de este paso y se usará el mismo durante todo el proyecto. - Recuerda que hay un tercer valor, un número de proyecto, que usan algunas APIs. Obtén más información sobre estos tres valores en la documentación.
- A continuación, deberás habilitar la facturación en la consola de Cloud para usar las APIs o los recursos de Cloud. Ejecutar este codelab no debería costar mucho, tal vez nada. Para cerrar recursos y evitar que se generen cobros más allá de este instructivo, puedes borrar los recursos que creaste o borrar todo el proyecto. Los usuarios nuevos de Google Cloud son aptos para participar en el programa Prueba gratuita de USD 300.
Inicia Cloud Shell
Si bien Google Cloud y Spanner se pueden operar de manera remota desde tu laptop, en este codelab usarás Google Cloud Shell, un entorno de línea de comandos que se ejecuta en la nube.
En Google Cloud Console, haz clic en el ícono de Cloud Shell en la barra de herramientas en la parte superior derecha:

El aprovisionamiento y la conexión al entorno deberían tomar solo unos minutos. Cuando termine el proceso, debería ver algo como lo siguiente:

Esta máquina virtual está cargada con todas las herramientas de desarrollo que necesitarás. Ofrece un directorio principal persistente de 5 GB y se ejecuta en Google Cloud, lo que permite mejorar considerablemente el rendimiento de la red y la autenticación. Todo tu trabajo en este codelab se puede hacer en un navegador. No es necesario que instales nada.
3. Habilita las APIs
En este lab, usarás Cloud Functions y la API de Vision, pero primero debes habilitarlas en la consola de Cloud o con gcloud.
Para habilitar la API de Vision en Cloud Console, busca Cloud Vision API en la barra de búsqueda:

Llegarás a la página de la API de Cloud Vision:

Haz clic en el botón ENABLE.
Como alternativa, también puedes habilitar Cloud Shell con la herramienta de línea de comandos de gcloud.
En Cloud Shell, ejecuta el siguiente comando:
gcloud services enable vision.googleapis.com
Deberías ver que la operación finaliza correctamente:
Operation "operations/acf.12dba18b-106f-4fd2-942d-fea80ecc5c1c" finished successfully.
También habilita Cloud Functions:
gcloud services enable cloudfunctions.googleapis.com
4. Crea el bucket (consola)
Crea un bucket de almacenamiento para las imágenes. Puedes hacerlo desde la consola de Google Cloud Platform ( console.cloud.google.com) o con la herramienta de línea de comandos de gsutil desde Cloud Shell o tu entorno de desarrollo local.
Navega a Almacenamiento
En el menú de hamburguesa (☰), navega a la página Storage.

Asigna un nombre a tu bucket
Haz clic en el botón CREATE BUCKET.

Haz clic en CONTINUE.
Elegir ubicación

Crea un bucket multirregional en la región que elijas (aquí Europe).
Haz clic en CONTINUE.
Elige la clase de almacenamiento predeterminada

Elige la clase de almacenamiento Standard para tus datos.
Haz clic en CONTINUE.
Establecer control de acceso

Como trabajarás con imágenes de acceso público, quieres que todas las imágenes almacenadas en este bucket tengan el mismo control de acceso uniforme.
Elige la opción de control de acceso Uniform.
Haz clic en CONTINUE.
Establecer protección/encriptación

Mantén la opción predeterminada (Google-managed key)), ya que no usarás tus propias claves de encriptación.
Haz clic en CREATE para finalizar la creación del bucket.
Agrega allUsers como visualizador de almacenamiento
Ve a la pestaña Permissions:

Agrega un miembro allUsers al bucket con el rol Storage > Storage Object Viewer de la siguiente manera:

Haz clic en SAVE.
5. Crea el bucket (gsutil)
También puedes usar la herramienta de línea de comandos de gsutil en Cloud Shell para crear buckets.
En Cloud Shell, configura una variable para el nombre único del bucket. Cloud Shell ya tiene GOOGLE_CLOUD_PROJECT establecido en tu ID del proyecto único. Puedes agregar ese valor al nombre del bucket.
Por ejemplo:
export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}
Crea una zona estándar multirregional en Europa:
gsutil mb -l EU gs://${BUCKET_PICTURES}
Asegúrate de que el acceso uniforme a nivel de bucket esté habilitado:
gsutil uniformbucketlevelaccess set on gs://${BUCKET_PICTURES}
Configura el bucket como público:
gsutil iam ch allUsers:objectViewer gs://${BUCKET_PICTURES}
Si vas a la sección Cloud Storage de la consola, deberías tener un bucket uploaded-pictures público:

Comprueba que puedes subir fotos al bucket y que las fotos subidas están disponibles de forma pública, como se explicó en el paso anterior.
6. Prueba el acceso público al bucket
Si vuelves al navegador de almacenamiento, verás tu bucket en la lista, con acceso "Público" (incluido un signo de advertencia que te recuerda que cualquier persona tiene acceso al contenido de ese bucket).

Tu bucket ahora está listo para recibir imágenes.
Si haces clic en el nombre del bucket, verás sus detalles.

Allí, puedes probar el botón Upload files para verificar que puedes agregar una imagen al bucket. Aparecerá una ventana emergente del selector de archivos en la que se te pedirá que selecciones un archivo. Una vez que lo selecciones, se subirá a tu bucket y volverás a ver el acceso public que se atribuyó automáticamente a este archivo nuevo.

Junto a la etiqueta de acceso Public, también verás un pequeño ícono de vínculo. Cuando hagas clic en ella, tu navegador navegará a la URL pública de esa imagen, que tendrá el siguiente formato:
https://storage.googleapis.com/BUCKET_NAME/PICTURE_FILE.png
donde BUCKET_NAME es el nombre único a nivel global que elegiste para tu bucket y, luego, el nombre de archivo de tu imagen.
Si haces clic en la casilla de verificación junto al nombre de la imagen, se habilitará el botón DELETE, y podrás borrar la primera imagen.
7. Crea la función
En este paso, crearás una función que reaccione a los eventos de carga de imágenes.
Visita la sección Cloud Functions de la consola de Google Cloud. Cuando la visites, se habilitará automáticamente el servicio de Cloud Functions.

Haz clic en Create function:
Elige un nombre (p. ej., picture-uploaded) y la región (recuerda ser coherente con la elección de la región para el bucket):

Existen dos tipos de funciones:
- Funciones de HTTP que se pueden invocar a través de una URL (es decir, una API web)
- Son funciones en segundo plano que se pueden activar con algún evento.
Quieres crear una función en segundo plano que se active cuando se suba un archivo nuevo a nuestro bucket de Cloud Storage:

Te interesa el tipo de evento Finalize/Create, que es el evento que se activa cuando se crea o actualiza un archivo en el bucket:

Selecciona el bucket creado anteriormente para indicarle a Cloud Functions que reciba notificaciones cuando se cree o actualice un archivo en ese bucket en particular:

Haz clic en Select para elegir el bucket que creaste antes y, luego, en Save.

Antes de hacer clic en Siguiente, puedes expandir y modificar los valores predeterminados (256 MB de memoria) en Configuración del entorno de ejecución, la compilación, las conexiones y la seguridad, y actualizarlo a 1 GB.

Después de hacer clic en Next, puedes ajustar el entorno de ejecución, el código fuente y el punto de entrada.
Conserva el Inline editor para esta función:

Selecciona uno de los entornos de ejecución de Node.js:

El código fuente consta de un archivo index.js de JavaScript y un archivo package.json que proporciona varios metadatos y dependencias.
Deja el fragmento de código predeterminado: registra el nombre del archivo de la imagen subida:

Por el momento, mantén el nombre de la función que se ejecutará en helloGCS para fines de prueba.
Haz clic en Deploy para crear e implementar la función. Una vez que la implementación se haya realizado correctamente, verás una marca de verificación con un círculo verde en la lista de funciones:

8. Prueba la función
En este paso, probarás que la función responda a los eventos de almacenamiento.
En el menú de navegación (☰), vuelve a la página Storage.
Haz clic en el bucket de imágenes y, luego, en Upload files para subir una imagen.

Vuelve a navegar en la consola de Cloud para ir a la página de Logging > Logs Explorer.
En el selector Log Fields, selecciona Cloud Function para ver los registros dedicados a tus funciones. Desplázate hacia abajo por los campos de registro y podrás seleccionar una función específica para tener una vista más detallada de los registros relacionados con las funciones. Selecciona la función picture-uploaded.
Deberías ver los elementos de registro que mencionan la creación de la función, las horas de inicio y finalización de la función, y nuestra instrucción de registro real:

Nuestra instrucción de registro dice: Processing file: pic-a-daily-architecture-events.png, lo que significa que el evento relacionado con la creación y el almacenamiento de esta imagen se activó según lo previsto.
9. Prepara la base de datos
Almacenarás la información sobre la imagen proporcionada por la API de Vision en la base de datos de Cloud Firestore, una base de datos de documentos NoSQL nativa de la nube, rápida, completamente administrada y sin servidores. Para preparar tu base de datos, ve a la sección Firestore de Cloud Console:

Se ofrecen dos opciones: Native mode o Datastore mode. Usa el modo nativo, que ofrece funciones adicionales, como asistencia sin conexión y sincronización en tiempo real.
Haz clic en SELECT NATIVE MODE.

Elige una región múltiple (aquí en Europa, pero lo ideal sería que fuera al menos la misma región en la que se encuentran tu función y tu bucket de almacenamiento).
Haz clic en el botón CREATE DATABASE.
Una vez que se cree la base de datos, deberías ver lo siguiente:

Haz clic en el botón + START COLLECTION para crear una colección nueva.
Colección de nombres pictures.

No es necesario que crees un documento. Los agregarás de forma programática a medida que se almacenen imágenes nuevas en Cloud Storage y se analicen con la API de Vision.
Haz clic en Save.
Firestore crea un primer documento predeterminado en la colección recién creada. Puedes borrarlo de forma segura, ya que no contiene información útil:

Los documentos que se crearán de forma programática en nuestra colección contendrán 4 campos:
- name (cadena): Es el nombre del archivo de la imagen subida, que también es la clave del documento.
- labels (array de cadenas): Son las etiquetas de los elementos reconocidos por la API de Vision.
- color (cadena): Es el código de color hexadecimal del color predominante (p. ej., #ab12ef)
- created (fecha): Es la marca de tiempo en la que se almacenaron los metadatos de esta imagen.
- thumbnail (booleano): Es un campo opcional que estará presente y será verdadero si se generó una imagen en miniatura para esta foto.
Como buscaremos en Firestore imágenes que tengan miniaturas disponibles y las ordenaremos según la fecha de creación, deberemos crear un índice de búsqueda.
Puedes crear el índice con el siguiente comando en Cloud Shell:
gcloud firestore indexes composite create \
--collection-group=pictures \
--field-config field-path=thumbnail,order=descending \
--field-config field-path=created,order=descending
También puedes hacerlo desde Cloud Console. Para ello, haz clic en Indexes en la columna de navegación de la izquierda y, luego, crea un índice compuesto como se muestra a continuación:

Haz clic en Create. La creación del índice puede tardar unos minutos.
10. Actualiza la función
Vuelve a la página Functions para actualizar la función de modo que invoque la API de Vision para analizar nuestras imágenes y almacenar los metadatos en Firestore.
En el menú “hamburguesa” (☰), navega a la sección Cloud Functions, haz clic en el nombre de la función, selecciona la pestaña Source y, luego, haz clic en el botón EDIT.
Primero, edita el archivo package.json, que enumera las dependencias de nuestra función de Node.js. Actualiza el código para agregar la dependencia de NPM de la API de Cloud Vision:
{
"name": "picture-analysis-function",
"version": "0.0.1",
"dependencies": {
"@google-cloud/storage": "^1.6.0",
"@google-cloud/vision": "^1.8.0",
"@google-cloud/firestore": "^3.4.1"
}
}
Ahora que las dependencias están actualizadas, trabajarás en el código de nuestra función actualizando el archivo index.js.
Reemplaza el código de index.js por el siguiente. Esto se explicará en el siguiente paso.
const vision = require('@google-cloud/vision');
const Storage = require('@google-cloud/storage');
const Firestore = require('@google-cloud/firestore');
const client = new vision.ImageAnnotatorClient();
exports.vision_analysis = async (event, context) => {
console.log(`Event: ${JSON.stringify(event)}`);
const filename = event.name;
const filebucket = event.bucket;
console.log(`New picture uploaded ${filename} in ${filebucket}`);
const request = {
image: { source: { imageUri: `gs://${filebucket}/${filename}` } },
features: [
{ type: 'LABEL_DETECTION' },
{ type: 'IMAGE_PROPERTIES' },
{ type: 'SAFE_SEARCH_DETECTION' }
]
};
// invoking the Vision API
const [response] = await client.annotateImage(request);
console.log(`Raw vision output for: ${filename}: ${JSON.stringify(response)}`);
if (response.error === null) {
// listing the labels found in the picture
const labels = response.labelAnnotations
.sort((ann1, ann2) => ann2.score - ann1.score)
.map(ann => ann.description)
console.log(`Labels: ${labels.join(', ')}`);
// retrieving the dominant color of the picture
const color = response.imagePropertiesAnnotation.dominantColors.colors
.sort((c1, c2) => c2.score - c1.score)[0].color;
const colorHex = decColorToHex(color.red, color.green, color.blue);
console.log(`Colors: ${colorHex}`);
// determining if the picture is safe to show
const safeSearch = response.safeSearchAnnotation;
const isSafe = ["adult", "spoof", "medical", "violence", "racy"].every(k =>
!['LIKELY', 'VERY_LIKELY'].includes(safeSearch[k]));
console.log(`Safe? ${isSafe}`);
// if the picture is safe to display, store it in Firestore
if (isSafe) {
const pictureStore = new Firestore().collection('pictures');
const doc = pictureStore.doc(filename);
await doc.set({
labels: labels,
color: colorHex,
created: Firestore.Timestamp.now()
}, {merge: true});
console.log("Stored metadata in Firestore");
}
} else {
throw new Error(`Vision API error: code ${response.error.code}, message: "${response.error.message}"`);
}
};
function decColorToHex(r, g, b) {
return '#' + Number(r).toString(16).padStart(2, '0') +
Number(g).toString(16).padStart(2, '0') +
Number(b).toString(16).padStart(2, '0');
}
11. Explora la función
Veamos con más detalle las diferentes partes interesantes.
Primero, requerimos los módulos necesarios para Vision, Storage y Firestore:
const vision = require('@google-cloud/vision');
const Storage = require('@google-cloud/storage');
const Firestore = require('@google-cloud/firestore');
Luego, preparamos un cliente para la API de Vision:
const client = new vision.ImageAnnotatorClient();
Ahora viene la estructura de nuestra función. La convertimos en una función asíncrona, ya que usamos las capacidades de async / await que se introdujeron en Node.js 8:
exports.vision_analysis = async (event, context) => {
...
const filename = event.name;
const filebucket = event.bucket;
...
}
Observa la firma, pero también cómo recuperamos el nombre del archivo y el bucket que activaron la Cloud Function.
Como referencia, a continuación, se muestra el aspecto de la carga útil del evento:
{
"bucket":"uploaded-pictures",
"contentType":"image/png",
"crc32c":"efhgyA==",
"etag":"CKqB956MmucCEAE=",
"generation":"1579795336773802",
"id":"uploaded-pictures/Screenshot.png/1579795336773802",
"kind":"storage#object",
"md5Hash":"PN8Hukfrt6C7IyhZ8d3gfQ==",
"mediaLink":"https://www.googleapis.com/download/storage/v1/b/uploaded-pictures/o/Screenshot.png?generation=1579795336773802&alt=media",
"metageneration":"1",
"name":"Screenshot.png",
"selfLink":"https://www.googleapis.com/storage/v1/b/uploaded-pictures/o/Screenshot.png",
"size":"173557",
"storageClass":"STANDARD",
"timeCreated":"2020-01-23T16:02:16.773Z",
"timeStorageClassUpdated":"2020-01-23T16:02:16.773Z",
"updated":"2020-01-23T16:02:16.773Z"
}
Preparamos una solicitud para enviarla a través del cliente de Vision:
const request = {
image: { source: { imageUri: `gs://${filebucket}/${filename}` } },
features: [
{ type: 'LABEL_DETECTION' },
{ type: 'IMAGE_PROPERTIES' },
{ type: 'SAFE_SEARCH_DETECTION' }
]
};
Solicitamos 3 capacidades clave de la API de Vision:
- Detección de etiquetas: Para comprender qué hay en esas imágenes
- Propiedades de la imagen: Para proporcionar atributos interesantes de la imagen (nos interesa el color predominante de la imagen)
- Búsqueda segura: Para saber si la imagen es segura para mostrar (no debe contener contenido para adultos, médico, subido de tono ni violento)
En este punto, podemos llamar a la API de Vision:
const [response] = await client.annotateImage(request);
Como referencia, así se ve la respuesta de la API de Vision:
{
"faceAnnotations": [],
"landmarkAnnotations": [],
"logoAnnotations": [],
"labelAnnotations": [
{
"locations": [],
"properties": [],
"mid": "/m/01yrx",
"locale": "",
"description": "Cat",
"score": 0.9959855675697327,
"confidence": 0,
"topicality": 0.9959855675697327,
"boundingPoly": null
},
✄ - - - ✄
],
"textAnnotations": [],
"localizedObjectAnnotations": [],
"safeSearchAnnotation": {
"adult": "VERY_UNLIKELY",
"spoof": "UNLIKELY",
"medical": "VERY_UNLIKELY",
"violence": "VERY_UNLIKELY",
"racy": "VERY_UNLIKELY",
"adultConfidence": 0,
"spoofConfidence": 0,
"medicalConfidence": 0,
"violenceConfidence": 0,
"racyConfidence": 0,
"nsfwConfidence": 0
},
"imagePropertiesAnnotation": {
"dominantColors": {
"colors": [
{
"color": {
"red": 203,
"green": 201,
"blue": 201,
"alpha": null
},
"score": 0.4175916016101837,
"pixelFraction": 0.44456374645233154
},
✄ - - - ✄
]
}
},
"error": null,
"cropHintsAnnotation": {
"cropHints": [
{
"boundingPoly": {
"vertices": [
{ "x": 0, "y": 118 },
{ "x": 1177, "y": 118 },
{ "x": 1177, "y": 783 },
{ "x": 0, "y": 783 }
],
"normalizedVertices": []
},
"confidence": 0.41695669293403625,
"importanceFraction": 1
}
]
},
"fullTextAnnotation": null,
"webDetection": null,
"productSearchResults": null,
"context": null
}
Si no se devuelve ningún error, podemos continuar, por lo que tenemos este bloque if:
if (response.error === null) {
...
} else {
throw new Error(`Vision API error: code ${response.error.code},
message: "${response.error.message}"`);
}
Obtendremos las etiquetas de las cosas, las categorías o los temas que se reconocen en la imagen:
const labels = response.labelAnnotations
.sort((ann1, ann2) => ann2.score - ann1.score)
.map(ann => ann.description)
Primero ordenamos las etiquetas por la puntuación más alta.
Nos interesa conocer el color dominante de la imagen:
const color = response.imagePropertiesAnnotation.dominantColors.colors
.sort((c1, c2) => c2.score - c1.score)[0].color;
const colorHex = decColorToHex(color.red, color.green, color.blue);
Volvemos a ordenar los colores por puntuación y tomamos el primero.
También usamos una función de utilidad para transformar los valores de rojo, verde y azul en un código de color hexadecimal que podemos usar en hojas de estilo CSS.
Comprobemos si la imagen es segura para mostrarla:
const safeSearch = response.safeSearchAnnotation;
const isSafe = ["adult", "spoof", "medical", "violence", "racy"]
.every(k => !['LIKELY', 'VERY_LIKELY'].includes(safeSearch[k]));
Verificamos los atributos de contenido para adultos, suplantación, médico, violencia y sugerente para determinar si es probable o muy probable que lo sean.
Si el resultado de la búsqueda segura es correcto, podemos almacenar metadatos en Firestore:
if (isSafe) {
const pictureStore = new Firestore().collection('pictures');
const doc = pictureStore.doc(filename);
await doc.set({
labels: labels,
color: colorHex,
created: Firestore.Timestamp.now()
}, {merge: true});
}
12. Implementa la función
Es el momento de implementar la función.

Presiona el botón DEPLOY y se implementará la versión nueva. Puedes ver el progreso:

13. Vuelve a probar la función
Una vez que la función se implemente correctamente, publicarás una imagen en Cloud Storage, verás si se invoca nuestra función, qué devuelve la API de Vision y si los metadatos se almacenan en Firestore.
Regresa a Cloud Storage y haz clic en el bucket que creamos al comienzo del lab:

Una vez que estés en la página de detalles del bucket, haz clic en el botón Upload files para subir una imagen.

En el menú de "hamburguesa" (☰), navega al Logging > Logs Explorer.
En el selector Log Fields, selecciona Cloud Function para ver los registros dedicados a tus funciones. Desplázate hacia abajo por los campos de registro y podrás seleccionar una función específica para tener una vista más detallada de los registros relacionados con las funciones. Selecciona la función picture-uploaded.

Y, de hecho, en la lista de registros, puedo ver que se invocó nuestra función:

Los registros indican el inicio y el final de la ejecución de la función. En el medio, podemos ver los registros que colocamos en nuestra función con las instrucciones console.log(). Vemos lo siguiente:
- Son los detalles del evento que activa nuestra función.
- Son los resultados sin procesar de la llamada a la API de Vision.
- Las etiquetas que se encontraron en la foto que subimos
- Es la información de los colores predominantes.
- Indica si la imagen es apta para mostrarse.
- Finalmente, esos metadatos sobre la imagen se almacenaron en Firestore.

Nuevamente, desde el menú de hamburguesa (☰), ve a la sección Firestore. En la subsección Data (que se muestra de forma predeterminada), deberías ver la colección pictures con un documento nuevo agregado, que corresponde a la imagen que acabas de subir:

14. Limpieza (opcional)
Si no quieres continuar con los otros labs de la serie, puedes limpiar los recursos para ahorrar costos y ser un buen ciudadano de la nube. Puedes limpiar los recursos de forma individual de la siguiente manera.
Borra el bucket:
gsutil rb gs://${BUCKET_PICTURES}
Borra la función:
gcloud functions delete picture-uploaded --region europe-west1 -q
Para borrar la colección de Firestore, selecciona Borrar colección en la colección:

Como alternativa, puedes borrar todo el proyecto:
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
15. ¡Felicitaciones!
¡Felicitaciones! Implementaste correctamente el primer servicio clave del proyecto.
Temas abordados
- Cloud Storage
- Cloud Functions
- API de Cloud Vision
- Cloud Firestore