1. Обзор
В первой лабораторной работе вам предстоит загрузить изображения в хранилище. Это вызовет событие создания файла, которое будет обработано функцией. Функция выполнит вызов API Vision для анализа изображений и сохранения результатов в хранилище данных.

Что вы узнаете
- Облачное хранилище
- Облачные функции
- API Cloud Vision
- Облачный Firestore
2. Настройка и требования
Настройка среды для самостоятельного обучения
- Войдите в консоль Google Cloud и создайте новый проект или используйте существующий. Если у вас еще нет учетной записи Gmail или Google Workspace, вам необходимо ее создать .



- Название проекта — это отображаемое имя участников данного проекта. Это строка символов, не используемая API Google. Вы можете изменить её в любое время.
- Идентификатор проекта должен быть уникальным для всех проектов Google Cloud и неизменяемым (его нельзя изменить после установки). Консоль Cloud автоматически генерирует уникальную строку; обычно вам неважно, какая она. В большинстве практических заданий вам потребуется указать идентификатор проекта (обычно он обозначается как
PROJECT_ID). Если сгенерированный идентификатор вас не устраивает, вы можете сгенерировать другой случайный идентификатор. В качестве альтернативы вы можете попробовать свой собственный и посмотреть, доступен ли он. После этого шага его нельзя изменить, и он останется неизменным на протяжении всего проекта. - К вашему сведению, существует третье значение — номер проекта , который используется некоторыми API. Подробнее обо всех трех значениях можно узнать в документации .
- Далее вам потребуется включить оплату в консоли Cloud для использования ресурсов/API Cloud. Выполнение этого практического задания не должно стоить дорого, если вообще что-либо. Чтобы отключить ресурсы и избежать дополнительных расходов после завершения этого урока, вы можете удалить созданные ресурсы или удалить весь проект. Новые пользователи Google Cloud имеют право на бесплатную пробную версию стоимостью 300 долларов США .
Запустить Cloud Shell
Хотя Google Cloud можно управлять удаленно с ноутбука, в этом практическом занятии вы будете использовать Google Cloud Shell — среду командной строки, работающую в облаке.
В консоли Google Cloud нажмите на значок Cloud Shell на панели инструментов в правом верхнем углу:

Подготовка и подключение к среде займут всего несколько минут. После завершения вы должны увидеть что-то подобное:

Эта виртуальная машина содержит все необходимые инструменты разработки. Она предоставляет постоянный домашний каталог объемом 5 ГБ и работает в облаке Google, что значительно повышает производительность сети и аутентификацию. Вся работа в этом практическом задании может выполняться в браузере. Вам не нужно ничего устанавливать.
3. Включите API.
Для этой лабораторной работы вы будете использовать Cloud Functions и Vision API, но сначала их необходимо включить либо в Cloud Console, либо с помощью gcloud .
Чтобы включить Vision API в Cloud Console, введите Cloud Vision API в строку поиска:

Вы попадете на страницу API Cloud Vision:

Нажмите кнопку ENABLE .
В качестве альтернативы, вы также можете включить Cloud Shell с помощью инструмента командной строки gcloud.
Внутри Cloud Shell выполните следующую команду:
gcloud services enable vision.googleapis.com
Вы должны увидеть сообщение об успешном завершении операции:
Operation "operations/acf.12dba18b-106f-4fd2-942d-fea80ecc5c1c" finished successfully.
Включите также облачные функции:
gcloud services enable cloudfunctions.googleapis.com
4. Создайте корзину (консоль).
Создайте хранилище для изображений. Это можно сделать через консоль Google Cloud Platform ( console.cloud.google.com ) или с помощью инструмента командной строки gsutil в Cloud Shell или в вашей локальной среде разработки.
Перейдите в раздел «Хранилище».
В меню-гамбургере (☰) перейдите на страницу « Storage ».

Назовите своё ведро
Нажмите кнопку CREATE BUCKET .

Нажмите CONTINUE .
Выберите местоположение

Создайте мультирегиональную группу в выбранном вами регионе (здесь Europe ).
Нажмите CONTINUE .
Выберите класс хранения по умолчанию

Выберите Standard класс хранения для ваших данных.
Нажмите CONTINUE .
Настроить контроль доступа

Поскольку вы будете работать с общедоступными изображениями, вам необходимо, чтобы все наши фотографии, хранящиеся в этом хранилище, имели одинаковый единый контроль доступа.
Выберите вариант Uniform контроль доступа».
Нажмите CONTINUE .
Защита/шифрование

Оставьте Google-managed key) , поскольку вы не будете использовать собственные ключи шифрования.
Нажмите кнопку CREATE , чтобы завершить создание корзины.
Добавить allUsers в качестве средства просмотра хранилища.
Перейдите на вкладку Permissions :

Добавьте в корзину allUsers с ролью Storage > Storage Object Viewer следующим образом:

Нажмите SAVE .
5. Создайте корзину (gsutil)
Для создания сегментов (buckets) в Cloud Shell также можно использовать инструмент командной строки gsutil .
В Cloud Shell задайте переменную для уникального имени корзины. В Cloud Shell уже установлена переменная GOOGLE_CLOUD_PROJECT , содержащая уникальный идентификатор вашего проекта. Вы можете добавить его к имени корзины.
Например:
export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}
Создайте стандартную многорегиональную зону в Европе:
gsutil mb -l EU gs://${BUCKET_PICTURES}
Обеспечьте единообразный доступ к уровню жидкости в ведре:
gsutil uniformbucketlevelaccess set on gs://${BUCKET_PICTURES}
Сделайте хранилище общедоступным:
gsutil iam ch allUsers:objectViewer gs://${BUCKET_PICTURES}
Если вы перейдете в раздел Cloud Storage в консоли, вы должны увидеть общедоступное хранилище uploaded-pictures :

Проверьте, можете ли вы загружать изображения в хранилище и доступны ли загруженные изображения для публичного просмотра, как объяснялось на предыдущем шаге.
6. Проверьте доступ общественности к ведру.
Вернувшись в браузер хранилища, вы увидите свой сегмент в списке с пометкой «Общедоступный» (включая предупреждение о том, что любой может получить доступ к содержимому этого сегмента).

Ваше хранилище теперь готово к приему фотографий.
Если вы щёлкнете по названию корзины, вы увидите подробную информацию о ней.

Там вы можете попробовать кнопку Upload files , чтобы проверить, можно ли добавить изображение в хранилище. Во всплывающем окне выбора файла вам будет предложено выбрать файл. После выбора он будет загружен в ваше хранилище, и вы снова увидите public доступ, автоматически присвоенный этому новому файлу.

Рядом с надписью «Доступно Public вы также увидите небольшой значок ссылки. При нажатии на него ваш браузер перейдет по общедоступному URL-адресу этого изображения, который будет иметь следующий вид:
https://storage.googleapis.com/BUCKET_NAME/PICTURE_FILE.png
Здесь BUCKET_NAME — это глобально уникальное имя, которое вы выбрали для своего хранилища, а затем имя файла вашего изображения.
Установив флажок рядом с названием изображения, вы активируете кнопку DELETE и сможете удалить первое изображение.
7. Создайте функцию.
На этом этапе вы создаете функцию, которая реагирует на события загрузки изображений.
Перейдите в раздел Cloud Functions в консоли Google Cloud. После перехода в этот раздел служба Cloud Functions будет автоматически включена.

Нажмите « Create function .
Выберите название (например, picture-uploaded ) и регион (не забудьте, чтобы он совпадал с регионом, выбранным для корзины):

Существует два типа функций:
- HTTP-функции, которые можно вызывать через URL (т.е. веб-API),
- Фоновые функции, которые могут быть запущены при возникновении какого-либо события.
Вы хотите создать фоновую функцию, которая будет запускаться при загрузке нового файла в наше Cloud Storage :

Вас интересует тип события Finalize/Create , который срабатывает при создании или обновлении файла в хранилище:

Выберите созданный ранее сегмент, чтобы настроить Cloud Functions на получение уведомлений о создании/обновлении файлов в этом конкретном сегменте:

Нажмите Select , чтобы выбрать созданный ранее сегмент, а затем Save

Перед тем как нажать «Далее», вы можете развернуть и изменить значения по умолчанию (256 МБ памяти) в разделе «Среда выполнения, сборка, подключения и параметры безопасности» , а также установить значение 1 ГБ.

После нажатия кнопки Next вы можете настроить среду выполнения , исходный код и точку входа .
Для этой функции сохраните Inline editor :

Выберите одну из сред выполнения Java, например Java 11:

Исходный код состоит из Java файла и Maven-файла pom.xml , который предоставляет различные метаданные и зависимости.
Оставьте фрагмент кода по умолчанию: он выводит в консоль имя файла загруженного изображения:

Пока что, для целей тестирования, оставьте имя выполняемой функции как Example .
Нажмите кнопку Deploy , чтобы создать и развернуть функцию. После успешного развертывания в списке функций должна появиться зеленая галочка:

8. Проверьте функцию.
На этом этапе проверьте, реагирует ли функция на события, связанные с хранилищем данных.
Из меню-гамбургера (☰) вернитесь на страницу « Storage .
Нажмите на раздел изображений, а затем на Upload files , чтобы загрузить изображение.

В консоли облака снова перейдите на страницу Logging > Logs Explorer .
В селекторе Log Fields выберите Cloud Function , чтобы просмотреть журналы, относящиеся к вашим функциям. Прокрутите вниз по полям журнала, и вы даже сможете выбрать конкретную функцию, чтобы получить более детальный обзор журналов, связанных с этой функцией. Выберите функцию picture-uploaded .
В журнале должны отобразиться записи, содержащие информацию о создании функции, времени её начала и окончания, а также сам текст сообщения:

В нашем журнале событий указано: Processing file: pic-a-daily-architecture-events.png , что означает, что событие, связанное с созданием и сохранением этого изображения, действительно было запущено, как и ожидалось.
9. Подготовьте базу данных.
Вы будете сохранять информацию об изображении, полученном с помощью Vision API, в базу данных Cloud Firestore — быструю, полностью управляемую, бессерверную, облачную документоориентированную базу данных NoSQL. Подготовьте свою базу данных, перейдя в раздел Firestore в консоли Cloud Console:

Предлагаются два варианта: Native mode или Datastore mode . Используйте собственный режим, который предлагает дополнительные функции, такие как поддержка работы в автономном режиме и синхронизация в реальном времени.
Нажмите кнопку SELECT NATIVE MODE .

Выберите многорегиональный вариант (здесь, в Европе, но в идеале — как минимум тот же регион, что и ваша функция и хранилище данных).
Нажмите кнопку CREATE DATABASE .
После создания базы данных вы должны увидеть следующее:

Создайте новую коллекцию , нажав кнопку + START COLLECTION .
pictures из коллекции имен.

Вам не нужно создавать документ. Вы будете добавлять их программно по мере того, как новые изображения будут сохраняться в облачном хранилище и анализироваться с помощью Vision API.
Нажмите « Save ».
Firestore создает первый документ по умолчанию в только что созданной коллекции; вы можете смело удалить этот документ, поскольку он не содержит никакой полезной информации.

Документы, которые будут созданы программным способом в нашей коллекции, будут содержать 4 поля:
- имя (строка): имя файла загруженного изображения, которое также является ключом документа.
- метки (массив строк): метки распознанных элементов в Vision API.
- цвет (строка): шестнадцатеричный код доминирующего цвета (например, #ab12ef)
- дата создания : метка времени сохранения метаданных этого изображения.
- миниатюра (логическое значение): необязательное поле, которое будет присутствовать и иметь значение true, если для этого изображения было сгенерировано миниатюрное изображение.
Поскольку мы будем искать в Firestore изображения с доступными миниатюрами и сортировать их по дате создания, нам потребуется создать поисковый индекс.
Создать индекс можно с помощью следующей команды в Cloud Shell:
gcloud firestore indexes composite create \
--collection-group=pictures \
--field-config field-path=thumbnail,order=descending \
--field-config field-path=created,order=descending
Или же вы можете сделать это из консоли Cloud Console, щелкнув Indexes в левой навигационной колонке, а затем создав составной индекс, как показано ниже:

Нажмите Create . Создание индекса может занять несколько минут.
10. Обновите функцию
Вернитесь на страницу Functions , чтобы обновить функцию, которая будет вызывать API Vision для анализа наших изображений и сохранять метаданные в Firestore.
В меню-гамбургере (☰) перейдите в раздел « Cloud Functions , щелкните по названию функции, выберите вкладку Source , а затем нажмите кнопку EDIT .
Сначала отредактируйте файл pom.xml , в котором перечислены зависимости нашей Java-функции. Обновите код, добавив зависимость Cloud Vision API Maven:
<?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>
Теперь, когда зависимости обновлены, вы займетесь кодом нашей функции, обновив файл Example.java нашим пользовательским кодом.
Наведите курсор мыши на файл Example.java и щелкните по значку карандаша. Замените имя пакета и имя файла на src/main/java/fn/ImageAnalysis.java .
Замените код в ImageAnalysis.java кодом, приведенным ниже. Это будет объяснено на следующем шаге.
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. Изучите функцию
Давайте подробнее рассмотрим различные интересные моменты.
Во-первых, мы включаем конкретные зависимости в файл pom.xml Maven. Библиотеки Google Java Client Libraries публикуют спецификацию Bill-of-Materials(BOM) , чтобы исключить любые конфликты зависимостей. Используя её, вам не нужно указывать версию для отдельных библиотек Google Client Libraries.
<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>
Затем мы подготавливаем клиент для Vision API:
...
try (ImageAnnotatorClient vision = ImageAnnotatorClient.create()) {
...
Теперь перейдем к структуре нашей функции. Мы получаем из входящего события интересующие нас поля и сопоставляем их со структурой 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;
}
Обратите внимание на подпись, а также на то, как мы получаем имя файла и сегмента, которые запустили облачную функцию.
Для справки, вот как выглядит полезная нагрузка события:
{
"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"
}
Мы подготавливаем запрос для отправки через клиент 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();
Мы запрашиваем 3 ключевые возможности Vision API:
- Распознавание меток : чтобы понять, что изображено на этих картинках.
- Свойства изображения : для придания изображению интересных характеристик (нас интересует преобладающий цвет).
- Безопасный поиск : чтобы узнать, безопасно ли показывать изображение (оно не должно содержать материалы для взрослых / медицинского характера / откровенные / жестокие).
На этом этапе мы можем обратиться к API Vision:
...
logger.info("Calling the Vision API...");
BatchAnnotateImagesResponse result =
vision.batchAnnotateImages(requests);
List<AnnotateImageResponse> responses = result.getResponsesList();
...
Для справки, вот как выглядит ответ от 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
}
Если ошибка не возвращается, мы можем продолжить, поэтому у нас и есть этот блок if:
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);
}
Мы также используем вспомогательную функцию для преобразования значений красного, зеленого и синего цветов в шестнадцатеричный цветовой код, который можно использовать в таблицах стилей CSS.
Давайте проверим, можно ли показывать это изображение:
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);
}
Мы проверяем наличие атрибутов "для взрослых" / "пародия" / "медицинский" / "насилие" / "пикантность", чтобы определить, насколько они вероятны или, наоборот, крайне вероятны .
Если результат безопасного поиска приемлемый, мы можем сохранить метаданные в 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. Разверните функцию
Пора развернуть функцию.

Нажмите кнопку DEPLOY , и новая версия будет развернута. Вы сможете отслеживать ход выполнения:

13. Проверьте функцию еще раз.
После успешного развертывания функции вам нужно будет загрузить изображение в Cloud Storage, проверить, была ли вызвана наша функция, что возвращает Vision API и сохранились ли метаданные в Firestore.
Вернитесь в Cloud Storage и щелкните по созданному нами в начале лабораторной работы сегменту:

На странице с подробной информацией о хранилище нажмите кнопку Upload files , чтобы загрузить изображение.

В меню «гамбургер» (☰) перейдите в раздел Logging > Logs .
В селекторе Log Fields выберите Cloud Function , чтобы просмотреть журналы, относящиеся к вашим функциям. Прокрутите вниз по полям журнала, и вы даже сможете выбрать конкретную функцию, чтобы получить более детальный обзор журналов, связанных с этой функцией. Выберите функцию picture-uploaded .

И действительно, в списке логов я вижу, что наша функция была вызвана:

В логах отображается начало и конец выполнения функции. А между этими событиями мы можем увидеть логи, которые мы добавили в нашу функцию с помощью операторов console.log(). Мы видим:
- Подробности события, запустившего нашу функцию:
- Исходные результаты вызова API Vision.
- Этикетки, которые были обнаружены на загруженном нами изображении,
- Информация о доминирующих цветах.
- Можно ли показывать это изображение?
- В конечном итоге эти метаданные об изображении были сохранены в Firestore.

Снова из меню-гамбургера (☰) перейдите в раздел Firestore . В подразделе Data » (отображается по умолчанию) вы должны увидеть коллекцию pictures с добавленным новым документом, соответствующим только что загруженному изображению:

14. Уборка (необязательно)
Если вы не планируете продолжать выполнение остальных лабораторных работ из этой серии, вы можете освободить ресурсы, чтобы сэкономить средства и в целом ответственно относиться к облачным технологиям. Освободить ресурсы можно по отдельности следующим образом.
Удалите корзину:
gsutil rb gs://${BUCKET_PICTURES}
Удалите функцию:
gcloud functions delete picture-uploaded --region europe-west1 -q
Удалите коллекцию Firestore, выбрав пункт «Удалить коллекцию» в списке коллекций:

В качестве альтернативы вы можете удалить весь проект:
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
15. Поздравляем!
Поздравляем! Вы успешно внедрили первый ключевой сервис проекта!
Что мы рассмотрели
- Облачное хранилище
- Облачные функции
- API Cloud Vision
- Облачный Firestore