1. Przegląd
W pierwszym module prześlesz zdjęcia do zasobnika. Spowoduje to wygenerowanie zdarzenia utworzenia pliku, które zostanie obsłużone przez funkcję. Funkcja wywoła interfejs Vision API, aby przeprowadzić analizę obrazu i zapisać wyniki w magazynie danych.

Czego się nauczysz
- Cloud Storage
- Cloud Functions
- Cloud Vision API
- Cloud Firestore
2. Konfiguracja i wymagania
Samodzielne konfigurowanie środowiska
- Zaloguj się w konsoli Google Cloud i utwórz nowy projekt lub użyj istniejącego. Jeśli nie masz jeszcze konta Gmail lub Google Workspace, musisz je utworzyć.



- Nazwa projektu to wyświetlana nazwa dla uczestników tego projektu. Jest to ciąg znaków, który nie jest używany przez interfejsy API Google. Możesz ją zaktualizować w dowolnym momencie.
- Identyfikator projektu musi być unikalny we wszystkich projektach Google Cloud i jest niezmienny (nie można go zmienić po ustawieniu). Konsola Cloud automatycznie generuje unikalny ciąg znaków. Zwykle nie musisz się nim przejmować. W większości przewodników programowania musisz odwoływać się do identyfikatora projektu (zwykle jest on oznaczony jako
PROJECT_ID). Jeśli wygenerowany identyfikator Ci się nie podoba, możesz wygenerować inny losowy identyfikator. Możesz też spróbować utworzyć własny adres i sprawdzić, czy jest dostępny. Po tym kroku nie można go zmienić i będzie obowiązywać przez cały czas trwania projektu. - Warto wiedzieć, że istnieje trzecia wartość, numer projektu, której używają niektóre interfejsy API. Więcej informacji o tych 3 wartościach znajdziesz w dokumentacji.
- Następnie musisz włączyć płatności w konsoli Cloud, aby korzystać z zasobów i interfejsów API Google Cloud. Ukończenie tego laboratorium nie powinno wiązać się z dużymi kosztami, a nawet z żadnymi. Aby wyłączyć zasoby i uniknąć naliczania opłat po zakończeniu tego samouczka, możesz usunąć utworzone zasoby lub cały projekt. Nowi użytkownicy Google Cloud mogą skorzystać z programu bezpłatnego okresu próbnego, w którym mają do dyspozycji środki w wysokości 300 USD.
Uruchom Cloud Shell
Z Google Cloud można korzystać zdalnie na laptopie, ale w tym ćwiczeniu użyjesz Google Cloud Shell, czyli środowiska wiersza poleceń działającego w chmurze.
W konsoli Google Cloud kliknij ikonę Cloud Shell na pasku narzędzi w prawym górnym rogu:

Uzyskanie dostępu do środowiska i połączenie się z nim powinno zająć tylko kilka chwil. Po zakończeniu powinno wyświetlić się coś takiego:

Ta maszyna wirtualna zawiera wszystkie potrzebne narzędzia dla programistów. Zawiera również stały katalog domowy o pojemności 5 GB i działa w Google Cloud, co znacznie zwiększa wydajność sieci i usprawnia proces uwierzytelniania. Wszystkie zadania w tym laboratorium możesz wykonać w przeglądarce. Nie musisz niczego instalować.
3. Włącz interfejsy API
W tym module będziesz korzystać z Cloud Functions i interfejsu Vision API, ale najpierw musisz je włączyć w konsoli Google Cloud lub za pomocą gcloud.
Aby włączyć interfejs Vision API w konsoli Cloud, na pasku wyszukiwania wpisz Cloud Vision API:

Wyświetli się strona Cloud Vision API:

Kliknij przycisk ENABLE.
Możesz też włączyć go w Cloud Shell za pomocą narzędzia wiersza poleceń gcloud.
W Cloud Shell uruchom to polecenie:
gcloud services enable vision.googleapis.com
Operacja powinna się zakończyć:
Operation "operations/acf.12dba18b-106f-4fd2-942d-fea80ecc5c1c" finished successfully.
Włącz też Cloud Functions:
gcloud services enable cloudfunctions.googleapis.com
4. Tworzenie zasobnika (konsola)
Utwórz zasobnik na zdjęcia. Możesz to zrobić w konsoli Google Cloud Platform ( console.cloud.google.com) lub za pomocą narzędzia wiersza poleceń gsutil w Cloud Shell lub w lokalnym środowisku programistycznym.
Otwórz Miejsce na dane
W menu „hamburger” (☰) otwórz stronę Storage.

Nazwij zasobnik
Kliknij przycisk CREATE BUCKET.

Kliknij CONTINUE.
Wybierz lokalizację

Utwórz zasobnik z wieloma regionami w wybranym regionie (w tym przypadku Europe).
Kliknij CONTINUE.
Wybierz domyślną klasę pamięci masowej

Wybierz klasę pamięci masowej Standard dla swoich danych.
Kliknij CONTINUE.
Konfigurowanie kontroli dostępu

Będziesz pracować z publicznie dostępnymi obrazami, więc chcesz, aby wszystkie zdjęcia przechowywane w tym zasobniku miały takie same, jednolite ustawienia kontroli dostępu.
Wybierz opcję kontroli dostępu Uniform.
Kliknij CONTINUE.
Ustawianie zabezpieczenia/szyfrowania

Zachowaj ustawienie domyślne (Google-managed key)), ponieważ nie będziesz używać własnych kluczy szyfrowania.
Kliknij CREATE, aby zakończyć tworzenie zasobnika.
Dodawanie grupy allUsers jako przeglądającego miejsce na dane
Otwórz kartę Permissions:

Dodaj użytkownika allUsers do zasobnika z rolą Storage > Storage Object Viewer w ten sposób:

Kliknij SAVE.
5. Tworzenie zasobnika (gsutil)
Do tworzenia zasobników możesz też używać narzędzia wiersza poleceń gsutil w Cloud Shell.
W Cloud Shell ustaw zmienną dla unikalnej nazwy zasobnika. W Cloud Shell zmienna GOOGLE_CLOUD_PROJECT jest już ustawiona na Twój unikalny identyfikator projektu. Możesz dodać go do nazwy zasobnika.
Na przykład:
export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}
Utwórz standardową strefę wieloregionową w Europie:
gsutil mb -l EU gs://${BUCKET_PICTURES}
Sprawdź, czy jest włączony jednolity dostęp na poziomie zasobnika:
gsutil uniformbucketlevelaccess set on gs://${BUCKET_PICTURES}
Ustaw zasobnik jako publiczny:
gsutil iam ch allUsers:objectViewer gs://${BUCKET_PICTURES}
Jeśli przejdziesz do sekcji Cloud Storage w konsoli, powinien być tam publiczny uploaded-pictures:

Sprawdź, czy możesz przesyłać zdjęcia do zasobnika i czy przesłane zdjęcia są dostępne publicznie, jak opisano w poprzednim kroku.
6. Testowanie dostępu publicznego do zasobnika
Wracając do przeglądarki pamięci, zobaczysz na liście swój zasobnik z dostępem „Publiczny” (wraz z ostrzeżeniem przypominającym, że każdy ma dostęp do zawartości tego zasobnika).

Zasobnik jest gotowy do odbierania zdjęć.
Jeśli klikniesz nazwę zasobnika, zobaczysz jego szczegóły.

Możesz tam kliknąć przycisk Upload files, aby sprawdzić, czy możesz dodać obraz do zasobnika. Pojawi się okienko wyboru pliku, w którym musisz wybrać plik. Po wybraniu plik zostanie przesłany do zasobnika i ponownie zobaczysz public dostęp, który został automatycznie przypisany do tego nowego pliku.

Obok etykiety dostępu Public zobaczysz też małą ikonę linku. Po kliknięciu przeglądarka przejdzie do publicznego adresu URL tego obrazu, który będzie miał postać:
https://storage.googleapis.com/BUCKET_NAME/PICTURE_FILE.png
gdzie BUCKET_NAME to niepowtarzalna globalnie nazwa wybranego zasobnika, a następnie nazwa pliku obrazu.
Kliknięcie pola wyboru obok nazwy obrazu spowoduje włączenie przycisku DELETE, dzięki czemu możesz usunąć pierwszy obraz.
7. Tworzenie funkcji
W tym kroku utworzysz funkcję, która reaguje na zdarzenia przesyłania obrazów.
Otwórz sekcję Cloud Functions w konsoli Google Cloud. Po otwarciu tej strony usługa Cloud Functions zostanie automatycznie włączona.

Kliknij Create function.
Wybierz nazwę (np. picture-uploaded) i regionu (pamiętaj, aby zachować spójność z regionem wybranym dla zasobnika):

Istnieją 2 rodzaje funkcji:
- funkcje HTTP, które można wywoływać za pomocą adresu URL (np. interfejs API sieci),
- Funkcje w tle, które mogą być wywoływane przez określone zdarzenie.
Chcesz utworzyć funkcję w tle, która będzie wywoływana, gdy do zasobnika Cloud Storage zostanie przesłany nowy plik:

Interesuje Cię typ zdarzenia Finalize/Create, czyli zdarzenie wywoływane, gdy plik jest tworzony lub aktualizowany w zasobniku:

Wybierz utworzony wcześniej zasobnik, aby poinformować Cloud Functions, że ma otrzymywać powiadomienia o utworzeniu lub zaktualizowaniu pliku w tym zasobniku:

Kliknij Select, aby wybrać utworzony wcześniej zasobnik, a następnie Save.

Zanim klikniesz Dalej, możesz rozwinąć i zmodyfikować ustawienia domyślne (256 MB pamięci) w sekcji Ustawienia środowiska wykonawczego, kompilacji, połączeń i zabezpieczeń i zmienić je na 1 GB.

Po kliknięciu Next możesz dostosować środowisko wykonawcze, kod źródłowy i punkt wejścia.
Zachowaj Inline editor dla tej funkcji:

Wybierz jedno ze środowisk wykonawczych Javy, np. Java 11:

Kod źródłowy składa się z pliku Java i pliku pom.xml Maven, który zawiera różne metadane i zależności.
Pozostaw domyślny fragment kodu: rejestruje on nazwę pliku przesłanego obrazu:

Na potrzeby testów pozostaw nazwę funkcji do wykonania ustawioną na Example.
Aby utworzyć i wdrożyć funkcję, kliknij Deploy. Po pomyślnym wdrożeniu na liście funkcji powinien pojawić się zielony znacznik wyboru:

8. Testowanie funkcji
W tym kroku sprawdzisz, czy funkcja reaguje na zdarzenia w Cloud Storage.
W menu (☰) wróć na stronę Storage.
Kliknij zasobnik obrazów, a potem Upload files, aby przesłać obraz.

W konsoli w chmurze ponownie otwórz stronę Logging > Logs Explorer.
W selektorze Log Fields wybierz Cloud Function, aby wyświetlić logi dotyczące Twoich funkcji. Przewiń w dół pola logów. Możesz nawet wybrać konkretną funkcję, aby uzyskać bardziej szczegółowy widok logów związanych z funkcjami. Wybierz funkcję picture-uploaded.
Powinny być widoczne wpisy w logu dotyczące utworzenia funkcji, czasu jej rozpoczęcia i zakończenia oraz naszej rzeczywistej instrukcji logowania:

W naszym dzienniku widnieje wpis: Processing file: pic-a-daily-architecture-events.png, co oznacza, że zdarzenie związane z utworzeniem i zapisaniem tego zdjęcia zostało wywołane zgodnie z oczekiwaniami.
9. Przygotowywanie bazy danych
Informacje o zdjęciu podane przez interfejs Vision API zapiszesz w bazie danych Cloud Firestore, czyli szybkiej, w pełni zarządzanej, bezserwerowej, chmurowej bazie danych dokumentów NoSQL. Przygotuj bazę danych, przechodząc do sekcji Firestore w konsoli Cloud:

Dostępne są 2 opcje: Native mode lub Datastore mode. Używaj trybu natywnego, który oferuje dodatkowe funkcje, takie jak obsługa offline i synchronizacja w czasie rzeczywistym.
Kliknij SELECT NATIVE MODE.

Wybierz region obejmujący wiele lokalizacji (w tym przypadku w Europie, ale najlepiej co najmniej ten sam region, w którym znajdują się funkcja i zasobnik pamięci).
Kliknij przycisk CREATE DATABASE.
Po utworzeniu bazy danych zobaczysz te informacje:

Utwórz nową kolekcję, klikając przycisk + START COLLECTION.
Nazwij kolekcję pictures.

Nie musisz tworzyć dokumentu. Dodasz je programowo, gdy nowe zdjęcia będą przechowywane w Cloud Storage i analizowane przez interfejs Vision API.
Kliknij Save.
Firestore tworzy pierwszy domyślny dokument w nowo utworzonej kolekcji. Możesz go bezpiecznie usunąć, ponieważ nie zawiera żadnych przydatnych informacji:

Dokumenty, które zostaną utworzone programowo w naszej kolekcji, będą zawierać 4 pola:
- name (string): nazwa pliku przesłanego zdjęcia, która jest też kluczem dokumentu.
- labels (tablica ciągów znaków): etykiety rozpoznanych elementów przez Vision API.
- color (string): szesnastkowy kod koloru dominującego (np. #ab12ef)
- created (data): sygnatura czasowa wskazująca, kiedy metadane tego obrazu zostały zapisane.
- thumbnail (wartość logiczna): pole opcjonalne, które będzie obecne i będzie miało wartość „true”, jeśli dla tego zdjęcia została wygenerowana miniatura.
Aby wyszukiwać w Firestore zdjęcia, dla których dostępne są miniatury, i sortować je według daty utworzenia, musimy utworzyć indeks wyszukiwania.
Możesz utworzyć indeks za pomocą tego polecenia w Cloud Shell:
gcloud firestore indexes composite create \
--collection-group=pictures \
--field-config field-path=thumbnail,order=descending \
--field-config field-path=created,order=descending
Możesz też to zrobić w Cloud Console. W kolumnie nawigacyjnej po lewej stronie kliknij Indexes, a potem utwórz indeks złożony, jak pokazano poniżej:

Kliknij Create. Tworzenie indeksu może potrwać kilka minut.
10. Aktualizowanie funkcji
Wróć na stronę Functions, aby zaktualizować funkcję, która będzie wywoływać interfejs Vision API w celu analizowania zdjęć i przechowywania metadanych w Firestore.
W menu (☰) przejdź do sekcji Cloud Functions, kliknij nazwę funkcji, wybierz kartę Source, a następnie kliknij przycisk EDIT.
Najpierw zmień plik pom.xml, który zawiera listę zależności naszej funkcji Java. Zaktualizuj kod, aby dodać zależność Maven interfejsu Cloud Vision API:
<?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>
Teraz, gdy zależności są aktualne, możesz pracować nad kodem funkcji, aktualizując plik Example.java za pomocą niestandardowego kodu.
Najedź kursorem myszy na plik Example.java i kliknij ołówek. Zastąp nazwę pakietu i nazwę pliku ciągiem src/main/java/fn/ImageAnalysis.java.
Zastąp kod w pliku ImageAnalysis.java kodem poniżej. Wyjaśnimy to w następnym kroku.
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. Poznaj funkcję
Przyjrzyjmy się bliżej różnym interesującym częściom.
Po pierwsze, uwzględniamy konkretne zależności w pliku pom.xml Maven. Biblioteki klienta Google Java publikują Bill-of-Materials(BOM), aby wyeliminować konflikty zależności. Dzięki temu nie musisz określać wersji poszczególnych bibliotek klienta 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>
Następnie przygotowujemy klienta do interfejsu Vision API:
...
try (ImageAnnotatorClient vision = ImageAnnotatorClient.create()) {
...
Teraz przejdźmy do struktury naszej funkcji. Z przychodzącego zdarzenia pobieramy interesujące nas pola i mapujemy je na zdefiniowaną przez nas strukturę GCSEvent:
...
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;
}
Zwróć uwagę na sygnaturę, ale też na to, jak pobieramy nazwę pliku i zasobnika, które wywołały funkcję w Cloud Functions.
Oto przykładowy ładunek zdarzenia:
{
"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"
}
Przygotowujemy żądanie do wysłania za pomocą klienta 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();
Prosimy o 3 kluczowe funkcje interfejsu Vision API:
- Wykrywanie etykiet: aby zrozumieć, co znajduje się na zdjęciach.
- Właściwości obrazu: aby podać interesujące atrybuty obrazu (interesuje nas dominujący kolor obrazu).
- Bezpieczne wyszukiwanie: aby sprawdzić, czy obraz jest bezpieczny do wyświetlenia (nie powinien zawierać treści dla dorosłych, medycznych, o charakterze erotycznym ani treści przedstawiających przemoc).
W tym momencie możemy wywołać interfejs Vision API:
...
logger.info("Calling the Vision API...");
BatchAnnotateImagesResponse result =
vision.batchAnnotateImages(requests);
List<AnnotateImageResponse> responses = result.getResponsesList();
...
Oto przykład odpowiedzi interfejsu Vision API:
{
"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
}
Jeśli nie zostanie zwrócony żaden błąd, możemy przejść dalej. Dlatego mamy ten blok if:
AnnotateImageResponse response = responses.get(0);
if (response.hasError()) {
logger.info("Error: " + response.getError().getMessage());
return;
}
Pobierzemy etykiety rzeczy, kategorii lub motywów rozpoznanych na zdjęciu:
List<String> labels = response.getLabelAnnotationsList().stream()
.map(annotation -> annotation.getDescription())
.collect(Collectors.toList());
logger.info("Annotations found:");
for (String label: labels) {
logger.info("- " + label);
}
Chcemy poznać dominujący kolor zdjęcia:
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);
}
Używamy też funkcji narzędziowej do przekształcania wartości czerwonej, zielonej i niebieskiej w szesnastkowy kod koloru, którego możemy używać w arkuszach stylów CSS.
Sprawdźmy, czy zdjęcie jest bezpieczne:
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);
}
Sprawdzamy atrybuty dotyczące treści dla dorosłych, parodii, treści medycznych, przemocy i treści o charakterze erotycznym, aby określić, czy nie są one prawdopodobne lub bardzo prawdopodobne.
Jeśli wynik bezpiecznego wyszukiwania jest prawidłowy, możemy zapisać metadane w 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. Wdrażanie funkcji
Czas wdrożyć funkcję.

Kliknij przycisk DEPLOY, a nowa wersja zostanie wdrożona. Możesz śledzić postęp:

13. Ponowne testowanie funkcji
Po pomyślnym wdrożeniu funkcji opublikujesz obraz w Cloud Storage, sprawdzisz, czy funkcja została wywołana, co zwraca interfejs Vision API i czy metadane są przechowywane w Firestore.
Wróć do Cloud Storage i kliknij zasobnik utworzony na początku modułu:

Na stronie z informacjami o zasobniku kliknij przycisk Upload files, aby przesłać zdjęcie.

W menu „hamburger” (☰) otwórz Logging > Logs Eksplorator.
W selektorze Log Fields wybierz Cloud Function, aby wyświetlić logi dotyczące Twoich funkcji. Przewiń w dół pola logów. Możesz nawet wybrać konkretną funkcję, aby uzyskać bardziej szczegółowy widok logów związanych z funkcjami. Wybierz funkcję picture-uploaded.

Na liście logów widać, że nasza funkcja została wywołana:

Logi wskazują początek i koniec wykonania funkcji. Pomiędzy nimi widzimy logi, które umieściliśmy w funkcji za pomocą instrukcji console.log(). Widzimy:
- szczegóły zdarzenia, które wywołało funkcję,
- Nieprzetworzone wyniki wywołania interfejsu Vision API.
- etykiety znalezione na przesłanym przez nas zdjęciu,
- informacje o dominujących kolorach,
- czy zdjęcie jest bezpieczne do wyświetlenia,
- Metadane obrazu zostały ostatecznie zapisane w Firestore.

Ponownie w menu „hamburger” (☰) otwórz sekcję Firestore. W podsekcji Data (wyświetlanej domyślnie) powinna pojawić się kolekcja pictures z dodanym nowym dokumentem odpowiadającym właśnie przesłanemu zdjęciu:

14. Zwalnianie miejsca (opcjonalnie)
Jeśli nie zamierzasz kontynuować pracy z pozostałymi ćwiczeniami z tej serii, możesz zwalniać miejsce, aby zaoszczędzić koszty i być dobrym obywatelem chmury. Możesz zwolnić miejsce, usuwając poszczególne zasoby w ten sposób.
Usuń zasobnik:
gsutil rb gs://${BUCKET_PICTURES}
Usuń funkcję:
gcloud functions delete picture-uploaded --region europe-west1 -q
Usuń kolekcję Firestore, wybierając opcję Usuń kolekcję:

Możesz też usunąć cały projekt:
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
15. Gratulacje!
Gratulacje! Udało Ci się wdrożyć pierwszą kluczową usługę projektu.
Omówione zagadnienia
- Cloud Storage
- Cloud Functions
- Cloud Vision API
- Cloud Firestore