Назад
475

CVAT serverless functions with nuclio

475

Введение

Для многих задач компьютерного зрения уже есть решения в виде обученных на большом количестве данных DL-моделей. Поэтому вместо разметки данных с нуля логичным кажется использование этих моделей для предварительной разметки данных (особенно в том случае, когда на каждом изображении больше 10 объектов).

В этой статье мы познакомимся с более продвинутыми функциями CVAT (с его помощью можно предразметить данные в самом инструменте) и с возможностью добавления практически любой модели для авто-разметки.

Мы не будем здесь подробно рассказывать о том, как поднять CVAT. Поэтому оставляем вам небольшое напоминание о предыдущих статьях по этой теме:

Serverless и nuclio

В CVAT интегрированы модели для автоматической разметки. Их можно запускать на cpu или gpu для быстрого инференса. Но для простой и беспроблемной работы (например, настройки общения между микросервисами или контролирования ресурсов) и универсального процесса интеграции в CVAT были интегрированы также serverless функции.

Serverless — модель облачных вычислений, в которой разработчики могут создавать и запускать приложения без необходимости управления инфраструктурой серверов. То есть это не значит что сервера нет, просто все базовые проблемы поддержки такого сервиса уже решены.

Nuclio — это как раз такая serverless платформа с открытым исходным кодом. Она позволяет разработчикам легко создавать и развертывать serverless функции.

Рисунок 1. Высокоуровневая архитектура Nuclio

Архитектура 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 и поднятые модели для их мониторинга:

Рисунок 2. Интерфейс 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)
Рисунок 3. Процесс сборки и развертывания функций (ссылка)

Добавление уже интегрированных моделей

Существует несколько основных категорий моделей:

  • 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:

https://segment-anything.com

Официальная документация и туториал по установке моделей в 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

Телеграм-канал

DeepSchool

Короткие посты по теории ML/DL, полезные
библиотеки и фреймворки, вопросы с собеседований
и советы, которые помогут в работе

Открыть Телеграм

Увидели ошибку?

Напишите нам в Telegram!