CVAT serverless functions with nuclio
Введение
Для многих задач компьютерного зрения уже есть решения в виде обученных на большом количестве данных DL-моделей. Поэтому вместо разметки данных с нуля логичным кажется использование этих моделей для предварительной разметки данных (особенно в том случае, когда на каждом изображении больше 10 объектов).
В этой статье мы познакомимся с более продвинутыми функциями CVAT (с его помощью можно предразметить данные в самом инструменте) и с возможностью добавления практически любой модели для авто-разметки.
Мы не будем здесь подробно рассказывать о том, как поднять CVAT. Поэтому оставляем вам небольшое напоминание о предыдущих статьях по этой теме:
- как разворачивать CVAT локально и получать предразмеченный датасет через fiftyone;
- как получать данные сразу в виде pytorch dataset через CVAT SDK pytorch.
Serverless и nuclio
В CVAT интегрированы модели для автоматической разметки. Их можно запускать на cpu или gpu для быстрого инференса. Но для простой и беспроблемной работы (например, настройки общения между микросервисами или контролирования ресурсов) и универсального процесса интеграции в CVAT были интегрированы также serverless функции.
Serverless — модель облачных вычислений, в которой разработчики могут создавать и запускать приложения без необходимости управления инфраструктурой серверов. То есть это не значит что сервера нет, просто все базовые проблемы поддержки такого сервиса уже решены.
Nuclio — это как раз такая serverless платформа с открытым исходным кодом. Она позволяет разработчикам легко создавать и развертывать serverless функции.
Архитектура Nuclio работает на основе модели сервера функций (Function Server), где каждая функция обрабатывает входные события. Когда событие поступает, оно отправляется на HTTP-эндпоинт Nuclio API Gateway, который автоматически масштабируется и балансирует нагрузку между экземплярами Function Server.
Function Server запускается в контейнере Docker и обрабатывает событие, используя заданную функцию. Функция может быть написана на любом языке программирования и иметь зависимости, которые устанавливаются автоматически.
После обработки события Function Server возвращает ответ обратно в Nuclio API Gateway, который, в свою очередь, отправляет его вызывающей стороне.
Таким образом, Nuclio обеспечивает масштабируемую платформу для создания, развертывания и управления функциями, которые могут обрабатывать события в режиме реального времени.
Установка и настройка
Перед использованием возможностей по предразметке в CVAT нужно развернуть сервис (в нашей предыдущей статье есть инструкция).
Дисклеймер: этот туториал тестировался на Ubuntu 20.04.6 LTS
Проверяем работу сервисов. Все они должны быть подняты. Так можно понять, почему не поднимается сервис — везде должны быть ... working
docker exec -t cvat_server python [manage.py](<http://manage.py/>) health_check
Если нужно отладить некоторые проблемы, можно воспользоваться следующей командой:
docker logs cvat_ops
Вывод команды выше — вариант с ошибкой, из-за которой CVAT не запустится.
2023-05-02 18:42:20.212142: E tensorflow/stream_executor/cuda/cuda_blas.cc:2981] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered
ERROR:health-check:warning: f13443281b95 90.0% disk usage exceeds 90%
Traceback (most recent call last):
File "/opt/venv/lib/python3.8/site-packages/health_check/backends.py", line 30, in run_check
self.check_status()
File "/opt/venv/lib/python3.8/site-packages/health_check/contrib/psutil/backends.py", line 21, in check_status
raise ServiceWarning(
health_check.exceptions.ServiceWarning: warning: f13443281b95 90.0% disk usage exceeds 90%
Cache backend: default ... working
Cache backend: media ... working
DatabaseBackend ... working
DiskUsage ... warning: f13443281b95 90.0% disk usage exceeds 90%
MemoryUsage ... working
MigrationsHealthCheck ... working
OPAHealthCheck ... working
- Вывод команды выше — вариант с ошибкой, из-за которой CVAT не запустится.
90.0% disk usage exceeds 90%
А сейчас мы рассмотрим последовательность шагов для добавления моделей в CVAT.
Но перед этим уточним: есть и другой способ поднятия nuclio с помощью второго docker-compose. Еще есть helm-charts, упрощающие развертывание CVAT в Kubernetes-кластере с использованием Helm.
Перед установкой nuclio необходимо остановить приложение CVAT (если оно запущено), находясь в root директории cvat:
docker-compose down
Скачиваем nuclio для его установки. В документации CVAT пишут о нужной нам версии — версии 1.8.14:
wget <https://github.com/nuclio/nuclio/releases/download/1.8.14/nuctl-1.8.14-linux-amd64>
Затем мы даем права на исполнение файла и команды nuctl
из любой директории на вашей машине:
sudo chmod +x nuctl-1.8.14-linux-amd64
sudo ln -sf $(pwd)/nuctl-1.8.14-linux-amd64 /usr/local/bin/nuctl
Далее создаем проект nuclio (дополнительные контейнеры к компоузу):
nuctl create project cvat
На порту your-ip-adress:8070 будет сервис nuclio и поднятые модели для их мониторинга:
Добавление кастомных моделей в CVAT
Интегрированные модели используются на разных фреймворках (openvino, pytorch, tensorflow) и для различных задач. Список моделей и задач можно посмотреть тут . Но там указаны далеко не все модели. На самом деле их еще больше (text detection, SAM, person-reidentification…). Все модели можно посмотреть в репозитории.
CVAT предоставляет возможность добавить свою модель помимо моделей из списка. Для этого нужно написать хендлер main.py
и файл fuctional.yaml
с инструкциями для nuclio и списком лейблов модели.
Давайте разберем, зачем нужны эти файлы и как сделать функции для своих моделей.
fuctional.yaml
— это конфиг функции, который будет выполнен при получении события (event). Этот файл определяет логику функции и зависимости, необходимые для ее работы. Он позволяет легко создавать функции и управлять ими в Nuclio. Это упрощает процесс разработки и развертывания пользовательских функций в CVAT.
В spec
указан handler
, где main — файл с функциями для обработки событий. Ниже мы разберем этот файл.
Также можно заметить лейблы и инструкцию по созданию контейнера для DL-модели.
Более простые примеры можно посмотреть тут.
Пример fuctional.yaml для Faster RCNN
metadata:
name: openvino-omz-public-faster-rcnn-inception-v2-coco
namespace: cvat
annotations:
name: Faster RCNN
type: detector
framework: openvino
spec: |
[
{ "id": 1, "name": "person" },
{ "id": 2, "name": "bicycle" },
{ "id": 3, "name": "car" },
...
{ "id":89, "name": "hair_drier" },
{ "id":90, "name": "toothbrush" }
]
spec:
description: Faster RCNN inception v2 COCO via Intel OpenVINO toolkit
runtime: 'python:3.6'
handler: main:handler
eventTimeout: 30s
env:
- name: NUCLIO_PYTHON_EXE_PATH
value: /opt/nuclio/common/openvino/python3
volumes:
- volume:
name: openvino-common
configMap:
name: "cvat-nuclio-openvino-common"
defaultMode: 0750
volumeMount:
name: openvino-common
mountPath: /opt/nuclio/common/openvino
build:
image: cvat.openvino.omz.public.faster_rcnn_inception_v2_coco
baseImage: openvino/ubuntu18_dev:2020.2
directives:
preCopy:
- kind: USER
value: root
- kind: WORKDIR
value: /opt/nuclio
- kind: RUN
value: ln -s /usr/bin/pip3 /usr/bin/pip
- kind: RUN
value: /opt/intel/openvino/deployment_tools/open_model_zoo/tools/downloader/downloader.py --name faster_rcnn_inception_v2_coco -o /opt/nuclio/open_model_zoo
- kind: RUN
value: /opt/intel/openvino/deployment_tools/open_model_zoo/tools/downloader/converter.py --name faster_rcnn_inception_v2_coco --precisions FP32 -d /opt/nuclio/open_model_zoo -o /opt/nuclio/open_model_zoo
triggers:
myHttpTrigger:
maxWorkers: 2
kind: 'http'
workerAvailabilityTimeoutMilliseconds: 10000
attributes:
maxRequestBodySize: 33554432 # 32MB
platform:
attributes:
restartPolicy:
name: always
maximumRetryCount: 3
mountMode: volume
Хендлер (Handler), или обработчик событий — это функция / блок кода, который выполняется в ответ на определенное событие (например, на щелчок мыши, отправку формы или нажатие клавиши). Обработчики событий обычно привязываются к соответствующим элементам интерфейса или объектам и реагируют на возникающие события. В нашем случае после нажатия кнопки в интерфейсе CVAT хендлер возвращает обернутый в context nuclio json с результатами работы модели и кодом 200.
context
— объект, предоставляемый Nuclio для выполнения функции в среде сервера. Он дает доступ к различным функциональным возможностям и ресурсам.
Пример хендлера model_handler.py , в котором загружается модель Faster RCNN, и подготавливается ее результат инференса к нужному формату. По сути это интерфейс к нашей модели.
# Copyright (C) 2020-2022 Intel Corporation
#
# SPDX-License-Identifier: MIT
import os
from model_loader import ModelLoader
class ModelHandler:
def __init__(self, labels):
base_dir = os.path.abspath(os.environ.get("MODEL_PATH",
"/opt/nuclio/open_model_zoo/public/faster_rcnn_inception_v2_coco/FP32"))
model_xml = os.path.join(base_dir, "faster_rcnn_inception_v2_coco.xml")
model_bin = os.path.join(base_dir, "faster_rcnn_inception_v2_coco.bin")
self.model = ModelLoader(model_xml, model_bin)
self.labels = labels
def infer(self, image, threshold):
output_layer = self.model.infer(image)
results = []
prediction = output_layer[0][0]
for obj in prediction:
obj_class = int(obj[1])
obj_value = obj[2]
obj_label = self.labels.get(obj_class, "unknown")
if obj_value >= threshold:
xtl = obj[3] * image.width
ytl = obj[4] * image.height
xbr = obj[5] * image.width
ybr = obj[6] * image.height
results.append({
"confidence": str(obj_value),
"label": obj_label,
"points": [xtl, ytl, xbr, ybr],
"type": "rectangle",
})
return results
main.py
— файл с двумя обязательными функциями: init_context(context)
и handler(context, event)
. Он также точка входа (мы это указывали выше в файле function.yaml
).
Теперь посмотрим на функцию init_context
в файле main.pу. Самое важное в ней — присваивание инициализированной модели в контекст для дальнейшего использования в функции handler
: context.user_data.model = model
В функции handler мы обрабатываем событие event
, в котором получаем закодированную картинку. После инференса модели мы должны вернуть результат, обернутый в context.Response
.
В него подаются следующие аргументы:
- body — тело ответа в виде строки или байтового массива (в нашем случае это json);
- headers — словарь с заголовками ответа, представленными в виде пар «ключ-значение”;
- content_type — MIME-тип ответа («application/json» или «text/html»), часто используемый для указания формата ответа;
- status_code — код статуса ответа (например, 200, 404, 500 и т. д.).
Пример файла c хендлером для модели Faster RCNN:
import json
import base64
from PIL import Image
import io
from model_handler import ModelHandler
import yaml
def init_context(context):
context.logger.info("Init context... 0%")
# Read labels
with open("/opt/nuclio/function.yaml", 'rb') as function_file:
functionconfig = yaml.safe_load(function_file)
labels_spec = functionconfig['metadata']['annotations']['spec']
labels = {item['id']: item['name'] for item in json.loads(labels_spec)}
# Read the DL model
model = ModelHandler(labels)
context.user_data.model = model
context.logger.info("Init context...100%")
def handler(context, event):
context.logger.info("Run faster_rcnn_inception_v2_coco model")
data = event.body
buf = io.BytesIO(base64.b64decode(data["image"]))
threshold = float(data.get("threshold", 0.5))
image = Image.open(buf)
results = context.user_data.model.infer(image, threshold)
return context.Response(body=json.dumps(results), headers={},
content_type='application/json', status_code=200)
Добавление уже интегрированных моделей
Существует несколько основных категорий моделей:
- Interactors — все, что помогает ускорить выделение объектов при разметке;
- Detectors — здесь также модели для сегментации и распознавания лиц;
- Trackers — трекинг объектов;
- OpenCV — трекинг и выравнивание гистограммы.
Хотя модели и разбиты на такие категории в документации, но ограничений на вид модели или решаемую задачу нет.
Перейдем к интеграции модели SAM из категории Interactors. Для нее мы не будем писать хендлеры, так как они недавно были уже написаны разработчиками CVAT. Если есть желание разобраться с тем, как работает эта модель — вот разбор SAM.
Каждая модель — отдельный docker контейнер. В нашем случае контейнер с SAM весит 10GB, поэтому важно разумно подойти к количеству запущенных одновременно моделей.
cd serverless && ./deploy_gpu.sh pytorch/facebookresearch/sam/nuclio/
Вывод после успешной интеграции модели
23.05.02 21:22:50.074 nuctl (I) Project created {"Name": "cvat", "Namespace": "nuclio"}
Deploying pytorch/facebookresearch/sam function...
23.05.02 21:22:50.574 nuctl (I) Deploying function {"name": ""}
23.05.02 21:22:50.575 nuctl (I) Building {"builderKind": "docker", "versionInfo": "Label: 1.8.14, Git commit: cbb0774230996a3eb4621c1a2079e2317578005b, OS: linux, Arch: amd64, Go version: go1.17.8", "name": ""}
23.05.02 21:22:51.016 nuctl (I) Staging files and preparing base images
23.05.02 21:22:51.018 nuctl (I) Building processor image {"registryURL": "", "imageName": "cvat.pth.facebookresearch.sam.vit_h:latest"}
23.05.02 21:22:51.018 nuctl.platform.docker (I) Pulling image {"imageName": "quay.io/nuclio/handler-builder-python-onbuild:1.8.14-amd64"}
23.05.02 21:22:54.923 nuctl.platform.docker (I) Pulling image {"imageName": "quay.io/nuclio/uhttpc:0.0.1-amd64"}
23.05.02 21:22:59.881 nuctl.platform (I) Building docker image {"image": "cvat.pth.facebookresearch.sam.vit_h:latest"}
23.05.02 21:33:37.781 nuctl.platform (I) Pushing docker image into registry {"image": "cvat.pth.facebookresearch.sam.vit_h:latest", "registry": ""}
23.05.02 21:33:37.781 nuctl.platform (I) Docker image was successfully built and pushed into docker registry {"image": "cvat.pth.facebookresearch.sam.vit_h:latest"}
23.05.02 21:33:37.782 nuctl (I) Build complete {"result": {"Image":"cvat.pth.facebookresearch.sam.vit_h:latest","UpdatedFunctionConfig":{"metadata":{"name":"pth.facebookresearch.sam.vit_h","namespace":"nuclio","labels":{"nuclio.io/project-name":"cvat"},"annotations":{"animated_gif":"https://raw.githubusercontent.com/opencv/cvat/develop/site/content/en/images/hrnet_example.gif","framework":"pytorch","help_message":"The interactor allows to get a mask of an object using at least one positive, and any negative points inside it","min_neg_points":"0","min_pos_points":"1","name":"Segment Anything","spec":"","type":"interactor","version":"2"}},"spec":{"description":"Interactive object segmentation with Segment-Anything","handler":"main:handler","runtime":"python:3.8","env":[{"name":"PYTHONPATH","value":"/opt/nuclio/sam"}],"resources":{"limits":{"nvidia.com/gpu":"1"},"requests":{"cpu":"25m","memory":"1Mi"}},"image":"cvat.pth.facebookresearch.sam.vit_h:latest","targetCPU":75,"triggers":{"myHttpTrigger":{"class":"","kind":"http","name":"myHttpTrigger","maxWorkers":1,"workerAvailabilityTimeoutMilliseconds":10000,"attributes":{"maxRequestBodySize":33554432}}},"volumes":[{"volume":{"name":"volume-1","hostPath":{"path":"/home/i.bakalec/label_tools/cvat/serverless/common"}},"volumeMount":{"name":"volume-1","mountPath":"/opt/nuclio/common"}}],"build":{"functionConfigPath":"pytorch/facebookresearch/sam/nuclio//function-gpu.yaml","image":"cvat.pth.facebookresearch.sam.vit_h","baseImage":"ubuntu:22.04","directives":{"preCopy":[{"kind":"ENV","value":"DEBIAN_FRONTEND=noninteractive"},{"kind":"WORKDIR","value":"/opt/nuclio/sam"},{"kind":"RUN","value":"apt-get update && apt-get -y install curl git python3 python3-pip ffmpeg libsm6 libxext6"},{"kind":"RUN","value":"pip3 install torch torchvision torchaudio opencv-python pycocotools matplotlib onnxruntime onnx"},{"kind":"RUN","value":"pip3 install git+https://github.com/facebookresearch/segment-anything.git"},{"kind":"RUN","value":"curl -O https://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth"},{"kind":"RUN","value":"ln -s /usr/bin/pip3 /usr/local/bin/pip && ln -s /usr/bin/python3 /usr/bin/python"}]},"codeEntryType":"image"},"platform":{"attributes":{"mountMode":"volume","restartPolicy":{"maximumRetryCount":3,"name":"always"}}},"readinessTimeoutSeconds":120,"securityContext":{},"eventTimeout":"30s"}}}}
23.05.02 21:33:37.796 nuctl (I) Cleaning up before deployment {"functionName": "pth.facebookresearch.sam.vit_h"}
23.05.02 21:33:40.006 nuctl.platform (I) Waiting for function to be ready {"timeout": 120}
23.05.02 21:33:41.475 nuctl (I) Function deploy complete {"functionName": "pth.facebookresearch.sam.vit_h", "httpPort": 49156, "internalInvocationURLs": ["172.16.1.4:8080"], "externalInvocationURLs": []}
NAMESPACE | NAME | PROJECT | STATE | REPLICAS | NODE PORT
nuclio | onnx-yolov7 | cvat | unhealthy | 1/1 | 49155
nuclio | openvino-dextr | cvat | unhealthy | 1/1 | 49153
nuclio | pth-foolwood-siammask | cvat | unhealthy | 1/1 | 49154
nuclio | pth.facebookresearch.sam.vit_h | cvat | ready | 1/1 | 49156
nuclio | tf-matterport-mask-rcnn | cvat | error | 1/1 |
После интеграции модели нужно поднять сервис CVAT. Команда несколько усложнилась. Аргумент -- build
необходимо прописать только в первый раз.
docker compose -f docker-compose.yml -f docker-compose.dev.yml -f up -d --build
Если надо остановить сервис CVAT
docker compose -f docker-compose.yml down
После всех выполненных выше действий должен запуститься CVAT, в котором появится вкладка Models. Там можно посмотреть все интегрированные вами модели. Каждую модель нужно добавлять отдельно.
Применение добавленных моделей на практике
Создадим новое задание для проверки работы добавленной модели SAM. Сначала мы переходим во вкладку Tasks. Затем создаем пробное задание для сегментации фруктов и ягод.
В CVAT есть возможность загрузки данных несколькими способами:
- My computer — загрузка данных с вашего компьютера;
- Connected file share — загрузка с примонтированного диска (надо указывать в docker-compose.yml);
- Remote sources — загрузка через ссылки на изображения из интернета;
- Cloud Storage — загрузка из вашего cloud’a.
Для создания задания и удобства воспроизведения туториала воспользуемся следующим:
данными (ссылками) для создания такого же задания;
https://images6.alphacoders.com/368/thumb-1920-368872.jpg
https://wallpaperaccess.com/full/4250118.jpg
https://e0.pxfuel.com/wallpapers/333/842/desktop-wallpaper-berries-blue-summer-skin-red-texture-fruit-berry-vara.jpg
https://e0.pxfuel.com/wallpapers/333/842/desktop-wallpaper-berries-blue-summer-skin-red-texture-fruit-berry-vara.jpg
https://wallpaperaccess.com/full/6360192.jpg
https://wallpapers.com/images/hd/purple-and-red-loganberries-ese84r1n8bv5ws99.jpg
лейблами (во вкладку Raw);
[
{
"name": "banana",
"color": "#33ddff",
"type": "any",
"attributes": []
},
{
"name": "bluebarry",
"type": "any",
"attributes": []
},
{
"name": "apple",
"color": "#34d1b7",
"type": "any",
"attributes": []
},
{
"name": "pineapple",
"color": "#ff007c",
"type": "any",
"attributes": []
},
{
"name": "other",
"type": "any",
"attributes": []
},
{
"name": "background",
"color": "#5757ff",
"type": "any",
"attributes": []
},
{
"name": "strawbarry",
"color": "#ff6037",
"type": "any",
"attributes": []
},
{
"name": "blackbarry",
"color": "#24b353",
"type": "any",
"attributes": []
}
]
скрином создания задания с данными из вкладок выше.
А теперь посмотрим, что у нас в результате получилось:
Итоги
Сервис CVAT и nuclio могут помочь интегрировать практически любые модели для получения качественной и быстрой разметки. Причем вид фреймворка и железа не важен. Частая практика — интеграция DL-модели с прода. Это позволяет получать качественную разметку с минимальными трудозатратами.
Итак, в нашей статье мы разобрали:
- Что такое serverless functions и nuclio;
- Как развернуть CVAT с возможностью интегрирования моделей;
- Что подразумевается под термином «хендлеры» и как их писать для добавления кастомных моделей на примере Faster RCNN;
- Как добавить модель SAM для разметки и опробовать ее на деле.
Дополнительно
Если использовать сервис CVAT на https://www.cvat.ai/, а не разворачивать его локально, можно добавить почти любую модель HuggingFace и RoboFlow. В два клика такое решение локально не получить, так как это платная фича. Но локально можно получить тоже самое, если писать свои хендлеры. После нашего разбора эта задача не очень сложная 🙂
Ссылки
Демо и описание модели SAM:
Официальная документация и туториал по установке моделей в CVAT:
https://opencv.github.io/cvat/docs/administration/advanced/installation_automatic_annotation
Добавление моделей Roboflow и HuggingFace для предразметки в CVAT:
https://www.cvat.ai/post/annotation-with-roboflow
https://www.cvat.ai/post/integrating-hugging-face-and-roboflow-models