@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
202 lines (189 loc) • 7.56 kB
text/typescript
import {Object3D, Vector4} from 'three';
import {FaceMesh, Options, Results} from '@mediapipe/face_mesh';
import {CoreComputerVisionFaceAttribute} from './Common';
import {ParamConfig} from '../../../engine/nodes/utils/params/ParamsConfig';
import {Constructor} from '../../../types/GlobalTypes';
import {isBoolean,isNumber,isString} from '../../Type';
import {DEFAULT_POSITION} from './Data';
import {coreObjectClassFactory} from '../../geometry/CoreObjectFactory';
interface FaceTrackingObjectAttributes {
selfieMode: boolean;
maxNumFaces: number;
refineLandmarks: boolean;
minDetectionConfidence: number;
minTrackingConfidence: number;
}
const DEFAULT: FaceTrackingObjectAttributes = {
selfieMode: false,
maxNumFaces: 1,
refineLandmarks: false,
minDetectionConfidence: 0.5,
minTrackingConfidence: 0.5,
};
export function CoreComputerVisionFaceParamConfig<TBase extends Constructor>(Base: TBase) {
return class Mixin extends Base {
/** @param selfieMode */
selfieMode = ParamConfig.BOOLEAN(DEFAULT.selfieMode);
/** @param Maximum number of faces to detect */
maxNumFaces = ParamConfig.INTEGER(DEFAULT.maxNumFaces, {
range: [0, 2],
rangeLocked: [true, false],
});
/** @param Whether to further refine the landmark coordinates around the eyes and lips, and output additional landmarks around the irises by applying the Attention Mesh Model */
refineLandmarks = ParamConfig.BOOLEAN(DEFAULT.refineLandmarks);
/** @param Minimum confidence value ([0.0, 1.0]) from the face detection model for the detection to be considered successful. Default to 0.5. */
minDetectionConfidence = ParamConfig.FLOAT(DEFAULT.minDetectionConfidence, {
range: [0, 1],
rangeLocked: [true, true],
});
/** @param Minimum confidence value ([0.0, 1.0]) from the landmark-tracking model for the face landmarks to be considered tracked successfully, or otherwise face detection will be invoked automatically on the next input image. Setting it to a higher value can increase robustness of the solution, at the expense of a higher latency. Ignored if static_image_mode is true, where face detection simply runs on every image. Default to 0.5. */
minTrackingConfidence = ParamConfig.FLOAT(DEFAULT.minTrackingConfidence, {
range: [0, 1],
rangeLocked: [true, true],
});
};
}
type AllowedSource = HTMLVideoElement | HTMLCanvasElement | HTMLImageElement;
function locateFile(file: string) {
return `https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh/${file}`;
}
function attributes(object: Object3D): FaceTrackingObjectAttributes {
const coreObjectClass = coreObjectClassFactory(object);
const selfieMode = coreObjectClass.attribValue(object, CoreComputerVisionFaceAttribute.SELFIE_MODE);
const maxNumFaces = coreObjectClass.attribValue(object, CoreComputerVisionFaceAttribute.MAX_NUM_FACES);
const refineLandmarks = coreObjectClass.attribValue(object, CoreComputerVisionFaceAttribute.REFINE_LANDMARKS);
const minDetectionConfidence = coreObjectClass.attribValue(
object,
CoreComputerVisionFaceAttribute.MIN_DETECTION_CONFIDENCE
);
const minTrackingConfidence = coreObjectClass.attribValue(
object,
CoreComputerVisionFaceAttribute.MAX_TRACKING_CONFIDENCE
);
const data = {
selfieMode: isBoolean(selfieMode) ? selfieMode : DEFAULT.selfieMode,
maxNumFaces: isNumber(maxNumFaces) ? maxNumFaces : DEFAULT.maxNumFaces,
refineLandmarks: isBoolean(refineLandmarks) ? refineLandmarks : DEFAULT.refineLandmarks,
minDetectionConfidence: isNumber(minDetectionConfidence)
? minDetectionConfidence
: DEFAULT.minDetectionConfidence,
minTrackingConfidence: isNumber(minTrackingConfidence)
? minTrackingConfidence
: DEFAULT.minTrackingConfidence,
};
return data;
}
function trackerOptions(attributes: FaceTrackingObjectAttributes): Options {
return {
selfieMode: attributes.selfieMode,
maxNumFaces: attributes.maxNumFaces,
refineLandmarks: attributes.refineLandmarks,
minDetectionConfidence: attributes.minDetectionConfidence,
minTrackingConfidence: attributes.minTrackingConfidence,
};
}
function createKey(object: Object3D) {
return JSON.stringify(attributes(object));
}
interface ConvertedResult {
multiFaceLandmarks: Vector4[];
}
type ConvertedResults = ConvertedResult[];
function createConvertedResult(): ConvertedResult {
const pointsCount = DEFAULT_POSITION.length / 3;
const multiFaceLandmarks: Vector4[] = new Array(pointsCount);
for (let i = 0; i < pointsCount; i++) {
multiFaceLandmarks[i] = new Vector4();
}
return {
multiFaceLandmarks,
};
}
function updateConvertedResult(convertedResult: ConvertedResult, results: Results, index: number) {
results.multiFaceLandmarks;
//
const multiFaceLandmarks = results.multiFaceLandmarks[index];
for (let i = 0; i < multiFaceLandmarks.length; i++) {
const landmark = multiFaceLandmarks[i];
convertedResult.multiFaceLandmarks[i].set(
1 - landmark.x,
1 - landmark.y,
landmark.z,
landmark.visibility != null ? landmark.visibility : 0
);
}
}
class TrackerContainer {
private _inProgress: boolean = false;
public results: ConvertedResults = [];
constructor(private tracker: FaceMesh) {
for (let i = 0; i < 3; i++) {
this.results.push(createConvertedResult());
}
tracker.onResults((results: Results) => {
this._inProgress = false;
const subResultsCount = results.multiFaceLandmarks.length;
for (let i = 0; i < subResultsCount; i++) {
updateConvertedResult(this.results[i], results, i);
}
});
}
track(source: AllowedSource) {
if (this._inProgress) {
return;
}
this._inProgress = true;
this.tracker.send({image: source});
}
}
export class CoreComputerVisionFace {
private static trackerByKey: Map<string, TrackerContainer> = new Map();
private static trackerForObject(object: Object3D): TrackerContainer {
const key = this.trackerKey(object);
let tracker = this.trackerByKey.get(key);
if (!tracker) {
tracker = this._createTracker(trackerOptions(attributes(object)));
this.trackerByKey.set(key, tracker);
}
return tracker;
}
static trackMedia(object: Object3D, source: AllowedSource) {
const tracker = this.trackerForObject(object);
tracker.track(source);
}
static trackerResults(object: Object3D) {
return this.trackerForObject(object).results;
}
private static _createTracker(options: Options) {
const faceMesh = new FaceMesh({
locateFile,
});
faceMesh.setOptions(options);
return new TrackerContainer(faceMesh);
}
static trackerKey(object: Object3D): string {
const coreObjectClass = coreObjectClassFactory(object);
let key = coreObjectClass.attribValue(object, CoreComputerVisionFaceAttribute.KEY);
if (!key || !isString(key)) {
key = createKey(object);
coreObjectClass.addAttribute(object, CoreComputerVisionFaceAttribute.KEY, key);
}
return key;
}
static setAttributes(object: Object3D, options: FaceTrackingObjectAttributes) {
const coreObjectClass = coreObjectClassFactory(object);
coreObjectClass.addAttribute(object, CoreComputerVisionFaceAttribute.SELFIE_MODE, options.selfieMode);
coreObjectClass.addAttribute(object, CoreComputerVisionFaceAttribute.MAX_NUM_FACES, options.maxNumFaces);
coreObjectClass.addAttribute(object, CoreComputerVisionFaceAttribute.REFINE_LANDMARKS, options.refineLandmarks);
coreObjectClass.addAttribute(
object,
CoreComputerVisionFaceAttribute.MIN_DETECTION_CONFIDENCE,
options.minDetectionConfidence
);
coreObjectClass.addAttribute(
object,
CoreComputerVisionFaceAttribute.MAX_TRACKING_CONFIDENCE,
options.minTrackingConfidence
);
}
}