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.