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 :

Выберите одну из сред выполнения Node.js:

Исходный код состоит из JavaScript-файла index.js и файла package.json , содержащего различные метаданные и зависимости.
Оставьте фрагмент кода по умолчанию: он выводит в консоль имя файла загруженного изображения:

Пока что, для целей тестирования, оставьте имя выполняемой функции как helloGCS .
Нажмите кнопку 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 .
Сначала отредактируйте файл package.json , в котором перечислены зависимости нашей функции Node.js. Обновите код, добавив зависимость Cloud Vision API NPM:
{
"name": "picture-analysis-function",
"version": "0.0.1",
"dependencies": {
"@google-cloud/storage": "^1.6.0",
"@google-cloud/vision": "^1.8.0",
"@google-cloud/firestore": "^3.4.1"
}
}
Теперь, когда зависимости обновлены, вы займетесь кодом нашей функции, обновив файл index.js .
Замените код в index.js на приведенный ниже код. Это будет объяснено на следующем шаге.
const vision = require('@google-cloud/vision');
const Storage = require('@google-cloud/storage');
const Firestore = require('@google-cloud/firestore');
const client = new vision.ImageAnnotatorClient();
exports.vision_analysis = async (event, context) => {
console.log(`Event: ${JSON.stringify(event)}`);
const filename = event.name;
const filebucket = event.bucket;
console.log(`New picture uploaded ${filename} in ${filebucket}`);
const request = {
image: { source: { imageUri: `gs://${filebucket}/${filename}` } },
features: [
{ type: 'LABEL_DETECTION' },
{ type: 'IMAGE_PROPERTIES' },
{ type: 'SAFE_SEARCH_DETECTION' }
]
};
// invoking the Vision API
const [response] = await client.annotateImage(request);
console.log(`Raw vision output for: ${filename}: ${JSON.stringify(response)}`);
if (response.error === null) {
// listing the labels found in the picture
const labels = response.labelAnnotations
.sort((ann1, ann2) => ann2.score - ann1.score)
.map(ann => ann.description)
console.log(`Labels: ${labels.join(', ')}`);
// retrieving the dominant color of the picture
const color = response.imagePropertiesAnnotation.dominantColors.colors
.sort((c1, c2) => c2.score - c1.score)[0].color;
const colorHex = decColorToHex(color.red, color.green, color.blue);
console.log(`Colors: ${colorHex}`);
// determining if the picture is safe to show
const safeSearch = response.safeSearchAnnotation;
const isSafe = ["adult", "spoof", "medical", "violence", "racy"].every(k =>
!['LIKELY', 'VERY_LIKELY'].includes(safeSearch[k]));
console.log(`Safe? ${isSafe}`);
// if the picture is safe to display, store it in Firestore
if (isSafe) {
const pictureStore = new Firestore().collection('pictures');
const doc = pictureStore.doc(filename);
await doc.set({
labels: labels,
color: colorHex,
created: Firestore.Timestamp.now()
}, {merge: true});
console.log("Stored metadata in Firestore");
}
} else {
throw new Error(`Vision API error: code ${response.error.code}, message: "${response.error.message}"`);
}
};
function decColorToHex(r, g, b) {
return '#' + Number(r).toString(16).padStart(2, '0') +
Number(g).toString(16).padStart(2, '0') +
Number(b).toString(16).padStart(2, '0');
}
11. Изучите функцию
Давайте подробнее рассмотрим различные интересные моменты.
Во-первых, нам требуются следующие модули: Vision, Storage и Firestore:
const vision = require('@google-cloud/vision');
const Storage = require('@google-cloud/storage');
const Firestore = require('@google-cloud/firestore');
Затем мы подготавливаем клиент для Vision API:
const client = new vision.ImageAnnotatorClient();
Теперь перейдём к структуре нашей функции. Мы делаем её асинхронной, поскольку используем возможности async/await, представленные в Node.js 8:
exports.vision_analysis = async (event, context) => {
...
const filename = event.name;
const filebucket = event.bucket;
...
}
Обратите внимание на подпись, а также на то, как мы получаем имя файла и сегмента, которые запустили облачную функцию.
Для справки, вот как выглядит полезная нагрузка события:
{
"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:
const request = {
image: { source: { imageUri: `gs://${filebucket}/${filename}` } },
features: [
{ type: 'LABEL_DETECTION' },
{ type: 'IMAGE_PROPERTIES' },
{ type: 'SAFE_SEARCH_DETECTION' }
]
};
Мы запрашиваем 3 ключевые возможности Vision API:
- Распознавание меток : чтобы понять, что изображено на этих картинках.
- Свойства изображения : для придания изображению интересных характеристик (нас интересует преобладающий цвет).
- Безопасный поиск : чтобы узнать, безопасно ли показывать изображение (оно не должно содержать материалы для взрослых / медицинского характера / откровенные / жестокие).
На этом этапе мы можем обратиться к API Vision:
const [response] = await client.annotateImage(request);
Для справки, вот как выглядит ответ от 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:
if (response.error === null) {
...
} else {
throw new Error(`Vision API error: code ${response.error.code},
message: "${response.error.message}"`);
}
Мы собираемся получить обозначения предметов, категорий или тем, распознанных на картинке:
const labels = response.labelAnnotations
.sort((ann1, ann2) => ann2.score - ann1.score)
.map(ann => ann.description)
Мы сортируем метки, начиная с наивысшего балла.
Нас интересует, какой цвет преобладает на картинке:
const color = response.imagePropertiesAnnotation.dominantColors.colors
.sort((c1, c2) => c2.score - c1.score)[0].color;
const colorHex = decColorToHex(color.red, color.green, color.blue);
Мы снова сортируем цвета по баллам и берём первый.
Мы также используем вспомогательную функцию для преобразования значений красного, зеленого и синего цветов в шестнадцатеричный цветовой код, который можно использовать в таблицах стилей CSS.
Давайте проверим, можно ли показывать это изображение:
const safeSearch = response.safeSearchAnnotation;
const isSafe = ["adult", "spoof", "medical", "violence", "racy"]
.every(k => !['LIKELY', 'VERY_LIKELY'].includes(safeSearch[k]));
Мы проверяем наличие атрибутов "для взрослых" / "пародия" / "медицинский" / "насилие" / "пикантность", чтобы определить, насколько они вероятны или, наоборот, очень вероятны .
Если результат безопасного поиска приемлемый, мы можем сохранить метаданные в Firestore:
if (isSafe) {
const pictureStore = new Firestore().collection('pictures');
const doc = pictureStore.doc(filename);
await doc.set({
labels: labels,
color: colorHex,
created: Firestore.Timestamp.now()
}, {merge: true});
}
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