Use OpenCV in NodeJS
该文章根据 CC-BY-4.0 协议发表,转载请遵循该协议。
本文地址:https://fenying.net/en/post/2024/09/30/using-opencv-in-nodejs/
Overview
Recently, I developed a simple image tools and implemented some features using OpenCV, in NodeJS.
Here is my experience of using OpenCV in NodeJS…
1. Compile OpenCV in Docker
TL;DR; If you just need image operation in linux/amd64 or linux/arm64 environment, you can use my prebuilt Docker image
docker pull fenying/node:20.17.0-alpine-opencv-4.10.0
, and skip into next section.
Let’s start building a Docker image containing OpenCV based on the node:20-alpine
image.
Note: If you are going to build image for linux/arm64 like raspberry pi 5, you should not use cross-platform build with docker buildx and QEMU. Just go to the raspberry pi 5 and build the image natively.
That’s because QEMU has compatibility and stability issues, which may cause various problems during the building/compilation.
Anyway, I didn’t make it on a linux/amd64 PC.
To compile OpenCV in alpine system, you need the following dependencies:
Build-time dependencies:
Which means these dependencies can be removed after compilation.
build-base
cmake
unzip
openblas-dev
libjpeg-turbo-dev
libpng-dev
tiff-dev
libwebp-dev
clang-dev
linux-headers
Run-time dependencies:
You may customized the dependencies according to the cmake parameters.
openblas
libtbb
libtbb-dev
libjpeg
tiff
libwebp
libpng
And then, the 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
There are something you may need to pay attention to:"
-
Use multi-stage build, and copy files from the temporary image to the final image.
This is because I built the image in my (unstable) local network. To save the time of downloading the OpenCV source code (200MiB), I used multi-stage build to put the OpenCV source code into a temporary image, and then mount and copy it out into the final image using
RUN --mount...
parameter. -
Use
--virtual .build-dependencies
inapk
command.This can remove all build-time dependencies after compilation, to reduce the final image size.
-
Compile OpenCV modules as you need, to save time and space.
To compile more OpenCV modules, you need to install more dependencies and time. Just compile it as you need, but you must install the related dependencies (for both build-time and run-time).
-
You must set the environment variable
LD_LIBRARY_PATH
, otherwise you will meet errors like missing library.
Now, just start building the image:
1if [[ -z "$NODE_IMAGE_VERSION" ]]; then # Specify the NodeJS version
2 NODE_IMAGE_VERSION=20.17.0
3fi
4
5if [[ -z "$OPENCV_VERSION" ]]; then # Specify the OpenCV version
6 OPENCV_VERSION=4.10.0
7fi
8
9if [[ ! -f "$OPENCV_VERSION.zip" ]]; then # Download the OpenCV source code
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 .
All done! Now you have a Docker image containing OpenCV, and you can use OpenCV in NodeJS, right?
OH, NO! Don’t forget that you need to write NodeJS Addon and compile it, so that you can call it in NodeJS.
2. Creating NodeJS Addon
Let’s create a demo of reading the size of a PNG file, click here to view the demo I wrote.
After writing the C++ Addon code, the key is how to compile it.
Here is a compilation command:
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
You could see that we write the compilation command into a script file build-insides-docker.sh
,
and then execute it insides the Docker container by mounting the current directory.
Note: The
node:alpine
image does not contain Python environment by default, so you must install it manually.
Finally, after the compilation is complete, the *.node
file generated by the compilation will be copied to the current directory.
And now you can call OpenCV in NodeJS through this file.
1const imgUtils = require('./image.node'); // Change to your .node file name
2
3const { height, width } = imgUtils.get_image_size('/path/to/your/image.png');
Of course, you must also execute this script file with the same Docker image.
Appendix: Compile OpenCV with NodeJS Docker Standard Image
If you prefer using standard images like node:20.17.0
instead of slim images like node:20.17.0-alpine
.
Here is an example of 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