System designTroubleshooting

OpenCV.js cascade file load in Node: mount NODEFS first

Fix the OpenCV.js cascade file load error in Node by mounting your working directory into the WebAssembly filesystem with NODEFS, then chdir and load.

LumaSpec Vision8 min read Share on LinkedIn

On this page 11 sections
  1. 01The symptom and what it tells you
  2. 02Why a valid Node path still fails
  3. 03The fix: mount NODEFS, then chdir
  4. 04Prove the load worked
  5. 05The chdir shortcut and errno 44
  6. 06The mount aborts: you have the browser build
  7. 07Feeding the classifier: where frames come from
  8. 08When this fix is the wrong fix
  9. 09Symptom to action at a glance
  10. 10Prevention checklist
  11. 11Questions

You load OpenCV.js into a Node process, point CascadeClassifier at lib/haarcascade_frontalface_default.xml, and the console answers with Can't open file ... in read mode from persistence.cpp — while fs.readFileSync opens the very same path without complaint. The path is correct; the filesystem it resolves against is not. OpenCV.js is a WebAssembly module carrying its own private, in-memory filesystem, and your Node paths never reach it. This guide walks the failure from symptom to fix: mount your real working directory into that virtual filesystem with NODEFS, change the module's working directory, and load the cascade again. It also covers the errno 44 FS error from cv.FS.chdir(process.cwd()), the abort you get when the browser build runs in Node, and where the frames for the classifier should come from.

The symptom and what it tells you

The console line looks like this:

[ERROR:0@0.074] global /build/master-contrib_docs-lin64/opencv/modules/core/src/persistence.cpp (505) open Can't open file: 'lib/haarcascade_frontalface_default.xml' in read mode

Three call forms produce the identical line: new cv.CascadeClassifier("lib/haarcascade_frontalface_default.xml"), the same with path.resolve(...) in front, and new cv.CascadeClassifier().load("lib/haarcascade_frontalface_default.xml"). Meanwhile readFileSync("lib/haarcascade_frontalface_default.xml") returns the file's bytes. That pair of facts is the whole diagnosis: the file exists on the real disk, and the loader is not looking at the real disk.

There is one more clue. The module exposes Emscripten file hooks such as cv.FS_createLazyFile on its cv.FS object. The filesystem layer is reachable from your script — and that is exactly where the repair belongs.

Why a valid Node path still fails

Emscripten compiles OpenCV into WebAssembly, and every file the module opens — a cascade XML included — goes through the module's own filesystem, an in-memory tree that starts essentially empty. The persistence layer calls its open inside that sandbox, where lib/ and any absolute host path simply do not exist. Node's fs module, meanwhile, reads the real disk. One string, two filesystems: that is why all three load forms fail identically and why path.resolve changes nothing — it produces a perfectly correct path to a directory the sandbox has never seen.

Model-load path failures are not an OpenCV.js quirk. Native stacks hit the same wall at deployment time, as machine vision model file paths and deployment errors shows, and acquisition scripts hit it across operating systems, as Python image file path errors across operating systems records. The OpenCV.js twist is harsher: the file exists and the process can read it, and only the sandbox cannot.

The fix: mount NODEFS, then chdir

Mount the real working directory into the virtual filesystem, point the module's working directory at the mount, then load the same relative path as before.

  1. Keep a reference to the module's filesystem object.
  2. Create the mount point inside the virtual filesystem.
  3. Mount your real working directory onto it with the NODEFS backend.
  4. Change the module's working directory to the mount point.
  5. Construct the classifier with the same relative path.
// cv is the OpenCV.js module, already loaded. runDetection() further down is
// your own function; every other name here is OpenCV.js / Emscripten API.
const emscriptenFs = cv.FS;              // Emscripten filesystem object
const virtualMountFolder = "/local";     // string: path inside the virtual filesystem

emscriptenFs.mkdir(virtualMountFolder);  // the mount point must exist first
emscriptenFs.mount(
  emscriptenFs.filesystems.NODEFS,       // Node-backed backend, reads the real disk
  { root: process.cwd() },               // the real directory to expose
  virtualMountFolder
);
emscriptenFs.chdir(virtualMountFolder);  // relative paths now resolve from /local

const faceClassifier = new cv.CascadeClassifier("lib/haarcascade_frontalface_default.xml");

From this point every file under process.cwd() is reachable from inside the module. The relative path lib/haarcascade_frontalface_default.xml resolves to /local/lib/haarcascade_frontalface_default.xml in the virtual tree, and NODEFS maps that straight onto your disk. Run the script again: the persistence.cpp line disappears and the constructor is silent.

Prove the load worked

A silent constructor is necessary but not sufficient, because a failed load does not throw at construction — it surfaces later, on the first detection. Guard that call once:

// Test: one detection pass, run exactly as production runs it.
// detectMultiScale() over an empty cascade throws instead of returning
// detections, so the first call tells you whether the file really loaded.
try {
  runDetection(faceClassifier, frame);   // your function wrapping detectMultiScale()
  console.log("cascade loaded and ran");
} catch (err) {
  console.error("cascade never loaded:", err);
}

If the catch fires, the cascade object is empty and the load path is still wrong — walk the mount steps again. If the success line prints, the mount holds for the process lifetime. Mount once at startup, never per request.

The chdir shortcut and errno 44

The OpenCV Node examples load cascades with a bare relative path and appear to mount nothing. They get away with it because the module's working directory already lines up with the host's — in practice through cv.FS.chdir(process.cwd()) — and that only works when the directory process.cwd() returns exists inside the virtual filesystem. On builds where it does not, chdir throws an object rather than an Error:

{ node: undefined, setErrno: [Function (anonymous)], errno: 44, message: 'FS error' }

Treat the bare chdir as unreliable across builds. The mount creates /local first, so chdir into it always succeeds. That is the dependable form, and it is the one to ship.

The mount aborts: you have the browser build

If emscriptenFs.mount(...) dies with:

ERROR abort(Assertion failed: undefined). Build with -s ASSERTIONS=1 for more info.

the backend you passed is undefined: this bundle ships no cv.FS.filesystems.NODEFS. NODEFS is the Node-only backend; browser builds exclude it, and a browser has no disk to mount regardless. Decide by environment. In Node, load the Node build of OpenCV.js so NODEFS is present. In a browser, serve the XML from your web server and load it by its URL, or fetch its bytes into the virtual filesystem before constructing the classifier.

Feeding the classifier: where frames come from

The cascade needs frames, and in this stack they do not come through OpenCV's own capture — why OpenCV VideoCapture cannot open a GigE Vision camera explains the interface reason. Grab frames from the camera SDK and hand the pixels over as buffers; barcode image buffer decoding covers keeping those pixels intact through the decode. Buffer-fed frames go straight into a Mat and never touch the virtual filesystem, so the mount matters for the cascade file, not for the image stream.

Frames for a host-side detector come from area scan cameras over GigE or USB3.

Area scan camerasHikrobot MV-CU020-90GC 2.5 MP Area Scan Camera, GigE, GMAX4002, ColorMV-CU020-90GC
Sensor format
1/1.7"
Lens mount
C-mount
$309.00View part
Area scan cameras12 MP USB3 area scan camera, IMX226, 1/1.7" rolling shutter, 30 fps, 4000 × 3000 resolution (LS-CMR-0166)LS-CMR-0166
Sensor format
1/1.7"
Interface
USB3
$560.00View part

The other route removes the host from the loop. Smart cameras run the inspection on the device and report results over the network, so no cascade file exists to mount. When the deliverable is the inspection rather than OpenCV specifically, that is the simpler architecture — and once images flow, smart camera misses first images after load explains the ready gate to set on the receiving end.

Smart cameras & vision sensorsHikrobot MV-SC5060XM-08M-WBN SC5000X series Smart Camera, 6 MP, Mono, 8 mm focal length, M12-mountMV-SC5060XM-08M-WBN
Interface
GigE
Resolution
6-10 MP
Sensor format
1/2.4"
Shutter
Rolling
$1,629.00View part

When this fix is the wrong fix

Three cases where the mount will not save you:

  • readFileSync fails too. The file is genuinely missing, misnamed or unreadable. Fix that first; a mount cannot expose what the disk does not have.
  • The app is packaged or containerised. process.cwd() at runtime points where the launcher started the process, not at your project root. Mount the directory that actually contains lib/ in the deployed layout.
  • You control the host and need throughput. WebAssembly adds a filesystem layer and costs speed. A native OpenCV build reads the cascade directly, and a smart camera moves detection on-device. Keep OpenCV.js in Node for pure-JavaScript deployments where native modules are not allowed.

Symptom to action at a glance

What you see What it means Do this
Can't open file ... in read mode from persistence.cpp The path resolves inside the empty virtual filesystem Mount NODEFS at /local, chdir, reload
{ errno: 44, message: 'FS error' } from chdir That directory does not exist in the virtual filesystem Mount first, then chdir into the mount point
abort(Assertion failed: undefined) at mount NODEFS missing from the bundle — browser build Use the Node build; in a browser, load the XML by URL
First detectMultiScale throws The cascade never loaded; the failure surfaced late Re-run the mount steps and guard the first call

Prevention checklist

  • Mount once at startup, before any classifier is constructed. Every later load inherits the virtual working directory.
  • Keep cascades under one lib/ folder inside the mounted root. Relative paths after chdir stay short and independent of the host layout.
  • Log the virtual working directory at boot. One console line turns this whole failure class into a five-second answer.
  • Pin your project to the Node build of OpenCV.js. The browser bundle fails at the mount instead of the load, which costs an afternoon to trace.
  • Mount the runtime directory, not the development cwd. Packaged apps start wherever the launcher puts them.

To feed the detector, the camera finder filters by interface and resolution, and the configurator walks camera, lens and light in one pass. For a review of the whole pipeline, the contact page reaches the applications team, and further integration notes sit on the systems topic page.

Questions

Why does fs.readFileSync open the cascade file but cv.CascadeClassifier cannot?

readFileSync runs in Node's real filesystem, where lib/haarcascade_frontalface_default.xml exists. CascadeClassifier runs inside the WebAssembly module, which sees only Emscripten's in-memory virtual filesystem, and your disk was never attached to it. Every variant — the constructor with a string, path.resolve, and .load() — resolves against that empty virtual filesystem, so all of them fail identically. Mount the directory with NODEFS first and the same relative path works.

Why does cv.FS.chdir(process.cwd()) throw errno 44 'FS error'?

chdir changes the working directory inside the virtual filesystem; it creates nothing. When the directory that process.cwd() returns does not exist inside the virtual filesystem, chdir fails with the errno 44 'FS error' object instead of a normal Error. The OpenCV Node examples appear to skip mounting because their harness aligns the working directory beforehand. Mounting /local with NODEFS and chdir into it works on every Node build.

What does 'abort(Assertion failed: undefined)' at cv.FS.mount mean?

You are running the browser build of OpenCV.js in a Node-sized hole. NODEFS is the Node-only filesystem backend; a browser bundle has no cv.FS.filesystems.NODEFS, so you mount undefined and Emscripten's assertion fires. There is no host disk for a browser module to attach in the first place. Fetch the cascade's bytes into the virtual filesystem before constructing the classifier, and in Node switch to the Node build.

Should I use OpenCV.js in Node for a production inspection pipeline?

Only when native modules are off the table, such as a locked-down host where you cannot compile or install native addons. OpenCV.js is WebAssembly: slower than native OpenCV and one filesystem layer away from your disk, as this error shows. When you control the host, a native OpenCV build reads the cascade directly, and a smart camera that runs the inspection on-device removes the host-side file problem entirely.

Does the same NODEFS mount fix loading other files in OpenCV.js?

Yes. Everything the module opens by path — cascade XMLs, trained model files, calibration data, images you read from disk — goes through the same virtual filesystem. One mount of your project root at /local plus cv.FS.chdir('/local') covers them all. Data that arrives as bytes over the network or from a camera SDK and goes straight into a Mat never touches the filesystem and needs no mount.