Vivien
Add project
0b12ad4
const MAX_WIDTH = 1280;
const FOCAL_LENGTH = 1430;
const DFOV = Math.PI / 3;
const parameter = (location.search.split('distanceMethod=')[1] || '').split('&')[0]
const distanceMethod = parameter ? parameter : 0;
const DEFAULT_DISTANCE = 0.5;
const IRIS_SIZE = 0.0117;
const NUM_KEYPOINTS = 468;
const NUM_IRIS_KEYPOINTS = 5;
const SMOOTHING = 0;
const HALF_DIAGONAL = 0.2;
const parameter2 = (location.search.split('blaze=')[1] || '').split('&')[0]
const BLAZE = (parameter2.length > 0);
let model, HFOV, VFOV;
let canvas, video;
let width, height;
function distance(a, b) {
return Math.sqrt(Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2));
}
function fov(dfov, w, h) {
const hypothenuse = Math.sqrt(Math.pow(w, 2) + Math.pow(h, 2));
const tan_dfov = Math.tan(dfov / 2);
return [2 * Math.atan(w * tan_dfov / hypothenuse), 2 * Math.atan(h * tan_dfov / hypothenuse)];
}
async function getLocation(ctx, width, height) {
let predictions;
if (BLAZE) {
predictions = await model.estimateFaces(ctx.getImageData(0, 0, width, height), false);
} else {
predictions = await model.estimateFaces({
input: ctx.getImageData(0, 0, width, height),
predictIrises: (distanceMethod == 1),
flipHorizontal: false
});
}
let centerX, centerY, keypoints, foreheadX, foreheadY;
if (predictions.length > 0) {
if (BLAZE) {
centerX = (predictions[0].landmarks[0][0] + predictions[0].landmarks[1][0])/2;
centerY = (predictions[0].landmarks[0][1] + predictions[0].landmarks[1][1])/2;
} else {
keypoints = predictions[0].scaledMesh;
centerX = keypoints[168][0];
centerY = keypoints[168][1];
foreheadX = keypoints[10][0];
foreheadY = keypoints[10][1];
}
let d;
if (distanceMethod == 0) {
d = DEFAULT_DISTANCE;
} else if (distanceMethod == 2) {
d = 0.06 * FOCAL_LENGTH * canvas.width / MAX_WIDTH / Math.sqrt(Math.pow(centerX - foreheadX, 2) + Math.pow(centerY - foreheadY, 2));
} else if (keypoints.length > NUM_KEYPOINTS) {
const leftDiameterY = distance(
keypoints[NUM_KEYPOINTS + 4],
keypoints[NUM_KEYPOINTS + 2]);
const leftDiameterX = distance(
keypoints[NUM_KEYPOINTS + 3],
keypoints[NUM_KEYPOINTS + 1]);
let diameter = Math.max(leftDiameterX, leftDiameterY);
if (keypoints.length > NUM_KEYPOINTS + NUM_IRIS_KEYPOINTS) {
const rightDiameterY = distance(
keypoints[NUM_KEYPOINTS + NUM_IRIS_KEYPOINTS + 2],
keypoints[NUM_KEYPOINTS + NUM_IRIS_KEYPOINTS + 4]);
const rightDiameterX = distance(
keypoints[NUM_KEYPOINTS + NUM_IRIS_KEYPOINTS + 3],
keypoints[NUM_KEYPOINTS + NUM_IRIS_KEYPOINTS + 1]);
diameter = Math.max(diameter, rightDiameterX, rightDiameterY);
}
d = IRIS_SIZE * FOCAL_LENGTH * canvas.width / MAX_WIDTH / diameter;
}
return [Math.atan(2 * (centerX - width / 2) / width * Math.tan(HFOV / 2)),
Math.atan(2 * (centerY - height / 2) / height * Math.tan(VFOV / 2)),
d
]
}
return null;
}
class FaceTracker {
constructor(container) {
this.angle1 = 0;
this.angle2 = 0;
this.distance = 0;
this.step = 1;
}
async init() {
await tf.setBackend('webgl');
if (BLAZE) {
model = await blazeface.load();
} else {
model = await faceLandmarksDetection.load(
faceLandmarksDetection.SupportedPackages.mediapipeFacemesh, {
shouldLoadIrisModel: (distanceMethod > 0),
maxFaces: 1
});
}
if (navigator.mediaDevices.getUserMedia) {
video = document.querySelector('#video');
await navigator.mediaDevices.getUserMedia({
video : { facingMode : 'user' }
})
.then(function (stream) {
video.srcObject = stream;
width = stream.getTracks()[0].getSettings().width;
height = stream.getTracks()[0].getSettings().height;
[HFOV, VFOV] = fov(DFOV, width, height);
})
// permission denied:
.catch(function (error) {
document.body.textContent = 'Could not access the camera. Error: ' + error.name;
});
}
canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
video.addEventListener('click', this.locateFace);
}
async getCameraParameters(outputWidth, outputHeight) {
let context = canvas.getContext('2d');
context.drawImage(video, 0, 0, width, height);
const result = await getLocation(context, width, height);
if (result !== null) {
const [angle1, angle2, distance] = result;
this.angle1 = angle1;
this.angle2 = angle2;
this.distance = (1 - SMOOTHING) * distance + SMOOTHING * this.distance;
if (this.step <= 10) {
this.distance = this.distance / (1 - Math.pow(SMOOTHING, this.step))
}
this.step = this.step + 1;
} else {
return null;
}
let d = this.distance;
const tan1 = -Math.tan(this.angle1);
const tan2 = -Math.tan(this.angle2);
const z = Math.sqrt(d * d / (1 + tan1 * tan1 + tan2 * tan2))
const cameraPosition = [z * tan1, z * tan2, z];
const fov = 180 / Math.PI * 2 * Math.atan(HALF_DIAGONAL / d);
return [cameraPosition, fov];
}
}
export {
FaceTracker
};