# HAAR.js: Viola-Jones face detection in the browser without OpenCV

> HAAR.js is a small JavaScript port of OpenCV's Haar cascade detector that runs in the browser and in Node. It is a good fit for simple face, eye or mouth detection on canvas pixels, and a poor fit for anything that needs a trained model, GPU acceleration or a maintained dependency chain.

**foo123/HAAR.js** — Feature Detection based on Haar Cascades in JavaScript (Viola-Jones-Lienhart et al Algorithm)

- Repository: https://github.com/foo123/HAAR.js
- Website: https://foo123.github.io/examples/face-detection/
- Stars: 343 · Forks: 63
- Language: JavaScript
- License: not declared
- Published: 2026-09-17 · Updated: 2026-09-17 · Language: en
- Canonical page: https://hysenlabs.com/projects/foo123-haar-js

## What HAAR.js actually solves, and for whom

The problem is narrow and old: you have image pixels in a browser or in Node, and you want rectangles around faces, eyes or mouths, without pulling in OpenCV, a native build step or a neural network runtime. HAAR.js answers that. It is a JavaScript port of OpenCV's C++ Haar detection code and of the JViolaJones Java project, implementing the Viola-Jones algorithm with the Lienhart improvements. The README states the library is around 11 kB minified and 5 kB gzipped, which is the whole pitch: a detector small enough to ship inside a page.

The audience is equally narrow. If you are building a demo, an art project, a browser toy, or a server-side pre-filter that crops candidate regions before something heavier looks at them, this is the right shape of tool. If you are building a product where detection accuracy is a selling point, this is not where you start. The algorithm family predates deep learning by more than a decade, and the library does not pretend otherwise: it detects what a cascade was trained to detect, from the angle the cascade was trained on.

## How the detector runs: cascades, canvas pixels and parallel workers

The mechanism follows the published algorithm. A cascade is a sequence of stages, each stage a set of Haar-like features evaluated over an integral image. A window is rejected as soon as one stage fails, which is why the detector is fast on mostly-empty images and slow on images full of faces. HAAR.js does not ship its own cascades. It consumes OpenCV's XML cascade files, converted ahead of time into JavaScript or JSON by two PHP tools in the cascades folder, haartojs and haartojson. The README says the .js and .json formats have exactly the same structure, so you can interchange them freely.

Pixel access is the second half of the design. In the browser the library uses HTML5 canvas; in Node it needs a canvas implementation, and the README names two alternatives, CanvasLite and node-canvas. The package.json lists canvas at ~1.6.10 as a dependency, and the README notes that node-canvas needs system dependencies documented in its own wiki. So the Node path is not a pure-JavaScript install; it is a native module with a build toolchain behind it.

For speed, the repository bundles parallel.js, and the README points at face.html and eye.html as examples of parallel computation, adding that in most cases it can be much faster. That is the documented claim, not a measured one. The README's own TODO list marks real-time browser optimization as done via parallel.js, and marks selection-limited detection (detecting a nose inside an already-detected face) as done as well.

## Installing HAAR.js and detecting a face in Node

The repository does not document an npm install line in the README; the package.json names the package HAAR.js, sets main to build/haar-detector.min.js, and declares canvas as its only dependency. The README's Node instructions point at examples/node.js, which it says covers both canvas alternatives. Because the cascade is not bundled, the first real step is converting an OpenCV XML cascade with the PHP tools in the cascades folder:

```bash
haartojs haarcascades_frontalface_alt.xml > haarcascades_frontalface_alt.js
```

The README states this produces a JavaScript file whose variable is haarcascades_frontalface_alt, and that the same name works in both the browser and Node. If you prefer JSON, the second tool writes the identical structure:

```bash
haartojson haarcascades_frontalface_alt.xml > haarcascades_frontalface_alt.json
```

With a cascade in hand, the Node example runs against a picture and prints detection rectangles. The README gives this exact output for examples/node.js:

```bash
node examples/node.js
processing the picture
[{"x":84,"y":90,"width":202,"height":202,"area":40804}]
```

What you should see is an array of objects, one per detection, each with x, y, width, height and area. Those five fields are the entire result contract. There is no confidence score and no landmark output, so downstream code has to work from the rectangle alone. The README also documents two other loading paths: plain script tags in the browser, and RequireJS, with examples/require.js showing the Node configuration that the README says would be the same in a browser.

## Where HAAR.js breaks down

The most honest limitation is inherited from the algorithm. A cascade trained on frontal faces detects frontal faces. Tilt the head, turn it in profile, and the frontal cascade stops firing; that is why OpenCV ships separate cascades for profile and for eyes, and why the README's own list of cascade sources includes an eye cascade contributed by Mar Canet. There is no rotation handling described anywhere in the README, so if your input is unaligned, you are responsible for aligning it.

The second limitation is the Node dependency chain. canvas at ~1.6.10 is a native module, and the README defers its system dependencies to an external wiki page. On a CI image or a serverless runtime that is a real cost, and the CanvasLite alternative exists precisely because that cost is annoying. Neither alternative is described in the README beyond a link.

The third is maintenance. The last push to the repository was on 2026-06-18, so the code has moved recently, but the newest release listed is 1.0.7 from 2023-10-28, and package.json still declares version 1.0.0 while the releases are tagged 1.0.5 through 1.0.7. That gap between the manifest and the release tags is worth noticing before you pin a version. The README's TODO also carries one open item: keeping up with changes in the OpenCV cascade XML format, with the note that the author will try. If OpenCV changes that format, conversion can break, and the README does not promise otherwise.

Finally, there is a succession note at the top of the README. It says the FILTER.js project includes a HaarDetector plugin which can be seen as the continuation of this project. Read that as the author's own signal about where new work is expected to land.

## FILTER.js HaarDetector and native OpenCV as the two real alternatives

The alternative the project itself names is the HaarDetector plugin inside FILTER.js. The difference is scope, not algorithm. FILTER.js is a full image and video processing library for browser and Node, and the plugin lives inside that pipeline, so you get the detector plus whatever else the library does to the frame before and after. HAAR.js is the standalone, smaller thing: 11 kB minified, one job, its own cascade-loading convention. If you already use FILTER.js, the plugin removes a second dependency and is the path the README recommends. If you do not, adding FILTER.js to get one detector is a larger commitment than adding HAAR.js.

The other alternative is not a JavaScript library at all: it is OpenCV itself, or a binding to it. The README points readers at OpenCV for cascades and at the OpenCV traincascade guide for training their own. That is the real trade. OpenCV gives you the training pipeline, more cascades, and a maintained XML format. HAAR.js gives you no native build in the browser, a tiny payload, and rectangle output. Choose based on where the detection has to run, not on which is more capable, because OpenCV is more capable on every axis except shipping a browser bundle.

There is also HAARPHP from the same author, a PHP port of the same algorithm, for the case where the server is PHP rather than Node. It shares the cascade concept but not the runtime, so it is a parallel choice rather than a migration path.

## Licence, upgrades and what to check before shipping

The licence situation is the one thing to resolve first. package.json declares "license": "Unlicense", which is a public-domain-style dedication, but the top-level repository entries list no LICENSE file, and the README does not discuss licensing at all. The repository metadata given here records the licence as unknown. Unlicense is permissive, and if the declaration is accurate there is little to comply with, but a declaration in a manifest and a licence file in the repository are different kinds of evidence. If your organisation requires a licence artifact, that gap is a question for whoever reviews dependencies, not something this article can settle.

On upgrades: the changelog lives in changelog.md, which is the place to read before moving between releases. Version numbering is inconsistent between the manifest (1.0.0) and the release tags (1.0.5, 1.0.6, 1.0.7), so pin by tag or by commit rather than trusting the manifest version. Because cascades are converted artifacts rather than library code, an upgrade can invalidate generated cascade files if the conversion tools change; regenerate them with haartojs or haartojson after upgrading rather than assuming the old output still matches. The README's open TODO about OpenCV XML format changes is the reason that matters.

## Conclusion

Adopt HAAR.js when you need a small, dependency-light detector that runs on canvas pixels in a browser or in Node with a canvas shim, and when a frontal-face, eye or mouth cascade is enough. Do not adopt it when you need rotated or profile faces, a trained model you can retrain in JavaScript, or a project whose licence and support you can point at in a procurement review: the package.json declares Unlicense, the repository has no LICENSE file at the top level, and the last push was on 2026-06-18. Before committing, verify three things: that the cascade XML you want converts cleanly with haartojs or haartojson, that your Node canvas alternative of choice installs on your platform, and that the detection rectangles from examples/node.js match what you expect on your own image.

## FAQ

### What are Haar cascades and how do they work?

A Haar cascade is a sequence of stages, each made of Haar-like features computed over an integral image, and a detection window is rejected as soon as one stage fails. HAAR.js consumes cascades in that format after converting OpenCV's XML files into JavaScript or JSON.

### How can I use Haar Cascades for face detection in OpenCV?

HAAR.js is a port of OpenCV's C++ Haar detection, and the README points at OpenCV as the source of cascade XML files. You convert a file such as haarcascades_frontalface_alt.xml with the haartojs tool, then use the resulting variable in the browser or in Node.

### What is a cascade classifier?

In this library it is the converted cascade file that drives detection: either a JavaScript file produced by haartojs or a JSON file produced by haartojson. The README states both formats have exactly the same structure, so they can be interchanged.

## Sources

- [foo123/HAAR.js on GitHub](https://github.com/foo123/HAAR.js)
- [Issues](https://github.com/foo123/HAAR.js/issues)
- [Project website](https://foo123.github.io/examples/face-detection/)
- [README](https://github.com/foo123/HAAR.js/blob/master/README.md)
- [Releases](https://github.com/foo123/HAAR.js/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/foo123-haar-js
