{ } 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 dnn — readNet*, 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.