Pic-a-daily: Lab 1 – Bilder speichern und analysieren (Java)

1. Übersicht

Im ersten Code-Lab laden Sie Bilder in einen Bucket hoch. Dadurch wird ein Dateierstellungsereignis 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.

d650ca5386ea71ad.png

Lerninhalte

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

2. Einrichtung und Anforderungen

Umgebung für das selbstbestimmte Lernen einrichten

  1. Melden Sie sich in der Google Cloud Console an und erstellen Sie ein neues Projekt oder verwenden Sie ein vorhandenes Projekt. Wenn Sie noch kein Gmail- oder Google Workspace-Konto haben, müssen Sie eines erstellen.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Der Projektname ist der Anzeigename für die Projektteilnehmer. Es handelt sich um eine Zeichenfolge, die von Google APIs nicht verwendet wird. Sie können sie jederzeit aktualisieren.
  • Die Projekt-ID muss für alle Google Cloud-Projekte eindeutig sein und ist unveränderlich. Sie kann nach dem Festlegen nicht mehr geändert werden. Die Cloud Console generiert automatisch einen eindeutigen String. ist Ihnen meist egal, was es ist. In den meisten Codelabs musst du auf die Projekt-ID verweisen, die üblicherweise als PROJECT_ID gekennzeichnet ist. Wenn Ihnen die generierte ID nicht gefällt, können Sie eine weitere zufällige ID generieren. Alternativ können Sie einen eigenen verwenden und nachsehen, ob er verfügbar ist. Sie kann nach diesem Schritt nicht mehr geändert werden und bleibt für die Dauer des Projekts bestehen.
  • Zur Information gibt es noch einen dritten Wert, die Projektnummer, die von manchen APIs verwendet wird. Weitere Informationen zu allen drei Werten finden Sie in der Dokumentation.
  1. Als Nächstes müssen Sie in der Cloud Console die Abrechnung aktivieren, um Cloud-Ressourcen/APIs verwenden zu können. Dieses Codelab sollte ohne großen Aufwand betrieben werden. Wenn Sie Ressourcen herunterfahren möchten, um über diese Anleitung hinaus keine Kosten zu verursachen, können Sie die von Ihnen erstellten Ressourcen oder das gesamte Projekt löschen. Neue Google Cloud-Nutzer haben Anspruch auf eine kostenlose Testversion von 300$.

Cloud Shell starten

Sie können Google Cloud zwar von Ihrem Laptop aus aus der Ferne bedienen, in diesem Codelab verwenden Sie jedoch Google Cloud Shell, 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:

55efc1aaa7a4d3ad.png

Die Bereitstellung und Verbindung mit der Umgebung dauert nur einen Moment. Wenn er abgeschlossen ist, sollten Sie in etwa Folgendes sehen:

7ffe5cbb04455448.png

Diese virtuelle Maschine verfügt über sämtliche Entwicklertools, die Sie benötigen. Es bietet ein Basisverzeichnis mit 5 GB nichtflüchtigem Speicher und läuft auf Google Cloud, wodurch die Netzwerkleistung und Authentifizierung erheblich verbessert werden. Alle Arbeiten in diesem Codelab können in einem Browser erledigt werden. Sie müssen nichts installieren.

3. APIs aktivieren

Für dieses Lab verwenden Sie Cloud Functions und die Vision API. Die Funktionen müssen jedoch zuerst entweder in der Cloud Console oder mit gcloud aktiviert werden.

Suchen Sie in der Suchleiste nach Cloud Vision API, um die Vision API in der Cloud Console zu aktivieren:

cf48b1747ba6a6fb.png

Daraufhin gelangen Sie zur Seite mit der Cloud Vision API:

ba4af419e6086fbb.png

Klicken Sie auf ENABLE.

Alternativ können Sie es 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 jetzt 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 (Console)

Erstellen Sie einen Storage-Bucket für die Bilder. Sie können dies über die Google Cloud Platform Console ( console.cloud.google.com) oder mit dem gsutil-Befehlszeilentool in Cloud Shell oder in Ihrer lokalen Entwicklungsumgebung erledigen.

Über das Feld „Hamburger“ (♢) öffnen Sie die Seite "Storage".

1930e055d138150a.png

Bucket benennen

Klicken Sie auf die Schaltfläche CREATE BUCKET.

34147939358517f8.png

Klicken Sie auf CONTINUE.

Standort auswählen

197817f20be07678.png

Erstellen Sie einen multiregionalen Bucket in der Region Ihrer Wahl (hier Europe).

Klicken Sie auf CONTINUE.

Standardspeicherklasse auswählen

53cd91441c8caf0e.png

Wählen Sie die Speicherklasse Standard für Ihre Daten aus.

Klicken Sie auf CONTINUE.

Zugriffssteuerung festlegen

8c2b3b459d934a51.png

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 Zugriffssteuerungsoption Uniform aus.

Klicken Sie auf CONTINUE.

Schutz/Verschlüsselung festlegen

d931c24c3e705a68.png

Behalten Sie die Standardeinstellung bei (Google-managed key), da Sie keine eigenen Verschlüsselungsschlüssel verwenden.

Klicken Sie auf CREATE, um die Bucket-Erstellung abzuschließen.

allUsers als Storage Viewer hinzufügen

Rufen Sie den Tab Permissions auf:

d0ecfdcff730ea51.png

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

e9f25ec1ea0b6cc6.png

Klicken Sie auf SAVE.

5. Bucket erstellen (gsutil)

Sie können Buckets auch mit dem gsutil-Befehlszeilentool in Cloud Shell 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 dies an den Bucket-Namen anhängen.

Beispiel:

export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}

Erstellen Sie eine multiregionale Standardzone in Europa:

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

Achten Sie auf einheitlichen Zugriff auf Bucket-Ebene:

gsutil uniformbucketlevelaccess set on gs://${BUCKET_PICTURES}

Veröffentlichen Sie den Bucket:

gsutil iam ch allUsers:objectViewer gs://${BUCKET_PICTURES}

Wenn Sie in der Console zum Abschnitt Cloud Storage gehen, sollten Sie einen öffentlichen uploaded-pictures-Bucket haben:

a98ed4ba17873e40.png

Testen Sie, ob Sie Bilder in den Bucket hochladen können und die hochgeladenen Bilder öffentlich zugänglich sind, wie im vorherigen Schritt erläutert.

6. Öffentlichen Zugriff auf den Bucket testen

Wenn Sie zum Storage-Browser zurückkehren, sehen Sie Ihren Bucket in der Liste mit „Öffentlich“ -Zugriff (einschließlich eines Warnsymbols, das Sie daran erinnert, dass jede Person Zugriff auf den Inhalt des Buckets hat).

89e7a4d2c80a0319.png

Ihr Bucket ist jetzt bereit zum Empfangen von Bildern.

Wenn Sie auf den Bucket-Namen klicken, werden die Bucket-Details angezeigt.

131387f12d3eb2d3.png

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

e87584471a6e9c6d.png

Neben dem Public-Zugriffslabel siehst du auch ein kleines Linksymbol. Wenn Sie darauf klicken, ruft Ihr Browser die öffentliche URL dieses Bildes auf, die folgendes Format hat:

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

Dabei ist BUCKET_NAME der global eindeutige Name, den Sie für Ihren Bucket ausgewählt haben, und dann der Dateiname Ihres Bildes.

Durch Klicken auf das Kästchen neben dem Bildnamen wird die Schaltfläche DELETE aktiviert und Sie können dieses erste Bild löschen.

7. Funktion erstellen

In diesem Schritt erstellen Sie eine Funktion, die auf Bild-Upload-Ereignisse reagiert.

Rufen Sie in der Google Cloud Console den Bereich Cloud Functions auf. Durch Aufrufen wird der Cloud Functions-Dienst automatisch aktiviert.

9d29e8c026a7a53f.png

Klicken Sie auf Create function.

Wählen Sie einen Namen aus (z. B. picture-uploaded) und der Region (achten Sie darauf, dass dies der Regionsauswahl für den Bucket entspricht):

4bb222633e6f278.png

Es gibt zwei Arten von Funktionen:

  • HTTP-Funktionen, die über eine URL aufgerufen werden können (d. h. eine Web-API),
  • Hintergrundfunktionen, die durch ein bestimmtes Ereignis ausgelöst werden können.

Sie möchten eine Hintergrundfunktion erstellen, die ausgelöst wird, wenn eine neue Datei in den Cloud Storage-Bucket hochgeladen wird:

d9a12fcf58f4813c.png

Sie interessieren sich für den Ereignistyp Finalize/Create. Das ist das Ereignis, das ausgelöst wird, wenn eine Datei im Bucket erstellt oder aktualisiert wird:

b30c8859b07dc4cb.png

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

cb15a1f4c7a1ca5f.png

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

c1933777fac32c6a.png

Bevor Sie auf „Weiter“ klicken, können Sie die Standardeinstellungen (256 MB Arbeitsspeicher) unter Laufzeit, Build, Verbindungen und Sicherheitseinstellungen erweitern und auf 1 GB aktualisieren.

83d757e6c38e10.png

Nachdem Sie auf Next geklickt haben, können Sie die Laufzeit, den Quellcode und den Einstiegspunkt optimieren.

Behalten Sie die Inline editor für diese Funktion bei:

b6646ec646082b32.png

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

f85b8a6f951f47a7.png

Der Quellcode besteht aus einer Java-Datei und einer pom.xml-Maven-Datei, die verschiedene Metadaten und Abhängigkeiten bereitstellt.

Behalten Sie das Standard-Code-Snippet bei: Es protokolliert den Dateinamen des hochgeladenen Bildes:

9b7b9801b42f6ca6.png

Behalten Sie vorerst den Namen der Funktion, die in Example ausgeführt werden soll, zu Testzwecken bei.

Klicken Sie auf Deploy, um die Funktion zu erstellen und bereitzustellen. Sobald die Bereitstellung erfolgreich war, sollten Sie in der Liste der Funktionen ein grün eingekreistes Häkchen sehen:

3732fdf409eefd1a.png

8. Funktion testen

Testen Sie in diesem Schritt, ob die Funktion auf Speicherereignisse reagiert.

Über das Feld „Hamburger“ (♢) auf, navigiere zurück zur Seite "Storage".

Klicken Sie auf den Bild-Bucket und dann auf Upload files, um ein Bild hochzuladen.

21767ec3cb8b18de.png

Rufen Sie noch einmal in der Cloud Console die Seite Logging > Logs Explorer auf.

Wählen Sie in der Log Fields-Auswahl Cloud Function aus, um die Logs aufzurufen, die Ihren Funktionen zugeordnet sind. Scrollen Sie nach unten durch die Logfelder und Sie können sogar eine bestimmte Funktion auswählen, um eine detailliertere Ansicht der funktionsbezogenen Logs zu erhalten. Wählen Sie die Funktion picture-uploaded aus.

Sie sollten die Log-Elemente sehen, in denen die Erstellung der Funktion, die Start- und Endzeiten der Funktion und unsere eigentliche Log-Anweisung angegeben sind:

e8ba7d39c36df36c.png

Unsere Log-Anweisung lautet: Processing file: pic-a-daily-architecture-events.png. Das bedeutet, dass das Ereignis, das sich auf die Erstellung und Speicherung dieses Bildes bezieht, tatsächlich wie erwartet ausgelöst wurde.

9. Datenbank vorbereiten

Sie speichern Informationen über das Bild der Vision API in der Cloud Firestore-Datenbank, einer schnellen, vollständig verwalteten, serverlosen, cloudnativen NoSQL-Dokumentendatenbank. Bereiten Sie Ihre Datenbank vor. Rufen Sie dazu in der Cloud Console den Bereich Firestore auf:

9e4708d2257de058.png

Es werden zwei Optionen angeboten: Native mode oder Datastore mode. Verwenden Sie den nativen Modus, der zusätzliche Funktionen wie Offlinesupport und Echtzeitsynchronisierung bietet.

Klicken Sie auf SELECT NATIVE MODE.

9449ace8cc84de43.png

Wählen Sie einen multiregionalen Standort aus (hier in Europa, idealerweise aber in derselben Region wie Ihre Funktion und Ihr Storage-Bucket).

Klicken Sie auf CREATE DATABASE.

Nachdem die Datenbank erstellt wurde, sollten Sie Folgendes sehen:

56265949a124819e.png

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

Sammlung „pictures“ benennen.

75806ee24c4e13a7.png

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 problemlos löschen, da es keine nützlichen Informationen enthält:

5c2f1e17ea47f48f.png

Die Dokumente, die in unserer Sammlung programmatisch erstellt werden, enthalten vier Felder:

  • name (String): der Dateiname des hochgeladenen Bildes, der auch der Schlüssel für das Dokument ist
  • labels (Array von Strings): die Labels erkannter Elemente von der Vision API
  • color (String): der hexadezimale Farbcode der dominanten Farbe (d. h. #ab12ef).
  • created (Datum): der Zeitstempel, der angibt, wann die Metadaten dieses Images gespeichert wurden
  • thumbnail (boolesch): ein optionales Feld, das angezeigt wird und „true“ ist, wenn eine Miniaturansicht für dieses Bild generiert wurde

Da wir in Firestore nach Bildern suchen, für die Miniaturansichten verfügbar sind, 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

Alternativ können Sie auch in der Cloud Console links in der Navigationsspalte auf Indexes klicken und dann wie unten gezeigt einen zusammengesetzten Index erstellen:

ecb8b95e3c791272.png

Klicken Sie auf Create. Die Indexerstellung kann einige Minuten dauern.

10. Funktion aktualisieren

Kehren Sie zur Seite Functions zurück, um die Funktion zu aktualisieren, um die Vision API aufzurufen, um die Bilder zu analysieren und die Metadaten in Firestore zu speichern.

Über das Feld „Hamburger“ (Ђ) auf, navigieren Sie zum Abschnitt Cloud Functions, 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 der Java-Funktion aufgelistet sind. Aktualisieren Sie den Code, um die Maven-Abhängigkeit für die 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>

Jetzt, da die Abhängigkeiten auf dem neuesten Stand sind, arbeiten Sie am Code der Funktion. Aktualisieren Sie dazu die Datei Example.java mit unserem benutzerdefinierten Code.

Bewegen Sie den Mauszeiger auf die Datei Example.java und klicken Sie auf den Bleistift. Ersetzen Sie den Paketnamen und den Dateinamen durch src/main/java/fn/ImageAnalysis.java.

Ersetzen Sie den Code in ImageAnalysis.java durch den folgenden Code. Dies wird im nächsten Schritt erläutert.

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. Funktion kennenlernen

Sehen wir uns die verschiedenen Aspekte genauer an.

Zuerst werden die spezifischen Abhängigkeiten in die Maven-Datei pom.xml aufgenommen. Google Java-Clientbibliotheken veröffentlichen eine Bill-of-Materials(BOM), um Abhängigkeitskonflikte zu vermeiden. Wenn Sie ihn 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()) {
...

Jetzt kommt die Struktur unserer Funktion. Wir erfassen aus dem eingehenden Ereignis die Felder, an denen wir interessiert sind, 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, aber auch, 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();

Sie benötigen drei wichtige Funktionen der Vision API:

  • Labelerkennung: Sie verstehen, was auf den Bildern zu sehen ist.
  • Bildeigenschaften: Hiermit können Sie interessante Attribute des Bildes angeben, da wir uns für die Hauptfarbe des Bildes interessieren.
  • SafeSearch: Herausfinden, ob das Bild sicher angezeigt werden kann (d. h., es darf keine Inhalte nur für Erwachsene / medizinische / nicht jugendfreie Inhalte / gewalttätige Inhalte enthalten)

Jetzt können Sie die Vision API aufrufen:

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

So sieht die Antwort der Vision API aus:

{
  "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. Daher haben wir diese Meldung, wenn sie blockieren:

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

Wir bekommen die Beschriftungen der Dinge, Kategorien oder Themen, die auf dem Bild erkannt werden:

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 die vorherrschende Farbe des Bildes herausfinden:

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 Dienstfunktion, um die Werte für Rot, Grün und Blau in einen hexadezimalen Farbcode umzuwandeln, den wir in CSS-Stylesheets verwenden können.

So überprüfen Sie, ob das Bild sicher angezeigt wird:

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, ob die Attribute nicht wahrscheinlich oder sehr wahrscheinlich sind.

Wenn das Ergebnis der sicheren Suche 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

Es ist an der Zeit, die Funktion bereitzustellen.

604f47aa11fbf8e.png

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

13da63f23e4dbbdd.png

13. Funktion noch einmal testen

Sobald die Funktion erfolgreich bereitgestellt wurde, posten Sie ein Bild in Cloud Storage. Prüfen Sie, ob die Funktion aufgerufen wird, was die Vision API zurückgibt und ob Metadaten in Firestore gespeichert sind.

Gehen Sie zurück zu Cloud Storage und klicken Sie auf den Bucket, den wir zu Beginn des Labs erstellt haben:

d44c1584122311c7.png

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

26bb31d35fb6aa3d.png

Über das Feld „Hamburger“ (♢) öffnen Sie den Explorer für Logging > Logs.

Wählen Sie in der Log Fields-Auswahl Cloud Function aus, um die Logs aufzurufen, die Ihren Funktionen zugeordnet sind. Scrollen Sie nach unten durch die Logfelder und Sie können sogar eine bestimmte Funktion auswählen, um eine detailliertere Ansicht der funktionsbezogenen Logs zu erhalten. Wählen Sie die Funktion picture-uploaded aus.

b651dca7e25d5b11.png

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

d22a7f24954e4f63.png

Die Logs geben Beginn und Ende der Funktionsausführung an. Und dazwischen sehen wir die Logs, die wir in unsere Funktion mit den console.log()-Anweisungen eingefügt haben. Wir sehen:

  • Die Details des Ereignisses, das unsere Funktion auslöst,
  • Die Rohergebnisse des Vision API-Aufrufs
  • Die Beschriftungen, die auf dem von uns hochgeladenen Bild gefunden wurden,
  • Die Informationen zu den vorherrschenden Farben,
  • Ob das Bild sicher gezeigt werden kann,
  • Diese Metadaten zum Bild wurden schließlich in Firestore gespeichert.

9ff7956a215c15da.png

Vom „Hamburger“ (∆) und zum Abschnitt Firestore gehen. Im Unterabschnitt Data (standardmäßig angezeigt) sollte die Sammlung pictures mit einem neuen Dokument zu sehen sein, das dem gerade hochgeladenen Bild entspricht:

a6137ab9687da370.png

14. Bereinigen (optional)

Wenn Sie nicht vorhaben, mit den anderen Labs dieser Reihe fortzufahren, können Sie Ressourcen bereinigen, um Kosten zu sparen und insgesamt ein guter Cloud-Nutzer zu sein. Sie können Ressourcen wie folgt einzeln bereinigen.

Löschen Sie den Bucket:

gsutil rb gs://${BUCKET_PICTURES}

Löschen Sie die Funktion:

gcloud functions delete picture-uploaded --region europe-west1 -q

Löschen Sie die Firestore-Sammlung, indem Sie Sammlung löschen aus der Sammlung auswählen:

410b551c3264f70a.png

Alternativ können Sie das gesamte Projekt löschen:

gcloud projects delete ${GOOGLE_CLOUD_PROJECT} 

15. Glückwunsch!

Glückwunsch! Sie haben den ersten Schlüsseldienst des Projekts erfolgreich implementiert!

Behandelte Themen

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

Nächste Schritte