直到最近,如果我需要针对具有本机运行时依赖项的特定处理器架构,我会构建一个针对该平台的专用 Docker 映像。虽然我知道 Docker 多架构支持,但出于某种原因构建一个听起来需要更多的努力。

这篇文章的目的是构建一个可重用的多架构 Docker 映像,其中包含本机 OpenCVSharpExtern 库以及所需的 OpenCV 库以及 amd64、arm32 和 arm64 处理器架构的额外运行时特定依赖项。

构建库映像后,将构建一个 .Net 应用程序以展示它在不同设备/处理器架构上运行的能力,如下所示:

  • amdshch

  • 带有 Docker WSL2 的 Windows 11 桌面

  • 带有 Docker WSL2 的 Windows 10 笔记本电脑(训练营)

  • macOS 12 蒙特雷笔记本电脑

  • 树莓派 3

  • 64 位 Ubuntu 22.04

  • 32 位树莓派操作系统精简版(Debian 靶心)

  • 树莓派 4

  • 64 位 Ubuntu 22.04

  • 32 位树莓派操作系统精简版(Debian 靶心)

  • 松树 A64-LTS

  • 64 位 Armbian Linux 4.19.59-sunxi64 (Debian Buster)

OpenCV 和 OpenCvSharp

OpenCV是一个开源、跨平台的计算机视觉和机器学习库,它支持一系列利用通用 CPU 支持的平台以及对某些算法的高性能 GPU 支持。

OpenCvSharp为 .Net 平台的 OpenCV 提供了一个包装器。使用这个库,我们可以使用 C# 等语言构建 .Net 应用程序,并在不同平台上运行它们,前提是我们的目标环境中提供了特定于平台的运行时依赖项。

.Net 中的本机互操作

有时 .Net 应用程序可能需要利用来自动态本机库 (dll / so) 的功能,而 .Net 开发人员拥有的选项之一是平台调用功能 (pinvoke)。自 .Net 早期以来,这一直是一种可能性。

要使用 OpenCvSharp,我们需要本地绑定 (OpenCvSharpExtern.(dll/so)) 以在运行时通过动态链接将方法调用定向到 OpenCV 库。

[pinvoke 高级概述](https://res.cloudinary.com/practicaldev/image/fetch/s--N1hjXf1h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev- to-uploads.s3.amazonaws.com/uploads/articles/r2roz1yvv7nwddztm4t7.jpg)

下图说明了 pinvoke 如何让我们使用 SIFT.Create();由于 OpenCvSharp 绑定,在运行时调用本机 OpenCV 库函数。

[演示如何使用 pinvoke 创建 SIFT 特征检测器的新实例的简化图。](https://res.cloudinary.com/practicaldev/image/fetch/s--X_0MzxF6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws .com/uploads/articles/bib9d4w97lznee2uvtt4.jpg)

Docker 多架构支持

Docker 多架构支持提供了使用单个命令/Dockerfile 为每个目标处理器架构构建发布镜像的工具。

为了能够构建支持多处理器架构的映像,需要执行以下步骤:

  • 选择支持多架构的基础镜像。

  • 如果处理本机库,请为每个处理器架构提供有条件的构建脚本。

  • 启用实验性 Docker 期货:Docker 桌面 Windows:启用实验功能

  • 创建一个新的构建器以在构建机器上启用多架构支持。

# create a new builder and set it active
docker buildx create --name mybuilder --use

进入全屏模式 退出全屏模式

  • Build并使用多处理器架构推送镜像构建。
# 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 运行时和 sdks。

如有疑问,可以使用docker buildx imagetools inspect命令来验证您的基础映像是否支持所需的架构:

docker buildx imagetools inspect mcr.microsoft.com/dotnet/runtime:6.0-alpine

进入全屏模式 退出全屏模式

结果将如下所示:

[dotnet/runtime 支持的平台:linux/amd64, linux/arm/v7, linux/arm64/v8](https://res.cloudinary.com/practicaldev/image/fetch/s--NLz_TvLL-- /c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ff8k76xmsy0eqafyczz.png)

通过 NuGet 分发本机绑定的挑战

查看 OpenCvSharp4 NuGet 包,已经有一个用于 ARM 设备的本机绑定包,名为OpenCvSharp4.runtime.linux-arm

通过 NuGet 分发此类绑定的挑战在于,鉴于编译本机库需要目标平台的特定版本依赖项,由于缺少/不匹配的依赖项,这些包通常无法开箱即用。这将导致手动验证依赖关系并在目标机器上安装/解决它们,这并不有趣。此外,像 Raspberry Pi 这样的设备可以运行 32 位和 64 位操作系统,这也可能导致进一步的复杂性。可以在OpenCvSharp repositroy看到一个示例问题。

改为使用 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

进入全屏模式 退出全屏模式

[内置映像](https://res.cloudinary.com/practicaldev/image/fetch/s--LPCPyeOo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to- uploads.s3.amazonaws.com/uploads/articles/bhv4lwbtb8zo3s7hyao4.png)

第 2 步:使用本机库映像的简单基准测试应用程序

为了测试与目标设备/平台的兼容性,使用BenchmarkDotNet构建了一个基本的基准测试应用程序

虽然基准测试不是目标,但会执行以下基准测试:

  • SIFT vs SURF 特征提取

  • 使用 OpenCV 提供的 FlannBasedMatcher 与 BFMatcher 类匹配特征。

我们的目标是确保应用程序在上述硬件和操作系统的任何组合中成功运行。

该代码可在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" ]

进入全屏模式 退出全屏模式

[使用 Docker 多架构支持构建 .Net 应用程序。](https://res.cloudinary.com/practicaldev/image/fetch/s--pGMWQ5ag--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws .com/uploads/articles/vj96j6vr03mwpakq298o.jpg)

后续步骤

下一步将构建一个应用程序,该应用程序将扫描目录中的照片,然后使用本文中构建的算法和 OpenCVSharp 本机库返回唯一照片以及可能与这些照片重复的照片列表。

结论

Docker 多架构支持简化了构建将在不同处理器架构上运行的应用程序。一旦使用多处理器架构构建并发布了一个镜像,我们就可以在目标平台上使用这个镜像,方法是在所有平台上使用相同的 image:tag 并让 docker 为当前机器拉取正确的架构。

例如,只要有 Docker 安装,以下命令将在 amd64、arm64 和 arm32 设备上运行:

docker run -it -v $(PWD)/reports/:/app/BenchmarkDotNet.Artifacts/ syamaner/opencvsharp-bench:1

它将提取具有匹配架构的图像并执行基准测试,然后将报告存储在 ./reports 目录中。

基准结果

基准应用程序的目的是验证新构建的多架构 Docker 映像是否可以在不同的设备/处理器架构上成功运行。到目前为止,它一直在各种 Linux 发行版上工作。

下面的数字不一定准确,因为温度和电源适配器的低功率可能会导致节流,需要谨慎查看。

意思是

操作系统主机

中央处理器

343.4 毫秒

桌上型电脑

视窗 amd64

英特尔 i9-7940X CPU 3.10GHz

499.1 毫秒

MBP 2016 15"

Mac OS 12 amd64

英特尔 i7-6820HQ CPU 2.70GHz

544.2 毫秒

MBP 2016 15"

Windows(训练营)amd64

英特尔 i7-6820HQ CPU 2.70GHz

6.855 秒

Pine64 Lts

Armbian (Debian buster) arm64

ARM Cortex-A53 CPU 1152Mhz

4.005 秒

树莓派 4

Raspbian 靶心 arm32

ARM Cortex-A72 CPU 1.5GHz

2.120 秒

树莓派 4

20.04.4 LTS arm64

ARM Cortex-A72 CPU 1.8GHz

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐