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:"

  1. 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.

  2. Use --virtual .build-dependencies in apk command.

    This can remove all build-time dependencies after compilation, to reduce the final image size.

  3. 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).

  4. 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
comments powered by Disqus

Translations: