Ben's notes

Custom Opencv WASM With Any Frontend Libray (ESM Imports)

If you especially had issues with TechStark’s opencv-js repo read on. Also, this works great with vite.

For convenience here’s a list of useful links on the subject:

  • Official section on the opencv docs website
    • The docs in general are out of date and wrong in many places. Also there is zero documentation for the javascript bindings or even official types to guide you. It’s left up to you to figure out what works and what doesn’t with trial and error.
  • Official build docs
    • May be useful in some places but I recommend following my instructions below.
  • Lambda labs build instructions:
    • This is out of date but was still useful to fill in some gaps.

1. Build process (works on every OS)

Install docker then run the following commands:

Option A: One line docker build (quick):

Setup (All OSs). Requires NPM be installed. Uses ghex to make this fast as it’s a big repo:

npx ghex opencv/opencv -d opencv
cd opencv

Build (MacOS / Linux):

docker run --rm -v $(pwd):/src -u $(id -u):$(id -g) emscripten/emsdk emcmake python3 ./platforms/js/build_js.py --clean_build_dir --disable_single_file --build_wasm --build_flags=" -s MODULARIZE=1 -s EXPORT_ES6=1 -s IGNORE_MISSING_MAIN=1 -s MAXIMUM_MEMORY=2147483648 -s ENVIRONMENT=web " cust_build

Build (Windows, powershell):

docker run --rm --workdir /src -v "$(get-location):/src" "emscripten/emsdk" emcmake python3 ./platforms/js/build_js.py --clean_build_dir --disable_single_file --build_wasm --build_flags=" -s MODULARIZE=1 -s EXPORT_ES6=1 -s IGNORE_MISSING_MAIN=1 -s MAXIMUM_MEMORY=2147483648 -s ENVIRONMENT=web " cust_build

In the folder build_js/bin/opencv.js you will see the following files (the rest are unimportant). Save just these. These are the files we want:

opencv_js.js
opencv_js.wasm

Option B: Minimal 3 MB build (advanced):

This option allows more control over the size of the bundle. To start:

  1. mount a local folder into the docker container (or use a vscode devcontainer—I used the basic ubuntu image) to save the build to.
  2. Run the following commands inside the container:

Prerequisites:

sudo apt install git cmake python

Set up emscripten’s sdk:

mkdir customopencv
cd customopencv/
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk/
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh

Opencv (using nano to edit the file):

git clone https://github.com/opencv/opencv.git
nano opencv/platforms/js/build_js.py

In this file (here’s a link to its source) find the def get_cmake_cmd(self): function. The first part of it looks like this (comments removed):

def get_cmake_cmd(self):
        cmd = [
            "cmake",
            "-DPYTHON_DEFAULT_EXECUTABLE=%s" % sys.executable,
               "-DENABLE_PIC=FALSE",
               "-DCMAKE_BUILD_TYPE=Release",
               "-DCPU_BASELINE=''",
               
	   # .......... continues (too big to paste it all here)

To get to 3 MB I changed the following to =OFF:

               "-DBUILD_opencv_calib3d=OFF",
               "-DBUILD_opencv_dnn=OFF",
               "-DBUILD_opencv_features2d=OFF",
               "-DBUILD_opencv_flann=OFF", 
               "-DBUILD_opencv_photo=OFF",

I found the java based docs to have the best UI and descriptions for what each of these do. E.g. “flan” (whatever that is).

Then I ran this command in the customopencv created before:

emcmake python ./opencv/platforms/js/build_js.py --clean_build_dir --disable_single_file --build_wasm --build_flags=" -s MODULARIZE=1 -s EXPORT_ES6=1 -s IGNORE_MISSING_MAIN=1 -s MAXIMUM_MEMORY=2147483648 -s ENVIRONMENT=web " ${PWD}/cust_build

In the folder build_js/bin/opencv.js you will see the following files (the rest are unimportant). Save just these. These are the files we want:

opencv_js.js
opencv_js.wasm

2. Use in javascript / typescript

TechStark has a repo that provides opencv in a way that I believe can be imported using ES import syntax (e.g.: import cv from ./opencv.js). I haven’t tested it because it requires applying an unnecessary patch to the js output from the build and setting up some manual way of waiting for the wasm to be ready to run. Emscripten has all of this built in and if you followed the steps above using opencv in javascript / typescript is as simple as:

  1. Adding the files opencv_js.js and opencv_js.wasm to your project (I added these to the $lib folder in sveltekit but it should work with any frontend framework):
  2. Importing from the javascript directly:
import cvLoader from "./opencv_js.js";
import type CV from "@techstark/opencv-js";
const cv: CV = await cvLoader();

And that’s it. cvLoader resolves with an instance of opencv when the wasm has been downloaded and initialised so opencv is ready to use.

I am using just the types from the @techstark/opencv-js package and with the import type statement nothing from that module will end up in my final bundle (I’m using vite via sveltekit).