1. Présentation
Dans le premier atelier de programmation, vous allez importer des images dans un bucket. Cela générera un événement de création de fichier qui sera géré par une fonction. La fonction appellera l'API Vision pour analyser l'image et enregistrer les résultats dans un datastore.

Points abordés
- Cloud Storage
- Cloud Functions
- API Cloud Vision
- Cloud Firestore
2. Préparation
Configuration de l'environnement au rythme de chacun
- Connectez-vous à la console Google Cloud, puis créez un projet ou réutilisez un projet existant. (Si vous ne possédez pas encore de compte Gmail ou Google Workspace, vous devez en créer un.)



- Le nom du projet est le nom à afficher pour les participants au projet. Il s'agit d'une chaîne de caractères non utilisée par les API Google. Vous pouvez le modifier à tout moment.
- L'ID du projet doit être unique sur l'ensemble des projets Google Cloud et doit être immuable (vous ne pouvez pas le modifier une fois que vous l'avez défini). La console Cloud génère automatiquement une chaîne unique (en général, vous n'y accordez d'importance particulière). Dans la plupart des ateliers de programmation, vous devrez indiquer l'ID du projet (généralement identifié par
PROJECT_ID). Si l'ID généré ne vous convient pas, vous pouvez en générer un autre de manière aléatoire. Vous pouvez également en spécifier un et voir s'il est disponible. Après cette étape, l'ID n'est plus modifiable et restera donc le même pour toute la durée du projet. - Pour information, il existe une troisième valeur (le numéro de projet) que certaines API utilisent. Pour en savoir plus sur ces trois valeurs, consultez la documentation.
- Vous devez ensuite activer la facturation dans la console Cloud pour utiliser les ressources/API Cloud. L'exécution de cet atelier de programmation est très peu coûteuse, voire sans frais. Pour désactiver les ressources et éviter ainsi que des frais ne vous soient facturés après ce tutoriel, vous pouvez supprimer le projet ou les ressources que vous avez créées. Les nouveaux utilisateurs de Google Cloud peuvent participer au programme d'essai sans frais pour bénéficier d'un crédit de 300$.
Démarrer Cloud Shell
Bien que Google Cloud puisse être utilisé à distance depuis votre ordinateur portable, nous allons nous servir de Google Cloud Shell pour cet atelier de programmation, un environnement de ligne de commande exécuté dans le cloud.
Dans la console Google Cloud, cliquez sur l'icône Cloud Shell dans la barre d'outils supérieure :

Le provisionnement et la connexion à l'environnement prennent quelques instants seulement. Une fois l'opération terminée, le résultat devrait ressembler à ceci :

Cette machine virtuelle contient tous les outils de développement nécessaires. Elle comprend un répertoire d'accueil persistant de 5 Go et s'exécute sur Google Cloud, ce qui améliore nettement les performances du réseau et l'authentification. Vous pouvez effectuer toutes les tâches de cet atelier de programmation dans un navigateur. Vous n'avez rien à installer.
3. Activer les API
Pour cet atelier, vous allez utiliser Cloud Functions et l'API Vision. Vous devez d'abord les activer dans la console Cloud ou avec gcloud.
Pour activer l'API Vision dans la console Cloud, recherchez Cloud Vision API dans la barre de recherche :

Vous êtes redirigé vers la page de l'API Cloud Vision :

Cliquez sur le bouton ENABLE.
Vous pouvez également l'activer dans Cloud Shell à l'aide de l'outil de ligne de commande gcloud.
Dans Cloud Shell, exécutez la commande suivante :
gcloud services enable vision.googleapis.com
L'opération devrait se terminer correctement :
Operation "operations/acf.12dba18b-106f-4fd2-942d-fea80ecc5c1c" finished successfully.
Activez également Cloud Functions :
gcloud services enable cloudfunctions.googleapis.com
4. Créer le bucket (console)
Créez un bucket de stockage pour les photos. Vous pouvez le faire à partir de la console Google Cloud Platform ( console.cloud.google.com) ou avec l'outil de ligne de commande gsutil depuis Cloud Shell ou votre environnement de développement local.
Accéder à Storage
Dans le menu hamburger (☰), accédez à la page Storage.

Nommer votre bucket
Cliquez sur le bouton CREATE BUCKET.

Cliquez sur CONTINUE.
Choisir un lieu

Créez un bucket multirégional dans la région de votre choix (ici Europe).
Cliquez sur CONTINUE.
Sélectionner la classe de stockage par défaut

Choisissez la classe de stockage Standard pour vos données.
Cliquez sur CONTINUE.
Définir le contrôle des accès

Comme vous allez travailler avec des images accessibles au public, vous souhaitez que toutes les images stockées dans ce bucket disposent du même contrôle d'accès uniforme.
Choisissez l'option de contrôle des accès Uniform.
Cliquez sur CONTINUE.
Définir la protection/le chiffrement

Conservez la valeur par défaut (Google-managed key)), car vous n'utiliserez pas vos propres clés de chiffrement.
Cliquez sur CREATE pour finaliser la création du bucket.
Ajouter allUsers en tant que lecteur de stockage
Accédez à l'onglet Permissions :

Ajoutez un membre allUsers au bucket, avec le rôle Storage > Storage Object Viewer, comme suit :

Cliquez sur SAVE.
5. Créer le bucket (gsutil)
Vous pouvez également utiliser l'outil de ligne de commande gsutil dans Cloud Shell pour créer des buckets.
Dans Cloud Shell, définissez une variable pour le nom unique du bucket. Cloud Shell a déjà défini GOOGLE_CLOUD_PROJECT sur votre ID de projet unique. Vous pouvez l'ajouter au nom du bucket.
Exemple :
export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}
Créez une zone multirégionale standard en Europe :
gsutil mb -l EU gs://${BUCKET_PICTURES}
Assurez-vous que l'accès uniforme au niveau du bucket est activé :
gsutil uniformbucketlevelaccess set on gs://${BUCKET_PICTURES}
Rendez le bucket public :
gsutil iam ch allUsers:objectViewer gs://${BUCKET_PICTURES}
Si vous accédez à la section Cloud Storage de la console, vous devriez disposer d'un bucket uploaded-pictures public :

Vérifiez que vous pouvez importer des photos dans le bucket et qu'elles sont accessibles au public, comme expliqué à l'étape précédente.
6. Tester l'accès public au bucket
Si vous revenez au navigateur de stockage, vous verrez votre bucket dans la liste, avec un accès "Public " (y compris un panneau d'avertissement vous rappelant que tout le monde a accès au contenu de ce bucket).

Votre bucket est désormais prêt à recevoir des photos.
Si vous cliquez sur le nom du bucket, vous verrez ses détails.

Vous pouvez y essayer le bouton Upload files pour vérifier que vous pouvez ajouter une image au bucket. Un pop-up de sélection de fichier vous demandera de sélectionner un fichier. Une fois sélectionné, il sera importé dans votre bucket et vous verrez à nouveau l'public qui a été automatiquement attribué à ce nouveau fichier.

À côté du libellé d'accès Public, vous verrez également une petite icône en forme de lien. Lorsque vous cliquez dessus, votre navigateur accède à l'URL publique de cette image, qui se présente sous la forme suivante :
https://storage.googleapis.com/BUCKET_NAME/PICTURE_FILE.png
BUCKET_NAME étant le nom unique que vous avez choisi pour votre bucket, suivi du nom de fichier de votre image.
Cochez la case à côté du nom de l'image pour activer le bouton DELETE, puis supprimez cette première image.
7. Créer la fonction
Dans cette étape, vous allez créer une fonction qui réagit aux événements d'importation d'images.
Accédez à la section Cloud Functions de la console Google Cloud. En le consultant, le service Cloud Functions sera automatiquement activé.

Cliquez sur Create function.
Choisissez un nom (par exemple, picture-uploaded) et la région (n'oubliez pas de choisir la même région pour le bucket) :

Il existe deux types de fonctions :
- les fonctions HTTP qui peuvent être appelées via une URL (c'est-à-dire une API Web) ;
- Fonctions d'arrière-plan pouvant être déclenchées par un événement.
Vous souhaitez créer une fonction d'arrière-plan qui se déclenche lorsqu'un fichier est importé dans votre bucket Cloud Storage :

Vous vous intéressez au type d'événement Finalize/Create, qui est déclenché lorsqu'un fichier est créé ou mis à jour dans le bucket :

Sélectionnez le bucket créé précédemment pour indiquer à Cloud Functions d'être averti lorsqu'un fichier est créé ou mis à jour dans ce bucket spécifique :

Cliquez sur Select pour choisir le bucket que vous avez créé précédemment, puis sur Save.

Avant de cliquer sur "Suivant", vous pouvez développer et modifier les valeurs par défaut (256 Mo de mémoire) sous Paramètres d'exécution, de compilation, de connexion et de sécurité, puis les remplacer par 1 Go.

Après avoir cliqué sur Next, vous pouvez ajuster l'environnement d'exécution, le code source et le point d'entrée.
Conservez le Inline editor pour cette fonction :

Sélectionnez l'un des environnements d'exécution Java, par exemple Java 11 :

Le code source se compose d'un fichier Java et d'un fichier Maven pom.xml qui fournit diverses métadonnées et dépendances.
Conservez l'extrait de code par défaut, qui enregistre le nom de fichier de l'image importée :

Pour l'instant, conservez le nom de la fonction à exécuter sur Example à des fins de test.
Cliquez sur Deploy pour créer et déployer la fonction. Une fois le déploiement réussi, une coche verte encerclée doit s'afficher dans la liste des fonctions :

8. Tester la fonction
Lors de cette étape, testez que la fonction répond aux événements de stockage.
Dans le menu à trois barres (☰), revenez à la page Storage.
Cliquez sur le bucket d'images, puis sur Upload files pour importer une image.

Dans la console Cloud, accédez de nouveau à la page Logging > Logs Explorer.
Dans le sélecteur Log Fields, sélectionnez Cloud Function pour afficher les journaux dédiés à vos fonctions. Faites défiler les champs de journaux vers le bas. Vous pouvez même sélectionner une fonction spécifique pour obtenir une vue plus précise des journaux associés. Sélectionnez la fonction picture-uploaded.
Vous devriez voir les éléments de journal mentionnant la création de la fonction, ses heures de début et de fin, ainsi que notre instruction de journalisation réelle :

Notre instruction de journalisation indique Processing file: pic-a-daily-architecture-events.png, ce qui signifie que l'événement lié à la création et au stockage de cette image a bien été déclenché comme prévu.
9. Préparer la base de données
Vous allez stocker les informations sur l'image fournies par l'API Vision dans la base de données Cloud Firestore, une base de données de documents NoSQL sans serveur, cloud native, entièrement gérée et rapide. Préparez votre base de données en accédant à la section Firestore de la console Cloud :

Deux options sont proposées : Native mode ou Datastore mode. Utilisez le mode natif, qui offre des fonctionnalités supplémentaires comme le fonctionnement hors connexion et la synchronisation en temps réel.
Cliquez sur SELECT NATIVE MODE.

Choisissez une région multiple (ici en Europe, mais idéalement au moins la même région que votre fonction et votre bucket de stockage).
Cliquez sur le bouton CREATE DATABASE.
Une fois la base de données créée, vous devriez voir ce qui suit :

Créez une collection en cliquant sur le bouton + START COLLECTION.
Nommez la collection pictures.

Vous n'avez pas besoin de créer de document. Vous les ajouterez de manière programmatique à mesure que de nouvelles images seront stockées dans Cloud Storage et analysées par l'API Vision.
Cliquez sur Save.
Firestore crée un premier document par défaut dans la collection nouvellement créée. Vous pouvez le supprimer sans risque, car il ne contient aucune information utile :

Les documents qui seront créés de manière programmatique dans notre collection contiendront quatre champs :
- name (chaîne) : nom du fichier de l'image importée, qui est également la clé du document
- labels (tableau de chaînes) : étiquettes des éléments reconnus par l'API Vision
- color (chaîne) : code hexadécimal de la couleur dominante (par exemple, #ab12ef)
- created (date) : code temporel indiquant quand les métadonnées de cette image ont été stockées.
- thumbnail (booléen) : champ facultatif qui est présent et défini sur "true" si une image miniature a été générée pour cette photo.
Comme nous allons effectuer des recherches dans Firestore pour trouver les photos qui ont des miniatures disponibles et les trier par date de création, nous devrons créer un index de recherche.
Vous pouvez créer l'index à l'aide de la commande suivante dans Cloud Shell :
gcloud firestore indexes composite create \
--collection-group=pictures \
--field-config field-path=thumbnail,order=descending \
--field-config field-path=created,order=descending
Vous pouvez également le faire depuis la console Cloud en cliquant sur Indexes dans la colonne de navigation de gauche, puis en créant un index composite comme indiqué ci-dessous :

Cliquez sur Create. La création de l'index peut prendre quelques minutes.
10. Mettre à jour la fonction
Revenez à la page Functions pour mettre à jour la fonction afin d'appeler l'API Vision pour analyser nos photos et stocker les métadonnées dans Firestore.
Dans le menu hamburger (☰), accédez à la section Cloud Functions, cliquez sur le nom de la fonction, sélectionnez l'onglet Source, puis cliquez sur le bouton EDIT.
Commencez par modifier le fichier pom.xml qui liste les dépendances de notre fonction Java. Mettez à jour le code pour ajouter la dépendance Maven de l'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>
Maintenant que les dépendances sont à jour, vous allez travailler sur le code de notre fonction en mettant à jour le fichier Example.java avec notre code personnalisé.
Déplacez la souris sur le fichier Example.java et cliquez sur le crayon. Remplacez le nom du package et le nom du fichier par src/main/java/fn/ImageAnalysis.java.
Remplacez le code dans ImageAnalysis.java par le code ci-dessous. Nous y reviendrons à l'étape suivante.
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. Explorer la fonction
Examinons de plus près les différentes parties intéressantes.
Tout d'abord, nous incluons les dépendances spécifiques dans le fichier Maven pom.xml. Les bibliothèques clientes Google Java publient un Bill-of-Materials(BOM) pour éliminer les conflits de dépendances. En l'utilisant, vous n'avez pas besoin de spécifier de version pour les différentes bibliothèques clientes 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>
Ensuite, nous préparons un client pour l'API Vision :
...
try (ImageAnnotatorClient vision = ImageAnnotatorClient.create()) {
...
Nous allons maintenant aborder la structure de notre fonction. Nous capturons les champs qui nous intéressent à partir de l'événement entrant et les mappons à la structure GCSEvent que nous définissons :
...
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;
}
Notez la signature, mais aussi la façon dont nous récupérons le nom du fichier et du bucket qui ont déclenché la fonction Cloud.
Pour référence, voici à quoi ressemble la charge utile de l'événement :
{
"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"
}
Nous préparons une requête à envoyer via le 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();
Nous vous demandons de décrire trois fonctionnalités clés de l'API Vision :
- Détection de libellés : pour comprendre le contenu de vos photos
- Propriétés de l'image : pour fournir des attributs intéressants de l'image (nous nous intéressons à la couleur dominante de l'image)
- Recherche sécurisée : pour savoir si l'image peut être affichée sans danger (elle ne doit pas contenir de contenu réservé aux adultes, médical, suggestif ou violent)
À ce stade, nous pouvons appeler l'API Vision :
...
logger.info("Calling the Vision API...");
BatchAnnotateImagesResponse result =
vision.batchAnnotateImages(requests);
List<AnnotateImageResponse> responses = result.getResponsesList();
...
Pour référence, voici à quoi ressemble la réponse de l'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
}
Si aucune erreur n'est renvoyée, nous pouvons passer à la suite. C'est pourquoi nous avons ce bloc "if" :
AnnotateImageResponse response = responses.get(0);
if (response.hasError()) {
logger.info("Error: " + response.getError().getMessage());
return;
}
Nous allons obtenir les libellés des choses, des catégories ou des thèmes reconnus dans l'image :
List<String> labels = response.getLabelAnnotationsList().stream()
.map(annotation -> annotation.getDescription())
.collect(Collectors.toList());
logger.info("Annotations found:");
for (String label: labels) {
logger.info("- " + label);
}
Nous souhaitons connaître la couleur dominante de l'image :
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);
}
Nous utilisons également une fonction utilitaire pour transformer les valeurs rouge / vert / bleu en code couleur hexadécimal que nous pouvons utiliser dans les feuilles de style CSS.
Vérifions si l'image peut être affichée :
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);
}
Nous vérifions les attributs "Contenu réservé aux adultes", "Parodie", "Contenu médical", "Violence" et "Contenu suggestif" pour voir s'ils sont probables ou très probables.
Si le résultat de la recherche sécurisée est correct, nous pouvons stocker les métadonnées dans 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. Déployer la fonction
Il est temps de déployer la fonction.

Appuyez sur le bouton DEPLOY pour déployer la nouvelle version. Vous pouvez suivre la progression :

13. Tester à nouveau la fonction
Une fois la fonction déployée, vous allez publier une image dans Cloud Storage, vérifier si notre fonction est appelée, ce que renvoie l'API Vision et si les métadonnées sont stockées dans Firestore.
Revenez à Cloud Storage et cliquez sur le bucket que vous avez créé au début de l'atelier :

Une fois sur la page d'informations sur le bucket, cliquez sur le bouton Upload files pour importer une image.

Dans le menu hamburger (☰), accédez à l'explorateur Logging > Logs.
Dans le sélecteur Log Fields, sélectionnez Cloud Function pour afficher les journaux dédiés à vos fonctions. Faites défiler les champs de journaux vers le bas. Vous pouvez même sélectionner une fonction spécifique pour obtenir une vue plus précise des journaux associés. Sélectionnez la fonction picture-uploaded.

Et en effet, dans la liste des journaux, je peux voir que notre fonction a été appelée :

Les journaux indiquent le début et la fin de l'exécution de la fonction. Entre les deux, nous pouvons voir les journaux que nous avons placés dans notre fonction avec les instructions console.log(). Nous constatons :
- les détails de l'événement qui déclenche notre fonction ;
- Résultats bruts de l'appel de l'API Vision
- les libellés trouvés dans l'image que nous avons importée ;
- Informations sur les couleurs dominantes
- Indique si l'image peut être affichée sans danger.
- Enfin, les métadonnées de l'image ont été stockées dans Firestore.

Dans le menu hamburger (☰), accédez à la section Firestore. Dans la sous-section Data (affichée par défaut), vous devriez voir la collection pictures avec un nouveau document ajouté, correspondant à l'image que vous venez d'importer :

14. Nettoyer (facultatif)
Si vous n'avez pas l'intention de continuer à suivre les autres ateliers de cette série, vous pouvez nettoyer les ressources pour limiter vos dépenses et utiliser le cloud de manière raisonnée. Vous pouvez nettoyer les ressources individuellement comme suit.
Supprimez le bucket :
gsutil rb gs://${BUCKET_PICTURES}
Supprimez la fonction :
gcloud functions delete picture-uploaded --region europe-west1 -q
Supprimez la collection Firestore en sélectionnant "Supprimer la collection" dans la collection :

Vous pouvez également supprimer l'intégralité du projet :
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
15. Félicitations !
Félicitations ! Vous avez implémenté avec succès le premier service clé du projet.
Points abordés
- Cloud Storage
- Cloud Functions
- API Cloud Vision
- Cloud Firestore