Многоархитектурность Docker, .NET 6.0 и OpenCVSharp

До недавнего времени, если мне требовалось использовать определенную архитектуру процессора с собственными зависимостями среды выполнения, я создавал специальный образ 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

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 ГГц

Оцените статью
Procodings.ru
Добавить комментарий