diff --git a/demo/facematch/facematch.js b/demo/facematch/facematch.js index fa0eab32..7af52403 100644 --- a/demo/facematch/facematch.js +++ b/demo/facematch/facematch.js @@ -21,12 +21,12 @@ const userConfig = { detector: { rotation: true, return: true, maxDetected: 20 }, mesh: { enabled: true }, embedding: { enabled: false }, - iris: { enabled: false }, + iris: { enabled: true }, emotion: { enabled: true }, description: { enabled: true }, }, hand: { enabled: false }, - gesture: { enabled: false }, + gesture: { enabled: true }, body: { enabled: false }, filter: { enabled: true }, segmentation: { enabled: false }, @@ -46,6 +46,10 @@ function log(...msg) { console.log(ts, ...msg); } +function title(msg) { + document.getElementById('title').innerHTML = msg; +} + async function loadFaceMatchDB() { // download db with known faces try { @@ -61,7 +65,9 @@ async function loadFaceMatchDB() { async function SelectFaceCanvas(face) { // if we have face image tensor, enhance it and display it let embedding; + document.getElementById('orig').style.filter = 'blur(16px)'; if (face.tensor) { + title('Sorting Faces by Similarity'); const enhanced = human.enhance(face); if (enhanced) { const c = document.getElementById('orig'); @@ -78,9 +84,14 @@ async function SelectFaceCanvas(face) { const arr = db.map((rec) => rec.embedding); const res = await human.match(face.embedding, arr); log('Match:', db[res.index].name); + const emotion = face.emotion[0] ? `${Math.round(100 * face.emotion[0].score)}% ${face.emotion[0].emotion}` : 'N/A'; document.getElementById('desc').innerHTML = ` - ${face.fileName}
- Match: ${Math.round(1000 * res.similarity) / 10}% ${db[res.index].name} + source: ${face.fileName}
+ match: ${Math.round(1000 * res.similarity) / 10}% ${db[res.index].name}
+ score: ${Math.round(100 * face.boxScore)}% detection ${Math.round(100 * face.faceScore)}% analysis
+ age: ${face.age} years
+ gender: ${Math.round(100 * face.genderScore)}% ${face.gender}
+ emotion: ${emotion}
`; embedding = face.embedding.map((a) => parseFloat(a.toFixed(4))); navigator.clipboard.writeText(`{"name":"unknown", "source":"${face.fileName}", "embedding":[${embedding}]},`); @@ -93,9 +104,9 @@ async function SelectFaceCanvas(face) { // calculate similarity from selected face to current one in the loop const current = all[canvas.tag.sample][canvas.tag.face]; const similarity = human.similarity(face.embedding, current.embedding); + canvas.tag.similarity = similarity; // get best match // draw the canvas - canvas.title = similarity; await human.tf.browser.toPixels(current.tensor, canvas); const ctx = canvas.getContext('2d'); ctx.font = 'small-caps 1rem "Lato"'; @@ -118,8 +129,10 @@ async function SelectFaceCanvas(face) { // sort all faces by similarity const sorted = document.getElementById('faces'); [...sorted.children] - .sort((a, b) => parseFloat(b.title) - parseFloat(a.title)) + .sort((a, b) => parseFloat(b.tag.similarity) - parseFloat(a.tag.similarity)) .forEach((canvas) => sorted.appendChild(canvas)); + document.getElementById('orig').style.filter = 'blur(0)'; + title('Selected Face'); } async function AddFaceCanvas(index, res, fileName) { @@ -134,6 +147,14 @@ async function AddFaceCanvas(index, res, fileName) { canvas.width = 200; canvas.height = 200; canvas.className = 'face'; + const emotion = res.face[i].emotion[0] ? `${Math.round(100 * res.face[i].emotion[0].score)}% ${res.face[i].emotion[0].emotion}` : 'N/A'; + canvas.title = ` + source: ${res.face[i].fileName} + score: ${Math.round(100 * res.face[i].boxScore)}% detection ${Math.round(100 * res.face[i].faceScore)}% analysis + age: ${res.face[i].age} years + gender: ${Math.round(100 * res.face[i].genderScore)}% ${res.face[i].gender} + emotion: ${emotion} + `.replace(/ /g, ' '); // mouse click on any face canvas triggers analysis canvas.addEventListener('click', (evt) => { log('Select:', 'Image:', evt.target.tag.sample, 'Face:', evt.target.tag.face, 'Source:', evt.target.tag.source, all[evt.target.tag.sample][evt.target.tag.face]); @@ -156,7 +177,9 @@ async function AddFaceCanvas(index, res, fileName) { return ok; } -async function AddImageElement(index, image) { +async function AddImageElement(index, image, length) { + const faces = all.reduce((prev, curr) => prev += curr.length, 0); + title(`Analyzing Input Images
${Math.round(100 * index / length)}% [${index} / ${length}]
Found ${faces} Faces`); return new Promise((resolve) => { const img = new Image(128, 128); img.onload = () => { // must wait until image is loaded @@ -188,12 +211,14 @@ async function main() { // pre-load human models await human.load(); + title('Loading Face Match Database'); let images = []; let dir = []; // load face descriptor database await loadFaceMatchDB(); // enumerate all sample images in /assets + title('Enumerating Input Images'); const res = await fetch('/samples/in'); dir = (res && res.ok) ? await res.json() : []; images = images.concat(dir.filter((img) => (img.endsWith('.jpg') && img.includes('sample')))); @@ -219,7 +244,7 @@ async function main() { } // download and analyze all images - for (let i = 0; i < images.length; i++) await AddImageElement(i, images[i]); + for (let i = 0; i < images.length; i++) await AddImageElement(i, images[i], images.length); // print stats const num = all.reduce((prev, cur) => prev += cur.length, 0); @@ -229,6 +254,7 @@ async function main() { // if we didn't download db, generate it from current faces if (!db || db.length === 0) await createFaceMatchDB(); + title(''); log('Ready'); } diff --git a/demo/facematch/index.html b/demo/facematch/index.html index e836d648..01360436 100644 --- a/demo/facematch/index.html +++ b/demo/facematch/index.html @@ -14,30 +14,36 @@
-
- Selected Face
- -
+
+
+ +
- Sample Images
-
+
Input Images
+
- Extracted Faces - click on a face to sort by similarity and get a known face match:
-
+
Select person to sort by similarity and get a known face match
+