/* eslint-disable no-return-assign */ /* global human, QuickSettings */ const config = { face: { enabled: false, detector: { maxFaces: 10, skipFrames: 5, minConfidence: 0.8, iouThreshold: 0.3, scoreThreshold: 0.75 }, mesh: { enabled: false }, iris: { enabled: false }, age: { enabled: false, skipFrames: 5 }, gender: { enabled: false }, }, body: { enabled: false, maxDetections: 5, scoreThreshold: 0.75, nmsRadius: 20 }, hand: { enabled: false, skipFrames: 5, minConfidence: 0.8, iouThreshold: 0.3, scoreThreshold: 0.75 }, }; let settings; async function drawFace(result) { const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); ctx.fillStyle = 'lightcoral'; ctx.strokeStyle = 'lightcoral'; ctx.font = 'small-caps 1rem "Segoe UI"'; for (const face of result) { ctx.beginPath(); ctx.rect(face.box[0], face.box[1], face.box[2], face.box[3]); ctx.fillText(`face ${face.gender || ''} ${face.age || ''} ${face.iris ? 'iris: ' + face.iris : ''}`, face.box[0] + 2, face.box[1] + 16, face.box[2]); ctx.stroke(); if (face.mesh) { if (settings.getValue('Draw Points')) { for (const point of face.mesh) { ctx.fillStyle = `rgba(${127.5 + (2 * point[2])}, ${127.5 - (2 * point[2])}, 255, 0.5)`; ctx.beginPath(); ctx.arc(point[0], point[1], 2, 0, 2 * Math.PI); ctx.fill(); } } if (settings.getValue('Draw Polygons')) { for (let i = 0; i < human.facemesh.triangulation.length / 3; i++) { const points = [ human.facemesh.triangulation[i * 3 + 0], human.facemesh.triangulation[i * 3 + 1], human.facemesh.triangulation[i * 3 + 2], ].map((index) => face.mesh[index]); const path = new Path2D(); path.moveTo(points[0][0], points[0][1]); for (const point of points) { path.lineTo(point[0], point[1]); } path.closePath(); ctx.fillStyle = `rgba(${127.5 + (2 * points[0][2])}, ${127.5 - (2 * points[0][2])}, 255, 0.5)`; ctx.strokeStyle = `rgba(${127.5 + (2 * points[0][2])}, ${127.5 - (2 * points[0][2])}, 255, 0.5)`; ctx.stroke(path); if (settings.getValue('Fill Polygons')) { ctx.fill(path); } } } } } } async function drawBody(result) { const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); ctx.fillStyle = 'lightcoral'; ctx.strokeStyle = 'lightcoral'; ctx.font = 'small-caps 1rem "Segoe UI"'; for (const pose of result) { if (settings.getValue('Draw Points')) { for (const point of pose.keypoints) { ctx.beginPath(); ctx.arc(point.position.x, point.position.y, 2, 0, 2 * Math.PI); ctx.fill(); } } if (settings.getValue('Draw Polygons')) { const path = new Path2D(); let part; // torso part = pose.keypoints.find((a) => a.part === 'leftShoulder'); path.moveTo(part.position.x, part.position.y); part = pose.keypoints.find((a) => a.part === 'rightShoulder'); path.lineTo(part.position.x, part.position.y); part = pose.keypoints.find((a) => a.part === 'rightHip'); path.lineTo(part.position.x, part.position.y); part = pose.keypoints.find((a) => a.part === 'leftHip'); path.lineTo(part.position.x, part.position.y); part = pose.keypoints.find((a) => a.part === 'leftShoulder'); path.lineTo(part.position.x, part.position.y); // legs part = pose.keypoints.find((a) => a.part === 'leftHip'); path.moveTo(part.position.x, part.position.y); part = pose.keypoints.find((a) => a.part === 'leftKnee'); path.lineTo(part.position.x, part.position.y); part = pose.keypoints.find((a) => a.part === 'leftAnkle'); path.lineTo(part.position.x, part.position.y); part = pose.keypoints.find((a) => a.part === 'rightHip'); path.moveTo(part.position.x, part.position.y); part = pose.keypoints.find((a) => a.part === 'rightKnee'); path.lineTo(part.position.x, part.position.y); part = pose.keypoints.find((a) => a.part === 'rightAnkle'); path.lineTo(part.position.x, part.position.y); // arms part = pose.keypoints.find((a) => a.part === 'leftShoulder'); path.moveTo(part.position.x, part.position.y); part = pose.keypoints.find((a) => a.part === 'leftElbow'); path.lineTo(part.position.x, part.position.y); part = pose.keypoints.find((a) => a.part === 'leftWrist'); path.lineTo(part.position.x, part.position.y); // arms part = pose.keypoints.find((a) => a.part === 'rightShoulder'); path.moveTo(part.position.x, part.position.y); part = pose.keypoints.find((a) => a.part === 'rightElbow'); path.lineTo(part.position.x, part.position.y); part = pose.keypoints.find((a) => a.part === 'rightWrist'); path.lineTo(part.position.x, part.position.y); // draw all ctx.stroke(path); } } } async function drawHand(result) { const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); ctx.font = 'small-caps 1rem "Segoe UI"'; window.result = result; for (const hand of result) { if (settings.getValue('Draw Points')) { for (const point of hand.landmarks) { ctx.fillStyle = `rgba(${127.5 + (2 * point[2])}, ${127.5 - (2 * point[2])}, 255, 0.5)`; ctx.beginPath(); ctx.arc(point[0], point[1], 2, 0, 2 * Math.PI); ctx.fill(); } } if (settings.getValue('Draw Polygons')) { const addPart = (part) => { ctx.beginPath(); for (const i in part) { ctx.strokeStyle = `rgba(${127.5 + (2 * part[i][2])}, ${127.5 - (2 * part[i][2])}, 255, 0.5)`; if (i === 0) ctx.moveTo(part[i][0], part[i][1]); else ctx.lineTo(part[i][0], part[i][1]); } ctx.stroke(); }; addPart(hand.annotations.indexFinger); addPart(hand.annotations.middleFinger); addPart(hand.annotations.ringFinger); addPart(hand.annotations.pinky); addPart(hand.annotations.thumb); addPart(hand.annotations.palmBase); } } } async function runHumanDetect() { const video = document.getElementById('video'); const canvas = document.getElementById('canvas'); const log = document.getElementById('log'); const live = video.srcObject ? ((video.srcObject.getVideoTracks()[0].readyState === 'live') && (video.readyState > 2) && (!video.paused)) : false; if (live) { // perform detection const t0 = performance.now(); const result = await human.detect(video, config); const t1 = performance.now(); // update fps settings.setValue('FPS', Math.round(1000 / (t1 - t0))); // draw image from video const ctx = canvas.getContext('2d'); ctx.drawImage(video, 0, 0, video.width, video.height, 0, 0, canvas.width, canvas.height); // draw all results drawFace(result.face); drawBody(result.body); drawHand(result.hand); // update log const engine = await human.tf.engine(); log.innerText = ` TFJS Version: ${human.tf.version_core} Memory: ${engine.state.numBytes.toLocaleString()} bytes ${engine.state.numDataBuffers.toLocaleString()} buffers ${engine.state.numTensors.toLocaleString()} tensors GPU Memory: used ${engine.backendInstance.numBytesInGPU.toLocaleString()} bytes free ${Math.floor(1024 * 1024 * engine.backendInstance.numMBBeforeWarning).toLocaleString()} bytes Result: Face: ${(JSON.stringify(result.face)).length.toLocaleString()} bytes Body: ${(JSON.stringify(result.body)).length.toLocaleString()} bytes Hand: ${(JSON.stringify(result.hand)).length.toLocaleString()} bytes `; // rinse & repeate requestAnimationFrame(runHumanDetect); } } function setupGUI() { settings.addRange('FPS', 0, 100, 0, 1); settings.addBoolean('Pause', false, (val) => { if (val) document.getElementById('video').pause(); else document.getElementById('video').play(); runHumanDetect(); }); settings.addHTML('line1', '