detection-lib
Version:
> A modular JavaScript detection library for face, QR, and future detection types with a clean, unified API.
114 lines (100 loc) • 3.45 kB
JavaScript
import { Detector } from './types.js';
import { FaceDetection } from '@mediapipe/face_detection';
import ImageUrl from './image.png';
import { FACE_DETECTOR_CODES } from './faceDetectorCodes.js';
export class FaceDetector extends Detector {
constructor() {
super();
this.modelReady = false;
this.initializationPromise = null; // To track initialization state
this.faceDetector = new FaceDetection({
locateFile: (file) =>
`https://cdn.jsdelivr.net/npm/@mediapipe/face_detection/${file}`,
});
this.faceDetector.setOptions({
model: 'short',
minDetectionConfidence: 0.8,
});
this.faceDetector.onResults((results) => {
this._latestResults = results;
});
}
/**
* Initializes and warms up the FaceDetector model.
* This method must be called and awaited before calling `detect()`.
* @returns {Promise<void>}
*/
async initialize() {
if (this.modelReady) {
return;
}
if (this.initializationPromise) {
// If initialization is already in progress or complete, return the existing promise.
return this.initializationPromise;
}
// Start the initialization and store the promise
this.initializationPromise = (async () => {
try {
// console.log("FaceDetector: Starting asynchronous initialization with static image...");
const img = new Image();
img.src = ImageUrl;
await new Promise((resolve, reject) => {
img.onload = resolve;
img.onerror = (e) => {
console.error("Failed to load warm-up image:", e);
reject(new Error("Failed to load warm-up image."));
};
});
// console.log("FaceDetector: Sending static warm-up image...");
await this.faceDetector.send({ image: img });
// console.log("FaceDetector: Warm-up complete. Model is now ready.");
this.modelReady = true;
} catch (error) {
console.error("FaceDetector: Initialization failed!", error);
this.modelReady = false;
this.initializationPromise = null;
throw error;
}
})();
return this.initializationPromise;
}
/**
* @param {HTMLVideoElement|HTMLImageElement|HTMLCanvasElement} input
* @returns {Promise<{status: number, message: string, boxes: Array}>}
*/
async detect(input) {
if (!this.modelReady) {
const {code,message}= FACE_DETECTOR_CODES.NOT_INITIALIZED;
return { status: code, message, boxes: [] };
}
try{
await this.faceDetector.send({ image: input });
}catch(e){
const {code,message}= FACE_DETECTOR_CODES.MODEL_ERROR;
return { status: code, message, boxes: [] };
}
const results = this._latestResults;
const detections = results?.detections || [];
let statusObj = FACE_DETECTOR_CODES.OK;
if (detections.length === 0) {
statusObj = FACE_DETECTOR_CODES.NO_FACE_DETECTED;
} else if (detections.length > 1) {
statusObj = FACE_DETECTOR_CODES.MULTIPLE_FACES_DETECTED;
}
const boxes = detections.map((det) => {
const b = det.boundingBox;
return {
x: b.xCenter - b.width / 2,
y: b.yCenter - b.height / 2,
w: b.width,
h: b.height,
score: det.score?.[0] || undefined,
};
});
return {
status: statusObj.code,
message: statusObj.message,
boxes,
};
}
}