Zdjęcia codziennie: moduł 1 – przechowywanie i analizowanie zdjęć (Java)
Informacje o tym ćwiczeniu (w Codelabs)
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 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 chmurze.
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