{ } qjs-opencv
QuickJS × OpenCV

Live-code OpenCV pipelines from a REPL.

qjs-opencv binds OpenCV into QuickJS as a single loadable module: Mat, contours, imgproc, calib3d, dnn and video I/O, with no copies across the JS/C++ boundary and no build step between an edit and a frame on screen.

import * as cv from 'opencv';

let mat = new cv.Mat();
let cap = new cv.VideoCapture(0);

for(;;) {
  cap.read(mat);

  let gray = new cv.Mat();
  cv.cvtColor(mat, gray, cv.COLOR_BGR2GRAY);
  cv.Canny(gray, gray, 50, 150);

  let contours = new cv.MatVector();
  cv.findContours(gray, contours, new cv.Mat(),
                  cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE);
  cv.drawContours(mat, contours, -1, new cv.Scalar(0, 255, 0), 2);

  cv.imshow('qjs-opencv', mat);
  if(cv.waitKey(20) == 27) break;
}

Why it exists

A C++ vision library and a very small interpreter, joined so that image data never has to be copied to cross between them.

No copies

A cv.Mat is backed by a real cv::Mat. Pixel data is reached through typed-array views (.data, .data32F, …) over the same buffer, and iteration yields Float64Array views rather than fresh objects. Finalizers do the freeing — no manual delete() discipline, though .delete() exists so opencv.js snippets paste in unchanged.

Interchangeable arrays

Anything taking cv::InputArray accepts a cv.Mat, a cv.Contour, a MatVector, a plain array or a TypedArray. The unwrapping happens once, in the binding layer, so call sites stay short.

opencv.js-shaped

The API tracks the official Emscripten opencv.js surface — same function names, same argument order, the same vector containers — so tutorial code and StackOverflow answers transfer over. Divergences are tracked as bugs, not accepted as design.

Native speed, script ergonomics

ES2020 modules, no bundler, no transpiler, no wasm. The module loads into qjsm in milliseconds, so the edit-run loop is a keystroke — and it cross-compiles to aarch64, mingw64 and wasm.

What's bound

Confirmed against the C++ sources under js_*.cpp and exercised by the tests.

Core & imgproc

Mat, UMat, Contour, Point, Rect, RotatedRect, Size, KeyPoint, Matx, Affine3 and their iterators — plus the classic pipeline: Canny, findContours, HoughLines, cvtColor, threshold, the blur/morphology families, warpAffine, contour metrics, watershed, grabCut, calcHist.

Beyond imgproc

calibrateCamera, findHomography, the fisheye::* set, VideoCapture/VideoWriter, the MOG2/KNN/bgsegm background subtractors, feature2d detectors and matchers, and dnnreadNet*, blobFromImage, NMSBoxes. Text rendering goes through FreeType. On Raspberry Pi, the camera comes in directly as a cv.Mat through LCCV or libcamera-opencv — no V4L2 VideoCapture round-trip.

Custom, non-OpenCV extras — skeletonization, line tracing and palette reduction — live under Custom algorithms. What isn't bound yet is tracked in the binding roadmap.

Get it running

git clone --recursive https://github.com/rsenn/qjs-opencv
cd qjs-opencv

. ./cfg.sh
prefix=/usr/local TYPE=Release cfg \
  -DOpenCV_DIR=/opt/opencv-4.7.0-x86_64/lib/cmake/opencv4
make -C build/x86_64-linux-gnu -j$(nproc)

QUICKJS_MODULE_PATH=$PWD/build/x86_64-linux-gnu qjsm tests/unittests/test_imgproc.js

Needs OpenCV 4.2 or newer and qjsm from rsenn/quickjs. Full option matrix, cross-compilation and build flavours are in Getting started.