Pic-a-daily: lab 1 - Archiviazione e analisi delle immagini (Java)

1. Panoramica

Nel primo lab di codice, 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.

d650ca5386ea71ad.png

Cosa imparerai a fare

  • Cloud Storage
  • Cloud Functions
  • API Cloud Vision
  • Cloud Firestore

2. Configurazione e requisiti

Configurazione dell'ambiente da seguire in modo autonomo

  1. 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.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Il Nome progetto è il nome visualizzato dei partecipanti del progetto. Si tratta di una stringa di caratteri non utilizzata dalle API di Google. Puoi aggiornarla 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 importa cosa sia. Nella maggior parte dei codelab, dovrai fare riferimento all'ID progetto (in genere è identificato come PROJECT_ID). Se l'ID generato non ti soddisfa, puoi generarne un altro casuale. In alternativa, puoi provarne una personalizzata per verificare se è disponibile. Non può essere modificato dopo questo passaggio e rimarrà per tutta la durata del progetto.
  • Per informazione, c'è un terzo valore, un numero di progetto, utilizzato da alcune API. Scopri di più su tutti e tre questi valori nella documentazione.
  1. Successivamente, dovrai abilitare la fatturazione nella console Cloud per utilizzare risorse/API Cloud. Eseguire questo codelab non dovrebbe costare molto. Per arrestare le risorse in modo da non incorrere in fatturazione oltre questo tutorial, puoi eliminare le risorse che hai creato o eliminare l'intero progetto. I nuovi utenti di Google Cloud sono idonei al programma prova senza costi di 300$.

Avvia Cloud Shell

Anche se Google Cloud può essere utilizzato da remoto dal tuo laptop, in questo codelab utilizzerai Google Cloud Shell, un ambiente a riga di comando in esecuzione nel cloud.

Dalla console Google Cloud, fai clic sull'icona di Cloud Shell nella barra degli strumenti in alto a destra:

55efc1aaa7a4d3ad.png

Dovrebbe richiedere solo qualche istante per eseguire il provisioning e connettersi all'ambiente. Al termine, dovresti vedere una schermata simile al seguente:

7ffe5cbb04455448.png

Questa macchina virtuale viene caricata con tutti gli strumenti di sviluppo necessari. Offre una home directory permanente da 5 GB e viene eseguita su Google Cloud, migliorando notevolmente le prestazioni di rete e l'autenticazione. Tutto il lavoro in questo codelab può essere svolto all'interno di un browser. Non occorre installare nulla.

3. Abilita API

Per questo lab utilizzerai Cloud Functions e l'API Vision, ma prima devi abilitarle nella console Cloud o con gcloud.

Per abilitare l'API Vision nella console Cloud, cerca Cloud Vision API nella barra di ricerca:

cf48b1747ba6a6fb.png

Verrà visualizzata la pagina dell'API Cloud Vision:

ba4af419e6086fbb.png

Fai clic sul pulsante ENABLE.

In alternativa, puoi anche abilitarlo Cloud Shell utilizzando lo strumento a riga di comando gcloud.

All'interno di Cloud Shell, esegui questo comando:

gcloud services enable vision.googleapis.com

L'operazione dovrebbe essere 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)

Creare un bucket di archiviazione per le immagini. Puoi eseguire questa operazione dalla console della piattaforma Google Cloud ( console.cloud.google.com) o con lo strumento a riga di comando gsutil da Cloud Shell o dall'ambiente di sviluppo locale.

Dall'"hamburger" (Segnaposto), vai alla pagina Storage.

1930e055d138150a.png

Assegna un nome al bucket

Fai clic sul pulsante CREATE BUCKET.

34147939358517f8.png

Fai clic su CONTINUE.

Scegli località

197817f20be07678.png

Crea un bucket multiregionale nella regione che preferisci (qui Europe).

Fai clic su CONTINUE.

Scegli la classe di archiviazione predefinita

53cd91441c8caf0e.png

Scegli la classe di archiviazione Standard per i tuoi dati.

Fai clic su CONTINUE.

Impostare il controllo dell'accesso

8c2b3b459d934a51.png

Dal momento che lavorerai con immagini accessibili pubblicamente, vuoi che tutte le nostre immagini archiviate in questo bucket abbiano lo stesso controllo dell'accesso uniforme.

Scegli l'opzione di controllo dell'accesso di Uniform.

Fai clic su CONTINUE.

Imposta protezione/crittografia

d931c24c3e705a68.png

Mantieni l'impostazione predefinita (Google-managed key), perché non userai le tue chiavi di crittografia.

Fai clic su CREATE per finalizzare la creazione del bucket.

Aggiungi allUsers come visualizzatore dello spazio di archiviazione

Vai alla scheda Permissions:

d0ecfdcff730ea51.png

Aggiungi un membro allUsers al bucket con il ruolo Storage > Storage Object Viewer, come segue:

e9f25ec1ea0b6cc6.png

Fai clic su SAVE.

5. Crea il bucket (gsutil)

Puoi anche utilizzare lo strumento a riga di comando gsutil in Cloud Shell per creare i bucket.

In Cloud Shell, imposta una variabile per il nome univoco del bucket. In Cloud Shell è 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 multiregionale standard in Europa:

gsutil mb -l EU gs://${BUCKET_PICTURES}

Garantisci un accesso uniforme a livello di bucket:

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:

a98ed4ba17873e40.png

Verifica di poter caricare le immagini nel bucket e che le immagini caricate saranno disponibili pubblicamente, come spiegato nel passaggio precedente.

6. Testa l'accesso pubblico al bucket

Se torni al browser di Storage, vedrai il bucket nell'elenco, con "Pubblico" (incluso un segnale di avviso che ti ricorda che chiunque ha accesso ai contenuti del bucket).

89e7a4d2c80a0319.png

Il bucket è ora pronto per ricevere immagini.

Se fai clic sul nome del bucket, vedrai i relativi dettagli.

131387f12d3eb2d3.png

Qui puoi provare il pulsante Upload files per verificare di poter aggiungere un'immagine al bucket. Un popup di selezione file ti chiederà di selezionare un file. Una volta selezionato, verrà caricato nel tuo bucket e vedrai di nuovo l'accesso public che è stato attribuito automaticamente a questo nuovo file.

e87584471a6e9c6d.png

Lungo l'etichetta di accesso Public, vedrai anche una piccola icona del link. Facendo clic sull'immagine, il browser viene indirizzato all'URL pubblico dell'immagine, che avrà il seguente formato:

https://storage.googleapis.com/BUCKET_NAME/PICTURE_FILE.png

BUCKET_NAME è il nome univoco a livello globale che hai scelto per il bucket, seguito dal nome del file della tua 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, creerai una funzione che reagisce agli eventi di caricamento delle immagini.

Visita la sezione Cloud Functions della console Google Cloud. Visitandolo, il servizio Cloud Functions verrà abilitato automaticamente.

9d29e8c026a7a53f.png

Fai clic su Create function.

Scegli un nome (ad es. picture-uploaded) e la regione (ricorda di essere coerente con la scelta della regione per il bucket):

4bb222633e6f278.png

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 un nuovo file viene caricato nel nostro bucket Cloud Storage:

d9a12fcf58f4813c.png

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

b30c8859b07dc4cb.png

Seleziona il bucket creato in precedenza per fare in modo che Cloud Functions riceva una notifica quando un file viene creato / aggiornato in questo particolare bucket:

cb15a1f4c7a1ca5f.png

Fai clic su Select per scegliere il bucket che hai creato in precedenza, poi fai clic su Save

c1933777fac32c6a.png

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 aggiornarlo a 1 GB.

83d757e6c38e10.png

Dopo aver fatto clic su Next, puoi ottimizzare Runtime, Codice sorgente e punto di ingresso.

Mantieni Inline editor per questa funzione:

b6646ec646082b32.png

Seleziona uno dei runtime Java, ad esempio Java 11:

f85b8a6f951f47a7.png

Il codice sorgente è costituito da un file Java e un file Maven pom.xml che fornisce vari metadati e dipendenze.

Lascia lo snippet di codice predefinito: registra il nome del file dell'immagine caricata:

9b7b9801b42f6ca6.png

Per il momento, mantieni il nome della funzione da eseguire in Example, a scopo di test.

Fai clic su Deploy per creare la funzione ed eseguirne il deployment. Una volta completato il deployment, dovresti vedere un segno di spunta verde nell'elenco delle funzioni:

3732fdf409eefd1a.png

8. testa la funzione

In questo passaggio, verifica che la funzione risponda agli eventi di archiviazione.

Dall'"hamburger" (Segnaposto), torna alla pagina Storage.

Fai clic sul bucket delle immagini, quindi su Upload files per caricare un'immagine.

21767ec3cb8b18de.png

Vai di nuovo all'interno della console Cloud per passare 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 avere una visualizzazione più dettagliata dei log correlati alle funzioni. Seleziona la funzione picture-uploaded.

Dovresti vedere gli elementi di log che menzionano la creazione della funzione, gli orari di inizio e di fine della funzione e l'effettiva istruzione di log:

e8ba7d39c36df36c.png

La nostra istruzione di log riporta: Processing file: pic-a-daily-architecture-events.png, a indicare che l'evento relativo alla creazione e all'archiviazione di questa immagine è stato effettivamente attivato come previsto.

9. prepara il database

Archivierai le informazioni sull'immagine fornita dall'API Vision nel database Cloud Firestore, un database di documenti NoSQL cloud-native, serverless, veloce e completamente gestito. Prepara il database accedendo alla sezione Firestore della console Cloud:

9e4708d2257de058.png

Sono disponibili due opzioni: Native mode o Datastore mode. Usa la modalità nativa, che offre funzionalità extra come il supporto offline e la sincronizzazione in tempo reale.

Fai clic su SELECT NATIVE MODE.

9449ace8cc84de43.png

Scegli una località multiregionale (qui in Europa, ma idealmente almeno la stessa regione della tua funzione e del tuo bucket di archiviazione).

Fai clic sul pulsante CREATE DATABASE.

Una volta creato il database, dovresti vedere quanto segue:

56265949a124819e.png

Crea una nuova raccolta facendo clic sul pulsante + START COLLECTION.

Nome raccolta pictures.

75806ee24c4e13a7.png

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 eliminare in tutta sicurezza quel documento poiché non contiene informazioni utili:

5c2f1e17ea47f48f.png

I documenti che verranno creati in modo programmatico nella nostra raccolta conterranno quattro campi:

  • name (stringa): il nome 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)
  • creato (data): il timestamp della data di archiviazione dei metadati di questa immagine
  • thumbnail (booleano): un campo facoltativo che sarà presente e che sarà true se è stata generata un'immagine in miniatura per questa immagine

Dato che cercheremo in Firestore per trovare immagini con miniature disponibili, ordinando i contenuti in base alla data di creazione, dovremo creare un indice di ricerca.

Puoi creare l'indice con il comando seguente 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 dalla console Cloud facendo clic su Indexes nella colonna di navigazione a sinistra, quindi creando un indice composto come mostrato di seguito:

ecb8b95e3c791272.png

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 immagini e archiviare i metadati in Firestore.

Dall'"hamburger" (PARTICIPANT), vai alla sezione Cloud Functions, fai clic sul nome della funzione, seleziona la scheda Source e fai clic sul pulsante EDIT.

In primo luogo, modifica il file pom.xml che elenca le dipendenze della 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, lavorerai 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 del file con src/main/java/fn/ImageAnalysis.java.

Sostituisci il codice in ImageAnalysis.java con il codice riportato di seguito. Questa operazione verrà spiegata 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;
    }
}

968749236c3f01da.png

11. Esplora la funzione

Diamo un'occhiata più da vicino alle varie parti interessanti.

Innanzitutto, abbiamo incluso le dipendenze specifiche nel file Maven pom.xml. Le librerie client Java di Google pubblicano un Bill-of-Materials(BOM) per eliminare eventuali conflitti di dipendenza. Utilizzandola, non è necessario specificare alcuna versione per le singole librerie client di 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>

Quindi, prepariamo un client per l'API Vision:

...
try (ImageAnnotatorClient vision = ImageAnnotatorClient.create()) {
...

Ora vediamo la struttura della nostra funzione. Dall'evento in arrivo, acquisisci i campi che ci interessano e li mapperemo 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;
    }

Osserva la firma, ma anche il modo in cui recuperiamo il nome del file e del bucket che ha attivato la funzione Cloud Functions.

Come riferimento, ecco l'aspetto del payload dell'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 3 funzionalità chiave dell'API Vision:

  • Rilevamento etichette: per capire cosa c'è in queste immagini.
  • Proprietà immagine: per assegnare attributi interessanti all'immagine (ci interessa il colore predominante dell'immagine).
  • SafeSearch: per sapere se l'immagine può essere mostrata in sicurezza (non deve includere contenuti per adulti / medici / per adulti / violenti).

A questo punto, possiamo chiamare l'API Vision:

...
logger.info("Calling the Vision API...");
BatchAnnotateImagesResponse result = 
                            vision.batchAnnotateImages(requests);
List<AnnotateImageResponse> responses = result.getResponsesList();
...

Come riferimento, ecco l'aspetto della 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 codice "If block":

AnnotateImageResponse response = responses.get(0);
if (response.hasError()) {
     logger.info("Error: " + response.getError().getMessage());
     return;
}

Raccoglieremo le etichette degli elementi, 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);
}

Stiamo anche utilizzando una funzione di utilità per trasformare i valori rosso / verde / blu in un codice colore esadecimale da utilizzare nei fogli di stile CSS.

Vediamo se è sicuro mostrare l'immagine:

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 / spoofing / medici / violenza / per adulti per capire se 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

Tempo necessario per il deployment della funzione.

604f47aa11fbf8e.png

Premi il pulsante DEPLOY e verrà eseguito il deployment della nuova versione. Puoi vedere lo stato di avanzamento:

13da63f23e4dbbdd.png

13. Riprova 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 sono archiviati in Firestore.

Torna a Cloud Storage e fai clic sul bucket che abbiamo creato all'inizio del lab:

d44c1584122311c7.png

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

26bb31d35fb6aa3d.png

Dall'"hamburger" (Segnaposto), vai a Spazio di esplorazione di 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 avere una visualizzazione più dettagliata dei log correlati alle funzioni. Seleziona la funzione picture-uploaded.

b651dca7e25d5b11.png

Nell'elenco dei log, vedo che la nostra funzione è stata richiamata:

d22a7f24954e4f63.png

I log indicano l'inizio e la fine dell'esecuzione della funzione. Nel frattempo, possiamo vedere i log che abbiamo inserito nella 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 caricata,
  • Le informazioni sui colori predominanti
  • se è sicuro mostrare l'immagine,
  • Alla fine i metadati sull'immagine sono stati archiviati in Firestore.

9ff7956a215c15da.png

Di nuovo dall'"hamburger" (Segnaposto), 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:

a6137ab9687da370.png

14. Libera spazio (facoltativo)

Se non intendi continuare con gli altri lab della serie, puoi eseguire la pulizia delle risorse per risparmiare sui costi ed essere nel complesso un buon cittadino del cloud. Puoi eseguire la pulizia delle risorse singolarmente come descritto di seguito.

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 la raccolta dalla raccolta:

410b551c3264f70a.png

In alternativa, puoi eliminare l'intero progetto:

gcloud projects delete ${GOOGLE_CLOUD_PROJECT} 

15. Complimenti!

Complimenti! Hai implementato il primo servizio chiavi del progetto.

Argomenti trattati

  • Cloud Storage
  • Cloud Functions
  • API Cloud Vision
  • Cloud Firestore

Passaggi successivi