Давайте узнаем, сможем ли мы создать простое приложение на Python 3.6, которое:
- Конвертирует обычную фотографию в черно белую фотографию;
- Использует OpenCV.
Доступно через API, которое
- Принимает бинарные данные (jpeg)
- Возвращает бинарные данные (jpeg)
Работает без
- без готового сервера
- без оплаты (за исключением случаев, где ваш трафик превысит бесплатный пакет Lambda)
Для начала, установим Docker.
Для этого примера мы используем сервис от AWS под названием Lambda, который позволит вам развернуть вашу функцию и ее зависимости, а также легко подключить ее к API. Чтобы создать API, мы воспользуемся API Gateway — еще один сервис, предоставляемый AWS.
Есть вопросы по Python?
На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!
Паблик VK
Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!
Для простоты данного руководства, мы развернем наш код, загрузив его в Lambda через веб консоль AWS. Мы также напишем код нашей функции внутри консоли AWS, чтобы сохранять процесс максимально простым. В любом другом случае вам нужно выполнять развертывание через AWS CLI.
1. Начнем с входа в консоль AWS и поиск Lambda.
2. Нажимаем на Create function (создать функцию).
3. Настраиваем параметры функции.
Мы назовем нашу функцию lambda-demo
. Нужно убедиться, что мы работаем с Python 3.6 в качестве среды выполнения и создайте новую роль из шаблонов политики AWS.
4. После создания функции, вам дадут определенный шаблонный код в консоли Lambda.
1 2 3 4 5 6 7 |
import json def lambda_handler(event, context): return { 'statusCode': 200, 'body': json.dumps('Hello from Lambda!') } |
Вы можете сразу вызвать эту функцию, настроив тестовое событие. Нажмите на Test и настройте первое тестовое событие. Для целей данной статьи, шаблон по умолчанию работает как надо.
После создания тестового события, нажмите Test. Вы должны получить следующее в логах функции:
1 2 3 4 |
{ "statusCode": 200, "body": "\"Hello from Lambda!\"" } |
Отлично.
Теперь создадим что-нибудь более полезное.
Построим функцию, которая получает изображение и делает её черно-белым. Для этого мы воспользуемся OpenCV. Кроме этого, использование OpenCV может быть идеальным для такой задачи, она показывает, как такая полезная библиотека может быть добавлена в вашу среду Lambda с относительной легкостью.
Сейчас мы сделаем следующее:
- Генерируем заточенный под Python пакет Lambda для OpenCV.
- Загрузим пакет в Lambda Layers, так что он может быть использован в любой функции, которую вы создаете.
- Импортируем OpenCV в нашу функцию Lambda.
Генерация заточенного под Python пакет Lambda для OpenCV
Я собрал очень простой инструмент — образ Docker, который может получить любой пакет pip и генерировать .ZIP
, который мы можем загружать в Lambda Layers. Если вы хотите изучить этот инструмент, вы можете найти его в LambdaZipper.
Если у вас есть установленный Docker, вы можете открыть терминал и запустить следующее:
1 |
docker run --rm -v $(pwd):/package tiivik/lambdazipper opencv-python |
Это все! В вашей текущей рабочей папке вы можете найти opencv-python.zip
Один из самых полезных безсерверных наборов инструментов — это serverless. Однако мы не будем использовать его в данном примере. Изобретение велосипеда далеко не всегда хорошая идея, за исключением тех случаев, когда вы хотите изучить, как и что устроено внутри. Несмотря на то, что такие продвинутые фреймворки как serverless существуют, хорошей идеей будет найти ряд корневых функций, которые эти фреймворки абстрагируют.
Давайте узнаем, что наш инструмент абстрагирует от нас.
Если вы взглянете на package.sh, то вы увидите, что он выполнил команду установки pip install
с аргументом opencv-python
. Все это было выполнено в среде amazonlinux:2017.03
, которая в некоторой степени имитирует среду AWS Lambda. Вы можете изучить среду выполнения в Dockerfile.
Загрузка пакета в Lambda Layers для использования в любой из созданых функций
Давайте загрузим opencv-python.zip
в Lambda Layers, чтобы мы могли использовать этот пакет во всех наших функциях. Относитесь к Layers как к данным, которые могут быть использованы в любой из написанных вами функций. Это могут быть модули Python, части кодов, бинарные файлы, и т.д.
Перейдите в панель Layers в AWS Lambda и нажмите создать слой (Create layer).
Укажите название слоя, его описание и загрузите zip файл. Убедитесь в том, что выбрали правильную среду выполнения (в нашем случае это Python 3.6). Нажмите создать слой (Create layer).
На момент написания этой статьи, загрузка zip файла из веб интерфейса ограничено 50МВ. К счастью, наш пакет opencv-python
весит меньше. В случае, если ваш пакет весит больше, вы можете загрузить пакет в качестве ссылки из корзины S3
. Обратите внимание на то, что Lambda указывает ограничение развертывание пакета в 250MB.
После создания функции, вас должно приветствовать уведомление:
1 |
Successfully created layer opencv-python version 1. |
Отлично!
Давайте вернемся к нашей lambda-demo
функции Lambda и добавим слой opencv-python
в среду выполнения нашей функции. Нажмите Layers > Add a layer
, и выберите ваш слой opencv-python
.
Импорт OpenCV
Давайте попробуем импортировать библиотеку стандартным образом:
1 2 3 4 5 6 7 8 9 |
import json import cv2 def lambda_handler(event, context): return { 'statusCode': 200, 'body': json.dumps('Hello from Lambda!') } |
После нажатия на Test, мы получим ответ:
1 2 3 |
{ "errorMessage": "Unable to import module 'lambda_function'" } |
По какой-то причине, Lambda на смогла найти наш пакет…
По умолчанию, все слои Lambda монтируются в /opt
. Давайте уберём наш импорт модуля cv2
и взглянем на то, что внутри /opt
.
1 2 3 4 5 6 7 8 9 10 11 |
import json #import cv2 from os import listdir def lambda_handler(event, context): print(listdir("/opt")) return { 'statusCode': 200, 'body': json.dumps('Hello from Lambda!') } |
В журналах функции мы можем увидеть наш модуль cv2
и numpy в /opt
.
1 |
[‘bin’, ‘cv2’, ‘numpy’, ‘numpy-1.16.2.dist-info’, ‘opencv_python-4.0.0.21.dist-info’] |
По умолчанию, /opt/bin
внесен в переменную среды $PATH
. Вы можете убедиться в этом в документации AWS. Однако наши модули слоя находятся в /opt/
, а не в /opt/bin
. Итак, давайте внесем /opt
в $PATH
, чтобы Lambda могла видеть наш пакет.
В разделе Environment Variables, внесите следующую переменную среды.
- Ключ:
PYTHONPATH
- значение:
/opt/
Как правило, вы можете просто импортировать пакет без изменения пути, но в нашем случае, это нужно для среды Lambda для обнаружения нашего пакета.
Давайте обновим наш код:
1 2 3 4 5 6 7 8 9 |
import json import cv2 def lambda_handler(event, context): print(cv2.__version__) return { 'statusCode': 200, 'body': json.dumps('Hello from Lambda!') } |
Сохраните изменения и нажмите Test. В консоли нас поприветствует 4.0.0
, информируя о том, какая версия OpenCV используется.
Великолепно, OpenCV запустился в Lambda!
Давайте продолжим реализацию корневой логики приложения — конвертация изображений в черно-белый. Давайте обновим код функции Lambda.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
import json import cv2 import base64 def write_to_file(save_path, data): with open(save_path, "wb") as f: f.write(base64.b64decode(data)) def lambda_handler(event, context): # Записываем содержимое в файл write_to_file("/tmp/photo.jpg", event["body"]) # Чтение изображения image = cv2.imread("/tmp/photo.jpg") # Конвертация в черно белый gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # Запись чернобелого изображения в /tmp cv2.imwrite("/tmp/gray.jpg", gray) # Конвертация черно белого изображения в кодировку utf-8 base64 with open("/tmp/gray.jpg", "rb") as imageFile: str = base64.b64encode(imageFile.read()) encoded_img = str.decode("utf-8") # Возвращаем данные в API Gateway в base64. # API Gateway будет обрабатывать конверсию обратно в бинарный вид # Настройка типа содержимого в заголовке как image/jpeg. return { "isBase64Encoded": True, "statusCode": 200, "headers": { "content-type": "image/jpeg"}, "body": encoded_img } |
API, который мы настроим буквально сейчас, будет принимать бинарное изображение от клиента. Бинарное изображение затем будет конвертировано в base64 через AWS API Gateway и передано в Lambda.
Разумеется, API Gateway еще не настроен, так что тестирование этого кода с нашим нынешним тестом приведет к неудаче. Однако, перед тем как мы перейдем к настройке API, который вызывает нашу Lambda, мы можем протестировать API в консоли Lambda, предоставив base64 кодированное изображение в теле событий.
Перенастраиваем Test с этим содержимым. Если вам интересно, это конвертированное в base64 изображение кошки 😺 https://imgur.com/a/0NpkzzL .
1 2 3 |
{ "body" : "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AAAAAAtjcm9wUmVjdFRvcGxvbmcAAAAAADhCSU0D7QAAAAAAEACQAAAAAQACAJAAAAABAAI4QklNBCYAAAAAAA4AAAAAAAAAAAAAP4AAADhCSU0EDQAAAAAABAAAAB44QklNBBkAAAAAAAQAAAAeOEJJTQPzAAAAAAAJAAAAAAAAAAABADhCSU0nEAAAAAAACgABAAAAAAAAAAI4QklNA/UAAAAAAEgAL2ZmAAEAbGZmAAYAAAAAAAEAL2ZmAAEAoZmaAAYAAAAAAAEAMgAAAAEAWgAAAAYAAAAAAAEANQAAAAEALQAAAAYAAAAAAAE4QklNA/gAAAAAAHAAAP////////////////////////////8D6AAAAAD/////////////////////////////A+gAAAAA/////////////////////////////wPoAAAAAP////////////////////////////8D6AAAOEJJTQQIAAAAAAAQAAAAAQAAAkAAAAJAAAAAADhCSU0EHgAAAAAABAAAAAA4QklNBBoAAAAAA3cAAAAGAAAAAAAAAAAAAABAAAAAQAAAACEAUwBjAHIAZQBlAG4AcwBoAG8AdAAgADIAMAAxADkALQAwADMALQAxADAAIABhAHQAIAAwADAALgAyADYALgAyADcAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAEAAAAAAABudWxsAAAAAgAAAAZib3VuZHNPYmpjAAAAAQAAAAAAAFJjdDEAAAAEAAAAAFRvcCBsb25nAAAAAAAAAABMZWZ0bG9uZwAAAAAAAAAAQnRvbWxvbmcAAABAAAAAAFJnaHRsb25nAAAAQAAAAAZzbGljZXNWbExzAAAAAU9iamMAAAABAAAAAAAFc2xpY2UAAAASAAAAB3NsaWNlSURsb25nAAAAAAAAAAdncm91cElEbG9uZwAAAAAAAAAGb3JpZ2luZW51bQAAAAxFU2xpY2VPcmlnaW4AAAANYXV0b0dlbmVyYXRlZAAAAABUeXBlZW51bQAAAApFU2xpY2VUeXBlAAAAAEltZyAAAAAGYm91bmRzT2JqYwAAAAEAAAAAAABSY3QxAAAABAAAAABUb3Ag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MCIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSIyOERCRTU2MDNFOTg1OTU0NjZEQjg5ODVFRTU2MEI0MCIgZGM6Zm9ybWF0PSJpbWFnZS9qcGVnIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiBwaG90b3Nob3A6SUNDUHJvZmlsZT0iZi5sdXggcHJvZmlsZSIgeG1wOkNyZWF0ZURhdGU9IjIwMTktMDMtMTBUMDA6MjY6MjkrMDI6MDAiIHhtcDpNb2RpZnlEYXRlPSIyMDE5LTAzLTEwVDAwOjI3OjMwKzAyOjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDE5LTAzLTEwVDAwOjI3OjMwKzAyOjAwIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MDE4MDExNzQwNzIwNjgxMTgyMkE5MEM0MkExQjY0QzAiIHN0RXZ0OndoZW49IjIwMTktMDMtMTBUMDA6Mjc6MzArMDI6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDUzYgKE1hY2ludG9zaCkiIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjAyODAxMTc0MDcyMDY4MTE4MjJBOTBDNDJBMUI2NEMwIiBzdEV2dDp3aGVuPSIyMDE5LTAzLTEwVDAwOjI3OjMwKzAyOjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ1M2IChNYWNpbnRvc2gpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDwvcmRmOlNlcT4gPC94bXBNTTpIaXN0b3J5PiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg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" } |
Вызов данного теста будет удачным:
1 2 3 4 5 6 7 8 9 |
Response: { "isBase64Encoded": true, "statusCode": 200, "headers": { "content-type": "image/jpeg" }, "body": "/9j/4AJRgAB.....P+WqHNf//Z" <- long base64 string of black and white image here } |
Теперь мы готовы к настройке API, которая вызывает данную функцию Lambda.
Настройка API
Открываем консоль AWS API Gateway. Нажимаем Create API.
Создаем новый REST API, даем API название и описание. В данном случае, мы назовем наш API lambda-demo
.
В Resources > Actions
выбираем Create Method
для определения метода POST
.
Для типа интеграции выбираем Lambda Function и выбираем нашу функцию Lambda из выпадающего меню. Включите интеграцию Use Lambda Proxy
и нажмите Save
.
Нам нужно, чтобы наш API мог обрабатывать бинарные данные.
В Settings > Binary Media Types
выбираем Add Binary Media Type
и определяем тип двоичных данных как:
1 2 3 |
image/jpeg image/png */* |
Нажимаем на Save Changes
.
Переходим обратно в метод POST
.
Под Method Response
добавляем Content-Type Response Header
и указываем тип image/jpeg
:
Перед публикацией API, вы можете протестировать его, нажав на кнопку Client Test
:
В нашем случае, мы предоставляем само тело изображения в base64, а не объект json. Для удобства, вы можете вставить необработанную строку base64 из следующей ссылки в поле Request Body
(ссылка).
Ответом будет строка base64 черно-белой фотографии.
Нажмите Actions > Deploy API
.
Создайте новый этап развертывания, дайте ему подходящее название, например development
и нажмите Deploy.
API теперь опубликован и прекрасно работает! Вы получите url
, в котором он развернут:
1 |
https://XXXXX.execute-api.XXXX.amazonaws.com/development |
Давайте опробуем!
1. Загрузим то же изображение в нашу локальную среду:
1 |
curl https://i.imgur.com/offvirS.jpg -o kitty.jpg |
2. Через Post-запрос отправим наше изображение в бинарном виде. Как результат, мы получим черно-белое изображение которая сохранилась с названием kitty_bw.jpg
.
1 |
curl -X POST --data-binary @kitty.jpg https://XXXXX.execute-api.eu-central-1.amazonaws.com/development -o kitty_bw.jpg |
Учитывайте, что для простоты данной статьи, мы не рассматривали обработку ошибок, проверки запроса и настройку авторизации. Для производственного приложения это должно быть настроено в API Gateway и в коде функции вашей Lambda.
Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.
E-mail: vasile.buldumac@ati.utm.md
Образование
Universitatea Tehnică a Moldovei (utm.md)
- 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»