До недавнего времени, если мне требовалось использовать определенную архитектуру процессора с собственными зависимостями среды выполнения, я создавал специальный образ Docker, предназначенный для этой платформы. Хотя я знал о поддержке Docker нескольких архитектур, создание такого образа почему-то казалось мне более трудоемким.
Целью этой заметки является создание многоразового мультиархитектурного образа Docker, содержащего родную библиотеку OpenCVSharpExtern, а также необходимые библиотеки OpenCV и дополнительные зависимости для процессоров amd64, arm32 и arm64.
После создания образа библиотеки будет создано приложение .Net для демонстрации его способности работать на различных устройствах / процессорных архитектурах, как показано ниже:
- amd64
- Настольная система Windows 11 с Docker WSL2
- Ноутбук Windows 10 с Docker WSL2 (bootcamp)
- macOS 12 Monterey Laptop
- Raspberry Pi 3
- 64 бит Ubuntu 22.04
- 32-битная Raspberry Pi OS Lite (Debian bullseye)
- Raspberry Pi 4
- 64 бит Ubuntu 22.04
- 32-битная Raspberry Pi OS Lite (Debian bullseye)
- PINE A64-LTS
- 64-битный Armbian Linux 4.19.59-sunxi64 (Debian Buster)
- OpenCV и OpenCvSharp
- Нативное взаимодействие в .Net
- Поддержка мультиархитектуры Docker
- Базовые образы и поддерживаемые процессорные архитектуры
- Проблемы с распространением нативных привязок через NuGet
- Использование многоархивной поддержки Docker для нативных зависимостей вместо этого.
- Шаг 1: Сборка OpenCV, OpenCvSharpExtern в пользовательском образе
- Шаг 2: Простое эталонное приложение, использующее образ родной библиотеки
- Следующие шаги
- Заключение
- Результаты бенчмарка
OpenCV и OpenCvSharp
OpenCV — это кроссплатформенная библиотека компьютерного зрения и машинного обучения с открытым исходным кодом, которая поддерживает ряд платформ, используя общую поддержку CPU, а также высокопроизводительную поддержку GPU для определенных алгоритмов.
OpenCvSharp предоставляет обертку для OpenCV для платформы .Net. Используя эту библиотеку, мы можем создавать приложения .Net на таких языках, как C#, и запускать их на различных платформах при условии, что в целевой среде доступны зависимости времени выполнения для конкретной платформы.
Нативное взаимодействие в .Net
В некоторых случаях приложению .Net может потребоваться использовать функциональность из динамической нативной библиотеки (dll / so), и одним из вариантов, который есть у разработчиков .Net, является вызов функциональности платформы (pinvoke). Такая возможность появилась с первых дней существования .Net.
Для использования OpenCvSharp нам нужны нативные привязки (OpenCvSharpExtern.(dll/so)), чтобы направлять вызовы методов в библиотеку OpenCV через динамическое связывание во время выполнения.
Следующая диаграмма иллюстрирует, как pinvoke позволяет нам использовать SIFT.Create(); для вызова нативной функции библиотеки OpenCV во время выполнения благодаря привязке OpenCvSharp.
Поддержка мультиархитектуры Docker
Поддержка мультиархитектурности в Docker предоставляет возможности для создания образа с конкретными сборками для каждой архитектуры целевого процессора с помощью одной команды / Dockerfile.
Чтобы иметь возможность собрать образ с поддержкой нескольких процессорных архитектур, необходимо выполнить следующие шаги:
- Выберите базовый образ с поддержкой нескольких архитектур.
- Если речь идет о нативных библиотеках, предоставьте условный сценарий сборки для каждой процессорной архитектуры.
- Включите экспериментальные фьючерсы Docker:
- Создайте новый билдер для включения поддержки нескольких архитектур на машине сборки.
# create a new builder and set it active
docker buildx create --name mybuilder --use
- Сборка и проталкивание образов с использованием многопроцессорной архитектуры.
# build the image for amd64, arm64 and arm32 architectures and push
docker buildx build
--push
--platform linux/arm/v7
--tag syamaner/opencvsharp-build:${build_number}
./opencv-sharp
Базовые образы и поддерживаемые процессорные архитектуры
Большинство базовых образов уже поддерживают многопроцессорную архитектуру, например .Net runtimes и sdks.
В случае сомнений можно воспользоваться командой docker buildx imagetools inspect
, чтобы проверить, поддерживает ли ваш базовый образ необходимые архитектуры:
docker buildx imagetools inspect mcr.microsoft.com/dotnet/runtime:6.0-alpine
Результаты будут выглядеть следующим образом:
Проблемы с распространением нативных привязок через NuGet
Если посмотреть на NuGet-пакеты OpenCvSharp4, то там уже есть пакет нативной привязки для ARM-устройств под названием OpenCvSharp4.runtime.linux-arm
.
Проблема с распространением таких привязок через NuGet заключается в том, что, поскольку компиляция нативных библиотек требует определенных версий зависимостей для целевой платформы, часто эти пакеты не будут работать из коробки из-за отсутствия / несоответствия зависимостей. Это приведет к необходимости вручную проверять зависимости и устанавливать/разрешать их на целевой машине, что не очень весело. Кроме того, на таких устройствах, как Raspberry Pi, могут работать как 32-битные, так и 64-битные операционные системы, что также может вызвать дополнительные сложности. Пример проблемы можно увидеть в репозитории OpenCvSharp.
Использование многоархивной поддержки Docker для нативных зависимостей вместо этого.
Для нативных привязок / зависимостей мы можем пропустить NuGet и вместо этого создать образ docker, где мы собираем эти привязки для наших целевых платформ (arm32, arm64, amd64). Это также позволит оптимизировать компилятор, поскольку мы знаем наши целевые архитектуры.
В следующем разделе будет продемонстрирован этот подход к сборке зависимостей OpenCV, а затем .Net-приложение, использующее это во время сборки.
Шаг 1: Сборка OpenCV, OpenCvSharpExtern в пользовательском образе
Этот шаг необходимо повторять только при выходе нового релиза OpenCV или OpenCvSharp. Dockerfile выглядит следующим образом:
FROM debian:bullseye-slim AS build-native-env
ARG TARGETPLATFORM
ENV DEBIAN_FRONTEND=noninteractive
# 4.5.5: released 25 Dec 2021
ENV OPENCV_VERSION=4.5.5
# 4.5.3.20211228: released 28 Dec 2021
ENV OPENCVSHARP_VERSION=4.5.3.20211228
WORKDIR /
# install dependencies required for building OpenCV and OpenCvSharpExtern
RUN apt-get update && apt-get -y install --no-install-recommends
# details omitted
libgdiplus
# Get OpenCV and opencv-contrib sources using the specified release.
RUN wget https://github.com/opencv/opencv/archive/${OPENCV_VERSION}.zip &&
unzip ${OPENCV_VERSION}.zip &&
rm ${OPENCV_VERSION}.zip &&
mv opencv-${OPENCV_VERSION} opencv
RUN wget https://github.com/opencv/opencv_contrib/archive/${OPENCV_VERSION}.zip &&
unzip ${OPENCV_VERSION}.zip &&
rm ${OPENCV_VERSION}.zip &&
mv opencv_contrib-${OPENCV_VERSION} opencv_contrib
# configure and build OpenCV optionally specifying architecture related cmake options.
RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then
ADDITIONAL_FLAGS='' ;
elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then
ADDITIONAL_FLAGS='-D ENABLE_NEON=ON -D CPU_BASELINE=NEON ' ;
elif [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then
ADDITIONAL_FLAGS='-D CPU_BASELINE=NEON -D ENABLE_NEON=ON ' ;
fi && cd opencv && mkdir build && cd build &&
cmake $ADDITIONAL_FLAGS
# additional flags omitted for clarity
&& make -j$(nproc)
&& make install
&& ldconfig
# Download OpenCvSharp to build OpenCvSharpExtern native library
RUN git clone https://github.com/shimat/opencvsharp.git
RUN cd opencvsharp && git fetch --all --tags --prune && git checkout ${OPENCVSHARP_VERSION}
WORKDIR /opencvsharp/src
RUN mkdir /opencvsharp/make
&& cd /opencvsharp/make
&& cmake -D CMAKE_INSTALL_PREFIX=/opencvsharp/make /opencvsharp/src
&& make -j$(nproc)
&& make install
&& cp /opencvsharp/make/OpenCvSharpExtern/libOpenCvSharpExtern.so /usr/lib/
&& ldconfig
# Copy the library and dependencies to /artifacts (to be used by images consuming this build)
# cpld.sh will copy the library we specify (./libOpenCvSharpExtern.so) and any dependencies
# to the /artifacts directory. This is useful for sharing the library with other images
# consuming this build.
# credits: Hemanth.HM -> https://h3manth.com/content/copying-shared-library-dependencies
WORKDIR /opencvsharp/make/OpenCvSharpExtern
COPY cpld.sh .
RUN chmod +x cpld.sh &&
mkdir /artifacts &&
./cpld.sh ./libOpenCvSharpExtern.so /artifacts/
RUN cp ./libOpenCvSharpExtern.so /artifacts/
# Publish the artefacts using a clean image
FROM debian:bullseye-slim AS final
RUN mkdir /artifacts
COPY --from=build-native-env /artifacts/ /artifacts
WORKDIR /artifacts
Шаг 2: Простое эталонное приложение, использующее образ родной библиотеки
Для того чтобы проверить совместимость с целевыми устройствами / платформами, было создано базовое эталонное приложение с использованием BenchmarkDotNet.
Хотя бенчмаркинг не является целью, были выполнены следующие тесты:
- Извлечение признаков SIFT против SURF
- Сопоставление признаков с использованием классов FlannBasedMatcher и BFMatcher, предоставляемых OpenCV.
Наша цель — обеспечить успешную работу приложения в любой комбинации перечисленных выше аппаратных средств и операционных систем.
Код доступен в репозитории docker-multi-arch-opencvsharp.
Приложение собирается с помощью следующего Dockerfile:
ARG OPENCV_SHARP_BUILD_TAG=2
ARG SDK_VERSION=6.0.202-bullseye-slim-amd64
ARG RUNTIME_VERSION=6.0.4-bullseye-slim
FROM syamaner/opencvsharp-build:$OPENCV_SHARP_BUILD_TAG AS opencv
# Given we are building a .Net application, the build does not have to be in the target architecture.
# Reference: https://github.com/dotnet/dotnet-docker/issues/1537#issuecomment-755351628
FROM mcr.microsoft.com/dotnet/sdk:$SDK_VERSION as build
ARG TARGETPLATFORM
WORKDIR /src
COPY . .
# Select the correct RID for the target architecture.
# run dotnet publish as usual and pass the RID.
RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then
RID=linux-x64 ;
elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then
RID=linux-arm64 ;
elif [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then
RID=linux-arm ;
fi &&
dotnet publish -c release -o /app -r $RID --self-contained false
# Copy the application as well as native dependencies to the final stage and build the final image without any unnecessary files.
FROM mcr.microsoft.com/dotnet/runtime:$RUNTIME_VERSION as final
WORKDIR /app
# Copy opencv sharp native binding and runtime dependencies.
COPY --from=opencv /artifacts/ /usr/lib/
RUN ldconfig
COPY --from=build /app/ /app/
ENTRYPOINT [ "dotnet", "/app/OpenCVSharpBenchmarkApp.dll" ]
Следующие шаги
Следующим шагом будет создание приложения, которое будет сканировать каталог на наличие фотографий, а затем возвращать список уникальных фотографий, а также фотографий, которые потенциально являются их дубликатами, используя алгоритмы и нативные библиотеки OpenCVSharp, созданные в этом посте.
Заключение
Поддержка мультиархитектурности в Docker упрощает создание приложений, которые будут работать на различных процессорных архитектурах. После создания и публикации образа с несколькими процессорными архитектурами, мы можем использовать этот образ на целевой платформе, используя один и тот же тег image:tag для всех платформ, и позволить docker извлечь правильную архитектуру для текущей машины.
В качестве примера следующая команда будет работать на устройствах amd64, arm64 и arm32 при условии наличия установки Docker:
docker run -it -v $(PWD)/reports/:/app/BenchmarkDotNet.Artifacts/ syamaner/opencvsharp-bench:1
Она извлечет образ с подходящей архитектурой и выполнит бенчмарки, а затем сохранит отчеты в каталоге ./reports.
Результаты бенчмарка
Целью приложения для бенчмарков было проверить, будут ли новые многоархитектурные образы Docker успешно работать на различных устройствах / процессорных архитектурах. До настоящего времени он работал стабильно на различных дистрибутивах Linux.
Приведенные ниже цифры не всегда точны, так как возможны дросселирование из-за температуры, а также низкая мощность от адаптера питания, и их следует рассматривать с осторожностью.
Среднее значение | Бренд | Хост ОС | ПРОЦЕССОР |
---|---|---|---|
343,4 мс | Настольный ПК | Windows amd64 | Процессор Intel i9-7940X 3,10 ГГц |
499,1 мс | MBP 2016 15″ | Mac OS 12 amd64 | Intel i7-6820HQ CPU 2.70GHz |
544,2 мс | MBP 2016 15″ | Windows (Bootcamp) amd64 | Процессор Intel i7-6820HQ 2,70 ГГц |
6.855 s | Pine64 Lts | Armbian (Debian buster) arm64 | ARM Cortex-A53 CPU 1152Mhz |
4.005 s | Raspberry Pi 4 | Raspbian bullseye arm32 | ARM Cortex-A72 CPU 1.5 ГГц |
2.120 s | Raspberry Pi 4 | 20.04.4 LTS arm64 | ARM Cortex-A72 CPU 1.8 ГГц |