mirror of https://github.com/vladmandic/human
121 lines
4.4 KiB
JavaScript
121 lines
4.4 KiB
JavaScript
/* global tf, ScatterGL, dat */
|
|
|
|
import human from '../dist/human.esm.js';
|
|
|
|
const state = {
|
|
backend: 'webgl',
|
|
triangulateMesh: true,
|
|
renderPointcloud: true,
|
|
stop: false,
|
|
videoSize: 700,
|
|
};
|
|
const options = {
|
|
};
|
|
|
|
let ctx;
|
|
let videoWidth;
|
|
let videoHeight;
|
|
let video;
|
|
let canvas;
|
|
let scatterGLHasInitialized = false;
|
|
let scatterGL;
|
|
|
|
async function renderPrediction() {
|
|
const predictions = await human.detect(video);
|
|
ctx.drawImage(video, 0, 0, videoWidth, videoHeight, 0, 0, canvas.width, canvas.height);
|
|
const div = document.getElementById('faces');
|
|
div.innerHTML = '';
|
|
for (const prediction of predictions) {
|
|
div.appendChild(prediction.canvas);
|
|
ctx.beginPath();
|
|
ctx.rect(prediction.box[0], prediction.box[1], prediction.box[2], prediction.box[3]);
|
|
ctx.font = 'small-caps 1rem "Segoe UI"';
|
|
ctx.fillText(`${prediction.gender} ${prediction.age}`, prediction.box[0] + 2, prediction.box[1] + 16, prediction.box[2]);
|
|
ctx.stroke();
|
|
if (state.triangulateMesh) {
|
|
for (let i = 0; i < human.triangulation.length / 3; i++) {
|
|
const points = [human.triangulation[i * 3], human.triangulation[i * 3 + 1], human.triangulation[i * 3 + 2]].map((index) => prediction.mesh[index]);
|
|
const region = new Path2D();
|
|
region.moveTo(points[0][0], points[0][1]);
|
|
for (let j = 1; i < points.length; j++) region.lineTo(points[j][0], points[j][1]);
|
|
region.closePath();
|
|
ctx.stroke(region);
|
|
}
|
|
} else {
|
|
for (let i = 0; i < prediction.mesh.length; i++) {
|
|
const x = prediction.mesh[i][0];
|
|
const y = prediction.mesh[i][1];
|
|
ctx.beginPath();
|
|
ctx.arc(x, y, 1 /* radius */, 0, 2 * Math.PI);
|
|
ctx.fill();
|
|
}
|
|
}
|
|
if (state.renderPointcloud && scatterGL != null) {
|
|
const pointsData = predictions.map((pred) => pred.mesh.map((point) => ([-point[0], -point[1], -point[2]])));
|
|
let flattenedPointsData = [];
|
|
for (let i = 0; i < pointsData.length; i++) {
|
|
flattenedPointsData = flattenedPointsData.concat(pointsData[i]);
|
|
}
|
|
const dataset = new ScatterGL.Dataset(flattenedPointsData);
|
|
if (!scatterGLHasInitialized) scatterGL.render(dataset);
|
|
else scatterGL.updateDataset(dataset);
|
|
scatterGLHasInitialized = true;
|
|
}
|
|
}
|
|
if (!state.stop) requestAnimationFrame(renderPrediction);
|
|
}
|
|
|
|
function setupDatGui() {
|
|
const gui = new dat.GUI();
|
|
gui.add(state, 'stop').onChange(() => { renderPrediction(); });
|
|
gui.add(state, 'backend', ['webgl', 'cpu']).onChange((backend) => { tf.setBackend(backend); });
|
|
gui.add(options, 'maxFaces', 1, 100, 1).onChange(() => { human.load(options); });
|
|
gui.add(options, 'detectionConfidence', 0, 1, 0.05).onChange(() => { human.load(options); });
|
|
gui.add(options, 'iouThreshold', 0, 1, 0.05).onChange(() => { human.load(options); });
|
|
gui.add(options, 'scoreThreshold', 0, 1, 0.05).onChange(() => { human.load(options); });
|
|
gui.add(state, 'triangulateMesh');
|
|
gui.add(state, 'renderPointcloud').onChange((render) => { document.querySelector('#scatter-gl-container').style.display = render ? 'inline-block' : 'none'; });
|
|
}
|
|
|
|
async function setupCamera() {
|
|
video = document.getElementById('video');
|
|
const stream = await navigator.mediaDevices.getUserMedia({
|
|
audio: false,
|
|
video: { facingMode: 'user', width: state.videoSize, height: state.videoSize },
|
|
});
|
|
video.srcObject = stream;
|
|
return new Promise((resolve) => {
|
|
video.onloadedmetadata = () => resolve(video);
|
|
});
|
|
}
|
|
|
|
async function main() {
|
|
await tf.setBackend(state.backend);
|
|
setupDatGui();
|
|
await setupCamera();
|
|
video.play();
|
|
videoWidth = video.videoWidth;
|
|
videoHeight = video.videoHeight;
|
|
video.width = videoWidth;
|
|
video.height = videoHeight;
|
|
canvas = document.getElementById('output');
|
|
canvas.width = videoWidth;
|
|
canvas.height = videoHeight;
|
|
const canvasContainer = document.querySelector('.canvas-wrapper');
|
|
canvasContainer.style = `width: ${videoWidth}px; height: ${videoHeight}px`;
|
|
ctx = canvas.getContext('2d');
|
|
// ctx.translate(canvas.width, 0);
|
|
// ctx.scale(-1, 1);
|
|
ctx.fillStyle = '#32EEDB';
|
|
ctx.strokeStyle = '#32EEDB';
|
|
ctx.lineWidth = 0.5;
|
|
human.load(options);
|
|
renderPrediction();
|
|
if (state.renderPointcloud) {
|
|
document.querySelector('#scatter-gl-container').style = `width: ${state.videoSize}px; height: ${state.videoSize}px;`;
|
|
scatterGL = new ScatterGL(document.querySelector('#scatter-gl-container'), { rotateOnStart: false, selectEnabled: false });
|
|
}
|
|
}
|
|
|
|
main();
|