Поговорим об использовании библиотеки OpenCV при решении задач машинного зрения в контексте развертывания в продакшен-среде.
Привет! Меня зовут Алексей Тюрин, я главный специалист направления по сталеплавильному производству в НЛМК.
Поговорим об использовании библиотеки OpenCV при решении задач машинного зрения в контексте развертывания в продакшен-среде.
В статье речь пойдет об оптимизации сборки контейнеров для запуска различных приложений машинного зрения на основе библиотеки OpenCV. Зачем вообще заниматься оптимизацией размеров docker-образов для развертывания контейнеров? Оптимизация сборки контейнеров имеет следующий практический смысл:
- Экономия места в хранилище артефактов (зачастую образы содержат гигабайты бесполезных примеров кода и вспомогательных утилит, которые будут использованы не в продакшене, а только в локальной среде разработчика);
- Оптимизация времени развертывания (экономим время на скачивание тяжелого образа);
- Экономия ресурсов среды выполнения (не тратим оперативную память на загрузку лишних утилит и экономим дисковое пространство).
В контексте небольших проектов эти вопросы не столь существенны, но если проект или их совокупность насчитывает сотни и тысячи образов, то их решение становится актуальной задачей. К примеру, оптимизировав всего 100 образов с 2ГБ до 1ГБ, мы получим экономию в размере 100ГБ дискового пространства.
🔥 Началась регистрация на соревнование Google для ИИ-специалистовtproger.ru
Кроме того, требования многих организаций подразумевают хранение не менее трех версий для каждого сервиса, что автоматически многократно умножает затраты на хранение артефактов.
В контексте рассматриваемой библиотеки OpenCV такие оптимизации могут многократно уменьшить занимаемое дисковое пространство или добавить нужный функционал, которого нет «из коробки».
Что такое и кому нужна OpenCV
OpenCV (Open Source Computer Vision Library) — это популярная производительная библиотека, которая предоставляет множество инструментов для компьютерного зрения и обработки изображений. OpenCV поддерживает Python и иные языки программирования, такие как Java, Kotlin и другие. Этот инструмент достаточно хорош в различных производственных задачах, среди которых:
- Определение границ на изображениях (например, детекция контуров заготовок, определение их формы и выявление дефектов);
- Детекция несложных явлений — появление человека в той зоне наблюдения, где его быть не должно (обеспечение безопасности процесса производства), детекция распространенных посторонних объектов в кадре (например, грузового транспорта);
- Подготовка изображений для более сложных нейросетевых моделей — сжатие, поворот, накладывание фильтров;
- Определение изменений в кадре (подсчеты градиентов, определение фокуса камеры и так далее).
Популярность инструмента во многом объясняется его широким функционалом.
1. Чтение и запись изображений и видео
- `cv2.imread(filename, flags)`: Загружает изображение из файла.
- `cv2.imwrite(filename, img)`: Сохраняет изображение в файл.
- `cv2.VideoCapture(index)`: Захватывает видео с камеры или файла.
- `cv2.VideoWriter(filename, fourcc, fps, frameSize)`: Записывает видео в файл.
2. Преобразования изображений
- `cv2.cvtColor(src, code)`: Преобразует изображение из одного цветового пространства в другое.
- `cv2.resize(src, dsize, fx, fy, interpolation)`: Изменяет размер изображения.
- – `cv2.flip(src, flipCode)`: Отражает изображение горизонтально, вертикально или в двух направлениях.
3. Фильтрация и сглаживание изображений
- `cv2.GaussianBlur(src, ksize, sigmaX, sigmaY)`: Применяет гауссово размытие к изображению.
- `cv2.medianBlur(src, ksize)`: Применяет медианный фильтр.
- `cv2.bilateralFilter(src, d, sigmaColor, sigmaSpace)`: Применяет билинейный фильтр.
4. Обнаружение краев и контуров
- `cv2.Canny(image, threshold1, threshold2)`: Обнаруживает края в изображении методом Кэнни.
- `cv2.findContours(image, mode, method)`: Находит контуры на бинарном изображении.
- `cv2.drawContours(image, contours, contourIdx, color, thickness)`: Рисует контуры на изображении.
5. Морфологические преобразования
- `cv2.erode(src, kernel, iterations)`: Применяет эрозию.
- `cv2.dilate(src, kernel, iterations)`: Применяет дилатацию.
- `cv2.morphologyEx(src, op, kernel)`: Выполняет расширенные морфологические преобразования (открытие, закрытие, градиент и т. д.).
6. Геометрические преобразования
- `cv2.getRotationMatrix2D(center, angle, scale)`: Возвращает матрицу для 2D-поворота.
- `cv2.warpAffine(src, M, dsize)`: Применяет аффинные преобразования к изображению.
- `cv2.warpPerspective(src, M, dsize)`: Применяет перспективные преобразования.
7. Обнаружение объектов и распознавание
- `cv2.CascadeClassifier.detectMultiScale(image, scaleFactor, minNeighbors)`: Используется для обнаружения объектов с использованием каскадов Хаара.
- `cv2.HOGDescriptor()`: Создает объект для обнаружения людей с использованием гистограмм направленных градиентов.
Установка OpenCV
Благодаря поддержке аппаратного ускорения обработки изображений OpenCV имеет хорошую производительность — в частности, видеокарты. Базовый функционал библиотеки даже позволяет распознавать несложные объекты, хотя на практике при решении задач обычно используют собственные реализации нейросетевых моделей или доученные базовые модели из открытого доступа.
Установить библиотеку достаточно легко, для этого достаточно выполнить:
pip install opencv-python
Серьезный плюс такого метода установки состоит в том, что при его использовании библиотека будет содержать большое количество разнообразных модулей. Однако для запуска библиотеки внутри контейнеров рекомендуется использовать «облегченную» версию:
pip install opencv-python-headless
В ней отсутствуют модули для отображения изображений и воспроизведения видео — это не требуется внутри контейнеров. В сложных случаях может возникнуть необходимость сделать собственную сборку библиотеки из исходного кода. Ниже — ряд примеров.
Использование библиотеки OpenCV для продакшен-средыtproger.ru
1. Использование последних изменений и исправлений
Если необходимо применить последние изменения, исправления или новые функции, которые еще не включены в официальные релизы, можно собрать OpenCV из исходников с использованием актуального кода из репозитория.
2. Оптимизация для конкретной системы
Сборка из исходников позволяет включать специфические оптимизации для конкретного оборудования — допустим, использование инструкций SIMD (SSE4, AVX).
3. Включение дополнительных модулей
Некоторые модули OpenCV отсутствуют в стандартных бинарных сборках — например, поддержка дополнительных форматов файлов или интеграция с другими библиотеками (GStreamer, FFMPEG). В таком случае сборка позволяет использовать различные кодеки для сохранения видеофайлов с распознанными объектами.
4. Изменение конфигурации сборки
В некоторых случаях для уменьшения размера библиотеки и улучшения производительности требуется специфическая конфигурация сборки, в которой отключены ненужные компоненты или, наоборот, включены специфические опции.
5. Интеграция с другими библиотеками и инструментами
Если требуется интеграция с определенными библиотеками (Intel MKL, TBB или OpenVX), может понадобиться сборка OpenCV из исходников с соответствующими флагами.
6. Использование экспериментальных функций
Некоторые экспериментальные функции доступны только в исходниках и не включены в стабильные релизы.
Процесс сборки OpenCV из исходников
Сборка OpenCV из исходников предоставляет разработчику гораздо больше гибкости и производительности, что может быть критично для специфических приложений и требований. Вся магия процесса создания собственной сборки OpenCV происходит в момент конфигурирования флагов. Перечень необходимых флагов можно найти в официальной документации.
К примеру, если вам нужно обрабатывать изображения исключительно в формате JPEG, то можно отключить все остальные форматы.
Для повторения процесса сборки в докер-контейнер достаточно вынести приведенные выше шаги в отдельный этап, что позволит снизить размер образа — это можно сделать несколькими способами.
Самым простым и доступным является использование multistage-сборки, где процесс разбивается на несколько этапов. Кроме того, разбивка на этапы может ускорить саму сборку при наличии кеширования. К примеру, если сборка состоит из двух этапов, где на первом мы только устанавливаем зависимости, а на втором копируем исходный код, то при изменении исходного кода нет смысла каждый раз выполнять первый этап.
Вместо повторения сборки незатронутых этапов Docker будет использовать кеш. Правильное разделение на этапы не является тривиальной задачей, но базово можно выделить этап установки зависимостей, сборки приложения и этап конфигурации запуска.
Этап 1: Сборка OpenCV из исходников
FROM ubuntu:20.04 as builder
Установка зависимостей (перечень пакетов основан на примере из репозитория, где можно найти примеры для различных потребностей — использование определенного базового образа, поддержка видеокарт nvidia и многое другое):
RUN apt-get update && apt-get install -y build-essential cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev python3-dev python3-numpy libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libdc1394-22-dev && apt-get clean
Далее клонируем в наш контейнер исходный код OpenCV и contrib-модулей. Здесь вы можете указать конкретную версию релиза. Для этого добавьте ссылку на Source code(zip) и распакуйте его утилитой unzip.
RUN git clone https://github.com/opencv/opencv.git /opencv RUN git clone https://github.com/opencv/opencv_contrib.git /opencv_contrib
Создание директории для сборки, куда будет помещен исполняемый файл библиотеки:
RUN mkdir -p /opencv/build
Конфигурация и сборка OpenCV:
WORKDIR /opencv/build #указываем папку, куда будет помещен бинарный файл после сборки RUN cmake -D CMAKE_BUILD_TYPE=Release #указываем, что сборка должна быть оптимизирована для продакшен-использования -D PYTHON_EXECUTABLE=$(which python3) #важно указать путь до исполняемого бинарного файла python -D CMAKE_INSTALL_PREFIX=/usr/local #указываем директорию, куда был установлен cmake на предыдущем шаге -D OPENCV_EXTRA_MODULES_PATH=/opencv_contrib/modules #здесь будут располагаться дополнительные модули OpenCV -D BUILD_opencv_python2=OFF #выключаем использование python версии 2 -D BUILD_EXAMPLES=OFF #исключаем из сборки примеры кода, так как они никогда не будут использованы в production-среде -D BUILD_opencv_python3=ON #добавляем в сборку обертку для использования в python -D INSTALL_C_EXAMPLES=OFF #не генерируем примеры кода на языке С -D INSTALL_PYTHON_EXAMPLES=OFF #аналогично — не генерируем примеры кода на python RUN make -j$(nproc) && make install
Этап 2: Создание минимального конечного образа для запуска вашего приложения
FROM ubuntu:20.04
Установка необходимых зависимостей для выполнения OpenCV-приложений (исходный пример взят из официальной документации).
RUN apt-get update && apt-get install -y python3 python3-numpy #для наших задач было полезно иметь numpy в базовом образе, поэтому добавили его сразу libtbb2 libjpeg-dev libpng-dev libtiff-dev libavcodec-dev libavformat-dev libswscale-dev && apt-get clean
Копирование установленной OpenCV из предыдущего этапа:
COPY --from=builder /usr/local /usr/local
Установка pip и необходимых Python-библиотек:
RUN apt-get install -y python3-pip && pip3 install --upgrade pip
Копирование и установка Python-зависимостей (если есть requirements.txt):
COPY requirements.txt /app/requirements.txt RUN pip3 install -r /app/requirements.txt
Копирование приложения в контейнер. Не забудьте добавить в вашем проекте файл .dockerignore, чтобы не скопировать лишних файлов, но все же лучше поместить необходимые файлы в отдельную папку:
COPY . /app WORKDIR /app
Команда по умолчанию для запуска приложения:
CMD ["python3", "your_script.py"]
Вместо итогов
Приведенный пример можно использовать и для сборки базового образа — для этого мы можем убрать последние шаги.
Копирование и установка Python-зависимостей (если есть requirements.txt):
COPY requirements.txt /app/requirements.txt RUN pip3 install -r /app/requirements.txt
Копирование приложения в контейнер:
COPY . /app WORKDIR /app
Команда по умолчанию для запуска приложения:
CMD ["python3", "your_script.py"]
Такой подход позволит нам добавить в контейнер приложение, использующее OpenCV, позднее — при сборке конечного контейнера. В этом случае мы используем собранный ранее образ в качестве базового в конструкции FROM. Предложенный подход не является единственно верным, поэтому вы можете экспериментировать с разбивкой на этапы и использованием флагов.
Готово!