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