2022-11-21 19:07:23 +01:00
|
|
|
/**
|
|
|
|
* Human demo for browsers
|
|
|
|
*
|
|
|
|
* Demo for face detection
|
|
|
|
*/
|
|
|
|
|
|
|
|
/** @type {Human} */
|
|
|
|
import { Human } from '../../dist/human.esm.js';
|
2022-11-21 20:05:00 +01:00
|
|
|
|
|
|
|
let loader;
|
2022-11-21 19:07:23 +01:00
|
|
|
|
|
|
|
const humanConfig = { // user configuration for human, used to fine-tune behavior
|
|
|
|
debug: true,
|
|
|
|
modelBasePath: 'https://vladmandic.github.io/human-models/models/',
|
|
|
|
filter: { enabled: true, equalization: false, flip: false },
|
|
|
|
face: {
|
|
|
|
enabled: true,
|
|
|
|
detector: { rotation: true, maxDetected: 100, minConfidence: 0.2, return: true },
|
|
|
|
iris: { enabled: true },
|
|
|
|
description: { enabled: true },
|
|
|
|
emotion: { enabled: true },
|
|
|
|
antispoof: { enabled: true },
|
|
|
|
liveness: { enabled: true },
|
|
|
|
},
|
|
|
|
body: { enabled: false },
|
|
|
|
hand: { enabled: false },
|
|
|
|
object: { enabled: false },
|
|
|
|
gesture: { enabled: false },
|
|
|
|
segmentation: { enabled: false },
|
|
|
|
};
|
|
|
|
|
|
|
|
const human = new Human(humanConfig); // new instance of human
|
|
|
|
|
2022-11-21 20:05:00 +01:00
|
|
|
export const showLoader = (msg) => { loader.setAttribute('msg', msg); loader.style.display = 'block'; };
|
|
|
|
export const hideLoader = () => loader.style.display = 'none';
|
|
|
|
|
|
|
|
class ComponentLoader extends HTMLElement { // watch for attributes
|
|
|
|
message = document.createElement('div');
|
|
|
|
|
|
|
|
static get observedAttributes() { return ['msg']; }
|
|
|
|
|
|
|
|
attributeChangedCallback(_name, _prevVal, currVal) {
|
|
|
|
this.message.innerHTML = currVal;
|
|
|
|
}
|
|
|
|
|
|
|
|
connectedCallback() { // triggered on insert
|
|
|
|
this.attachShadow({ mode: 'open' });
|
|
|
|
const css = document.createElement('style');
|
|
|
|
css.innerHTML = `
|
|
|
|
.loader-container { top: 450px; justify-content: center; position: fixed; width: 100%; }
|
|
|
|
.loader-message { font-size: 1.5rem; padding: 1rem; }
|
|
|
|
.loader { width: 300px; height: 300px; border: 3px solid transparent; border-radius: 50%; border-top: 4px solid #f15e41; animation: spin 4s linear infinite; position: relative; }
|
|
|
|
.loader::before, .loader::after { content: ""; position: absolute; top: 6px; bottom: 6px; left: 6px; right: 6px; border-radius: 50%; border: 4px solid transparent; }
|
|
|
|
.loader::before { border-top-color: #bad375; animation: 3s spin linear infinite; }
|
|
|
|
.loader::after { border-top-color: #26a9e0; animation: spin 1.5s linear infinite; }
|
|
|
|
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
|
|
|
`;
|
|
|
|
const container = document.createElement('div');
|
|
|
|
container.id = 'loader-container';
|
|
|
|
container.className = 'loader-container';
|
|
|
|
loader = document.createElement('div');
|
|
|
|
loader.id = 'loader';
|
|
|
|
loader.className = 'loader';
|
|
|
|
this.message.id = 'loader-message';
|
|
|
|
this.message.className = 'loader-message';
|
|
|
|
this.message.innerHTML = '';
|
|
|
|
container.appendChild(this.message);
|
|
|
|
container.appendChild(loader);
|
|
|
|
this.shadowRoot?.append(css, container);
|
|
|
|
loader = this; // eslint-disable-line @typescript-eslint/no-this-alias
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
customElements.define('component-loader', ComponentLoader);
|
|
|
|
|
|
|
|
function addFace(face, source) {
|
|
|
|
const deg = (rad) => Math.round((rad || 0) * 180 / Math.PI);
|
|
|
|
const canvas = document.createElement('canvas');
|
|
|
|
const emotion = face.emotion?.map((e) => `${Math.round(100 * e.score)}% ${e.emotion}`) || [];
|
|
|
|
const rotation = `pitch ${deg(face.rotation?.angle.pitch)}° | roll ${deg(face.rotation?.angle.roll)}° | yaw ${deg(face.rotation?.angle.yaw)}°`;
|
|
|
|
const gaze = `direction ${deg(face.rotation?.gaze.bearing)}° strength ${Math.round(100 * (face.rotation.gaze.strength || 0))}%`;
|
|
|
|
canvas.title = `
|
|
|
|
source: ${source}
|
|
|
|
score: ${Math.round(100 * face.boxScore)}% detection ${Math.round(100 * face.faceScore)}% analysis
|
|
|
|
age: ${face.age} years | gender: ${face.gender} score ${Math.round(100 * face.genderScore)}%
|
|
|
|
emotion: ${emotion.join(' | ')}
|
|
|
|
head rotation: ${rotation}
|
|
|
|
eyes gaze: ${gaze}
|
|
|
|
camera distance: ${face.distance}m | ${Math.round(100 * face.distance / 2.54)}in
|
|
|
|
check: ${Math.round(100 * face.real)}% real ${Math.round(100 * face.live)}% live
|
|
|
|
`.replace(/ /g, ' ');
|
|
|
|
canvas.onclick = (e) => {
|
|
|
|
e.preventDefault();
|
|
|
|
document.getElementById('description').innerHTML = canvas.title;
|
|
|
|
};
|
|
|
|
human.tf.browser.toPixels(face.tensor, canvas);
|
|
|
|
human.tf.dispose(face.tensor);
|
|
|
|
return canvas;
|
|
|
|
}
|
|
|
|
|
2022-11-21 19:07:23 +01:00
|
|
|
async function addFaces(imgEl) {
|
|
|
|
showLoader('human: busy');
|
|
|
|
const faceEl = document.getElementById('faces');
|
|
|
|
faceEl.innerHTML = '';
|
|
|
|
const res = await human.detect(imgEl);
|
2022-11-21 20:05:00 +01:00
|
|
|
console.log(res); // eslint-disable-line no-console
|
|
|
|
document.getElementById('description').innerHTML = `detected ${res.face.length} faces`;
|
2022-11-21 19:07:23 +01:00
|
|
|
for (const face of res.face) {
|
2022-11-21 20:05:00 +01:00
|
|
|
const canvas = addFace(face, imgEl.src.substring(0, 64));
|
|
|
|
faceEl.appendChild(canvas);
|
2022-11-21 19:07:23 +01:00
|
|
|
}
|
|
|
|
hideLoader();
|
|
|
|
}
|
|
|
|
|
|
|
|
function addImage(imageUri) {
|
|
|
|
const imgEl = new Image(256, 256);
|
|
|
|
imgEl.onload = () => {
|
|
|
|
const images = document.getElementById('images');
|
|
|
|
images.appendChild(imgEl); // add image if loaded ok
|
|
|
|
images.scroll(images?.offsetWidth, 0);
|
|
|
|
};
|
|
|
|
imgEl.onerror = () => console.error('addImage', { imageUri }); // eslint-disable-line no-console
|
|
|
|
imgEl.onclick = () => addFaces(imgEl);
|
|
|
|
imgEl.title = imageUri.substring(0, 64);
|
|
|
|
imgEl.src = encodeURI(imageUri);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function initDragAndDrop() {
|
|
|
|
const reader = new FileReader();
|
|
|
|
reader.onload = async (e) => {
|
|
|
|
if (e.target.result.startsWith('data:image')) await addImage(e.target.result);
|
|
|
|
};
|
|
|
|
document.body.addEventListener('dragenter', (evt) => evt.preventDefault());
|
|
|
|
document.body.addEventListener('dragleave', (evt) => evt.preventDefault());
|
|
|
|
document.body.addEventListener('dragover', (evt) => evt.preventDefault());
|
|
|
|
document.body.addEventListener('drop', async (evt) => {
|
|
|
|
evt.preventDefault();
|
|
|
|
evt.dataTransfer.dropEffect = 'copy';
|
|
|
|
for (const f of evt.dataTransfer.files) reader.readAsDataURL(f);
|
|
|
|
});
|
|
|
|
document.body.onclick = (e) => {
|
|
|
|
if (e.target.localName !== 'canvas') document.getElementById('description').innerHTML = '';
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
async function main() {
|
|
|
|
showLoader('loading models');
|
|
|
|
await human.load();
|
|
|
|
showLoader('compiling models');
|
|
|
|
await human.warmup();
|
|
|
|
showLoader('loading images');
|
|
|
|
const images = ['group-1.jpg', 'group-2.jpg', 'group-3.jpg', 'group-4.jpg', 'group-5.jpg', 'group-6.jpg', 'group-7.jpg', 'solvay1927.jpg', 'stock-group-1.jpg', 'stock-group-2.jpg'];
|
|
|
|
const imageUris = images.map((a) => `../../samples/in/${a}`);
|
|
|
|
for (let i = 0; i < imageUris.length; i++) addImage(imageUris[i]);
|
|
|
|
initDragAndDrop();
|
|
|
|
hideLoader();
|
|
|
|
}
|
|
|
|
|
|
|
|
window.onload = main;
|