facenet-js
Version:
Face detection and recognition library using TensorFlow.js and MediaPipe along with helpful React hooks and components
1 lines • 13.8 kB
Source Map (JSON)
{"version":3,"file":"FaceDetector-FhIAuqGI.cjs","sources":["../src/FaceDetector.ts"],"sourcesContent":["import {\n Detection,\n FaceDetector as FaceDetectorMediaPipe,\n FilesetResolver,\n ImageEmbedder,\n} from '@mediapipe/tasks-vision';\nimport {\n Embedding,\n EmbeddingRequest,\n EmbeddingResult,\n FaceDetection,\n FaceDetectionOptions,\n FaceDetectorState\n} from './types';\n\nconst DEFAULT_DETECTION_MODEL =\n 'https://storage.googleapis.com/mediapipe-models/face_detector/blaze_face_short_range/float16/1/blaze_face_short_range.tflite';\nconst DEFAULT_WASM_PATH = 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/wasm';\n\n/**\n * A class for detecting and embedding faces.\n * \n * @example\n * ```ts\n * const faceDetector = new FaceDetector({\n * device: 'GPU',\n * mode: 'IMAGE',\n * minDetectionConfidence: 0.5,\n * });\n * await faceDetector.initialize();\n * const detections = faceDetector.detectFromImage(imageElement);\n * const embeddings = faceDetector.embed({\n * source: imageElement,\n * detection: detections[0],\n * });\n * const similarity = FaceDetector.cosineSimilarity(embeddings[0], embeddings[1]);\n * if (similarity > 0.5) {\n * console.log('The faces are similar');\n * } else {\n * console.log('The faces are different');\n * }\n * ```\n */\nexport class FaceDetector {\n private faceDetector: FaceDetectorMediaPipe | null = null;\n private faceEmbedder: ImageEmbedder | null = null;\n private options: FaceDetectionOptions;\n private _state: FaceDetectorState = 'not_initialized';\n private _error: Error | null = null;\n\n constructor(options: FaceDetectionOptions) {\n this.options = options;\n }\n\n get state() {\n return this._state;\n }\n\n get error() {\n return this._error;\n }\n\n async initialize() {\n if (this.state === 'initialized') {\n return;\n }\n if (this.state === 'initializing') {\n return;\n }\n\n try {\n this._state = 'initializing';\n this._error = null;\n const vision = await FilesetResolver.forVisionTasks(\n this.options.wasmPath || DEFAULT_WASM_PATH\n );\n\n if (this.options.embeddingModelPath) {\n // create face detector and face embedder in parallel\n const [faceDetector, faceEmbedder] = await Promise.all([\n FaceDetectorMediaPipe.createFromOptions(vision, {\n baseOptions: {\n modelAssetPath: this.options.detectionModelPath || DEFAULT_DETECTION_MODEL,\n delegate: this.options.device,\n },\n runningMode: this.options.mode,\n minDetectionConfidence: this.options.minDetectionConfidence,\n }),\n ImageEmbedder.createFromOptions(vision, {\n baseOptions: {\n modelAssetPath: this.options.embeddingModelPath,\n delegate: this.options.device,\n },\n runningMode: this.options.mode,\n })\n ]);\n\n this.faceDetector = faceDetector;\n this.faceEmbedder = faceEmbedder;\n } else {\n // Create face detector but not face embedder if no embedding model path\n this.faceDetector = await FaceDetectorMediaPipe.createFromOptions(vision, {\n baseOptions: {\n modelAssetPath: this.options.detectionModelPath || DEFAULT_DETECTION_MODEL,\n delegate: this.options.device,\n },\n runningMode: this.options.mode,\n minDetectionConfidence: this.options.minDetectionConfidence,\n });\n }\n\n this._state = 'initialized';\n } catch (error) {\n this._state = 'error';\n this._error = error instanceof Error ? error : new Error(String(error));\n throw error;\n }\n }\n\n /**\n * Detects faces from an image element.\n * \n * @example\n * ```ts\n * const detections = faceDetector.detectFromImage(imageElement);\n * if (detections.length > 0) {\n * console.log('The image contains ' + detections.length + ' faces');\n * } else {\n * console.log('The image does not contain any faces');\n * }\n * ```\n * @param imageElement - The image element to detect faces from.\n * @returns The detections. If no faces are detected, returns an empty array.\n */\n detectFromImage(imageElement: HTMLImageElement): FaceDetection[] {\n if (this.state !== 'initialized') {\n throw new Error('Face detector not initialized');\n }\n return this.faceDetector?.detect(imageElement).detections ?? [];\n }\n\n /**\n * Detects faces from a video element.\n * \n * @example\n * ```ts\n * const detections = faceDetector.detectFromVideo(videoElement, timestamp);\n * if (detections.length > 0) {\n * console.log('The video contains ' + detections.length + ' faces at timestamp ' + timestamp);\n * } else {\n * console.log('The video does not contain any faces');\n * }\n * ```\n * @param videoElement - The video element to detect faces from.\n * @param timestamp - The timestamp of the video element.\n * @returns The detections.\n */\n detectFromVideo(\n videoElement: HTMLVideoElement,\n timestamp: number\n ): FaceDetection[] {\n if (this.state !== 'initialized') {\n throw new Error('Face detector not initialized');\n }\n const detections = this.faceDetector?.detectForVideo(videoElement, timestamp);\n return detections?.detections ?? [];\n }\n\n /**\n * Validates and normalizes a bounding box.\n * @param bbox - The bounding box to validate and normalize.\n * @param mediaWidth - The width of the media.\n * @param mediaHeight - The height of the media.\n * @returns The normalized bounding box.\n */\n private validateAndNormalizeRegion(\n bbox: NonNullable<Detection['boundingBox']>,\n mediaWidth: number,\n mediaHeight: number\n ) {\n const isNormalized =\n bbox.originX <= 1 && bbox.originY <= 1 && bbox.width <= 1 && bbox.height <= 1;\n let left, top, right, bottom;\n if (isNormalized) {\n left = bbox.originX;\n top = bbox.originY;\n right = bbox.originX + bbox.width;\n bottom = bbox.originY + bbox.height;\n } else {\n left = bbox.originX / mediaWidth;\n top = bbox.originY / mediaHeight;\n right = (bbox.originX + bbox.width) / mediaWidth;\n bottom = (bbox.originY + bbox.height) / mediaHeight;\n }\n left = Math.max(0, Math.min(1, left));\n top = Math.max(0, Math.min(1, top));\n right = Math.max(0, Math.min(1, right));\n bottom = Math.max(0, Math.min(1, bottom));\n if (right <= left || bottom <= top) {\n throw new Error('Invalid bounding box dimensions');\n }\n return { left, top, right, bottom };\n }\n\n /**\n * Embeds a detected face into a tensor.\n * The resulting tensor can be then compared to other embeddings using cosine similarity or other distance metrics.\n * \n * @example\n * ```ts\n * const result = imageFaceDetector.embed({\n * source: imageElement,\n * detection: faceDetectedFromImageElement,\n * });\n * \n * const result2 = videoFaceDetector.embed({\n * source: videoElement,\n * detection: faceDetectedFromVideoElement,\n * timestamp: performance.now()\n * });\n *\n * const similarity = FaceDetector.cosineSimilarity(result.embeddings[0], result2.embeddings[0]);\n * if (similarity > 0.5) {\n * console.log('The faces are similar');\n * } else {\n * console.log('The faces are different');\n * }\n * ```\n * @param request - The request for embedding a face.\n * @returns The embedded face tensor or null if no bounding box is found.\n * @throws Error if face embedder is not initialized (embeddingModelPath not provided)\n */\n embed(request: EmbeddingRequest): EmbeddingResult | null {\n if (this.state !== 'initialized') {\n throw new Error('Face detector not initialized');\n }\n\n if (!this.faceEmbedder) {\n throw new Error(\n 'Face embedder not initialized. Please provide embeddingModelPath in options.'\n );\n }\n\n const { source, detection, timestamp } = request;\n const bbox = detection.boundingBox;\n if (!bbox) {\n console.warn('No bounding box found for embedding');\n return null;\n }\n let mediaWidth: number, mediaHeight: number;\n if (source instanceof HTMLImageElement) {\n mediaWidth = source.naturalWidth;\n mediaHeight = source.naturalHeight;\n if (this.options.mode !== 'IMAGE') {\n throw new Error('Cannot embed image in video mode');\n }\n const regionOfInterest = this.validateAndNormalizeRegion(bbox, mediaWidth, mediaHeight);\n return this.faceEmbedder.embed(source, {\n regionOfInterest,\n rotationDegrees: bbox.angle,\n });\n } else if (source instanceof HTMLVideoElement) {\n mediaWidth = source.videoWidth;\n mediaHeight = source.videoHeight;\n if (this.options.mode !== 'VIDEO') {\n throw new Error('Cannot embed video in image mode');\n }\n const regionOfInterest = this.validateAndNormalizeRegion(bbox, mediaWidth, mediaHeight);\n return this.faceEmbedder.embedForVideo(source, timestamp ?? performance.now(), {\n regionOfInterest,\n rotationDegrees: bbox.angle,\n });\n }\n\n throw new Error('Invalid source type');\n }\n\n /**\n * Computes the cosine similarity between two embeddings.\n * The cosine similarity score is between -1 and 1, where 1 means the embeddings are identical, and -1 means they are completely different.\n * \n * @example\n * ```ts\n * const similarity = FaceDetector.cosineSimilarity(faceEmbedding1, faceEmbedding2);\n * if (similarity > 0.5) {\n * console.log('The embeddings are similar');\n * } else {\n * console.log('The embeddings are different');\n * }\n * ```\n * @param a - The first embedding.\n * @param b - The second embedding.\n * @returns The cosine similarity score.\n */\n static cosineSimilarity(a: Embedding, b: Embedding): number {\n return ImageEmbedder.cosineSimilarity(a, b);\n }\n}\n"],"names":["FilesetResolver","FaceDetectorMediaPipe","ImageEmbedder"],"mappings":"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"}