1. Übersicht
Im ersten Codelab laden Sie Bilder in einen Bucket hoch. Dadurch wird ein Ereignis zum Erstellen einer Datei generiert, das von einer Funktion verarbeitet wird. Die Funktion ruft die Vision API auf, um eine Bildanalyse durchzuführen und die Ergebnisse in einem Datenspeicher zu speichern.

Lerninhalte
- Cloud Storage
- Cloud Functions
- Cloud Vision API
- Cloud Firestore
2. Einrichtung und Anforderungen
Umgebung zum selbstbestimmten Lernen einrichten
- Melden Sie sich in der Google Cloud Console an und erstellen Sie ein neues Projekt oder verwenden Sie ein vorhandenes. Wenn Sie noch kein Gmail- oder Google Workspace-Konto haben, müssen Sie eines erstellen.



- Der Projektname ist der Anzeigename für die Teilnehmer dieses Projekts. Es handelt sich um einen String, der nicht von Google APIs verwendet wird. Sie können ihn jederzeit aktualisieren.
- Die Projekt-ID muss für alle Google Cloud-Projekte eindeutig sein und ist unveränderlich (kann nach der Festlegung nicht mehr geändert werden). In der Cloud Console wird automatisch ein eindeutiger String generiert. Normalerweise ist es nicht wichtig, wie dieser String aussieht. In den meisten Codelabs müssen Sie auf die Projekt-ID verweisen (sie wird in der Regel als
PROJECT_IDangegeben). Wenn Ihnen die generierte ID nicht gefällt, können Sie eine andere zufällige ID generieren. Alternativ können Sie es mit einem eigenen versuchen und sehen, ob es verfügbar ist. Sie kann nach diesem Schritt nicht mehr geändert werden und bleibt für die Dauer des Projekts bestehen. - Zur Information: Es gibt einen dritten Wert, die Projektnummer, die von einigen APIs verwendet wird. Weitere Informationen zu diesen drei Werten
- Als Nächstes müssen Sie die Abrechnung in der Cloud Console aktivieren, um Cloud-Ressourcen/-APIs zu verwenden. Die Durchführung dieses Codelabs sollte keine oder nur geringe Kosten verursachen. Wenn Sie Ressourcen herunterfahren möchten, damit Ihnen nach Abschluss dieser Anleitung keine Kosten mehr in Rechnung gestellt werden, können Sie die von Ihnen erstellten Ressourcen oder das gesamte Projekt löschen. Neue Nutzer von Google Cloud kommen für das Programm für kostenlose Testversionen mit einem Guthaben von 300$ infrage.
Cloud Shell starten
Während Sie Google Cloud von Ihrem Laptop aus per Fernzugriff nutzen können, wird in diesem Codelab Google Cloud Shell verwendet, eine Befehlszeilenumgebung, die in der Cloud ausgeführt wird.
Klicken Sie in der Google Cloud Console rechts oben in der Symbolleiste auf das Cloud Shell-Symbol:

Die Bereitstellung und Verbindung mit der Umgebung sollte nur wenige Augenblicke dauern. Anschließend sehen Sie in etwa Folgendes:

Diese virtuelle Maschine verfügt über sämtliche Entwicklertools, die Sie benötigen. Sie bietet ein Basisverzeichnis mit 5 GB nichtflüchtigem Speicher und läuft in Google Cloud, was die Netzwerkleistung und Authentifizierung erheblich verbessert. Alle Aufgaben in diesem Codelab können in einem Browser ausgeführt werden. Sie müssen nichts installieren.
3. APIs aktivieren
In diesem Lab verwenden Sie Cloud Functions und die Vision API. Diese müssen jedoch zuerst in der Cloud Console oder mit gcloud aktiviert werden.
So aktivieren Sie die Vision API in der Cloud Console: Suchen Sie in der Suchleiste nach Cloud Vision API:

Sie gelangen auf die Seite der Cloud Vision API:

Klicken Sie auf ENABLE.
Alternativ können Sie sie auch in Cloud Shell mit dem gcloud-Befehlszeilentool aktivieren.
Führen Sie in Cloud Shell den folgenden Befehl aus:
gcloud services enable vision.googleapis.com
Der Vorgang sollte erfolgreich abgeschlossen werden:
Operation "operations/acf.12dba18b-106f-4fd2-942d-fea80ecc5c1c" finished successfully.
Aktivieren Sie auch Cloud Functions:
gcloud services enable cloudfunctions.googleapis.com
4. Bucket erstellen (Konsole)
Erstellen Sie einen Storage-Bucket für die Bilder. Dies ist über die Google Cloud Console ( console.cloud.google.com) oder mit dem gsutil-Befehlszeilentool in Cloud Shell oder Ihrer lokalen Entwicklungsumgebung möglich.
Zu „Speicher“ navigieren
Rufen Sie über das Dreistrich-Menü (☰) die Seite Storage auf.

Bucket benennen
Klicke auf die Schaltfläche CREATE BUCKET.

Klicken Sie auf CONTINUE.
Standort auswählen

Erstellen Sie einen multiregionalen Bucket in der Region Ihrer Wahl (hier Europe).
Klicken Sie auf CONTINUE.
Standardspeicherklasse auswählen

Wählen Sie die Speicherklasse Standard für Ihre Daten aus.
Klicken Sie auf CONTINUE.
Zugriffssteuerung festlegen

Da Sie mit öffentlich zugänglichen Bildern arbeiten, möchten Sie, dass alle in diesem Bucket gespeicherten Bilder dieselbe einheitliche Zugriffssteuerung haben.
Wählen Sie die Option Uniform aus.
Klicken Sie auf CONTINUE.
Schutz/Verschlüsselung einrichten

Behalten Sie die Standardeinstellung (Google-managed key)) bei, da Sie keine eigenen Verschlüsselungsschlüssel verwenden.
Klicken Sie auf CREATE, um die Bucket-Erstellung abzuschließen.
„allUsers“ als Speicherbetrachter hinzufügen
Rufen Sie den Tab Permissions auf:

Fügen Sie dem Bucket ein allUsers-Mitglied mit der Rolle Storage > Storage Object Viewer hinzu:

Klicken Sie auf SAVE.
5. Bucket erstellen (gsutil)
Sie können auch das gsutil-Befehlszeilentool in Cloud Shell verwenden, um Buckets zu erstellen.
Legen Sie in Cloud Shell eine Variable für den eindeutigen Bucket-Namen fest. In Cloud Shell ist GOOGLE_CLOUD_PROJECT bereits auf Ihre eindeutige Projekt-ID festgelegt. Sie können das an den Bucket-Namen anhängen.
Beispiel:
export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}
So erstellen Sie eine Standard-Multiregionenzone in Europa:
gsutil mb -l EU gs://${BUCKET_PICTURES}
Einheitlichen Zugriff auf Bucket-Ebene sicherstellen:
gsutil uniformbucketlevelaccess set on gs://${BUCKET_PICTURES}
Veröffentlichen Sie den Bucket:
gsutil iam ch allUsers:objectViewer gs://${BUCKET_PICTURES}
Wenn Sie den Bereich Cloud Storage der Console aufrufen, sollte ein öffentlicher uploaded-pictures-Bucket vorhanden sein:

Testen Sie, ob Sie Bilder in den Bucket hochladen können und ob die hochgeladenen Bilder öffentlich verfügbar sind, wie im vorherigen Schritt beschrieben.
6. Öffentlichen Zugriff auf den Bucket testen
Wenn Sie zum Speicherbrowser zurückkehren, sehen Sie Ihren Bucket in der Liste mit dem Zugriff „Öffentlich“ (einschließlich eines Warnzeichens, das Sie daran erinnert, dass jeder auf den Inhalt dieses Buckets zugreifen kann).

Ihr Bucket kann jetzt Bilder empfangen.
Wenn Sie auf den Bucket-Namen klicken, werden die Bucket-Details angezeigt.

Dort können Sie die Schaltfläche Upload files ausprobieren, um zu testen, ob Sie dem Bucket ein Bild hinzufügen können. In einem Pop‑up-Fenster werden Sie aufgefordert, eine Datei auszuwählen. Nach der Auswahl wird die Datei in Ihren Bucket hochgeladen und Sie sehen wieder den public-Zugriff, der dieser neuen Datei automatisch zugewiesen wurde.

Neben dem Zugriffslabel Public sehen Sie auch ein kleines Linksymbol. Wenn Sie darauf klicken, wird in Ihrem Browser die öffentliche URL des Bildes aufgerufen, die so aussieht:
https://storage.googleapis.com/BUCKET_NAME/PICTURE_FILE.png
BUCKET_NAME ist der global eindeutige Name, den Sie für Ihren Bucket ausgewählt haben, gefolgt vom Dateinamen Ihres Bildes.
Wenn Sie auf das Kästchen neben dem Bildnamen klicken, wird die Schaltfläche DELETE aktiviert und Sie können das erste Bild löschen.
7. Funktion erstellen
In diesem Schritt erstellen Sie eine Funktion, die auf Ereignisse beim Hochladen von Bildern reagiert.
Rufen Sie in der Google Cloud Console den Bereich Cloud Functions auf. Wenn Sie die Seite aufrufen, wird der Cloud Functions-Dienst automatisch aktiviert.

Klicken Sie auf Create function.
Wählen Sie einen Namen aus (z. B. picture-uploaded) und die Region (die Region muss mit der für den Bucket übereinstimmen):

Es gibt zwei Arten von Funktionen:
- HTTP-Funktionen, die über eine URL (d. h. eine Web-API) aufgerufen werden können.
- Hintergrundfunktionen, die durch ein Ereignis ausgelöst werden können.
Sie möchten eine Hintergrundfunktion erstellen, die ausgelöst wird, wenn eine neue Datei in den Bucket Cloud Storage hochgeladen wird:

Sie sind am Ereignistyp Finalize/Create interessiert. Dieses Ereignis wird ausgelöst, wenn eine Datei im Bucket erstellt oder aktualisiert wird:

Wählen Sie den zuvor erstellten Bucket aus, damit Cloud Functions benachrichtigt wird, wenn in diesem Bucket eine Datei erstellt oder aktualisiert wird:

Klicken Sie auf Select, um den zuvor erstellten Bucket auszuwählen, und dann auf Save.

Bevor Sie auf „Weiter“ klicken, können Sie die Standardeinstellungen (256 MB Arbeitsspeicher) unter Laufzeit, Build, Verbindungen und Sicherheitseinstellungen maximieren und ändern, z. B. auf 1 GB.

Nachdem Sie auf Next geklickt haben, können Sie die Laufzeit, den Quellcode und den Einstiegspunkt anpassen.
Behalten Sie die Inline editor für diese Funktion bei:

Wählen Sie eine der Java-Laufzeiten aus, z. B. Java 11:

Der Quellcode besteht aus einer Java-Datei und einer pom.xml-Maven-Datei, die verschiedene Metadaten und Abhängigkeiten enthält.
Lassen Sie das Standard-Code-Snippet unverändert. Es protokolliert den Dateinamen des hochgeladenen Bildes:

Lassen Sie den Namen der auszuführenden Funktion vorerst auf Example, um sie zu testen.
Klicken Sie auf Deploy, um die Funktion zu erstellen und bereitzustellen. Wenn die Bereitstellung erfolgreich war, wird in der Liste der Funktionen ein grünes Häkchen angezeigt:

8. Funktion testen
In diesem Schritt testen Sie, ob die Funktion auf Speicherereignisse reagiert.
Rufen Sie über das Menü ☰ die Seite Storage auf.
Klicken Sie auf den Bilder-Bucket und dann auf Upload files, um ein Bild hochzuladen.

Rufen Sie in der Cloud Console noch einmal die Seite Logging > Logs Explorer auf.
Wählen Sie in der Auswahl Log Fields die Option Cloud Function aus, um die Logs für Ihre Funktionen aufzurufen. Scrollen Sie in den Logfeldern nach unten. Sie können auch eine bestimmte Funktion auswählen, um eine detailliertere Ansicht der zugehörigen Logs zu erhalten. Wählen Sie die Funktion picture-uploaded aus.
Sie sollten die Logeinträge sehen, in denen die Erstellung der Funktion, die Start- und Endzeiten der Funktion und unsere tatsächliche Loganweisung erwähnt werden:

In unserer Log-Anweisung steht: Processing file: pic-a-daily-architecture-events.png. Das bedeutet, dass das Ereignis im Zusammenhang mit der Erstellung und Speicherung dieses Bildes wie erwartet ausgelöst wurde.
9. Datenbank vorbereiten
Sie speichern Informationen zum Bild, die von der Vision API bereitgestellt werden, in der Cloud Firestore-Datenbank. Das ist eine schnelle, vollständig verwaltete, serverlose, cloudnative NoSQL-Dokumentendatenbank. Bereiten Sie Ihre Datenbank vor, indem Sie in der Cloud Console den Bereich Firestore aufrufen:

Es gibt zwei Optionen: Native mode und Datastore mode. Verwenden Sie den nativen Modus, der zusätzliche Funktionen wie Offlineunterstützung und Echtzeitsynchronisierung bietet.
Klicken Sie auf SELECT NATIVE MODE.

Wählen Sie eine Multi-Region aus (hier in Europa, aber idealerweise mindestens dieselbe Region wie Ihre Funktion und Ihr Speicher-Bucket).
Klicken Sie auf CREATE DATABASE.
Nachdem die Datenbank erstellt wurde, sollten Sie Folgendes sehen:

Erstellen Sie eine neue Sammlung, indem Sie auf die Schaltfläche + START COLLECTION klicken.
Name der Sammlung: pictures

Sie müssen kein Dokument erstellen. Sie fügen sie programmatisch hinzu, wenn neue Bilder in Cloud Storage gespeichert und von der Vision API analysiert werden.
Klicken Sie auf Save.
Firestore erstellt ein erstes Standarddokument in der neu erstellten Sammlung. Sie können dieses Dokument bedenkenlos löschen, da es keine nützlichen Informationen enthält:

Die Dokumente, die programmatisch in unserer Sammlung erstellt werden, enthalten vier Felder:
- name (String): Der Dateiname des hochgeladenen Bildes, der auch der Schlüssel des Dokuments ist.
- labels (Array von Strings): die Labels der von der Vision API erkannten Elemente
- color (String): Der hexadezimale Farbcode der dominanten Farbe (z. B. #ab12ef)
- created (Datum): Der Zeitstempel, zu dem die Metadaten dieses Bildes gespeichert wurden.
- thumbnail (boolescher Wert): Ein optionales Feld, das vorhanden ist und „true“ enthält, wenn für dieses Bild eine Miniaturansicht generiert wurde.
Da wir in Firestore nach Bildern mit verfügbaren Thumbnails suchen und nach dem Erstellungsdatum sortieren, müssen wir einen Suchindex erstellen.
Sie können den Index mit dem folgenden Befehl in Cloud Shell erstellen:
gcloud firestore indexes composite create \
--collection-group=pictures \
--field-config field-path=thumbnail,order=descending \
--field-config field-path=created,order=descending
Sie können dies auch in der Cloud Console tun. Klicken Sie dazu in der Navigationsspalte links auf Indexes und erstellen Sie dann einen zusammengesetzten Index, wie unten dargestellt:

Klicken Sie auf Create. Die Indexerstellung kann einige Minuten dauern.
10. Funktion aktualisieren
Kehren Sie zur Seite Functions zurück, um die Funktion so zu aktualisieren, dass die Vision API aufgerufen wird, um unsere Bilder zu analysieren und die Metadaten in Firestore zu speichern.
Rufen Sie über das Menü ☰ den Bereich Cloud Functions auf, klicken Sie auf den Funktionsnamen, wählen Sie den Tab Source aus und klicken Sie dann auf die Schaltfläche EDIT.
Bearbeiten Sie zuerst die Datei pom.xml, in der die Abhängigkeiten unserer Java-Funktion aufgeführt sind. Aktualisieren Sie den Code, um die Maven-Abhängigkeit der Cloud Vision API hinzuzufügen:
<?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>
Nachdem die Abhängigkeiten auf dem neuesten Stand sind, aktualisieren Sie die Example.java-Datei mit unserem benutzerdefinierten Code.
Bewegen Sie den Mauszeiger auf die Datei Example.java und klicken Sie auf das Stiftsymbol. Ersetzen Sie den Paketnamen und den Dateinamen durch src/main/java/fn/ImageAnalysis.java.
Ersetzen Sie den Code in ImageAnalysis.java durch den unten stehenden Code. Das wird im nächsten Schritt erklärt.
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. Funktion ausprobieren
Sehen wir uns die verschiedenen interessanten Teile genauer an.
Zuerst fügen wir die spezifischen Abhängigkeiten in die Maven-Datei pom.xml ein. In den Google Java-Clientbibliotheken wird Bill-of-Materials(BOM) veröffentlicht, um Abhängigkeitskonflikte zu vermeiden. Wenn Sie es verwenden, müssen Sie keine Version für die einzelnen Google-Clientbibliotheken angeben.
<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>
Anschließend bereiten wir einen Client für die Vision API vor:
...
try (ImageAnnotatorClient vision = ImageAnnotatorClient.create()) {
...
Nun kommt die Struktur unserer Funktion. Wir erfassen die Felder, die uns interessieren, aus dem eingehenden Ereignis und ordnen sie der von uns definierten GCSEvent-Struktur zu:
...
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;
}
Beachten Sie die Signatur und wie wir den Namen der Datei und des Buckets abrufen, die die Cloud Functions-Funktion ausgelöst haben.
So sieht die Ereignisnutzlast aus:
{
"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"
}
Wir bereiten eine Anfrage vor, die über den Vision-Client gesendet werden soll:
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();
Wir bitten um drei wichtige Funktionen der Vision API:
- Labelerkennung: um zu verstehen, was auf den Bildern zu sehen ist
- Bildattribute: um interessante Attribute des Bildes zu liefern (wir sind an der dominanten Farbe des Bildes interessiert)
- SafeSearch: um zu wissen, ob das Bild sicher angezeigt werden kann (es sollte keine Inhalte nur für Erwachsene, medizinische, anstößige oder gewalttätige Inhalte enthalten)
An dieser Stelle können wir den Aufruf an die Vision API senden:
...
logger.info("Calling the Vision API...");
BatchAnnotateImagesResponse result =
vision.batchAnnotateImages(requests);
List<AnnotateImageResponse> responses = result.getResponsesList();
...
Zur Referenz sehen Sie hier, wie die Antwort der Vision API aussieht:
{
"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
}
Wenn kein Fehler zurückgegeben wird, können wir fortfahren. Deshalb haben wir diesen if-Block:
AnnotateImageResponse response = responses.get(0);
if (response.hasError()) {
logger.info("Error: " + response.getError().getMessage());
return;
}
Wir rufen die Labels der Dinge, Kategorien oder Themen ab, die auf dem Bild erkannt wurden:
List<String> labels = response.getLabelAnnotationsList().stream()
.map(annotation -> annotation.getDescription())
.collect(Collectors.toList());
logger.info("Annotations found:");
for (String label: labels) {
logger.info("- " + label);
}
Wir möchten wissen, welche Farbe im Bild dominiert:
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);
}
Außerdem verwenden wir eine Hilfsfunktion, um die Rot-, Grün- und Blauwerte in einen hexadezimalen Farbcode umzuwandeln, den wir in CSS-Stylesheets verwenden können.
Prüfen wir, ob das Bild sicher angezeigt werden kann:
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);
}
Wir prüfen die Attribute „Inhalte für Erwachsene“, „Spoof“, „Medizinisch“, „Gewalt“ und „Freizügig“, um festzustellen, ob sie wahrscheinlich oder sehr wahrscheinlich sind.
Wenn das Ergebnis der SafeSearch-Erkennung in Ordnung ist, können wir Metadaten in Firestore speichern:
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. Funktion implementieren
Zeit für die Bereitstellung der Funktion.

Klicken Sie auf die Schaltfläche DEPLOY. Die neue Version wird bereitgestellt. Den Fortschritt können Sie hier sehen:

13. Funktion noch einmal testen
Sobald die Funktion erfolgreich bereitgestellt wurde, laden Sie ein Bild in Cloud Storage hoch, um zu prüfen, ob unsere Funktion aufgerufen wird, was die Vision API zurückgibt und ob Metadaten in Firestore gespeichert werden.
Kehren Sie zu Cloud Storage zurück und klicken Sie auf den Bucket, den wir am Anfang des Labs erstellt haben:

Klicken Sie auf der Seite mit den Bucket-Details auf die Schaltfläche Upload files, um ein Bild hochzuladen.

Rufen Sie über das Dreistrich-Menü (☰) den Logging > Logs-Explorer auf.
Wählen Sie in der Auswahl Log Fields die Option Cloud Function aus, um die Logs für Ihre Funktionen aufzurufen. Scrollen Sie in den Logfeldern nach unten. Sie können auch eine bestimmte Funktion auswählen, um eine detailliertere Ansicht der zugehörigen Logs zu erhalten. Wählen Sie die Funktion picture-uploaded aus.

In der Liste der Logs sehe ich, dass unsere Funktion aufgerufen wurde:

Die Logs geben den Beginn und das Ende der Funktionsausführung an. Dazwischen sehen wir die Logs, die wir mit den console.log()-Anweisungen in unsere Funktion eingefügt haben. Wir sehen:
- Details zum Ereignis, das unsere Funktion auslöst,
- Die Rohdaten aus dem Vision API-Aufruf
- Die Labels, die auf dem von uns hochgeladenen Bild gefunden wurden.
- Informationen zu dominanten Farben,
- ob das Bild sicher angezeigt werden kann,
- Schließlich werden diese Metadaten zum Bild in Firestore gespeichert.

Rufen Sie noch einmal über das Dreistrich-Menü (☰) den Bereich Firestore auf. Im Unterabschnitt Data (standardmäßig angezeigt) sollte die Sammlung pictures mit einem neuen Dokument angezeigt werden, das dem gerade hochgeladenen Bild entspricht:

14. Bereinigen (optional)
Wenn Sie nicht mit den anderen Labs der Reihe fortfahren möchten, können Sie Ressourcen bereinigen, um Kosten zu sparen und nicht mehr benötigte Ressourcen für andere freizugeben. Sie können Ressourcen einzeln so bereinigen:
Löschen Sie den Bucket:
gsutil rb gs://${BUCKET_PICTURES}
Funktion löschen:
gcloud functions delete picture-uploaded --region europe-west1 -q
Löschen Sie die Firestore-Sammlung, indem Sie in der Sammlung „Sammlung löschen“ auswählen:

Alternativ können Sie das gesamte Projekt löschen:
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
15. Glückwunsch!
Glückwunsch! Sie haben den ersten wichtigen Dienst des Projekts erfolgreich implementiert.
Behandelte Themen
- Cloud Storage
- Cloud Functions
- Cloud Vision API
- Cloud Firestore