在 NodeJS 里使用 OpenCV

该文章根据 CC-BY-4.0 协议发表,转载请遵循该协议。
本文地址:https://fenying.net/post/2024/09/30/using-opencv-in-nodejs/

文章目录

最近在开发一个简单的图片管理功能,用到了 OpenCV 的一些功能,

这里整理一下在 NodeJS 里使用 OpenCV 的一些经验总结……

1. 在 Docker 里编译 OpenCV

省流:如果只需要图片操作,可以直接使用我构建好的 Docker 镜像 docker pull fenying/node:20.17.0-alpine-opencv-4.10.0

此处以 node:20-alpine 镜像为基础,构建一个包含 OpenCV 的镜像。

值得注意的是,如果你想在树莓派5上使用,那么请到树莓派上编译本镜像,或者直接使用我上面构建好的镜像。

因为 docker buildx 虽然可以使用 QEMU 构建跨平台的镜像,但是 QEMU 存在兼容性和稳定性问题,导致编译过程中可能会出现各种问题。 反正我是没在 X86 PC 上成功编译过 Linux/ARM64 的 OpenCV 镜像。

在 alpine 系统里编译 OpenCV,需要如下编译时依赖和运行时依赖:

编译时依赖:

也就是说这些依赖在编译完成后可以删除

  • build-base
  • cmake
  • unzip
  • openblas-dev
  • libjpeg-turbo-dev
  • libpng-dev
  • tiff-dev
  • libwebp-dev
  • clang-dev
  • linux-headers

运行时依赖:

以下组件部分可选,可以根据 cmake 参数自行选择

  • openblas
  • libtbb
  • libtbb-dev
  • libjpeg
  • tiff
  • libwebp
  • libpng

下面是 Dockerfile:

 1ARG NODE_IMAGE_VERSION=20.17.0
 2ARG OPENCV_VERSION=4.10.0
 3
 4FROM node:$NODE_IMAGE_VERSION-alpine AS imported_data
 5ENV OPENCV_VERSION=${OPENCV_VERSION}
 6RUN mkdir /imported_data
 7COPY $OPENCV_VERSION.zip /imported_data/
 8
 9FROM node:$NODE_IMAGE_VERSION-alpine
10ENV OPENCV_VERSION=${OPENCV_VERSION}
11
12RUN --mount=type=bind,from=imported_data,source=/imported_data,target=/imported_data \
13    apk add -U \
14        openblas \
15        libtbb \
16        libtbb-dev \
17        libjpeg \
18        tiff \
19        libwebp \
20        libpng && \
21    apk add -U \
22        --virtual .build-dependencies \
23        build-base \
24        cmake \
25        unzip \
26        openblas-dev \
27        libjpeg-turbo-dev \
28        libpng-dev \
29        tiff-dev \
30        libwebp-dev \
31        clang-dev \
32        linux-headers && \
33    mkdir -p /usr/src/ && \
34    cp /imported_data/$OPENCV_VERSION.zip /usr/src/ && \
35    cd /usr/src/ && \
36    unzip $OPENCV_VERSION.zip && \
37    cd /usr/src/opencv-$OPENCV_VERSION/ && \
38    mkdir build && \
39    cd build && \
40    cmake \
41        -DCMAKE_BUILD_TYPE=RELEASE \
42        -DBUILD_EXAMPLES=NO \
43        -DBUILD_ANDROID_EXAMPLES=NO \
44        -DINSTALL_PYTHON_EXAMPLES=NO \
45        -DBUILD_DOCS=NO \
46        -DWITH_TBB=YES \
47        -DBUILD_opencv_python2=NO \
48        -DBUILD_opencv_video=OFF \
49        -DBUILD_opencv_videoio=OFF \
50        -DBUILD_opencv_calib3d=OFF \
51        -DBUILD_opencv_ml=OFF \
52        -DBUILD_opencv_dnn=OFF \
53        -DBUILD_opencv_highgui=OFF \
54        .. && \
55    make -j$(grep -c ^processor /proc/cpuinfo) && \
56    make install && \
57    rm -rf /usr/src/opencv-$OPENCV_VERSION/ /usr/src/$OPENCV_VERSION.zip && \
58    apk del .build-dependencies && \
59    apk cache clean
60
61ENV LD_LIBRARY_PATH=/usr/local/lib

上面的 Dockerfile 有那么几个需要注意的点:

  1. 使用了分阶段构建,在最终阶段将前面的临时镜像中的文件拷贝出来使用: 这是因为在我本地网络进行构建,为了节约 opencv 源码的下载时间(200MiB),此处使用了分阶段构建,将 opencv 源码文件 直接放入为一个独立的临时镜像,再从正式镜像中使用 RUN --mount... 参数将该临时镜像挂载并拷贝出来使用。
  2. apk 命令中使用了 --virtual .build-dependencies,这样可以在使用完所有编译时依赖后,再一次性删除所有编译时依赖,以达到减小最终镜像体积的目的;
  3. cmake 时只保留了图片处理相关的模块,其他模块都未编译和构建,你可以按需自行调整,但必须安装相关的依赖(编译时和运行时依赖都要安装)。
  4. 必须配置环境变量 LD_LIBRARY_PATH,否则在 NodeJS 里调用 OpenCV 时会找不到相关的动态库。

然后使用以下命令构建镜像:

 1if [[ -z "$NODE_IMAGE_VERSION" ]]; then # 确定 NodeJS 版本
 2    NODE_IMAGE_VERSION=20.17.0
 3fi
 4
 5if [[ -z "$OPENCV_VERSION" ]]; then # 确定 OpenCV 版本
 6    OPENCV_VERSION=4.10.0
 7fi
 8
 9if [[ ! -f "$OPENCV_VERSION.zip" ]]; then # 下载 OpenCV 源码文件
10    wget https://github.com/opencv/opencv/archive/$OPENCV_VERSION.zip
11fi
12
13docker build \
14    --build-arg OPENCV_VERSION=$OPENCV_VERSION \
15    --build-arg NODE_IMAGE_VERSION=$NODE_IMAGE_VERSION \
16    -t node:$NODE_IMAGE_VERSION-alpine-opencv-$OPENCV_VERSION-linux-amd64 \
17    .

这样构建出来的镜像就包含了 OpenCV 的库,可以直接在 NodeJS 里使用了。

——骗你的,还需要编写 NodeJS Addon 并编译,才能在 NodeJS 里调用。

2. 编译 NodeJS Addon

以读取 PNG 文件大小为例,编写一个简单的 NodeJS Addon,点击查看我写好的 DEMO

除了编写 C++ Addon 代码外,关键在于如何编译。

此处可以使用一个编译命令,如下:

 1cat > build-insides-docker.sh <<EOF
 2apk add --no-cache python3 build-base &&
 3    npm ci && \
 4    rm -rf build && \
 5    npx node-gyp configure && \
 6    npx node-gyp build && \
 7    cp build/Release/*.node . && \
 8    rm -rf build node_modules
 9EOF
10
11chmod 0755 build-insides-docker.sh
12
13docker run \
14    -it \
15    -v$PWD:$PWD \
16    -w$PWD \
17    --rm \
18    node:20.17.0-alpine-opencv-4.10.0 \
19        ./build-insides-docker.sh
20
21rm -f build-insides-docker.sh

这里我们将编译命令写入一个脚本文件 build-insides-docker.sh,然后使用 Docker 挂载当前目录,并运行这个脚本文件。

这样就可以在 Docker 里编译 NodeJS Addon 了。

node:alpine 镜像里默认没有 Python 环境,无法使用 node-gyp,因此必须手动安装。

编译完毕后,会把编译产生的 *.node 文件复制到当前目录,然后你就可以在 NodeJS 里通过这个文件调用 OpenCV 了。

附录:使用 NodeJS Docker 标准镜像编译 OpenCV

也就是说使用如 node:20.17.0 这样的标准镜像,而不是 node:20.17.0-alpine 这样的精简镜像。

下面是参考的 Dockerfile:

 1ARG NODE_IMAGE_VERSION=20.17.0
 2ARG OPENCV_VERSION=4.10.0
 3
 4FROM node:$NODE_IMAGE_VERSION AS imported_data
 5ENV OPENCV_VERSION=${OPENCV_VERSION}
 6RUN mkdir /imported_data
 7COPY $OPENCV_VERSION.zip /imported_data/
 8
 9FROM node:$NODE_IMAGE_VERSION
10ENV OPENCV_VERSION=${OPENCV_VERSION}
11
12RUN --mount=type=bind,from=imported_data,source=/imported_data,target=/imported_data \
13    apt-get update && \
14    apt-get install -y \
15        build-essential \
16        cmake \
17        pkg-config \
18        libjpeg-dev \
19        libpng-dev \
20        libwebp-dev \
21        libopenblas-dev \
22        libtbb-dev && \
23    mkdir -p /usr/src/ && \
24    cp /imported_data/$OPENCV_VERSION.zip /usr/src/ && \
25    cd /usr/src/ && \
26    unzip $OPENCV_VERSION.zip && \
27    cd /usr/src/opencv-$OPENCV_VERSION/ && \
28    mkdir build && \
29    cd build && \
30    cmake \
31        -DCMAKE_BUILD_TYPE=RELEASE \
32        -DBUILD_EXAMPLES=NO \
33        -DBUILD_ANDROID_EXAMPLES=NO \
34        -DINSTALL_PYTHON_EXAMPLES=NO \
35        -DBUILD_DOCS=NO \
36        -DWITH_TBB=YES \
37        -DBUILD_opencv_python2=NO \
38        -DBUILD_opencv_video=OFF \
39        -DBUILD_opencv_videoio=OFF \
40        -DBUILD_opencv_calib3d=OFF \
41        -DBUILD_opencv_ml=OFF \
42        -DBUILD_opencv_dnn=OFF \
43        -DBUILD_opencv_highgui=OFF \
44        .. && \
45    make -j$(grep -c ^processor /proc/cpuinfo) && \
46    make install && \
47    rm -rf /usr/src/opencv-$OPENCV_VERSION/ /usr/src/$OPENCV_VERSION.zip && \
48    apt-get clean
49
50ENV LD_LIBRARY_PATH=/usr/local/lib
comments powered by Disqus

翻译: