1. Panoramica
Nel primo codelab, caricherai le immagini in un bucket. Verrà generato un evento di creazione di file che verrà gestito da una funzione. La funzione effettuerà una chiamata all'API Vision per eseguire l'analisi delle immagini e salvare i risultati in un datastore.

Obiettivi didattici
- Cloud Storage
- Cloud Functions
- API Cloud Vision
- Cloud Firestore
2. Configurazione e requisiti
Configurazione dell'ambiente autonomo
- Accedi alla console Google Cloud e crea un nuovo progetto o riutilizzane uno esistente. Se non hai ancora un account Gmail o Google Workspace, devi crearne uno.



- Il nome del progetto è il nome visualizzato per i partecipanti a questo progetto. È una stringa di caratteri non utilizzata dalle API di Google. Puoi aggiornarlo in qualsiasi momento.
- L'ID progetto deve essere univoco in tutti i progetti Google Cloud ed è immutabile (non può essere modificato dopo essere stato impostato). La console Cloud genera automaticamente una stringa univoca, di solito non ti interessa di cosa si tratta. Nella maggior parte dei codelab, devi fare riferimento all'ID progetto (in genere è identificato come
PROJECT_ID). Se non ti piace l'ID generato, puoi generarne un altro casuale. In alternativa, puoi provare a crearne uno e vedere se è disponibile. Non può essere modificato dopo questo passaggio e rimarrà per tutta la durata del progetto. - Per tua informazione, esiste un terzo valore, un numero di progetto, utilizzato da alcune API. Scopri di più su tutti e tre questi valori nella documentazione.
- Successivamente, devi abilitare la fatturazione in Cloud Console per utilizzare le risorse/API Cloud. L'esecuzione di questo codelab non dovrebbe costare molto, se non nulla. Per arrestare le risorse in modo da non incorrere in costi di fatturazione al termine di questo tutorial, puoi eliminare le risorse che hai creato o l'intero progetto. I nuovi utenti di Google Cloud possono beneficiare del programma prova senza costi di 300$.
Avvia Cloud Shell
Sebbene Google Cloud possa essere gestito da remoto dal tuo laptop, in questo codelab utilizzerai Google Cloud Shell, un ambiente a riga di comando in esecuzione nel cloud.
Nella console Google Cloud, fai clic sull'icona di Cloud Shell nella barra degli strumenti in alto a destra:

Bastano pochi istanti per eseguire il provisioning e connettersi all'ambiente. Al termine, dovresti vedere un risultato simile a questo:

Questa macchina virtuale è caricata con tutti gli strumenti per sviluppatori di cui avrai bisogno. Offre una home directory permanente da 5 GB e viene eseguita su Google Cloud, migliorando notevolmente le prestazioni e l'autenticazione della rete. Tutto il lavoro in questo codelab può essere svolto all'interno di un browser. Non devi installare nulla.
3. Abilita API
Per questo lab, utilizzerai Cloud Functions e l'API Vision, ma prima devi abilitarle in Cloud Console o con gcloud.
Per abilitare l'API Vision in Cloud Console, cerca Cloud Vision API nella barra di ricerca:

Verrà visualizzata la pagina dell'API Cloud Vision:

Fai clic sul pulsante ENABLE.
In alternativa, puoi abilitarlo anche in Cloud Shell utilizzando lo strumento a riga di comando gcloud.
In Cloud Shell, esegui questo comando:
gcloud services enable vision.googleapis.com
Dovresti vedere che l'operazione è stata completata correttamente:
Operation "operations/acf.12dba18b-106f-4fd2-942d-fea80ecc5c1c" finished successfully.
Abilita anche Cloud Functions:
gcloud services enable cloudfunctions.googleapis.com
4. Crea il bucket (console)
Crea un bucket di archiviazione per le immagini. Puoi farlo dalla console Google Cloud ( console.cloud.google.com) o con lo strumento a riga di comando gsutil da Cloud Shell o dal tuo ambiente di sviluppo locale.
Vai ad Archiviazione
Dal menu "hamburger" (☰), vai alla pagina Storage.

Assegna un nome al bucket
Fai clic sul pulsante CREATE BUCKET.

Fai clic su CONTINUE.
Scegliere la posizione

Crea un bucket multiregionale nella regione che preferisci (in questo caso Europe).
Fai clic su CONTINUE.
Scegli la classe di archiviazione predefinita

Scegli la classe di archiviazione Standard per i tuoi dati.
Fai clic su CONTINUE.
Impostare il controllo dell'accesso

Poiché lavorerai con immagini accessibili pubblicamente, vuoi che tutte le immagini archiviate in questo bucket abbiano lo stesso controllo dell'accesso uniforme.
Scegli l'opzione di controllo dell'accesso Uniform.
Fai clic su CONTINUE.
Impostare la protezione/crittografia

Mantieni il valore predefinito (Google-managed key)), in quanto non utilizzerai le tue chiavi di crittografia.
Fai clic su CREATE per completare la creazione del bucket.
Aggiungere allUsers come visualizzatore dello spazio di archiviazione
Vai alla scheda Permissions:

Aggiungi un membro allUsers al bucket con il ruolo Storage > Storage Object Viewer nel seguente modo:

Fai clic su SAVE.
5. Crea il bucket (gsutil)
Puoi anche utilizzare lo strumento a riga di comando gsutil in Cloud Shell per creare bucket.
In Cloud Shell, imposta una variabile per il nome univoco del bucket. Cloud Shell ha già impostato GOOGLE_CLOUD_PROJECT sul tuo ID progetto univoco. Puoi aggiungerlo al nome del bucket.
Ad esempio:
export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}
Crea una zona standard multi-regione in Europa:
gsutil mb -l EU gs://${BUCKET_PICTURES}
Assicurati che l'accesso uniforme a livello di bucket sia abilitato:
gsutil uniformbucketlevelaccess set on gs://${BUCKET_PICTURES}
Rendi pubblico il bucket:
gsutil iam ch allUsers:objectViewer gs://${BUCKET_PICTURES}
Se vai alla sezione Cloud Storage della console, dovresti avere un bucket uploaded-pictures pubblico:

Verifica di poter caricare immagini nel bucket e che le immagini caricate siano disponibili pubblicamente, come spiegato nel passaggio precedente.
6. Testa l'accesso pubblico al bucket
Tornando al browser di archiviazione, vedrai il tuo bucket nell'elenco, con accesso "Pubblico" (inclusa un'icona di avviso che ti ricorda che chiunque ha accesso ai contenuti del bucket).

Il tuo bucket è ora pronto a ricevere le immagini.
Se fai clic sul nome del bucket, vengono visualizzati i dettagli.

Lì puoi provare il pulsante Upload files per verificare di poter aggiungere un'immagine al bucket. Verrà visualizzato un popup di selezione dei file che ti chiederà di selezionare un file. Una volta selezionato, verrà caricato nel bucket e vedrai di nuovo l'accesso publicattribuito automaticamente a questo nuovo file.

Accanto all'etichetta di accesso Public, vedrai anche una piccola icona a forma di link. Quando fai clic, il browser si sposta sull'URL pubblico dell'immagine, che avrà il seguente formato:
https://storage.googleapis.com/BUCKET_NAME/PICTURE_FILE.png
dove BUCKET_NAME è il nome univoco globale che hai scelto per il bucket e il nome del file dell'immagine.
Se fai clic sulla casella di controllo accanto al nome dell'immagine, il pulsante DELETE verrà attivato e potrai eliminare la prima immagine.
7. Crea la funzione
In questo passaggio, crei una funzione che reagisce agli eventi di caricamento delle immagini.
Visita la sezione Cloud Functions della console Google Cloud. Se lo visiti, il servizio Cloud Functions verrà attivato automaticamente.

Fai clic su Create function.
Scegli un nome (ad es. picture-uploaded) e la regione (ricorda di scegliere la stessa regione del bucket):

Esistono due tipi di funzioni:
- Funzioni HTTP che possono essere richiamate tramite un URL (ad es. un'API web).
- Funzioni in background che possono essere attivate da un evento.
Vuoi creare una funzione in background che venga attivata quando viene caricato un nuovo file nel bucket Cloud Storage:

Ti interessa il tipo di evento Finalize/Create, ovvero l'evento che viene attivato quando un file viene creato o aggiornato nel bucket:

Seleziona il bucket creato in precedenza per indicare a Cloud Functions di ricevere una notifica quando un file viene creato / aggiornato in questo bucket specifico:

Fai clic su Select per scegliere il bucket creato in precedenza, quindi su Save.

Prima di fare clic su Avanti, puoi espandere e modificare i valori predefiniti (256 MB di memoria) in Impostazioni di runtime, build, connessioni e sicurezza e aggiornarli a 1 GB.

Dopo aver fatto clic su Next, puoi modificare Runtime, Codice sorgente e Punto di ingresso.
Mantieni Inline editor per questa funzione:

Seleziona uno dei runtime Java, ad esempio Java 11:

Il codice sorgente è costituito da un file Java e da un file pom.xml Maven che fornisce vari metadati e dipendenze.
Lascia lo snippet di codice predefinito: registra il nome del file dell'immagine caricata:

Per ora, mantieni il nome della funzione da eseguire su Example a scopo di test.
Fai clic su Deploy per creare ed eseguire il deployment della funzione. Una volta completato il deployment, dovresti vedere un segno di spunta verde cerchiato nell'elenco delle funzioni:

8. Testa la funzione
In questo passaggio, verifica che la funzione risponda agli eventi di archiviazione.
Dal menu "hamburger" (☰), torna alla pagina Storage.
Fai clic sul bucket delle immagini e poi su Upload files per caricare un'immagine.

Nella console Cloud, vai di nuovo alla pagina Logging > Logs Explorer.
Nel selettore Log Fields, seleziona Cloud Function per visualizzare i log dedicati alle tue funzioni. Scorri verso il basso i campi log e puoi anche selezionare una funzione specifica per visualizzare in modo più granulare i log correlati alle funzioni. Seleziona la funzione picture-uploaded.
Dovresti visualizzare le voci di log che menzionano la creazione della funzione, gli orari di inizio e fine della funzione e la nostra istruzione di log effettiva:

La nostra istruzione di log recita: Processing file: pic-a-daily-architecture-events.png, il che significa che l'evento relativo alla creazione e all'archiviazione di questa immagine è stato effettivamente attivato come previsto.
9. Prepara il database
Memorizzerai le informazioni sull'immagine fornite dall'API Vision nel database Cloud Firestore, un database di documenti NoSQL veloce, serverless, completamente gestito e cloud-native. Prepara il database andando alla sezione Firestore di Cloud Console:

Sono disponibili due opzioni: Native mode o Datastore mode. Utilizza la modalità nativa, che offre funzionalità aggiuntive come il supporto offline e la sincronizzazione in tempo reale.
Fai clic su SELECT NATIVE MODE.

Scegli una regione multipla (in questo caso in Europa, ma idealmente almeno la stessa regione della funzione e del bucket di archiviazione).
Fai clic sul pulsante CREATE DATABASE.
Una volta creato il database, dovresti visualizzare quanto segue:

Crea una nuova raccolta facendo clic sul pulsante + START COLLECTION.
Raccolta di nomi pictures.

Non è necessario creare un documento. Le aggiungerai in modo programmatico man mano che le nuove immagini vengono archiviate in Cloud Storage e analizzate dall'API Vision.
Fai clic su Save.
Firestore crea un primo documento predefinito nella raccolta appena creata. Puoi eliminarlo in sicurezza perché non contiene informazioni utili:

I documenti che verranno creati a livello di programmazione nella nostra raccolta conterranno quattro campi:
- name (stringa): il nome del file dell'immagine caricata, che è anche la chiave del documento
- labels (array di stringhe): le etichette degli elementi riconosciuti dall'API Vision
- color (stringa): il codice colore esadecimale del colore dominante (ad es. #ab12ef)
- created (data): il timestamp di quando sono stati archiviati i metadati di questa immagine
- thumbnail (booleano): un campo facoltativo che sarà presente e sarà true se è stata generata un'immagine miniatura per questa immagine
Poiché eseguiremo la ricerca in Firestore per trovare le immagini per cui sono disponibili miniature e l'ordinamento in base alla data di creazione, dovremo creare un indice di ricerca.
Puoi creare l'indice con il seguente comando in Cloud Shell:
gcloud firestore indexes composite create \
--collection-group=pictures \
--field-config field-path=thumbnail,order=descending \
--field-config field-path=created,order=descending
In alternativa, puoi farlo anche da Cloud Console, facendo clic su Indexes nella colonna di navigazione a sinistra e poi creando un indice composito come mostrato di seguito:

Fai clic su Create. La creazione dell'indice può richiedere alcuni minuti.
10. Aggiorna la funzione
Torna alla pagina Functions per aggiornare la funzione in modo da richiamare l'API Vision per analizzare le nostre immagini e archiviare i metadati in Firestore.
Nel menu "hamburger" (☰), vai alla sezione Cloud Functions, fai clic sul nome della funzione, seleziona la scheda Source e poi fai clic sul pulsante EDIT.
Innanzitutto, modifica il file pom.xml che elenca le dipendenze della nostra funzione Java. Aggiorna il codice per aggiungere la dipendenza Maven dell'API Cloud Vision:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cloudfunctions</groupId>
<artifactId>gcs-function</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>libraries-bom</artifactId>
<version>26.1.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.cloud.functions</groupId>
<artifactId>functions-framework-api</artifactId>
<version>1.0.4</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-firestore</artifactId>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-vision</artifactId>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-storage</artifactId>
</dependency>
</dependencies>
<!-- Required for Java 11 functions in the inline editor -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<excludes>
<exclude>.google/</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
Ora che le dipendenze sono aggiornate, lavoreremo sul codice della nostra funzione aggiornando il file Example.java con il nostro codice personalizzato.
Sposta il mouse sul file Example.java e fai clic sulla matita. Sostituisci il nome del pacchetto e il nome del file con src/main/java/fn/ImageAnalysis.java.
Sostituisci il codice in ImageAnalysis.java con il codice che segue. Verrà spiegato nel passaggio successivo.
package fn;
import com.google.cloud.functions.*;
import com.google.cloud.vision.v1.*;
import com.google.cloud.vision.v1.Feature.Type;
import com.google.cloud.firestore.*;
import com.google.api.core.ApiFuture;
import java.io.*;
import java.util.*;
import java.util.stream.*;
import java.util.concurrent.*;
import java.util.logging.Logger;
import fn.ImageAnalysis.GCSEvent;
public class ImageAnalysis implements BackgroundFunction<GCSEvent> {
private static final Logger logger = Logger.getLogger(ImageAnalysis.class.getName());
@Override
public void accept(GCSEvent event, Context context)
throws IOException, InterruptedException, ExecutionException {
String fileName = event.name;
String bucketName = event.bucket;
logger.info("New picture uploaded " + fileName);
try (ImageAnnotatorClient vision = ImageAnnotatorClient.create()) {
List<AnnotateImageRequest> requests = new ArrayList<>();
ImageSource imageSource = ImageSource.newBuilder()
.setGcsImageUri("gs://" + bucketName + "/" + fileName)
.build();
Image image = Image.newBuilder()
.setSource(imageSource)
.build();
Feature featureLabel = Feature.newBuilder()
.setType(Type.LABEL_DETECTION)
.build();
Feature featureImageProps = Feature.newBuilder()
.setType(Type.IMAGE_PROPERTIES)
.build();
Feature featureSafeSearch = Feature.newBuilder()
.setType(Type.SAFE_SEARCH_DETECTION)
.build();
AnnotateImageRequest request = AnnotateImageRequest.newBuilder()
.addFeatures(featureLabel)
.addFeatures(featureImageProps)
.addFeatures(featureSafeSearch)
.setImage(image)
.build();
requests.add(request);
logger.info("Calling the Vision API...");
BatchAnnotateImagesResponse result = vision.batchAnnotateImages(requests);
List<AnnotateImageResponse> responses = result.getResponsesList();
if (responses.size() == 0) {
logger.info("No response received from Vision API.");
return;
}
AnnotateImageResponse response = responses.get(0);
if (response.hasError()) {
logger.info("Error: " + response.getError().getMessage());
return;
}
List<String> labels = response.getLabelAnnotationsList().stream()
.map(annotation -> annotation.getDescription())
.collect(Collectors.toList());
logger.info("Annotations found:");
for (String label: labels) {
logger.info("- " + label);
}
String mainColor = "#FFFFFF";
ImageProperties imgProps = response.getImagePropertiesAnnotation();
if (imgProps.hasDominantColors()) {
DominantColorsAnnotation colorsAnn = imgProps.getDominantColors();
ColorInfo colorInfo = colorsAnn.getColors(0);
mainColor = rgbHex(
colorInfo.getColor().getRed(),
colorInfo.getColor().getGreen(),
colorInfo.getColor().getBlue());
logger.info("Color: " + mainColor);
}
boolean isSafe = false;
if (response.hasSafeSearchAnnotation()) {
SafeSearchAnnotation safeSearch = response.getSafeSearchAnnotation();
isSafe = Stream.of(
safeSearch.getAdult(), safeSearch.getMedical(), safeSearch.getRacy(),
safeSearch.getSpoof(), safeSearch.getViolence())
.allMatch( likelihood ->
likelihood != Likelihood.LIKELY && likelihood != Likelihood.VERY_LIKELY
);
logger.info("Safe? " + isSafe);
}
// Saving result to Firestore
if (isSafe) {
FirestoreOptions firestoreOptions = FirestoreOptions.getDefaultInstance();
Firestore pictureStore = firestoreOptions.getService();
DocumentReference doc = pictureStore.collection("pictures").document(fileName);
Map<String, Object> data = new HashMap<>();
data.put("labels", labels);
data.put("color", mainColor);
data.put("created", new Date());
ApiFuture<WriteResult> writeResult = doc.set(data, SetOptions.merge());
logger.info("Picture metadata saved in Firestore at " + writeResult.get().getUpdateTime());
}
}
}
private static String rgbHex(float red, float green, float blue) {
return String.format("#%02x%02x%02x", (int)red, (int)green, (int)blue);
}
public static class GCSEvent {
String bucket;
String name;
}
}

11. Esplorare la funzione
Diamo un'occhiata più da vicino alle varie parti interessanti.
Innanzitutto, includeremo le dipendenze specifiche nel file pom.xml di Maven. Le librerie client Java di Google pubblicano un Bill-of-Materials(BOM) per eliminare eventuali conflitti di dipendenze. Se lo utilizzi, non devi specificare alcuna versione per le singole librerie client Google.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>libraries-bom</artifactId>
<version>26.1.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Poi prepariamo un client per l'API Vision:
...
try (ImageAnnotatorClient vision = ImageAnnotatorClient.create()) {
...
Ora vediamo la struttura della nostra funzione. Acquisiamo dall'evento in entrata i campi che ci interessano e li mappiamo alla struttura GCSEvent che definiamo:
...
public class ImageAnalysis implements BackgroundFunction<GCSEvent> {
@Override
public void accept(GCSEvent event, Context context)
throws IOException, InterruptedException,
ExecutionException {
...
public static class GCSEvent {
String bucket;
String name;
}
Nota la firma, ma anche come recuperiamo il nome del file e del bucket che hanno attivato la funzione Cloud Functions.
Per riferimento, ecco l'aspetto del payload 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"
}
Prepariamo una richiesta da inviare tramite il client Vision:
ImageSource imageSource = ImageSource.newBuilder()
.setGcsImageUri("gs://" + bucketName + "/" + fileName)
.build();
Image image = Image.newBuilder()
.setSource(imageSource)
.build();
Feature featureLabel = Feature.newBuilder()
.setType(Type.LABEL_DETECTION)
.build();
Feature featureImageProps = Feature.newBuilder()
.setType(Type.IMAGE_PROPERTIES)
.build();
Feature featureSafeSearch = Feature.newBuilder()
.setType(Type.SAFE_SEARCH_DETECTION)
.build();
AnnotateImageRequest request = AnnotateImageRequest.newBuilder()
.addFeatures(featureLabel)
.addFeatures(featureImageProps)
.addFeatures(featureSafeSearch)
.setImage(image)
.build();
Ti chiediamo di utilizzare tre funzionalità chiave dell'API Vision:
- Rilevamento delle etichette: per capire cosa c'è nelle foto
- Proprietà dell'immagine: per fornire attributi interessanti dell'immagine (ci interessa il colore dominante dell'immagine)
- SafeSearch: per sapere se l'immagine è sicura da mostrare (non deve contenere contenuti per adulti, medici, osé o violenti)
A questo punto, possiamo effettuare la chiamata all'API Vision:
...
logger.info("Calling the Vision API...");
BatchAnnotateImagesResponse result =
vision.batchAnnotateImages(requests);
List<AnnotateImageResponse> responses = result.getResponsesList();
...
Per riferimento, ecco come appare la risposta dell'API 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
}
Se non viene restituito alcun errore, possiamo andare avanti, ecco perché abbiamo questo blocco if:
AnnotateImageResponse response = responses.get(0);
if (response.hasError()) {
logger.info("Error: " + response.getError().getMessage());
return;
}
Recupereremo le etichette delle cose, delle categorie o dei temi riconosciuti nell'immagine:
List<String> labels = response.getLabelAnnotationsList().stream()
.map(annotation -> annotation.getDescription())
.collect(Collectors.toList());
logger.info("Annotations found:");
for (String label: labels) {
logger.info("- " + label);
}
Ci interessa conoscere il colore dominante dell'immagine:
String mainColor = "#FFFFFF";
ImageProperties imgProps = response.getImagePropertiesAnnotation();
if (imgProps.hasDominantColors()) {
DominantColorsAnnotation colorsAnn =
imgProps.getDominantColors();
ColorInfo colorInfo = colorsAnn.getColors(0);
mainColor = rgbHex(
colorInfo.getColor().getRed(),
colorInfo.getColor().getGreen(),
colorInfo.getColor().getBlue());
logger.info("Color: " + mainColor);
}
Utilizziamo anche una funzione di utilità per trasformare i valori rosso / verde / blu in un codice colore esadecimale che possiamo utilizzare nei fogli di stile CSS.
Controlliamo se l'immagine è sicura da mostrare:
boolean isSafe = false;
if (response.hasSafeSearchAnnotation()) {
SafeSearchAnnotation safeSearch =
response.getSafeSearchAnnotation();
isSafe = Stream.of(
safeSearch.getAdult(), safeSearch.getMedical(), safeSearch.getRacy(),
safeSearch.getSpoof(), safeSearch.getViolence())
.allMatch( likelihood ->
likelihood != Likelihood.LIKELY && likelihood != Likelihood.VERY_LIKELY
);
logger.info("Safe? " + isSafe);
}
Stiamo controllando gli attributi per adulti / spoof / medici / violenza / contenuti allusivi per verificare se non sono probabili o molto probabili.
Se il risultato della ricerca sicura è accettabile, possiamo archiviare i metadati in Firestore:
if (isSafe) {
FirestoreOptions firestoreOptions = FirestoreOptions.getDefaultInstance();
Firestore pictureStore = firestoreOptions.getService();
DocumentReference doc = pictureStore.collection("pictures").document(fileName);
Map<String, Object> data = new HashMap<>();
data.put("labels", labels);
data.put("color", mainColor);
data.put("created", new Date());
ApiFuture<WriteResult> writeResult = doc.set(data, SetOptions.merge());
logger.info("Picture metadata saved in Firestore at " + writeResult.get().getUpdateTime());
}
12. esegui il deployment della funzione
È ora di eseguire il deployment della funzione.

Premi il pulsante DEPLOY e verrà eseguito il deployment della nuova versione. Puoi visualizzare l'avanzamento:

13. Testa di nuovo la funzione
Una volta eseguito il deployment della funzione, pubblicherai un'immagine in Cloud Storage, verificherai se la nostra funzione viene richiamata, cosa restituisce l'API Vision e se i metadati vengono archiviati in Firestore.
Torna a Cloud Storage e fai clic sul bucket che abbiamo creato all'inizio del lab:

Una volta nella pagina dei dettagli del bucket, fai clic sul pulsante Upload files per caricare un'immagine.

Dal menu "hamburger" (☰), vai a Esplora Logging > Logs.
Nel selettore Log Fields, seleziona Cloud Function per visualizzare i log dedicati alle tue funzioni. Scorri verso il basso i campi log e puoi anche selezionare una funzione specifica per visualizzare in modo più granulare i log correlati alle funzioni. Seleziona la funzione picture-uploaded.

Infatti, nell'elenco dei log, posso vedere che la nostra funzione è stata richiamata:

I log indicano l'inizio e la fine dell'esecuzione della funzione. Nel mezzo, possiamo vedere i log inseriti nella nostra funzione con le istruzioni console.log(). Vediamo:
- I dettagli dell'evento che attiva la nostra funzione.
- I risultati non elaborati della chiamata API Vision,
- Le etichette trovate nell'immagine che abbiamo caricato,
- Le informazioni sui colori dominanti,
- Se l'immagine è sicura da mostrare.
- e alla fine i metadati dell'immagine sono stati archiviati in Firestore.

Sempre dal menu "hamburger" (☰), vai alla sezione Firestore. Nella sottosezione Data (visualizzata per impostazione predefinita), dovresti vedere la raccolta pictures con un nuovo documento aggiunto, corrispondente all'immagine che hai appena caricato:

14. Pulizia (facoltativo)
Se non intendi continuare con gli altri lab della serie, puoi eseguire la pulizia delle risorse per risparmiare sui costi e per essere un buon cittadino del cloud. Puoi ripulire le risorse singolarmente nel seguente modo.
Elimina il bucket:
gsutil rb gs://${BUCKET_PICTURES}
Elimina la funzione:
gcloud functions delete picture-uploaded --region europe-west1 -q
Elimina la raccolta Firestore selezionando Elimina raccolta dalla raccolta:

In alternativa, puoi eliminare l'intero progetto:
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
15. Complimenti!
Complimenti! Hai implementato correttamente il primo servizio chiave del progetto.
Argomenti trattati
- Cloud Storage
- Cloud Functions
- API Cloud Vision
- Cloud Firestore