Skip to content

Commit 913bc27

Browse files
committed
add face detection and eyes detection
1 parent 082e162 commit 913bc27

File tree

12 files changed

+34111
-14
lines changed

12 files changed

+34111
-14
lines changed

README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,42 @@ See: [How to extend ppu-ocv operations](./docs/how-to-extend-ppu-ocv-operations.
165165
| `getCornerPoints` | Canvas, contour | Get four corner points for a given contour. Useful for perspective transformation (warp). |
166166
| `destroy` | | Destroy & clean-up the memory from the contours |
167167

168+
#### `FaceDetector`
169+
170+
Singleton class for face and eye detection using Haar Cascade classifiers.
171+
172+
| Method | Args | Description |
173+
| ------------- | --------------------------- | ---------------------------------------------------------------------- |
174+
| `getInstance` | | Get the singleton instance of FaceDetector |
175+
| `detectFace` | Canvas, options | Detect faces in the given canvas, returns `{ faces: BoundingBox[] }` |
176+
| `detectEye` | Canvas, options | Detect eyes in the given canvas, returns `{ eyes: { left, right }[] }` |
177+
| `alignFace` | Canvas, [leftEye, rightEye] | Align face based on eye positions, returns aligned Canvas |
178+
179+
**Usage Example:**
180+
181+
```ts
182+
import { FaceDetector, ImageProcessor } from "ppu-ocv";
183+
184+
await ImageProcessor.initRuntime();
185+
const file = Bun.file("./image.jpg");
186+
const canvas = await ImageProcessor.prepareCanvas(await file.arrayBuffer());
187+
188+
const faceDetector = await FaceDetector.getInstance();
189+
190+
// Detect faces
191+
const faceResult = await faceDetector.detectFace(canvas);
192+
console.log(`Found ${faceResult.faces.length} faces`);
193+
194+
// Detect eyes
195+
const eyeResult = await faceDetector.detectEye(canvas);
196+
197+
// Align face if eyes detected
198+
if (eyeResult.eyes.length > 0) {
199+
const { left, right } = eyeResult.eyes[0];
200+
const alignedCanvas = await faceDetector.alignFace(canvas, [left, right]);
201+
}
202+
```
203+
168204
#### `ImageAnalysis`
169205

170206
Just a collection of utility functions for analyzing image properties.

assets/haaland-face.png

73.7 KB
Loading

assets/komeng-person.jpg

69.2 KB
Loading

examples/README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Examples
2+
3+
This directory contains example scripts demonstrating various features of ppu-ocv.
4+
5+
## Running Examples
6+
7+
All examples can be run using Bun:
8+
9+
```bash
10+
bun run examples/<example-name>.ts
11+
```
12+
13+
## Available Examples
14+
15+
### 1. Face Detection and Alignment
16+
17+
**File:** `face-detection-and-alignment.ts`
18+
19+
Demonstrates how to:
20+
- Detect faces in an image using Haar Cascade classifiers
21+
- Detect eyes with automatic fallback to eyeglasses detection
22+
- Align faces based on eye positions
23+
24+
**Usage:**
25+
```bash
26+
bun run examples/face-detection-and-alignment.ts
27+
```
28+
29+
**Output:**
30+
- `out/0. 1-detected-faces.png` - Original image with detected faces marked in red
31+
- `out/1. 2-detected-eyes.png` - Image with detected eyes (left in green, right in blue)
32+
- `out/2. 3-aligned-face.png` - Face aligned based on eye positions
33+
34+
**Key Features:**
35+
- Uses `FaceDetector.getInstance()` singleton pattern
36+
- Demonstrates face detection with `detectFace()`
37+
- Shows eye detection with `detectEye()`
38+
- Illustrates face alignment with `alignFace()`
39+
- Generates debug images with bounding boxes
40+
41+
### 2. Perspective Correction with Debug
42+
43+
**File:** `perspective-correction-with-debug.ts`
44+
45+
Demonstrates document scanning and perspective correction using contour detection and warping.
46+
47+
**Usage:**
48+
```bash
49+
bun run examples/perspective-correction-with-debug.ts
50+
```
51+
52+
## Output Directory
53+
54+
All examples save their output to the `out/` directory in the project root. This directory is automatically created if it doesn't exist.
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import { CanvasToolkit, FaceDetector, ImageProcessor } from "../src/index";
2+
3+
/**
4+
* Example: Face Detection and Alignment
5+
*
6+
* This example demonstrates:
7+
* 1. Detecting faces in an image
8+
* 2. Detecting eyes in the detected face
9+
* 3. Aligning the face based on eye positions
10+
*/
11+
12+
const file = Bun.file("./assets/komeng-person.jpg");
13+
const image = await file.arrayBuffer();
14+
const DEBUG_FOLDER = "out";
15+
16+
let canvas = await ImageProcessor.prepareCanvas(image);
17+
let originalCanvas = canvas;
18+
19+
await ImageProcessor.initRuntime();
20+
21+
const canvasToolkit = CanvasToolkit.getInstance();
22+
const faceDetector = await FaceDetector.getInstance();
23+
24+
canvasToolkit.clearOutput(DEBUG_FOLDER);
25+
26+
// Step 1: Detect faces
27+
console.log("\n--- Step 1: Detecting Faces ---");
28+
const faceResult = await faceDetector.detectFace(canvas);
29+
console.log(`Found ${faceResult.faces.length} face(s)`);
30+
31+
if (faceResult.faces.length === 0) {
32+
console.log("No faces detected. Exiting.");
33+
process.exit(0);
34+
}
35+
36+
// Draw detected faces on the original canvas
37+
let ctx = canvas.getContext("2d");
38+
for (const face of faceResult.faces) {
39+
console.log(
40+
` Face at: (${face.x0}, ${face.y0}) to (${face.x1}, ${face.y1})`,
41+
);
42+
canvasToolkit.drawLine({
43+
ctx,
44+
x: face.x0,
45+
y: face.y0,
46+
width: face.x1 - face.x0,
47+
height: face.y1 - face.y0,
48+
color: "red",
49+
lineWidth: 3,
50+
});
51+
}
52+
53+
await canvasToolkit.saveImage({
54+
canvas,
55+
filename: "detected-faces",
56+
path: DEBUG_FOLDER,
57+
});
58+
console.log("Saved: out/detected-faces.png");
59+
60+
canvas = canvasToolkit.crop({
61+
canvas: originalCanvas,
62+
bbox: faceResult.faces[0],
63+
});
64+
ctx = canvas.getContext("2d");
65+
let eyeCanvas = canvas;
66+
67+
await canvasToolkit.saveImage({
68+
canvas,
69+
filename: "face-cropped",
70+
path: DEBUG_FOLDER,
71+
});
72+
console.log("Saved: out/face-cropped.png");
73+
74+
// Step 2: Detect eyes
75+
console.log("\n--- Step 2: Detecting Eyes ---");
76+
// Adjust detection parameters for cropped face (smaller image requires smaller minSize)
77+
const eyeResult = await faceDetector.detectEye(canvas, {
78+
scaleFactor: 1.05, // More sensitive scale factor for smaller faces
79+
minNeighbors: 3, // Lower threshold for detection
80+
minSize: { width: 10, height: 10 }, // Much smaller minimum size for cropped face
81+
});
82+
console.log(`Found ${eyeResult.eyes.length} eye pair(s)`);
83+
84+
if (eyeResult.eyes.length === 0) {
85+
console.log("No eyes detected. Cannot align face.");
86+
process.exit(0);
87+
}
88+
89+
// Draw detected eyes
90+
for (const eyePair of eyeResult.eyes) {
91+
console.log(
92+
` Left eye: (${eyePair.left.x0}, ${eyePair.left.y0}) to (${eyePair.left.x1}, ${eyePair.left.y1})`,
93+
);
94+
console.log(
95+
` Right eye: (${eyePair.right.x0}, ${eyePair.right.y0}) to (${eyePair.right.x1}, ${eyePair.right.y1})`,
96+
);
97+
98+
// Draw left eye in green
99+
canvasToolkit.drawLine({
100+
ctx,
101+
x: eyePair.left.x0,
102+
y: eyePair.left.y0,
103+
width: eyePair.left.x1 - eyePair.left.x0,
104+
height: eyePair.left.y1 - eyePair.left.y0,
105+
color: "green",
106+
lineWidth: 2,
107+
});
108+
109+
// Draw right eye in blue
110+
canvasToolkit.drawLine({
111+
ctx,
112+
x: eyePair.right.x0,
113+
y: eyePair.right.y0,
114+
width: eyePair.right.x1 - eyePair.right.x0,
115+
height: eyePair.right.y1 - eyePair.right.y0,
116+
color: "blue",
117+
lineWidth: 2,
118+
});
119+
}
120+
121+
await canvasToolkit.saveImage({
122+
canvas,
123+
filename: "detected-eyes",
124+
path: DEBUG_FOLDER,
125+
});
126+
console.log("Saved: out/detected-eyes.png");
127+
128+
// Step 3: Align face based on first eye pair
129+
console.log("\n--- Step 3: Aligning Face ---");
130+
const firstEyePair = eyeResult.eyes[0]!;
131+
132+
// Calculate eye centers for display
133+
const leftEyeCenter = {
134+
x: (firstEyePair.left.x0 + firstEyePair.left.x1) / 2,
135+
y: (firstEyePair.left.y0 + firstEyePair.left.y1) / 2,
136+
};
137+
const rightEyeCenter = {
138+
x: (firstEyePair.right.x0 + firstEyePair.right.x1) / 2,
139+
y: (firstEyePair.right.y0 + firstEyePair.right.y1) / 2,
140+
};
141+
142+
const deltaX = rightEyeCenter.x - leftEyeCenter.x;
143+
const deltaY = rightEyeCenter.y - leftEyeCenter.y;
144+
const angle = Math.atan2(deltaY, deltaX) * (180 / Math.PI);
145+
146+
console.log(` Eye angle: ${angle.toFixed(2)}°`);
147+
console.log(` Rotating to align eyes horizontally...`);
148+
149+
// Reload original image for alignment (without drawn rectangles)
150+
const alignedCanvas = await faceDetector.alignFace(eyeCanvas, [
151+
firstEyePair.left,
152+
firstEyePair.right,
153+
]);
154+
155+
await canvasToolkit.saveImage({
156+
canvas: alignedCanvas,
157+
filename: "aligned-face",
158+
path: DEBUG_FOLDER,
159+
});
160+
console.log("Saved: out/aligned-face.png");
161+
162+
console.log("\n✅ Complete! Check the 'out' directory for results.");
163+
console.log("\nGenerated files:");
164+
console.log(" - detected-faces.png (faces marked with red rectangles)");
165+
console.log(" - detected-eyes.png (left eye in green, right eye in blue)");
166+
console.log(" - aligned-face.png (face aligned based on eye positions)");

jsr.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@snowfluke/ppu-ocv",
3-
"version": "1.4.0",
3+
"version": "1.5.0",
44
"license": "MIT",
55
"exports": "./src/index.ts",
66
"publish": {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ppu-ocv",
3-
"version": "1.4.0",
3+
"version": "1.5.0",
44
"description": "A type-safe, modular, chainable image processing library built on top of OpenCV.js with a fluent API leveraging pipeline processing.",
55
"keywords": [
66
"open-cv",

0 commit comments

Comments
 (0)