1. Omówienie
W pierwszym module dotyczącym programowania umieścisz zdjęcia w zasobniku. Spowoduje to wygenerowanie zdarzenia utworzenia pliku, które będzie obsługiwane przez funkcję. Ta 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 wykorzystaj już istniejący. Jeśli nie masz jeszcze konta Gmail ani Google Workspace, musisz je utworzyć.
- Nazwa projektu jest wyświetlaną nazwą uczestników tego projektu. To ciąg znaków, który nie jest używany przez interfejsy API Google. W każdej chwili możesz ją zmienić.
- Identyfikator projektu musi być unikalny we wszystkich projektach Google Cloud i nie można go zmienić (nie można go zmienić po ustawieniu). Cloud Console automatycznie wygeneruje unikalny ciąg znaków. zwykle nieważne, co ona jest. W większości ćwiczeń z programowania konieczne jest odwołanie się do identyfikatora projektu (zwykle nazywa się on
PROJECT_ID
). Jeśli nie podoba Ci się wygenerowany identyfikator, możesz wygenerować kolejny losowy. Możesz też spróbować własnych sił i sprawdzić, czy jest dostępna. Potem nie będzie można go zmienić. Pozostanie ono przez czas trwania projektu. - Dostępna jest trzecia wartość, numer projektu, z którego korzystają niektóre interfejsy API. Więcej informacji o wszystkich 3 wartościach znajdziesz w dokumentacji.
- Następnie musisz włączyć płatności w Cloud Console, aby korzystać z zasobów Cloud/interfejsów API. Ukończenie tego ćwiczenia z programowania nie powinno kosztować zbyt wiele. Aby wyłączyć zasoby, aby nie naliczać opłat po zakończeniu tego samouczka, możesz usunąć utworzone przez siebie zasoby lub cały projekt. Nowi użytkownicy Google Cloud mogą skorzystać z programu bezpłatnego okresu próbnego o wartości 300 USD.
Uruchamianie Cloud Shell
Google Cloud można obsługiwać zdalnie z laptopa, ale w ramach tego ćwiczenia z programowania wykorzystasz Google Cloud Shell – środowisko wiersza poleceń działające w Cloud.
W konsoli Google Cloud kliknij ikonę Cloud Shell na górnym pasku narzędzi:
Uzyskanie dostępu do środowiska i połączenie się z nim powinno zająć tylko kilka chwil. Po zakończeniu powinno pojawić się coś takiego:
Ta maszyna wirtualna ma wszystkie potrzebne narzędzia dla programistów. Zawiera stały katalog domowy o pojemności 5 GB i działa w Google Cloud, znacząco zwiększając wydajność sieci i uwierzytelnianie. Wszystkie zadania w ramach tego ćwiczenia z programowania można wykonywać w przeglądarce. Nie musisz niczego instalować.
3. Włącz interfejsy API
W tym module będziesz używać Cloud Functions i Vision API, ale najpierw musisz włączyć te funkcje w konsoli Cloud lub za pomocą usługi gcloud
.
Aby włączyć Vision API w konsoli Cloud, wyszukaj Cloud Vision API
na pasku wyszukiwania:
Wyświetli się strona Cloud Vision API:
Kliknij przycisk ENABLE
.
Możesz też włączyć Cloud Shell za pomocą narzędzia wiersza poleceń gcloud.
W Cloud Shell uruchom to polecenie:
gcloud services enable vision.googleapis.com
Powinna pojawić się operacja dokończenia:
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 z Cloud Shell albo lokalnego środowiska programistycznego.
Otwórz Pamięć
Z „hamburgera” (<!--) przejdź na stronę Storage
.
Nazwij zasobnik
Kliknij przycisk CREATE BUCKET
.
Kliknij CONTINUE
.
Wybierz lokalizację
Utwórz zasobnik z wieloma regionami w wybranym regionie (tutaj Europe
).
Kliknij CONTINUE
.
Wybierz domyślną klasę pamięci
Wybierz klasę pamięci Standard
dla swoich danych.
Kliknij CONTINUE
.
Ustaw kontrolę dostępu
Ponieważ pracujesz z obrazami dostępnymi publicznie, chcesz, aby wszystkie zdjęcia przechowywane w tym zasobniku miały taką samą jednolitą kontrolę dostępu.
Wybierz opcję kontroli dostępu w usłudze Uniform
.
Kliknij CONTINUE
.
Ustaw zabezpieczenia/szyfrowanie
Zachowaj domyślne (Google-managed key)
, ponieważ nie będziesz używać własnych kluczy szyfrowania.
Kliknij CREATE
, aby zakończyć tworzenie zasobnika.
Dodawanie użytkownika allUsers jako wyświetlającego miejsce na dane
Otwórz kartę Permissions
:
Dodaj do zasobnika użytkownika allUsers
z rolą Storage > Storage Object Viewer
w następujący sposób:
Kliknij SAVE
.
5. Tworzenie zasobnika (gsutil)
Zasobniki możesz też tworzyć za pomocą narzędzia wiersza poleceń gsutil
w Cloud Shell.
W Cloud Shell ustaw zmienną dla unikalnej nazwy zasobnika. W Cloud Shell wartość GOOGLE_CLOUD_PROJECT
jest już ustawiona na Twój unikalny identyfikator projektu. Możesz to dodać do nazwy zasobnika.
Na przykład:
export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}
Tworzenie standardowej strefy obejmującej wiele regionów w Europie:
gsutil mb -l EU gs://${BUCKET_PICTURES}
Sprawdź, czy 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 otworzysz sekcję Cloud Storage
w konsoli, powinien być już publiczny zasobnik uploaded-pictures
:
Sprawdź, czy możesz przesyłać zdjęcia do zasobnika i czy są one publicznie dostępne, jak wyjaśniliśmy w poprzednim kroku.
6. Testowanie dostępu publicznego do zasobnika
Gdy wrócisz do przeglądarki, zobaczysz zasobnik na liście z oznaczeniem „Publiczny”. dostępu (w tym znak ostrzegawczy przypominający o tym, że każdy ma dostęp do zawartości danego zasobnika).
Twój zasobnik jest teraz gotowy na otrzymywanie zdjęć.
Po kliknięciu nazwy zasobnika zobaczysz jego szczegóły.
Tam możesz wypróbować przycisk Upload files
, aby sprawdzić, czy możesz dodać zdjęcie do zasobnika. Pojawi się wyskakujące okienko wyboru plików z prośbą o wybranie pliku. Po wybraniu plik zostanie przesłany do zasobnika i ponownie zobaczysz uprawnienia dostępu na poziomie public
, które zostały automatycznie przypisane do tego nowego pliku.
Na etykiecie dostępu Public
zobaczysz też małą ikonę linku. Po kliknięciu obrazu przeglądarka otworzy jego publiczny adres URL, który będzie miał postać:
https://storage.googleapis.com/BUCKET_NAME/PICTURE_FILE.png
BUCKET_NAME
to globalnie unikalna nazwa nadana przez Ciebie dla zasobnika oraz nazwa pliku zdjęcia.
Gdy klikniesz pole wyboru obok nazwy zdjęcia, przycisk DELETE
stanie się aktywny i będzie można 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. Gdy ją otworzysz, usługa w Cloud Functions zostanie automatycznie włączona.
Kliknij Create function
.
Wybierz nazwę (np. picture-uploaded
) i region (pamiętaj, aby zachować zgodność z wyborem regionu dla zasobnika):
Są 2 rodzaje funkcji:
- funkcji HTTP, które można wywoływać za pomocą adresu URL (np. internetowego interfejsu API);
- Funkcje działające w tle, które mogą być aktywowane przez niektóre zdarzenia.
Chcesz utworzyć funkcję działającą w tle, która będzie aktywowana po przesłaniu nowego pliku do naszego zasobnika Cloud Storage
:
Interesuje Cię typ zdarzenia Finalize/Create
, który jest wywoływany, gdy plik jest tworzony lub aktualizowany w zasobniku:
Wybierz utworzony wcześniej zasobnik, aby wskazać usłudze Cloud Functions, że ma otrzymywać powiadomienia o utworzeniu lub zaktualizowaniu pliku w tym konkretnym zasobniku:
Kliknij Select
, aby wybrać utworzony wcześniej zasobnik, a następnie Save
Zanim klikniesz Dalej, możesz rozwinąć i zmienić wartości domyślne (256 MB pamięci) w sekcji Ustawienia środowiska wykonawczego, kompilacji, połączeń i zabezpieczeń i zaktualizować je do 1 GB.
Po kliknięciu Next
możesz dostroić środowisko wykonawcze, kod źródłowy i punkt wejścia.
Zachowaj Inline editor
dla tej funkcji:
Wybierz jedno ze środowisk wykonawczych Java, na przykład Java 11:
Kod źródłowy składa się z pliku Java
i pliku Maven pom.xml
z różnymi metadanymi i zależnościami.
Pozostaw domyślny fragment kodu – spowoduje to zarejestrowanie nazwy pliku przesłanego zdjęcia:
Na razie zachowaj nazwę funkcji do wykonania w polu Example
na potrzeby testowania.
Kliknij Deploy
, aby utworzyć i wdrożyć funkcję. Po pomyślnym wdrożeniu powinien pojawić się zielony znacznik wyboru na liście funkcji:
8. Testowanie funkcji
W tym kroku sprawdź, czy funkcja odpowiada na zdarzenia związane z przechowywaniem danych.
Z „hamburgera” (<!--) wróć na stronę Storage
.
Kliknij zasobnik obrazów, a następnie Upload files
, aby przesłać obraz.
W Cloud Console otwórz stronę Logging > Logs Explorer
.
W selektorze Log Fields
wybierz Cloud Function
, aby wyświetlić logi przeznaczone dla Twoich funkcji. Przewiń stronę Pola logów w dół, aby wybrać konkretną funkcję, aby zobaczyć bardziej szczegółowe logi związane z funkcjami. Wybierz funkcję picture-uploaded
.
Powinny się wyświetlić pozycje dziennika wspominające o utworzeniu funkcji, czasy rozpoczęcia i zakończenia funkcji oraz nasza instrukcja logu:
Komunikat dziennika o treści: Processing file: pic-a-daily-architecture-events.png
, co oznacza, że zdarzenie związane z tworzeniem i przechowywaniem tego zdjęcia zostało rzeczywiście wywołane zgodnie z oczekiwaniami.
9. Przygotowywanie bazy danych
Informacje o zdjęciu wyświetlanym przez interfejs Vision API będziesz przechowywać w bazie danych Cloud Firestore – szybkiej, w pełni zarządzanej, bezserwerowej, chmurowej bazie dokumentów NoSQL. Przygotuj bazę danych w sekcji Firestore
w konsoli Cloud:
Dostępne są 2 opcje: Native mode
i Datastore mode
. Korzystaj z trybu natywnego, który oferuje dodatkowe funkcje, takie jak obsługa offline i synchronizacja w czasie rzeczywistym.
Kliknij SELECT NATIVE MODE
.
Wybierz wiele regionów (w Europie, ale najlepiej co najmniej taki sam region, w jakim są Twoje funkcja i zasobnik na dane).
Kliknij przycisk CREATE DATABASE
.
Po utworzeniu bazy danych powinien wyświetlić się ten komunikat:
Utwórz nową kolekcję, klikając przycisk + START COLLECTION
.
Nazwij kolekcję pictures
.
Nie musisz tworzyć dokumentu. Będziesz dodawać je automatycznie w miarę zapisywania nowych zdjęć w Cloud Storage i analizowania ich przez interfejs Vision API.
Kliknij Save
.
Firestore tworzy w nowo utworzonej kolekcji pierwszy dokument domyślny. Dokument możesz bezpiecznie usunąć, ponieważ nie zawiera on żadnych przydatnych informacji.
Dokumenty, które zostaną utworzone automatycznie w naszej kolekcji, będą zawierać 4 pola:
- name (ciąg znaków): nazwa pliku przesłanego zdjęcia, który jest również kluczem dokumentu.
- labels (tablica ciągów znaków): etykiety rozpoznawane przez interfejs Vision API
- color (ciąg znaków): szesnastkowy kod koloru dominującego (np. #ab12ef)
- created (data): sygnatura czasowa przechowywania metadanych obrazu.
- thumbnail (wartość logiczna): opcjonalne pole, które będzie obecne i będzie mieć wartość true (prawda), jeśli dla danego zdjęcia wygenerowano obraz miniatury
Będziemy szukać w Firestore zdjęć, które mają dostępne miniatury, i sortujemy według daty utworzenia, więc konieczne będzie utworzenie indeksu wyszukiwania.
Indeks możesz utworzyć 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 to też zrobić w konsoli Cloud. Aby to zrobić, w kolumnie nawigacji po lewej stronie kliknij Indexes
, a potem utwórz indeks złożony w ten sposób:
Kliknij Create
. Tworzenie indeksu może potrwać kilka minut.
10. Zaktualizuj funkcję
Wróć na stronę Functions
, aby zaktualizować funkcję wywoływania interfejsu Vision API w celu analizy obrazów oraz przechowywać metadane w Firestore.
Z „hamburgera” (<!--) przejdź do sekcji Cloud Functions
, kliknij nazwę funkcji, wybierz kartę Source
, a następnie kliknij przycisk EDIT
.
Najpierw edytuj plik pom.xml
, który zawiera listę zależności naszej funkcji w Javie. 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ą już aktualne, będziesz pracować nad kodem naszej funkcji, aktualizując plik Example.java
za pomocą kodu niestandardowego.
Najedź kursorem myszy na plik Example.java
i kliknij ołówek. Zastąp nazwę pakietu i pliku src/main/java/fn/ImageAnalysis.java
.
Zastąp kod w polu ImageAnalysis.java
poniższym kodem. 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 elementom.
Najpierw uwzględniamy konkretne zależności w pliku Maven pom.xml
. Biblioteki klienta w języku Java firmy Google publikują plik Bill-of-Materials(BOM)
, aby wyeliminować wszelkie konflikty zależności. Dzięki niemu nie musisz określać żadnej wersji dla 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 korzystania z interfejsu Vision API:
...
try (ImageAnnotatorClient vision = ImageAnnotatorClient.create()) {
...
Teraz zaczyna się struktura naszej funkcji. Przechwytujemy ze zdarzenia przychodzącego te pola, które nas interesują, 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 podpis oraz to, jak pobieramy nazwę pliku i zasobnika, które aktywowały funkcję w Cloud Functions.
Poniżej znajdziesz 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: sprawdzanie, co znajduje się na zdjęciach.
- Właściwości obrazu: w celu nadania nam interesujących atrybutów zdjęcia (chcemy zwrócić uwagę na dominujący kolor zdjęcia).
- Bezpieczne wyszukiwanie: pozwala określić, czy obraz jest bezpieczny do wyświetlenia (nie może zawierać treści dla dorosłych, medycyny, treści dla dorosłych ani przemocy).
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, jak wygląda odpowiedź z 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, z tego powodu w przypadku blokady:
AnnotateImageResponse response = responses.get(0);
if (response.hasError()) {
logger.info("Error: " + response.getError().getMessage());
return;
}
Poznamy etykiety elementów, kategorii i tematów rozpoznawane na ilustracji:
List<String> labels = response.getLabelAnnotationsList().stream()
.map(annotation -> annotation.getDescription())
.collect(Collectors.toList());
logger.info("Annotations found:");
for (String label: labels) {
logger.info("- " + label);
}
Interesuje nas 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);
}
Korzystamy również z funkcji użytkowej, która przekształca wartości koloru czerwonego, zielonego i niebieskiego w szesnastkowy kod koloru, którego można użyć w arkuszach stylów CSS.
Sprawdźmy, czy zdjęcie można bezpiecznie wyświetlać:
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, czy treści dla dorosłych / parodie / medycyna / przemoc / treści dla dorosłych i czy są one prawdopodobne lub bardzo prawdopodobne.
Jeśli wynik bezpiecznego wyszukiwania jest prawidłowy, możemy przechowywać 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ę.
Naciśnij przycisk DEPLOY
, aby wdrożyć nową wersję. Możesz sprawdzić postęp:
13. Ponownie przetestuj funkcję
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 szczegółów zasobnika kliknij przycisk Upload files
, aby przesłać zdjęcie.
Z „hamburgera” (<!--) otwórz Eksploratora Logging > Logs
.
W selektorze Log Fields
wybierz Cloud Function
, aby wyświetlić logi przeznaczone dla Twoich funkcji. Przewiń stronę Pola logów w dół, aby wybrać konkretną funkcję, aby zobaczyć bardziej szczegółowe logi związane 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 widać logi umieszczone w funkcji za pomocą instrukcji Console.log(). Widzimy:
- szczegóły zdarzenia aktywującego naszą funkcję,
- Nieprzetworzone wyniki z wywołania interfejsu Vision API
- Etykiety znalezione na przesłanym zdjęciu,
- informacje o kolorach dominujących,
- Whether the picture is safe to show,
- Metadane obrazu zostały zapisane w Firestore.
Ponownie od „hamburgera” (<!--) przejdź do sekcji Firestore
. W podsekcji Data
(wyświetlanej domyślnie) powinna być widoczna kolekcja pictures
z nowym dokumentem odpowiadającym przesłanym właśnie zdjęciu:
14. Czyszczenie (opcjonalnie)
Jeśli nie chcesz przechodzić do innych modułów z tej serii, możesz zwolnić zasoby, aby zmniejszyć koszty i zachować dobre praktyki związane z chmurą. Zasoby możesz wyczyścić pojedynczo w ten sposób.
Usuń zasobnik:
gsutil rb gs://${BUCKET_PICTURES}
Usuń tę funkcję:
gcloud functions delete picture-uploaded --region europe-west1 -q
Usuń kolekcję Firestore, wybierając z niej 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ą usługę kluczy w projekcie.
Omówione zagadnienia
- Cloud Storage
- Cloud Functions
- Cloud Vision API
- Cloud Firestore