From 3e4d856ac37a8b5315d693b44e4125b744b90b08 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 25 Sep 2022 10:15:47 -0400 Subject: [PATCH] update faceid --- CHANGELOG.md | 3 +- README.md | 12 ++- TODO.md | 1 + assets/screenshot-faceid.jpg | Bin 0 -> 47746 bytes demo/faceid/index.html | 15 +++- demo/faceid/index.js | 4 +- demo/faceid/index.js.map | 6 +- demo/faceid/index.ts | 166 +++++++++++++++++++++-------------- package.json | 2 +- test/build.log | 78 ++++++++-------- wiki | 2 +- 11 files changed, 172 insertions(+), 117 deletions(-) create mode 100644 assets/screenshot-faceid.jpg diff --git a/CHANGELOG.md b/CHANGELOG.md index 79879daf..b95b7449 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,9 @@ ## Changelog -### **HEAD -> main** 2022/09/21 mandic00@live.com +### **HEAD -> main** 2022/09/25 mandic00@live.com +- create funding.yml - fix rotation interpolation ### **2.10.3** 2022/09/21 mandic00@live.com diff --git a/README.md b/README.md index d98e8a2f..c0a849e7 100644 --- a/README.md +++ b/README.md @@ -170,9 +170,15 @@ and optionally matches detected face with database of known people to guess thei ![Face Matching](assets/screenshot-facematch.jpg) +2. **Face ID:** +Performs validation check on a webcam input to detect a real face and matches it to known faces stored in database +> [demo/faceid](demo/faceid/index.html) + +![Face Matching](assets/screenshot-faceid.jpg) +
-2. **3D Rendering:** +3. **3D Rendering:** > [human-motion](https://github.com/vladmandic/human-motion) ![Face3D](https://github.com/vladmandic/human-motion/raw/main/assets/screenshot-face.jpg) @@ -181,14 +187,14 @@ and optionally matches detected face with database of known people to guess thei
-3. **VR Model Tracking:** +4. **VR Model Tracking:** > [human-three-vrm](https://github.com/vladmandic/human-three-vrm) > [human-bjs-vrm](https://github.com/vladmandic/human-bjs-vrm) ![ThreeVRM](https://github.com/vladmandic/human-three-vrm/raw/main/assets/human-vrm-screenshot.jpg) -4. **Human as OS native application:** +5. **Human as OS native application:** > [human-electron](https://github.com/vladmandic/human-electron)
diff --git a/TODO.md b/TODO.md index bfca2f2e..f9f16337 100644 --- a/TODO.md +++ b/TODO.md @@ -50,5 +50,6 @@ Enable via `about:config` -> `gfx.offscreencanvas.enabled` - Enable model cache when using web workers - Fix for `face.rotation` interpolation - Improve NodeJS resolver when using ESM +- Update demo `demo/faceid` - Update demo `demo/nodejs/process-folder.js` and re-process `/samples` diff --git a/assets/screenshot-faceid.jpg b/assets/screenshot-faceid.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ed529ca3bd06c40a4b3bb52d64a8014e345d0d0b GIT binary patch literal 47746 zcmd43bzB_Hwl3T-z~BUTch}$q3xm4{x8Uv&f&~Z!_Yf?&1Rr3q;I6@fL(t$s6a3D* z&)xgpeZF(=Z}0QZcbl$adRAA}THP~UtDg0&d0Ked0G=s96d?ct0s>$T{{x;ja+j#D0;KGXZ7 z{`TL^!rsM;%f-fvMu3Z-i(o|&DS|Gu!e z^kZ{tduMm==l;Rj`Niec_3uA7w}07%0D%6(tbbVcpX|bg+l7dP1VRG;WfuaXFT8+o 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£vHxC#C-rQ5rO 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 SSG<<@sr&yOFKB$ +
+

faceid demo using human library

+ look directly at camera and make sure that detection passes all of the required tests noted on the right hand side of the screen
+ if input does not satisfies tests within specific timeout, no image will be selected
+ once face image is approved, it will be compared with existing face database
+ you can also store face descriptor with label in a browser's indexdb for future usage
+
+ note: this is not equivalent to full faceid methods as used by modern mobile phones or windows hello
+ as they rely on additional infrared sensors and depth-sensing and not just camera image for additional levels of security
+
-

     

     
-    
retry
+
retry
diff --git a/demo/faceid/index.js b/demo/faceid/index.js index 35441db1..2cd259dd 100644 --- a/demo/faceid/index.js +++ b/demo/faceid/index.js @@ -4,6 +4,6 @@ author: ' */ -import*as I from"../../dist/human.esm.js";var l,z="human",g="person",p=(...t)=>console.log("indexdb",...t);async function v(){return l?!0:new Promise(t=>{let n=indexedDB.open(z,1);n.onerror=r=>p("error:",r),n.onupgradeneeded=r=>{p("create:",r.target),l=r.target.result,l.createObjectStore(g,{keyPath:"id",autoIncrement:!0})},n.onsuccess=r=>{l=r.target.result,p("open:",l),t(!0)}})}async function D(){let t=[];return l||await v(),new Promise(n=>{let r=l.transaction([g],"readwrite").objectStore(g).openCursor(null,"next");r.onerror=i=>p("load error:",i),r.onsuccess=i=>{i.target.result?(t.push(i.target.result.value),i.target.result.continue()):n(t)}})}async function y(){return l||await v(),new Promise(t=>{let n=l.transaction([g],"readwrite").objectStore(g).count();n.onerror=r=>p("count error:",r),n.onsuccess=()=>t(n.result)})}async function M(t){l||await v();let n={name:t.name,descriptor:t.descriptor,image:t.image};l.transaction([g],"readwrite").objectStore(g).put(n),p("save:",n)}async function B(t){l||await v(),l.transaction([g],"readwrite").objectStore(g).delete(t.id),p("delete:",t)}var b={modelBasePath:"../../models",filter:{equalization:!0},face:{enabled:!0,detector:{rotation:!0,return:!0,cropFactor:1.6,mask:!1},description:{enabled:!0},iris:{enabled:!0},emotion:{enabled:!1},antispoof:{enabled:!0},liveness:{enabled:!0}},body:{enabled:!1},hand:{enabled:!1},object:{enabled:!1},gesture:{enabled:!0}},S={order:2,multiplier:25,min:.2,max:.8},c={minConfidence:.6,minSize:224,maxTime:1e4,blinkMin:10,blinkMax:800,threshold:.5,mask:b.face.detector.mask,rotation:b.face.detector.rotation,cropFactor:b.face.detector.cropFactor,...S},o={faceCount:!1,faceConfidence:!1,facingCenter:!1,lookingCenter:!1,blinkDetected:!1,faceSize:!1,antispoofCheck:!1,livenessCheck:!1,elapsedMs:0},H=()=>o.faceCount&&o.faceSize&&o.blinkDetected&&o.facingCenter&&o.lookingCenter&&o.faceConfidence&&o.antispoofCheck&&o.livenessCheck,s={face:null,record:null},f={start:0,end:0,time:0},a=new I.Human(b);a.env.perfadd=!1;a.draw.options.font='small-caps 18px "Lato"';a.draw.options.lineHeight=20;var e={video:document.getElementById("video"),canvas:document.getElementById("canvas"),log:document.getElementById("log"),fps:document.getElementById("fps"),match:document.getElementById("match"),name:document.getElementById("name"),save:document.getElementById("save"),delete:document.getElementById("delete"),retry:document.getElementById("retry"),source:document.getElementById("source"),ok:document.getElementById("ok")},w={detect:0,draw:0},h={detect:0,draw:0},T=0,u=(...t)=>{e.log.innerText+=t.join(" ")+` -`,console.log(...t)},k=t=>e.fps.innerText=t;async function L(){k("starting webcam...");let t={audio:!1,video:{facingMode:"user",resizeMode:"none",width:{ideal:document.body.clientWidth}}},n=await navigator.mediaDevices.getUserMedia(t),r=new Promise(i=>{e.video.onloadeddata=()=>i(!0)});e.video.srcObject=n,e.video.play(),await r,e.canvas.width=e.video.videoWidth,e.canvas.height=e.video.videoHeight,a.env.initial&&u("video:",e.video.videoWidth,e.video.videoHeight,"|",n.getVideoTracks()[0].label),e.canvas.onclick=()=>{e.video.paused?e.video.play():e.video.pause()}}async function R(){var t;if(!e.video.paused){(t=s.face)!=null&&t.tensor&&a.tf.dispose(s.face.tensor),await a.detect(e.video);let n=a.now();h.detect=1e3/(n-w.detect),w.detect=n,requestAnimationFrame(R)}}async function F(){let t=a.next(a.result);a.draw.canvas(e.video,e.canvas),await a.draw.all(e.canvas,t);let n=a.now();if(h.draw=1e3/(n-w.draw),w.draw=n,k(`fps: ${h.detect.toFixed(1).padStart(5," ")} detect | ${h.draw.toFixed(1).padStart(5," ")} draw`),o.faceCount=a.result.face.length===1,o.faceCount){let i=Object.values(a.result.gesture).map(m=>m.gesture);(i.includes("blink left eye")||i.includes("blink right eye"))&&(f.start=a.now()),f.start>0&&!i.includes("blink left eye")&&!i.includes("blink right eye")&&(f.end=a.now()),o.blinkDetected=o.blinkDetected||Math.abs(f.end-f.start)>c.blinkMin&&Math.abs(f.end-f.start)c.minConfidence&&(a.result.face[0].faceScore||0)>c.minConfidence,o.antispoofCheck=(a.result.face[0].real||0)>c.minConfidence,o.livenessCheck=(a.result.face[0].live||0)>c.minConfidence,o.faceSize=a.result.face[0].box[2]>=c.minSize&&a.result.face[0].box[3]>=c.minSize}let r=32;for(let[i,m]of Object.entries(o)){let d=document.getElementById(`ok-${i}`);d||(d=document.createElement("div"),d.innerText=i,d.className="ok",d.style.top=`${r}px`,e.ok.appendChild(d)),typeof m=="boolean"?d.style.backgroundColor=m?"lightgreen":"lightcoral":d.innerText=`${i}:${m}`,r+=28}return H()||o.elapsedMs>c.maxTime?(e.video.pause(),a.result.face[0]):(o.elapsedMs=Math.trunc(a.now()-T),new Promise(i=>{setTimeout(async()=>{await F(),i(a.result.face[0])},30)}))}async function j(){var t,n,r,i;if(e.name.value.length>0){let m=(t=e.canvas.getContext("2d"))==null?void 0:t.getImageData(0,0,e.canvas.width,e.canvas.height),d={id:0,name:e.name.value,descriptor:(n=s.face)==null?void 0:n.embedding,image:m};await M(d),u("saved face record:",d.name,"descriptor length:",(i=(r=s.face)==null?void 0:r.embedding)==null?void 0:i.length),u("known face records:",await y())}else u("invalid name")}async function q(){s.record&&s.record.id>0&&await B(s.record)}async function O(){var i,m,d,C;if((i=e.canvas.getContext("2d"))==null||i.clearRect(0,0,c.minSize,c.minSize),!((m=s==null?void 0:s.face)!=null&&m.tensor)||!((d=s==null?void 0:s.face)!=null&&d.embedding))return!1;if(console.log("face record:",s.face),a.tf.browser.toPixels(s.face.tensor,e.canvas),await y()===0)return u("face database is empty"),document.body.style.background="black",e.delete.style.display="none",!1;let t=await D(),n=t.map(x=>x.descriptor).filter(x=>x.length>0),r=a.match(s.face.embedding,n,S);return s.record=t[r.index]||null,s.record&&(u(`best match: ${s.record.name} | id: ${s.record.id} | similarity: ${Math.round(1e3*r.similarity)/10}%`),e.name.value=s.record.name,e.source.style.display="",(C=e.source.getContext("2d"))==null||C.putImageData(s.record.image,0,0)),document.body.style.background=r.similarity>c.threshold?"darkgreen":"maroon",r.similarity>c.threshold}async function E(){var t,n;return o.faceCount=!1,o.faceConfidence=!1,o.facingCenter=!1,o.blinkDetected=!1,o.faceSize=!1,o.antispoofCheck=!1,o.livenessCheck=!1,o.elapsedMs=0,e.match.style.display="none",e.retry.style.display="none",e.source.style.display="none",document.body.style.background="black",await L(),await R(),T=a.now(),s.face=await F(),e.canvas.width=((t=s.face.tensor)==null?void 0:t.shape[1])||c.minSize,e.canvas.height=((n=s.face.tensor)==null?void 0:n.shape[0])||c.minSize,e.source.width=e.canvas.width,e.source.height=e.canvas.height,e.canvas.style.width="",e.match.style.display="flex",e.save.style.display="flex",e.delete.style.display="flex",e.retry.style.display="block",H()?O():(u("did not find valid face"),!1)}async function $(){var t,n;u("human version:",a.version,"| tfjs version:",a.tf.version["tfjs-core"]),u("face embedding model:",b.face.description.enabled?"faceres":"",(t=b.face.mobilefacenet)!=null&&t.enabled?"mobilefacenet":"",(n=b.face.insightface)!=null&&n.enabled?"insightface":""),u("options:",JSON.stringify(c).replace(/{|}|"|\[|\]/g,"").replace(/,/g," ")),k("loading..."),u("known face records:",await y()),await L(),await a.load(),k("initializing..."),e.retry.addEventListener("click",E),e.save.addEventListener("click",j),e.delete.addEventListener("click",q),await a.warmup(),await E()}window.onload=$; +import*as S from"../../dist/human.esm.js";var l,L="human",f="person",v=(...a)=>console.log("indexdb",...a);async function h(){return l?!0:new Promise(a=>{let n=indexedDB.open(L,1);n.onerror=o=>v("error:",o),n.onupgradeneeded=o=>{v("create:",o.target),l=o.target.result,l.createObjectStore(f,{keyPath:"id",autoIncrement:!0})},n.onsuccess=o=>{l=o.target.result,v("open:",l),a(!0)}})}async function C(){let a=[];return l||await h(),new Promise(n=>{let o=l.transaction([f],"readwrite").objectStore(f).openCursor(null,"next");o.onerror=i=>v("load error:",i),o.onsuccess=i=>{i.target.result?(a.push(i.target.result.value),i.target.result.continue()):n(a)}})}async function b(){return l||await h(),new Promise(a=>{let n=l.transaction([f],"readwrite").objectStore(f).count();n.onerror=o=>v("count error:",o),n.onsuccess=()=>a(n.result)})}async function x(a){l||await h();let n={name:a.name,descriptor:a.descriptor,image:a.image};l.transaction([f],"readwrite").objectStore(f).put(n),v("save:",n)}async function D(a){l||await h(),l.transaction([f],"readwrite").objectStore(f).delete(a.id),v("delete:",a)}var g={cacheSensitivity:0,modelBasePath:"../../models",filter:{equalization:!0},face:{enabled:!0,detector:{rotation:!0,return:!0,cropFactor:1.6,mask:!1},description:{enabled:!0},iris:{enabled:!0},emotion:{enabled:!1},antispoof:{enabled:!0},liveness:{enabled:!0}},body:{enabled:!1},hand:{enabled:!1},object:{enabled:!1},gesture:{enabled:!0}},B={order:2,multiplier:25,min:.2,max:.8},d={minConfidence:.6,minSize:224,maxTime:3e4,blinkMin:10,blinkMax:800,threshold:.5,mask:g.face.detector.mask,rotation:g.face.detector.rotation,cropFactor:g.face.detector.cropFactor,...B},e={faceCount:{status:!1,val:0},faceConfidence:{status:!1,val:0},facingCenter:{status:!1,val:0},lookingCenter:{status:!1,val:0},blinkDetected:{status:!1,val:0},faceSize:{status:!1,val:0},antispoofCheck:{status:!1,val:0},livenessCheck:{status:!1,val:0},age:{status:!1,val:0},gender:{status:!1,val:0},timeout:{status:!0,val:0},descriptor:{status:!1,val:0},elapsedMs:{status:void 0,val:0},detectFPS:{status:void 0,val:0},drawFPS:{status:void 0,val:0}},E=()=>e.faceCount.status&&e.faceSize.status&&e.blinkDetected.status&&e.facingCenter.status&&e.lookingCenter.status&&e.faceConfidence.status&&e.antispoofCheck.status&&e.livenessCheck.status&&e.descriptor.status&&e.age.status&&e.gender.status,c={face:null,record:null},u={start:0,end:0,time:0},s=new S.Human(g);s.env.perfadd=!1;s.draw.options.font='small-caps 18px "Lato"';s.draw.options.lineHeight=20;var t={video:document.getElementById("video"),canvas:document.getElementById("canvas"),log:document.getElementById("log"),fps:document.getElementById("fps"),match:document.getElementById("match"),name:document.getElementById("name"),save:document.getElementById("save"),delete:document.getElementById("delete"),retry:document.getElementById("retry"),source:document.getElementById("source"),ok:document.getElementById("ok")},y={detect:0,draw:0},I=0,r=(...a)=>{t.log.innerText+=a.join(" ")+` +`,console.log(...a)};async function H(){let a={audio:!1,video:{facingMode:"user",resizeMode:"none",width:{ideal:document.body.clientWidth}}},n=await navigator.mediaDevices.getUserMedia(a),o=new Promise(i=>{t.video.onloadeddata=()=>i(!0)});t.video.srcObject=n,t.video.play(),await o,t.canvas.width=t.video.videoWidth,t.canvas.height=t.video.videoHeight,t.canvas.style.width="50%",t.canvas.style.height="50%",s.env.initial&&r("video:",t.video.videoWidth,t.video.videoHeight,"|",n.getVideoTracks()[0].label),t.canvas.onclick=()=>{t.video.paused?t.video.play():t.video.pause()}}async function T(){var a;if(!t.video.paused){(a=c.face)!=null&&a.tensor&&s.tf.dispose(c.face.tensor),await s.detect(t.video);let n=s.now();e.detectFPS.val=Math.round(1e4/(n-y.detect))/10,y.detect=n,requestAnimationFrame(T)}}function P(){let a=32;for(let[n,o]of Object.entries(e)){let i=document.getElementById(`ok-${n}`);i||(i=document.createElement("div"),i.id=`ok-${n}`,i.innerText=n,i.className="ok",i.style.top=`${a}px`,t.ok.appendChild(i)),typeof o.status=="boolean"&&(i.style.backgroundColor=o.status?"lightgreen":"lightcoral");let m=o.status?"ok":"fail";i.innerText=`${n}: ${o.val===0?m:o.val}`,a+=28}}async function R(){var o;let a=s.next(s.result);s.draw.canvas(t.video,t.canvas),await s.draw.all(t.canvas,a);let n=s.now();if(e.drawFPS.val=Math.round(1e4/(n-y.draw))/10,y.draw=n,e.faceCount.val=s.result.face.length,e.faceCount.status=e.faceCount.val===1,e.faceCount.status){let i=Object.values(s.result.gesture).map(m=>m.gesture);(i.includes("blink left eye")||i.includes("blink right eye"))&&(u.start=s.now()),u.start>0&&!i.includes("blink left eye")&&!i.includes("blink right eye")&&(u.end=s.now()),e.blinkDetected.status=e.blinkDetected.status||Math.abs(u.end-u.start)>d.blinkMin&&Math.abs(u.end-u.start)=d.minConfidence,e.antispoofCheck.val=s.result.face[0].real||0,e.antispoofCheck.status=e.antispoofCheck.val>=d.minConfidence,e.livenessCheck.val=s.result.face[0].live||0,e.livenessCheck.status=e.livenessCheck.val>=d.minConfidence,e.faceSize.val=Math.min(s.result.face[0].box[2],s.result.face[0].box[3]),e.faceSize.status=e.faceSize.val>=d.minSize,e.descriptor.val=((o=s.result.face[0].embedding)==null?void 0:o.length)||0,e.descriptor.status=e.descriptor.val>0,e.age.val=s.result.face[0].age||0,e.age.status=e.age.val>0,e.gender.val=s.result.face[0].genderScore||0,e.gender.status=e.gender.val>=d.minConfidence}return e.timeout.status=e.elapsedMs.val<=d.maxTime,P(),E()||!e.timeout.status?(t.video.pause(),s.result.face[0]):(e.elapsedMs.val=Math.trunc(s.now()-I),new Promise(i=>{setTimeout(async()=>{await R(),i(s.result.face[0])},30)}))}async function z(){var a,n,o,i;if(t.name.value.length>0){let m=(a=t.canvas.getContext("2d"))==null?void 0:a.getImageData(0,0,t.canvas.width,t.canvas.height),p={id:0,name:t.name.value,descriptor:(n=c.face)==null?void 0:n.embedding,image:m};await x(p),r("saved face record:",p.name,"descriptor length:",(i=(o=c.face)==null?void 0:o.embedding)==null?void 0:i.length),r("known face records:",await b())}else r("invalid name")}async function j(){c.record&&c.record.id>0&&await D(c.record)}async function $(){var i,m,p,k;if(t.canvas.style.height="",(i=t.canvas.getContext("2d"))==null||i.clearRect(0,0,d.minSize,d.minSize),!((m=c==null?void 0:c.face)!=null&&m.tensor)||!((p=c==null?void 0:c.face)!=null&&p.embedding))return!1;if(console.log("face record:",c.face),r(`detected face: ${c.face.gender} ${c.face.age||0}y distance ${c.face.iris||0}cm/${Math.round(100*(c.face.iris||0)/2.54)/100}in`),s.tf.browser.toPixels(c.face.tensor,t.canvas),await b()===0)return r("face database is empty: nothing to compare face with"),document.body.style.background="black",t.delete.style.display="none",!1;let a=await C(),n=a.map(w=>w.descriptor).filter(w=>w.length>0),o=s.match(c.face.embedding,n,B);return c.record=a[o.index]||null,c.record&&(r(`best match: ${c.record.name} | id: ${c.record.id} | similarity: ${Math.round(1e3*o.similarity)/10}%`),t.name.value=c.record.name,t.source.style.display="",(k=t.source.getContext("2d"))==null||k.putImageData(c.record.image,0,0)),document.body.style.background=o.similarity>d.threshold?"darkgreen":"maroon",o.similarity>d.threshold}async function M(){var a,n;return e.faceCount.status=!1,e.faceConfidence.status=!1,e.facingCenter.status=!1,e.blinkDetected.status=!1,e.faceSize.status=!1,e.antispoofCheck.status=!1,e.livenessCheck.status=!1,e.age.status=!1,e.gender.status=!1,e.elapsedMs.val=0,t.match.style.display="none",t.retry.style.display="none",t.source.style.display="none",t.canvas.style.height="50%",document.body.style.background="black",await H(),await T(),I=s.now(),c.face=await R(),t.canvas.width=((a=c.face.tensor)==null?void 0:a.shape[1])||d.minSize,t.canvas.height=((n=c.face.tensor)==null?void 0:n.shape[0])||d.minSize,t.source.width=t.canvas.width,t.source.height=t.canvas.height,t.canvas.style.width="",t.match.style.display="flex",t.save.style.display="flex",t.delete.style.display="flex",t.retry.style.display="block",E()?$():(r("did not find valid face"),!1)}async function q(){var a,n;r("human version:",s.version,"| tfjs version:",s.tf.version["tfjs-core"]),r("options:",JSON.stringify(d).replace(/{|}|"|\[|\]/g,"").replace(/,/g," ")),r("initializing webcam..."),await H(),r("loading human models..."),await s.load(),r("initializing human..."),r("face embedding model:",g.face.description.enabled?"faceres":"",(a=g.face.mobilefacenet)!=null&&a.enabled?"mobilefacenet":"",(n=g.face.insightface)!=null&&n.enabled?"insightface":""),r("loading face database..."),r("known face records:",await b()),t.retry.addEventListener("click",M),t.save.addEventListener("click",z),t.delete.addEventListener("click",j),await s.warmup(),await M()}window.onload=q; //# sourceMappingURL=index.js.map diff --git a/demo/faceid/index.js.map b/demo/faceid/index.js.map index d9e1c020..ba7ac80d 100644 --- a/demo/faceid/index.js.map +++ b/demo/faceid/index.js.map @@ -1,7 +1,7 @@ { "version": 3, "sources": ["index.ts", "indexdb.ts"], - "sourcesContent": ["/**\n * Human demo for browsers\n * @default Human Library\n * @summary \n * @author \n * @copyright \n * @license MIT\n */\n\nimport * as H from '../../dist/human.esm.js'; // equivalent of @vladmandic/Human\nimport * as indexDb from './indexdb'; // methods to deal with indexdb\n\nconst humanConfig = { // user configuration for human, used to fine-tune behavior\n modelBasePath: '../../models',\n filter: { equalization: true }, // lets run with histogram equilizer\n face: {\n enabled: true,\n detector: { rotation: true, return: true, cropFactor: 1.6, mask: false }, // return tensor is used to get detected face image\n description: { enabled: true }, // default model for face descriptor extraction is faceres\n // mobilefacenet: { enabled: true, modelPath: 'https://vladmandic.github.io/human-models/models/mobilefacenet.json' }, // alternative model\n // insightface: { enabled: true, modelPath: 'https://vladmandic.github.io/insightface/models/insightface-mobilenet-swish.json' }, // alternative model\n iris: { enabled: true }, // needed to determine gaze direction\n emotion: { enabled: false }, // not needed\n antispoof: { enabled: true }, // enable optional antispoof module\n liveness: { enabled: true }, // enable optional liveness module\n },\n body: { enabled: false },\n hand: { enabled: false },\n object: { enabled: false },\n gesture: { enabled: true }, // parses face and iris gestures\n};\n\n// const matchOptions = { order: 2, multiplier: 1000, min: 0.0, max: 1.0 }; // for embedding model\nconst matchOptions = { order: 2, multiplier: 25, min: 0.2, max: 0.8 }; // for faceres model\n\nconst options = {\n minConfidence: 0.6, // overal face confidence for box, face, gender, real, live\n minSize: 224, // min input to face descriptor model before degradation\n maxTime: 10000, // max time before giving up\n blinkMin: 10, // minimum duration of a valid blink\n blinkMax: 800, // maximum duration of a valid blink\n threshold: 0.5, // minimum similarity\n mask: humanConfig.face.detector.mask,\n rotation: humanConfig.face.detector.rotation,\n cropFactor: humanConfig.face.detector.cropFactor,\n ...matchOptions,\n};\n\nconst ok = { // must meet all rules\n faceCount: false,\n faceConfidence: false,\n facingCenter: false,\n lookingCenter: false,\n blinkDetected: false,\n faceSize: false,\n antispoofCheck: false,\n livenessCheck: false,\n elapsedMs: 0, // total time while waiting for valid face\n};\nconst allOk = () => ok.faceCount && ok.faceSize && ok.blinkDetected && ok.facingCenter && ok.lookingCenter && ok.faceConfidence && ok.antispoofCheck && ok.livenessCheck;\nconst current: { face: H.FaceResult | null, record: indexDb.FaceRecord | null } = { face: null, record: null }; // current face record and matched database record\n\nconst blink = { // internal timers for blink start/end/duration\n start: 0,\n end: 0,\n time: 0,\n};\n\n// let db: Array<{ name: string, source: string, embedding: number[] }> = []; // holds loaded face descriptor database\nconst human = new H.Human(humanConfig); // create instance of human with overrides from user configuration\n\nhuman.env.perfadd = false; // is performance data showing instant or total values\nhuman.draw.options.font = 'small-caps 18px \"Lato\"'; // set font used to draw labels when using draw methods\nhuman.draw.options.lineHeight = 20;\n\nconst dom = { // grab instances of dom objects so we dont have to look them up later\n video: document.getElementById('video') as HTMLVideoElement,\n canvas: document.getElementById('canvas') as HTMLCanvasElement,\n log: document.getElementById('log') as HTMLPreElement,\n fps: document.getElementById('fps') as HTMLPreElement,\n match: document.getElementById('match') as HTMLDivElement,\n name: document.getElementById('name') as HTMLInputElement,\n save: document.getElementById('save') as HTMLSpanElement,\n delete: document.getElementById('delete') as HTMLSpanElement,\n retry: document.getElementById('retry') as HTMLDivElement,\n source: document.getElementById('source') as HTMLCanvasElement,\n ok: document.getElementById('ok') as HTMLDivElement,\n};\nconst timestamp = { detect: 0, draw: 0 }; // holds information used to calculate performance and possible memory leaks\nconst fps = { detect: 0, draw: 0 }; // holds calculated fps information for both detect and screen refresh\nlet startTime = 0;\n\nconst log = (...msg) => { // helper method to output messages\n dom.log.innerText += msg.join(' ') + '\\n';\n console.log(...msg); // eslint-disable-line no-console\n};\nconst printFPS = (msg) => dom.fps.innerText = msg; // print status element\n\nasync function webCam() { // initialize webcam\n printFPS('starting webcam...');\n // @ts-ignore resizeMode is not yet defined in tslib\n const cameraOptions: MediaStreamConstraints = { audio: false, video: { facingMode: 'user', resizeMode: 'none', width: { ideal: document.body.clientWidth } } };\n const stream: MediaStream = await navigator.mediaDevices.getUserMedia(cameraOptions);\n const ready = new Promise((resolve) => { dom.video.onloadeddata = () => resolve(true); });\n dom.video.srcObject = stream;\n void dom.video.play();\n await ready;\n dom.canvas.width = dom.video.videoWidth;\n dom.canvas.height = dom.video.videoHeight;\n if (human.env.initial) log('video:', dom.video.videoWidth, dom.video.videoHeight, '|', stream.getVideoTracks()[0].label);\n dom.canvas.onclick = () => { // pause when clicked on screen and resume on next click\n if (dom.video.paused) void dom.video.play();\n else dom.video.pause();\n };\n}\n\nasync function detectionLoop() { // main detection loop\n if (!dom.video.paused) {\n if (current.face?.tensor) human.tf.dispose(current.face.tensor); // dispose previous tensor\n await human.detect(dom.video); // actual detection; were not capturing output in a local variable as it can also be reached via human.result\n const now = human.now();\n fps.detect = 1000 / (now - timestamp.detect);\n timestamp.detect = now;\n requestAnimationFrame(detectionLoop); // start new frame immediately\n }\n}\n\nasync function validationLoop(): Promise { // main screen refresh loop\n const interpolated = human.next(human.result); // smoothen result using last-known results\n human.draw.canvas(dom.video, dom.canvas); // draw canvas to screen\n await human.draw.all(dom.canvas, interpolated); // draw labels, boxes, lines, etc.\n const now = human.now();\n fps.draw = 1000 / (now - timestamp.draw);\n timestamp.draw = now;\n printFPS(`fps: ${fps.detect.toFixed(1).padStart(5, ' ')} detect | ${fps.draw.toFixed(1).padStart(5, ' ')} draw`); // write status\n ok.faceCount = human.result.face.length === 1; // must be exactly detected face\n if (ok.faceCount) { // skip the rest if no face\n const gestures: string[] = Object.values(human.result.gesture).map((gesture: H.GestureResult) => gesture.gesture); // flatten all gestures\n if (gestures.includes('blink left eye') || gestures.includes('blink right eye')) blink.start = human.now(); // blink starts when eyes get closed\n if (blink.start > 0 && !gestures.includes('blink left eye') && !gestures.includes('blink right eye')) blink.end = human.now(); // if blink started how long until eyes are back open\n ok.blinkDetected = ok.blinkDetected || (Math.abs(blink.end - blink.start) > options.blinkMin && Math.abs(blink.end - blink.start) < options.blinkMax);\n if (ok.blinkDetected && blink.time === 0) blink.time = Math.trunc(blink.end - blink.start);\n ok.facingCenter = gestures.includes('facing center');\n ok.lookingCenter = gestures.includes('looking center'); // must face camera and look at camera\n ok.faceConfidence = (human.result.face[0].boxScore || 0) > options.minConfidence && (human.result.face[0].faceScore || 0) > options.minConfidence;\n ok.antispoofCheck = (human.result.face[0].real || 0) > options.minConfidence;\n ok.livenessCheck = (human.result.face[0].live || 0) > options.minConfidence;\n ok.faceSize = human.result.face[0].box[2] >= options.minSize && human.result.face[0].box[3] >= options.minSize;\n }\n let y = 32;\n for (const [key, val] of Object.entries(ok)) {\n let el = document.getElementById(`ok-${key}`);\n if (!el) {\n el = document.createElement('div');\n el.innerText = key;\n el.className = 'ok';\n el.style.top = `${y}px`;\n dom.ok.appendChild(el);\n }\n if (typeof val === 'boolean') el.style.backgroundColor = val ? 'lightgreen' : 'lightcoral';\n else el.innerText = `${key}:${val}`;\n y += 28;\n }\n if (allOk()) { // all criteria met\n dom.video.pause();\n return human.result.face[0];\n }\n if (ok.elapsedMs > options.maxTime) { // give up\n dom.video.pause();\n return human.result.face[0];\n }\n // run again\n ok.elapsedMs = Math.trunc(human.now() - startTime);\n return new Promise((resolve) => {\n setTimeout(async () => {\n await validationLoop(); // run validation loop until conditions are met\n resolve(human.result.face[0]); // recursive promise resolve\n }, 30); // use to slow down refresh from max refresh rate to target of 30 fps\n });\n}\n\nasync function saveRecords() {\n if (dom.name.value.length > 0) {\n const image = dom.canvas.getContext('2d')?.getImageData(0, 0, dom.canvas.width, dom.canvas.height) as ImageData;\n const rec = { id: 0, name: dom.name.value, descriptor: current.face?.embedding as number[], image };\n await indexDb.save(rec);\n log('saved face record:', rec.name, 'descriptor length:', current.face?.embedding?.length);\n log('known face records:', await indexDb.count());\n } else {\n log('invalid name');\n }\n}\n\nasync function deleteRecord() {\n if (current.record && current.record.id > 0) {\n await indexDb.remove(current.record);\n }\n}\n\nasync function detectFace() {\n dom.canvas.getContext('2d')?.clearRect(0, 0, options.minSize, options.minSize);\n if (!current?.face?.tensor || !current?.face?.embedding) return false;\n console.log('face record:', current.face); // eslint-disable-line no-console\n human.tf.browser.toPixels(current.face.tensor as unknown as H.TensorLike, dom.canvas);\n if (await indexDb.count() === 0) {\n log('face database is empty');\n document.body.style.background = 'black';\n dom.delete.style.display = 'none';\n return false;\n }\n const db = await indexDb.load();\n const descriptors = db.map((rec) => rec.descriptor).filter((desc) => desc.length > 0);\n const res = human.match(current.face.embedding, descriptors, matchOptions);\n current.record = db[res.index] || null;\n if (current.record) {\n log(`best match: ${current.record.name} | id: ${current.record.id} | similarity: ${Math.round(1000 * res.similarity) / 10}%`);\n dom.name.value = current.record.name;\n dom.source.style.display = '';\n dom.source.getContext('2d')?.putImageData(current.record.image, 0, 0);\n }\n document.body.style.background = res.similarity > options.threshold ? 'darkgreen' : 'maroon';\n return res.similarity > options.threshold;\n}\n\nasync function main() { // main entry point\n ok.faceCount = false;\n ok.faceConfidence = false;\n ok.facingCenter = false;\n ok.blinkDetected = false;\n ok.faceSize = false;\n ok.antispoofCheck = false;\n ok.livenessCheck = false;\n ok.elapsedMs = 0;\n dom.match.style.display = 'none';\n dom.retry.style.display = 'none';\n dom.source.style.display = 'none';\n document.body.style.background = 'black';\n await webCam();\n await detectionLoop(); // start detection loop\n startTime = human.now();\n current.face = await validationLoop(); // start validation loop\n dom.canvas.width = current.face.tensor?.shape[1] || options.minSize;\n dom.canvas.height = current.face.tensor?.shape[0] || options.minSize;\n dom.source.width = dom.canvas.width;\n dom.source.height = dom.canvas.height;\n dom.canvas.style.width = '';\n dom.match.style.display = 'flex';\n dom.save.style.display = 'flex';\n dom.delete.style.display = 'flex';\n dom.retry.style.display = 'block';\n if (!allOk()) { // is all criteria met?\n log('did not find valid face');\n return false;\n }\n return detectFace();\n}\n\nasync function init() {\n log('human version:', human.version, '| tfjs version:', human.tf.version['tfjs-core']);\n log('face embedding model:', humanConfig.face.description.enabled ? 'faceres' : '', humanConfig.face['mobilefacenet']?.enabled ? 'mobilefacenet' : '', humanConfig.face['insightface']?.enabled ? 'insightface' : '');\n log('options:', JSON.stringify(options).replace(/{|}|\"|\\[|\\]/g, '').replace(/,/g, ' '));\n printFPS('loading...');\n log('known face records:', await indexDb.count());\n await webCam(); // start webcam\n await human.load(); // preload all models\n printFPS('initializing...');\n dom.retry.addEventListener('click', main);\n dom.save.addEventListener('click', saveRecords);\n dom.delete.addEventListener('click', deleteRecord);\n await human.warmup(); // warmup function to initialize backend for future faster detection\n await main();\n}\n\nwindow.onload = init;\n", "let db: IDBDatabase; // instance of indexdb\n\nconst database = 'human';\nconst table = 'person';\n\nexport interface FaceRecord { id: number, name: string, descriptor: number[], image: ImageData }\n\nconst log = (...msg) => console.log('indexdb', ...msg); // eslint-disable-line no-console\n\nexport async function open() {\n if (db) return true;\n return new Promise((resolve) => {\n const request: IDBOpenDBRequest = indexedDB.open(database, 1);\n request.onerror = (evt) => log('error:', evt);\n request.onupgradeneeded = (evt: IDBVersionChangeEvent) => { // create if doesnt exist\n log('create:', evt.target);\n db = (evt.target as IDBOpenDBRequest).result;\n db.createObjectStore(table, { keyPath: 'id', autoIncrement: true });\n };\n request.onsuccess = (evt) => { // open\n db = (evt.target as IDBOpenDBRequest).result;\n log('open:', db);\n resolve(true);\n };\n });\n}\n\nexport async function load(): Promise {\n const faceDB: FaceRecord[] = [];\n if (!db) await open(); // open or create if not already done\n return new Promise((resolve) => {\n const cursor: IDBRequest = db.transaction([table], 'readwrite').objectStore(table).openCursor(null, 'next');\n cursor.onerror = (evt) => log('load error:', evt);\n cursor.onsuccess = (evt) => {\n if ((evt.target as IDBRequest).result) {\n faceDB.push((evt.target as IDBRequest).result.value);\n (evt.target as IDBRequest).result.continue();\n } else {\n resolve(faceDB);\n }\n };\n });\n}\n\nexport async function count(): Promise {\n if (!db) await open(); // open or create if not already done\n return new Promise((resolve) => {\n const store: IDBRequest = db.transaction([table], 'readwrite').objectStore(table).count();\n store.onerror = (evt) => log('count error:', evt);\n store.onsuccess = () => resolve(store.result);\n });\n}\n\nexport async function save(faceRecord: FaceRecord) {\n if (!db) await open(); // open or create if not already done\n const newRecord = { name: faceRecord.name, descriptor: faceRecord.descriptor, image: faceRecord.image }; // omit id as its autoincrement\n db.transaction([table], 'readwrite').objectStore(table).put(newRecord);\n log('save:', newRecord);\n}\n\nexport async function remove(faceRecord: FaceRecord) {\n if (!db) await open(); // open or create if not already done\n db.transaction([table], 'readwrite').objectStore(table).delete(faceRecord.id); // delete based on id\n log('delete:', faceRecord);\n}\n"], - "mappings": "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", - "names": ["H", "db", "database", "table", "log", "msg", "open", "resolve", "request", "evt", "load", "faceDB", "cursor", "count", "store", "save", "faceRecord", "newRecord", "remove", "humanConfig", "matchOptions", "options", "ok", "allOk", "current", "blink", "human", "dom", "timestamp", "fps", "startTime", "log", "msg", "printFPS", "webCam", "cameraOptions", "stream", "ready", "resolve", "detectionLoop", "_a", "now", "validationLoop", "interpolated", "gestures", "gesture", "y", "key", "val", "el", "saveRecords", "_b", "_c", "_d", "image", "rec", "save", "count", "deleteRecord", "remove", "detectFace", "db", "load", "descriptors", "desc", "res", "main", "init"] + "sourcesContent": ["/**\n * Human demo for browsers\n * @default Human Library\n * @summary \n * @author \n * @copyright \n * @license MIT\n */\n\nimport * as H from '../../dist/human.esm.js'; // equivalent of @vladmandic/Human\nimport * as indexDb from './indexdb'; // methods to deal with indexdb\n\nconst humanConfig = { // user configuration for human, used to fine-tune behavior\n cacheSensitivity: 0,\n modelBasePath: '../../models',\n filter: { equalization: true }, // lets run with histogram equilizer\n face: {\n enabled: true,\n detector: { rotation: true, return: true, cropFactor: 1.6, mask: false }, // return tensor is used to get detected face image\n description: { enabled: true }, // default model for face descriptor extraction is faceres\n // mobilefacenet: { enabled: true, modelPath: 'https://vladmandic.github.io/human-models/models/mobilefacenet.json' }, // alternative model\n // insightface: { enabled: true, modelPath: 'https://vladmandic.github.io/insightface/models/insightface-mobilenet-swish.json' }, // alternative model\n iris: { enabled: true }, // needed to determine gaze direction\n emotion: { enabled: false }, // not needed\n antispoof: { enabled: true }, // enable optional antispoof module\n liveness: { enabled: true }, // enable optional liveness module\n },\n body: { enabled: false },\n hand: { enabled: false },\n object: { enabled: false },\n gesture: { enabled: true }, // parses face and iris gestures\n};\n\n// const matchOptions = { order: 2, multiplier: 1000, min: 0.0, max: 1.0 }; // for embedding model\nconst matchOptions = { order: 2, multiplier: 25, min: 0.2, max: 0.8 }; // for faceres model\n\nconst options = {\n minConfidence: 0.6, // overal face confidence for box, face, gender, real, live\n minSize: 224, // min input to face descriptor model before degradation\n maxTime: 30000, // max time before giving up\n blinkMin: 10, // minimum duration of a valid blink\n blinkMax: 800, // maximum duration of a valid blink\n threshold: 0.5, // minimum similarity\n mask: humanConfig.face.detector.mask,\n rotation: humanConfig.face.detector.rotation,\n cropFactor: humanConfig.face.detector.cropFactor,\n ...matchOptions,\n};\n\nconst ok: Record = { // must meet all rules\n faceCount: { status: false, val: 0 },\n faceConfidence: { status: false, val: 0 },\n facingCenter: { status: false, val: 0 },\n lookingCenter: { status: false, val: 0 },\n blinkDetected: { status: false, val: 0 },\n faceSize: { status: false, val: 0 },\n antispoofCheck: { status: false, val: 0 },\n livenessCheck: { status: false, val: 0 },\n age: { status: false, val: 0 },\n gender: { status: false, val: 0 },\n timeout: { status: true, val: 0 },\n descriptor: { status: false, val: 0 },\n elapsedMs: { status: undefined, val: 0 }, // total time while waiting for valid face\n detectFPS: { status: undefined, val: 0 }, // mark detection fps performance\n drawFPS: { status: undefined, val: 0 }, // mark redraw fps performance\n};\n\nconst allOk = () => ok.faceCount.status\n && ok.faceSize.status\n && ok.blinkDetected.status\n && ok.facingCenter.status\n && ok.lookingCenter.status\n && ok.faceConfidence.status\n && ok.antispoofCheck.status\n && ok.livenessCheck.status\n && ok.descriptor.status\n && ok.age.status\n && ok.gender.status;\n\nconst current: { face: H.FaceResult | null, record: indexDb.FaceRecord | null } = { face: null, record: null }; // current face record and matched database record\n\nconst blink = { // internal timers for blink start/end/duration\n start: 0,\n end: 0,\n time: 0,\n};\n\n// let db: Array<{ name: string, source: string, embedding: number[] }> = []; // holds loaded face descriptor database\nconst human = new H.Human(humanConfig); // create instance of human with overrides from user configuration\n\nhuman.env.perfadd = false; // is performance data showing instant or total values\nhuman.draw.options.font = 'small-caps 18px \"Lato\"'; // set font used to draw labels when using draw methods\nhuman.draw.options.lineHeight = 20;\n\nconst dom = { // grab instances of dom objects so we dont have to look them up later\n video: document.getElementById('video') as HTMLVideoElement,\n canvas: document.getElementById('canvas') as HTMLCanvasElement,\n log: document.getElementById('log') as HTMLPreElement,\n fps: document.getElementById('fps') as HTMLPreElement,\n match: document.getElementById('match') as HTMLDivElement,\n name: document.getElementById('name') as HTMLInputElement,\n save: document.getElementById('save') as HTMLSpanElement,\n delete: document.getElementById('delete') as HTMLSpanElement,\n retry: document.getElementById('retry') as HTMLDivElement,\n source: document.getElementById('source') as HTMLCanvasElement,\n ok: document.getElementById('ok') as HTMLDivElement,\n};\nconst timestamp = { detect: 0, draw: 0 }; // holds information used to calculate performance and possible memory leaks\nlet startTime = 0;\n\nconst log = (...msg) => { // helper method to output messages\n dom.log.innerText += msg.join(' ') + '\\n';\n console.log(...msg); // eslint-disable-line no-console\n};\n\nasync function webCam() { // initialize webcam\n // @ts-ignore resizeMode is not yet defined in tslib\n const cameraOptions: MediaStreamConstraints = { audio: false, video: { facingMode: 'user', resizeMode: 'none', width: { ideal: document.body.clientWidth } } };\n const stream: MediaStream = await navigator.mediaDevices.getUserMedia(cameraOptions);\n const ready = new Promise((resolve) => { dom.video.onloadeddata = () => resolve(true); });\n dom.video.srcObject = stream;\n void dom.video.play();\n await ready;\n dom.canvas.width = dom.video.videoWidth;\n dom.canvas.height = dom.video.videoHeight;\n dom.canvas.style.width = '50%';\n dom.canvas.style.height = '50%';\n if (human.env.initial) log('video:', dom.video.videoWidth, dom.video.videoHeight, '|', stream.getVideoTracks()[0].label);\n dom.canvas.onclick = () => { // pause when clicked on screen and resume on next click\n if (dom.video.paused) void dom.video.play();\n else dom.video.pause();\n };\n}\n\nasync function detectionLoop() { // main detection loop\n if (!dom.video.paused) {\n if (current.face?.tensor) human.tf.dispose(current.face.tensor); // dispose previous tensor\n await human.detect(dom.video); // actual detection; were not capturing output in a local variable as it can also be reached via human.result\n const now = human.now();\n ok.detectFPS.val = Math.round(10000 / (now - timestamp.detect)) / 10;\n timestamp.detect = now;\n requestAnimationFrame(detectionLoop); // start new frame immediately\n }\n}\n\nfunction drawValidationTests() {\n let y = 32;\n for (const [key, val] of Object.entries(ok)) {\n let el = document.getElementById(`ok-${key}`);\n if (!el) {\n el = document.createElement('div');\n el.id = `ok-${key}`;\n el.innerText = key;\n el.className = 'ok';\n el.style.top = `${y}px`;\n dom.ok.appendChild(el);\n }\n if (typeof val.status === 'boolean') el.style.backgroundColor = val.status ? 'lightgreen' : 'lightcoral';\n const status = val.status ? 'ok' : 'fail';\n el.innerText = `${key}: ${val.val === 0 ? status : val.val}`;\n y += 28;\n }\n}\n\nasync function validationLoop(): Promise { // main screen refresh loop\n const interpolated = human.next(human.result); // smoothen result using last-known results\n human.draw.canvas(dom.video, dom.canvas); // draw canvas to screen\n await human.draw.all(dom.canvas, interpolated); // draw labels, boxes, lines, etc.\n const now = human.now();\n ok.drawFPS.val = Math.round(10000 / (now - timestamp.draw)) / 10;\n timestamp.draw = now;\n ok.faceCount.val = human.result.face.length;\n ok.faceCount.status = ok.faceCount.val === 1; // must be exactly detected face\n if (ok.faceCount.status) { // skip the rest if no face\n const gestures: string[] = Object.values(human.result.gesture).map((gesture: H.GestureResult) => gesture.gesture); // flatten all gestures\n if (gestures.includes('blink left eye') || gestures.includes('blink right eye')) blink.start = human.now(); // blink starts when eyes get closed\n if (blink.start > 0 && !gestures.includes('blink left eye') && !gestures.includes('blink right eye')) blink.end = human.now(); // if blink started how long until eyes are back open\n ok.blinkDetected.status = ok.blinkDetected.status || (Math.abs(blink.end - blink.start) > options.blinkMin && Math.abs(blink.end - blink.start) < options.blinkMax);\n if (ok.blinkDetected.status && blink.time === 0) blink.time = Math.trunc(blink.end - blink.start);\n ok.facingCenter.status = gestures.includes('facing center');\n ok.lookingCenter.status = gestures.includes('looking center'); // must face camera and look at camera\n ok.faceConfidence.val = human.result.face[0].faceScore || human.result.face[0].boxScore || 0;\n ok.faceConfidence.status = ok.faceConfidence.val >= options.minConfidence;\n ok.antispoofCheck.val = human.result.face[0].real || 0;\n ok.antispoofCheck.status = ok.antispoofCheck.val >= options.minConfidence;\n ok.livenessCheck.val = human.result.face[0].live || 0;\n ok.livenessCheck.status = ok.livenessCheck.val >= options.minConfidence;\n ok.faceSize.val = Math.min(human.result.face[0].box[2], human.result.face[0].box[3]);\n ok.faceSize.status = ok.faceSize.val >= options.minSize;\n ok.descriptor.val = human.result.face[0].embedding?.length || 0;\n ok.descriptor.status = ok.descriptor.val > 0;\n ok.age.val = human.result.face[0].age || 0;\n ok.age.status = ok.age.val > 0;\n ok.gender.val = human.result.face[0].genderScore || 0;\n ok.gender.status = ok.gender.val >= options.minConfidence;\n }\n // run again\n ok.timeout.status = ok.elapsedMs.val <= options.maxTime;\n drawValidationTests();\n if (allOk() || !ok.timeout.status) { // all criteria met\n dom.video.pause();\n return human.result.face[0];\n }\n ok.elapsedMs.val = Math.trunc(human.now() - startTime);\n return new Promise((resolve) => {\n setTimeout(async () => {\n await validationLoop(); // run validation loop until conditions are met\n resolve(human.result.face[0]); // recursive promise resolve\n }, 30); // use to slow down refresh from max refresh rate to target of 30 fps\n });\n}\n\nasync function saveRecords() {\n if (dom.name.value.length > 0) {\n const image = dom.canvas.getContext('2d')?.getImageData(0, 0, dom.canvas.width, dom.canvas.height) as ImageData;\n const rec = { id: 0, name: dom.name.value, descriptor: current.face?.embedding as number[], image };\n await indexDb.save(rec);\n log('saved face record:', rec.name, 'descriptor length:', current.face?.embedding?.length);\n log('known face records:', await indexDb.count());\n } else {\n log('invalid name');\n }\n}\n\nasync function deleteRecord() {\n if (current.record && current.record.id > 0) {\n await indexDb.remove(current.record);\n }\n}\n\nasync function detectFace() {\n dom.canvas.style.height = '';\n dom.canvas.getContext('2d')?.clearRect(0, 0, options.minSize, options.minSize);\n if (!current?.face?.tensor || !current?.face?.embedding) return false;\n console.log('face record:', current.face); // eslint-disable-line no-console\n log(`detected face: ${current.face.gender} ${current.face.age || 0}y distance ${current.face.iris || 0}cm/${Math.round(100 * (current.face.iris || 0) / 2.54) / 100}in`);\n human.tf.browser.toPixels(current.face.tensor as unknown as H.TensorLike, dom.canvas);\n if (await indexDb.count() === 0) {\n log('face database is empty: nothing to compare face with');\n document.body.style.background = 'black';\n dom.delete.style.display = 'none';\n return false;\n }\n const db = await indexDb.load();\n const descriptors = db.map((rec) => rec.descriptor).filter((desc) => desc.length > 0);\n const res = human.match(current.face.embedding, descriptors, matchOptions);\n current.record = db[res.index] || null;\n if (current.record) {\n log(`best match: ${current.record.name} | id: ${current.record.id} | similarity: ${Math.round(1000 * res.similarity) / 10}%`);\n dom.name.value = current.record.name;\n dom.source.style.display = '';\n dom.source.getContext('2d')?.putImageData(current.record.image, 0, 0);\n }\n document.body.style.background = res.similarity > options.threshold ? 'darkgreen' : 'maroon';\n return res.similarity > options.threshold;\n}\n\nasync function main() { // main entry point\n ok.faceCount.status = false;\n ok.faceConfidence.status = false;\n ok.facingCenter.status = false;\n ok.blinkDetected.status = false;\n ok.faceSize.status = false;\n ok.antispoofCheck.status = false;\n ok.livenessCheck.status = false;\n ok.age.status = false;\n ok.gender.status = false;\n ok.elapsedMs.val = 0;\n dom.match.style.display = 'none';\n dom.retry.style.display = 'none';\n dom.source.style.display = 'none';\n dom.canvas.style.height = '50%';\n document.body.style.background = 'black';\n await webCam();\n await detectionLoop(); // start detection loop\n startTime = human.now();\n current.face = await validationLoop(); // start validation loop\n dom.canvas.width = current.face.tensor?.shape[1] || options.minSize;\n dom.canvas.height = current.face.tensor?.shape[0] || options.minSize;\n dom.source.width = dom.canvas.width;\n dom.source.height = dom.canvas.height;\n dom.canvas.style.width = '';\n dom.match.style.display = 'flex';\n dom.save.style.display = 'flex';\n dom.delete.style.display = 'flex';\n dom.retry.style.display = 'block';\n if (!allOk()) { // is all criteria met?\n log('did not find valid face');\n return false;\n }\n return detectFace();\n}\n\nasync function init() {\n log('human version:', human.version, '| tfjs version:', human.tf.version['tfjs-core']);\n log('options:', JSON.stringify(options).replace(/{|}|\"|\\[|\\]/g, '').replace(/,/g, ' '));\n log('initializing webcam...');\n await webCam(); // start webcam\n log('loading human models...');\n await human.load(); // preload all models\n log('initializing human...');\n log('face embedding model:', humanConfig.face.description.enabled ? 'faceres' : '', humanConfig.face['mobilefacenet']?.enabled ? 'mobilefacenet' : '', humanConfig.face['insightface']?.enabled ? 'insightface' : '');\n log('loading face database...');\n log('known face records:', await indexDb.count());\n dom.retry.addEventListener('click', main);\n dom.save.addEventListener('click', saveRecords);\n dom.delete.addEventListener('click', deleteRecord);\n await human.warmup(); // warmup function to initialize backend for future faster detection\n await main();\n}\n\nwindow.onload = init;\n", "let db: IDBDatabase; // instance of indexdb\n\nconst database = 'human';\nconst table = 'person';\n\nexport interface FaceRecord { id: number, name: string, descriptor: number[], image: ImageData }\n\nconst log = (...msg) => console.log('indexdb', ...msg); // eslint-disable-line no-console\n\nexport async function open() {\n if (db) return true;\n return new Promise((resolve) => {\n const request: IDBOpenDBRequest = indexedDB.open(database, 1);\n request.onerror = (evt) => log('error:', evt);\n request.onupgradeneeded = (evt: IDBVersionChangeEvent) => { // create if doesnt exist\n log('create:', evt.target);\n db = (evt.target as IDBOpenDBRequest).result;\n db.createObjectStore(table, { keyPath: 'id', autoIncrement: true });\n };\n request.onsuccess = (evt) => { // open\n db = (evt.target as IDBOpenDBRequest).result;\n log('open:', db);\n resolve(true);\n };\n });\n}\n\nexport async function load(): Promise {\n const faceDB: FaceRecord[] = [];\n if (!db) await open(); // open or create if not already done\n return new Promise((resolve) => {\n const cursor: IDBRequest = db.transaction([table], 'readwrite').objectStore(table).openCursor(null, 'next');\n cursor.onerror = (evt) => log('load error:', evt);\n cursor.onsuccess = (evt) => {\n if ((evt.target as IDBRequest).result) {\n faceDB.push((evt.target as IDBRequest).result.value);\n (evt.target as IDBRequest).result.continue();\n } else {\n resolve(faceDB);\n }\n };\n });\n}\n\nexport async function count(): Promise {\n if (!db) await open(); // open or create if not already done\n return new Promise((resolve) => {\n const store: IDBRequest = db.transaction([table], 'readwrite').objectStore(table).count();\n store.onerror = (evt) => log('count error:', evt);\n store.onsuccess = () => resolve(store.result);\n });\n}\n\nexport async function save(faceRecord: FaceRecord) {\n if (!db) await open(); // open or create if not already done\n const newRecord = { name: faceRecord.name, descriptor: faceRecord.descriptor, image: faceRecord.image }; // omit id as its autoincrement\n db.transaction([table], 'readwrite').objectStore(table).put(newRecord);\n log('save:', newRecord);\n}\n\nexport async function remove(faceRecord: FaceRecord) {\n if (!db) await open(); // open or create if not already done\n db.transaction([table], 'readwrite').objectStore(table).delete(faceRecord.id); // delete based on id\n log('delete:', faceRecord);\n}\n"], + "mappings": "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", + "names": ["H", "db", "database", "table", "log", "msg", "open", "resolve", "request", "evt", "load", "faceDB", "cursor", "count", "store", "save", "faceRecord", "newRecord", "remove", "humanConfig", "matchOptions", "options", "ok", "allOk", "current", "blink", "human", "dom", "timestamp", "startTime", "log", "msg", "webCam", "cameraOptions", "stream", "ready", "resolve", "detectionLoop", "_a", "now", "drawValidationTests", "y", "key", "val", "el", "status", "validationLoop", "interpolated", "gestures", "gesture", "saveRecords", "_b", "_c", "_d", "image", "rec", "save", "count", "deleteRecord", "remove", "detectFace", "db", "load", "descriptors", "desc", "res", "main", "init"] } diff --git a/demo/faceid/index.ts b/demo/faceid/index.ts index aea5028f..b7507a23 100644 --- a/demo/faceid/index.ts +++ b/demo/faceid/index.ts @@ -11,6 +11,7 @@ import * as H from '../../dist/human.esm.js'; // equivalent of @vladmandic/Human import * as indexDb from './indexdb'; // methods to deal with indexdb const humanConfig = { // user configuration for human, used to fine-tune behavior + cacheSensitivity: 0, modelBasePath: '../../models', filter: { equalization: true }, // lets run with histogram equilizer face: { @@ -36,7 +37,7 @@ const matchOptions = { order: 2, multiplier: 25, min: 0.2, max: 0.8 }; // for fa const options = { minConfidence: 0.6, // overal face confidence for box, face, gender, real, live minSize: 224, // min input to face descriptor model before degradation - maxTime: 10000, // max time before giving up + maxTime: 30000, // max time before giving up blinkMin: 10, // minimum duration of a valid blink blinkMax: 800, // maximum duration of a valid blink threshold: 0.5, // minimum similarity @@ -46,18 +47,36 @@ const options = { ...matchOptions, }; -const ok = { // must meet all rules - faceCount: false, - faceConfidence: false, - facingCenter: false, - lookingCenter: false, - blinkDetected: false, - faceSize: false, - antispoofCheck: false, - livenessCheck: false, - elapsedMs: 0, // total time while waiting for valid face +const ok: Record = { // must meet all rules + faceCount: { status: false, val: 0 }, + faceConfidence: { status: false, val: 0 }, + facingCenter: { status: false, val: 0 }, + lookingCenter: { status: false, val: 0 }, + blinkDetected: { status: false, val: 0 }, + faceSize: { status: false, val: 0 }, + antispoofCheck: { status: false, val: 0 }, + livenessCheck: { status: false, val: 0 }, + age: { status: false, val: 0 }, + gender: { status: false, val: 0 }, + timeout: { status: true, val: 0 }, + descriptor: { status: false, val: 0 }, + elapsedMs: { status: undefined, val: 0 }, // total time while waiting for valid face + detectFPS: { status: undefined, val: 0 }, // mark detection fps performance + drawFPS: { status: undefined, val: 0 }, // mark redraw fps performance }; -const allOk = () => ok.faceCount && ok.faceSize && ok.blinkDetected && ok.facingCenter && ok.lookingCenter && ok.faceConfidence && ok.antispoofCheck && ok.livenessCheck; + +const allOk = () => ok.faceCount.status + && ok.faceSize.status + && ok.blinkDetected.status + && ok.facingCenter.status + && ok.lookingCenter.status + && ok.faceConfidence.status + && ok.antispoofCheck.status + && ok.livenessCheck.status + && ok.descriptor.status + && ok.age.status + && ok.gender.status; + const current: { face: H.FaceResult | null, record: indexDb.FaceRecord | null } = { face: null, record: null }; // current face record and matched database record const blink = { // internal timers for blink start/end/duration @@ -87,17 +106,14 @@ const dom = { // grab instances of dom objects so we dont have to look them up l ok: document.getElementById('ok') as HTMLDivElement, }; const timestamp = { detect: 0, draw: 0 }; // holds information used to calculate performance and possible memory leaks -const fps = { detect: 0, draw: 0 }; // holds calculated fps information for both detect and screen refresh let startTime = 0; const log = (...msg) => { // helper method to output messages dom.log.innerText += msg.join(' ') + '\n'; console.log(...msg); // eslint-disable-line no-console }; -const printFPS = (msg) => dom.fps.innerText = msg; // print status element async function webCam() { // initialize webcam - printFPS('starting webcam...'); // @ts-ignore resizeMode is not yet defined in tslib const cameraOptions: MediaStreamConstraints = { audio: false, video: { facingMode: 'user', resizeMode: 'none', width: { ideal: document.body.clientWidth } } }; const stream: MediaStream = await navigator.mediaDevices.getUserMedia(cameraOptions); @@ -107,6 +123,8 @@ async function webCam() { // initialize webcam await ready; dom.canvas.width = dom.video.videoWidth; dom.canvas.height = dom.video.videoHeight; + dom.canvas.style.width = '50%'; + dom.canvas.style.height = '50%'; if (human.env.initial) log('video:', dom.video.videoWidth, dom.video.videoHeight, '|', stream.getVideoTracks()[0].label); dom.canvas.onclick = () => { // pause when clicked on screen and resume on next click if (dom.video.paused) void dom.video.play(); @@ -119,58 +137,71 @@ async function detectionLoop() { // main detection loop if (current.face?.tensor) human.tf.dispose(current.face.tensor); // dispose previous tensor await human.detect(dom.video); // actual detection; were not capturing output in a local variable as it can also be reached via human.result const now = human.now(); - fps.detect = 1000 / (now - timestamp.detect); + ok.detectFPS.val = Math.round(10000 / (now - timestamp.detect)) / 10; timestamp.detect = now; requestAnimationFrame(detectionLoop); // start new frame immediately } } +function drawValidationTests() { + let y = 32; + for (const [key, val] of Object.entries(ok)) { + let el = document.getElementById(`ok-${key}`); + if (!el) { + el = document.createElement('div'); + el.id = `ok-${key}`; + el.innerText = key; + el.className = 'ok'; + el.style.top = `${y}px`; + dom.ok.appendChild(el); + } + if (typeof val.status === 'boolean') el.style.backgroundColor = val.status ? 'lightgreen' : 'lightcoral'; + const status = val.status ? 'ok' : 'fail'; + el.innerText = `${key}: ${val.val === 0 ? status : val.val}`; + y += 28; + } +} + async function validationLoop(): Promise { // main screen refresh loop const interpolated = human.next(human.result); // smoothen result using last-known results human.draw.canvas(dom.video, dom.canvas); // draw canvas to screen await human.draw.all(dom.canvas, interpolated); // draw labels, boxes, lines, etc. const now = human.now(); - fps.draw = 1000 / (now - timestamp.draw); + ok.drawFPS.val = Math.round(10000 / (now - timestamp.draw)) / 10; timestamp.draw = now; - printFPS(`fps: ${fps.detect.toFixed(1).padStart(5, ' ')} detect | ${fps.draw.toFixed(1).padStart(5, ' ')} draw`); // write status - ok.faceCount = human.result.face.length === 1; // must be exactly detected face - if (ok.faceCount) { // skip the rest if no face + ok.faceCount.val = human.result.face.length; + ok.faceCount.status = ok.faceCount.val === 1; // must be exactly detected face + if (ok.faceCount.status) { // skip the rest if no face const gestures: string[] = Object.values(human.result.gesture).map((gesture: H.GestureResult) => gesture.gesture); // flatten all gestures if (gestures.includes('blink left eye') || gestures.includes('blink right eye')) blink.start = human.now(); // blink starts when eyes get closed if (blink.start > 0 && !gestures.includes('blink left eye') && !gestures.includes('blink right eye')) blink.end = human.now(); // if blink started how long until eyes are back open - ok.blinkDetected = ok.blinkDetected || (Math.abs(blink.end - blink.start) > options.blinkMin && Math.abs(blink.end - blink.start) < options.blinkMax); - if (ok.blinkDetected && blink.time === 0) blink.time = Math.trunc(blink.end - blink.start); - ok.facingCenter = gestures.includes('facing center'); - ok.lookingCenter = gestures.includes('looking center'); // must face camera and look at camera - ok.faceConfidence = (human.result.face[0].boxScore || 0) > options.minConfidence && (human.result.face[0].faceScore || 0) > options.minConfidence; - ok.antispoofCheck = (human.result.face[0].real || 0) > options.minConfidence; - ok.livenessCheck = (human.result.face[0].live || 0) > options.minConfidence; - ok.faceSize = human.result.face[0].box[2] >= options.minSize && human.result.face[0].box[3] >= options.minSize; - } - let y = 32; - for (const [key, val] of Object.entries(ok)) { - let el = document.getElementById(`ok-${key}`); - if (!el) { - el = document.createElement('div'); - el.innerText = key; - el.className = 'ok'; - el.style.top = `${y}px`; - dom.ok.appendChild(el); - } - if (typeof val === 'boolean') el.style.backgroundColor = val ? 'lightgreen' : 'lightcoral'; - else el.innerText = `${key}:${val}`; - y += 28; - } - if (allOk()) { // all criteria met - dom.video.pause(); - return human.result.face[0]; - } - if (ok.elapsedMs > options.maxTime) { // give up - dom.video.pause(); - return human.result.face[0]; + ok.blinkDetected.status = ok.blinkDetected.status || (Math.abs(blink.end - blink.start) > options.blinkMin && Math.abs(blink.end - blink.start) < options.blinkMax); + if (ok.blinkDetected.status && blink.time === 0) blink.time = Math.trunc(blink.end - blink.start); + ok.facingCenter.status = gestures.includes('facing center'); + ok.lookingCenter.status = gestures.includes('looking center'); // must face camera and look at camera + ok.faceConfidence.val = human.result.face[0].faceScore || human.result.face[0].boxScore || 0; + ok.faceConfidence.status = ok.faceConfidence.val >= options.minConfidence; + ok.antispoofCheck.val = human.result.face[0].real || 0; + ok.antispoofCheck.status = ok.antispoofCheck.val >= options.minConfidence; + ok.livenessCheck.val = human.result.face[0].live || 0; + ok.livenessCheck.status = ok.livenessCheck.val >= options.minConfidence; + ok.faceSize.val = Math.min(human.result.face[0].box[2], human.result.face[0].box[3]); + ok.faceSize.status = ok.faceSize.val >= options.minSize; + ok.descriptor.val = human.result.face[0].embedding?.length || 0; + ok.descriptor.status = ok.descriptor.val > 0; + ok.age.val = human.result.face[0].age || 0; + ok.age.status = ok.age.val > 0; + ok.gender.val = human.result.face[0].genderScore || 0; + ok.gender.status = ok.gender.val >= options.minConfidence; } // run again - ok.elapsedMs = Math.trunc(human.now() - startTime); + ok.timeout.status = ok.elapsedMs.val <= options.maxTime; + drawValidationTests(); + if (allOk() || !ok.timeout.status) { // all criteria met + dom.video.pause(); + return human.result.face[0]; + } + ok.elapsedMs.val = Math.trunc(human.now() - startTime); return new Promise((resolve) => { setTimeout(async () => { await validationLoop(); // run validation loop until conditions are met @@ -198,12 +229,14 @@ async function deleteRecord() { } async function detectFace() { + dom.canvas.style.height = ''; dom.canvas.getContext('2d')?.clearRect(0, 0, options.minSize, options.minSize); if (!current?.face?.tensor || !current?.face?.embedding) return false; console.log('face record:', current.face); // eslint-disable-line no-console + log(`detected face: ${current.face.gender} ${current.face.age || 0}y distance ${current.face.iris || 0}cm/${Math.round(100 * (current.face.iris || 0) / 2.54) / 100}in`); human.tf.browser.toPixels(current.face.tensor as unknown as H.TensorLike, dom.canvas); if (await indexDb.count() === 0) { - log('face database is empty'); + log('face database is empty: nothing to compare face with'); document.body.style.background = 'black'; dom.delete.style.display = 'none'; return false; @@ -223,17 +256,20 @@ async function detectFace() { } async function main() { // main entry point - ok.faceCount = false; - ok.faceConfidence = false; - ok.facingCenter = false; - ok.blinkDetected = false; - ok.faceSize = false; - ok.antispoofCheck = false; - ok.livenessCheck = false; - ok.elapsedMs = 0; + ok.faceCount.status = false; + ok.faceConfidence.status = false; + ok.facingCenter.status = false; + ok.blinkDetected.status = false; + ok.faceSize.status = false; + ok.antispoofCheck.status = false; + ok.livenessCheck.status = false; + ok.age.status = false; + ok.gender.status = false; + ok.elapsedMs.val = 0; dom.match.style.display = 'none'; dom.retry.style.display = 'none'; dom.source.style.display = 'none'; + dom.canvas.style.height = '50%'; document.body.style.background = 'black'; await webCam(); await detectionLoop(); // start detection loop @@ -257,13 +293,15 @@ async function main() { // main entry point async function init() { log('human version:', human.version, '| tfjs version:', human.tf.version['tfjs-core']); - log('face embedding model:', humanConfig.face.description.enabled ? 'faceres' : '', humanConfig.face['mobilefacenet']?.enabled ? 'mobilefacenet' : '', humanConfig.face['insightface']?.enabled ? 'insightface' : ''); log('options:', JSON.stringify(options).replace(/{|}|"|\[|\]/g, '').replace(/,/g, ' ')); - printFPS('loading...'); - log('known face records:', await indexDb.count()); + log('initializing webcam...'); await webCam(); // start webcam + log('loading human models...'); await human.load(); // preload all models - printFPS('initializing...'); + log('initializing human...'); + log('face embedding model:', humanConfig.face.description.enabled ? 'faceres' : '', humanConfig.face['mobilefacenet']?.enabled ? 'mobilefacenet' : '', humanConfig.face['insightface']?.enabled ? 'insightface' : ''); + log('loading face database...'); + log('known face records:', await indexDb.count()); dom.retry.addEventListener('click', main); dom.save.addEventListener('click', saveRecords); dom.delete.addEventListener('click', deleteRecord); diff --git a/package.json b/package.json index 9f8c017a..7bd378ed 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "@tensorflow/tfjs-node": "^3.20.0", "@tensorflow/tfjs-node-gpu": "^3.20.0", "@tensorflow/tfjs-tflite": "0.0.1-alpha.8", - "@types/node": "^18.7.20", + "@types/node": "^18.7.21", "@types/offscreencanvas": "^2019.7.0", "@typescript-eslint/eslint-plugin": "^5.38.0", "@typescript-eslint/parser": "^5.38.0", diff --git a/test/build.log b/test/build.log index 58678654..b6f93fb1 100644 --- a/test/build.log +++ b/test/build.log @@ -1,39 +1,39 @@ -2022-09-24 11:38:12 DATA:  Build {"name":"@vladmandic/human","version":"2.10.3"} -2022-09-24 11:38:12 INFO:  Application: {"name":"@vladmandic/human","version":"2.10.3"} -2022-09-24 11:38:12 INFO:  Environment: {"profile":"production","config":".build.json","package":"package.json","tsconfig":true,"eslintrc":true,"git":true} -2022-09-24 11:38:12 INFO:  Toolchain: {"build":"0.7.13","esbuild":"0.15.9","typescript":"4.8.3","typedoc":"0.23.15","eslint":"8.24.0"} -2022-09-24 11:38:12 INFO:  Build: {"profile":"production","steps":["clean","compile","typings","typedoc","lint","changelog"]} -2022-09-24 11:38:12 STATE: Clean: {"locations":["dist/*","types/lib/*","typedoc/*"]} -2022-09-24 11:38:12 STATE: Compile: {"name":"tfjs/nodejs/cpu","format":"cjs","platform":"node","input":"tfjs/tf-node.ts","output":"dist/tfjs.esm.js","files":1,"inputBytes":159,"outputBytes":608} -2022-09-24 11:38:12 STATE: Compile: {"name":"human/nodejs/cpu","format":"cjs","platform":"node","input":"src/human.ts","output":"dist/human.node.js","files":75,"inputBytes":658536,"outputBytes":309564} -2022-09-24 11:38:12 STATE: Compile: {"name":"tfjs/nodejs/gpu","format":"cjs","platform":"node","input":"tfjs/tf-node-gpu.ts","output":"dist/tfjs.esm.js","files":1,"inputBytes":167,"outputBytes":612} -2022-09-24 11:38:12 STATE: Compile: {"name":"human/nodejs/gpu","format":"cjs","platform":"node","input":"src/human.ts","output":"dist/human.node-gpu.js","files":75,"inputBytes":658540,"outputBytes":309568} -2022-09-24 11:38:12 STATE: Compile: {"name":"tfjs/nodejs/wasm","format":"cjs","platform":"node","input":"tfjs/tf-node-wasm.ts","output":"dist/tfjs.esm.js","files":1,"inputBytes":206,"outputBytes":664} -2022-09-24 11:38:12 STATE: Compile: {"name":"human/nodejs/wasm","format":"cjs","platform":"node","input":"src/human.ts","output":"dist/human.node-wasm.js","files":75,"inputBytes":658592,"outputBytes":309618} -2022-09-24 11:38:12 STATE: Compile: {"name":"tfjs/browser/version","format":"esm","platform":"browser","input":"tfjs/tf-version.ts","output":"dist/tfjs.version.js","files":1,"inputBytes":1125,"outputBytes":358} -2022-09-24 11:38:12 STATE: Compile: {"name":"tfjs/browser/esm/nobundle","format":"esm","platform":"browser","input":"tfjs/tf-browser.ts","output":"dist/tfjs.esm.js","files":2,"inputBytes":1088,"outputBytes":583} -2022-09-24 11:38:12 STATE: Compile: {"name":"human/browser/esm/nobundle","format":"esm","platform":"browser","input":"src/human.ts","output":"dist/human.esm-nobundle.js","files":75,"inputBytes":658511,"outputBytes":308423} -2022-09-24 11:38:12 STATE: Compile: {"name":"tfjs/browser/esm/custom","format":"esm","platform":"browser","input":"tfjs/tf-custom.ts","output":"dist/tfjs.esm.js","files":11,"inputBytes":1344,"outputBytes":2821914} -2022-09-24 11:38:12 STATE: Compile: {"name":"human/browser/iife/bundle","format":"iife","platform":"browser","input":"src/human.ts","output":"dist/human.js","files":75,"inputBytes":3479842,"outputBytes":1688553} -2022-09-24 11:38:12 STATE: Compile: {"name":"human/browser/esm/bundle","format":"esm","platform":"browser","input":"src/human.ts","output":"dist/human.esm.js","files":75,"inputBytes":3479842,"outputBytes":3110385} -2022-09-24 11:38:17 STATE: Typings: {"input":"src/human.ts","output":"types/lib","files":15} -2022-09-24 11:38:19 STATE: TypeDoc: {"input":"src/human.ts","output":"typedoc","objects":77,"generated":true} -2022-09-24 11:38:19 STATE: Compile: {"name":"demo/typescript","format":"esm","platform":"browser","input":"demo/typescript/index.ts","output":"demo/typescript/index.js","files":1,"inputBytes":6714,"outputBytes":3134} -2022-09-24 11:38:19 STATE: Compile: {"name":"demo/faceid","format":"esm","platform":"browser","input":"demo/faceid/index.ts","output":"demo/faceid/index.js","files":2,"inputBytes":15488,"outputBytes":7788} -2022-09-24 11:38:29 STATE: Lint: {"locations":["*.json","src/**/*.ts","test/**/*.js","demo/**/*.js"],"files":110,"errors":0,"warnings":0} -2022-09-24 11:38:29 STATE: ChangeLog: {"repository":"https://github.com/vladmandic/human","branch":"main","output":"CHANGELOG.md"} -2022-09-24 11:38:29 STATE: Copy: {"input":"tfjs/tfjs.esm.d.ts"} -2022-09-24 11:38:29 INFO:  Done... -2022-09-24 11:38:30 STATE: API-Extractor: {"succeeeded":true,"errors":0,"warnings":193} -2022-09-24 11:38:30 STATE: Copy: {"input":"types/human.d.ts"} -2022-09-24 11:38:30 INFO:  Analyze models: {"folders":8,"result":"models/models.json"} -2022-09-24 11:38:30 STATE: Models {"folder":"./models","models":13} -2022-09-24 11:38:30 STATE: Models {"folder":"../human-models/models","models":42} -2022-09-24 11:38:30 STATE: Models {"folder":"../blazepose/model/","models":4} -2022-09-24 11:38:30 STATE: Models {"folder":"../anti-spoofing/model","models":1} -2022-09-24 11:38:30 STATE: Models {"folder":"../efficientpose/models","models":3} -2022-09-24 11:38:30 STATE: Models {"folder":"../insightface/models","models":5} -2022-09-24 11:38:30 STATE: Models {"folder":"../movenet/models","models":3} -2022-09-24 11:38:30 STATE: Models {"folder":"../nanodet/models","models":4} -2022-09-24 11:38:31 STATE: Models: {"count":57,"totalSize":383017442} -2022-09-24 11:38:31 INFO:  Human Build complete... {"logFile":"test/build.log"} +2022-09-25 10:14:55 DATA:  Build {"name":"@vladmandic/human","version":"2.10.3"} +2022-09-25 10:14:55 INFO:  Application: {"name":"@vladmandic/human","version":"2.10.3"} +2022-09-25 10:14:55 INFO:  Environment: {"profile":"production","config":".build.json","package":"package.json","tsconfig":true,"eslintrc":true,"git":true} +2022-09-25 10:14:55 INFO:  Toolchain: {"build":"0.7.13","esbuild":"0.15.9","typescript":"4.8.3","typedoc":"0.23.15","eslint":"8.24.0"} +2022-09-25 10:14:55 INFO:  Build: {"profile":"production","steps":["clean","compile","typings","typedoc","lint","changelog"]} +2022-09-25 10:14:55 STATE: Clean: {"locations":["dist/*","types/lib/*","typedoc/*"]} +2022-09-25 10:14:55 STATE: Compile: {"name":"tfjs/nodejs/cpu","format":"cjs","platform":"node","input":"tfjs/tf-node.ts","output":"dist/tfjs.esm.js","files":1,"inputBytes":159,"outputBytes":608} +2022-09-25 10:14:55 STATE: Compile: {"name":"human/nodejs/cpu","format":"cjs","platform":"node","input":"src/human.ts","output":"dist/human.node.js","files":75,"inputBytes":658536,"outputBytes":309564} +2022-09-25 10:14:55 STATE: Compile: {"name":"tfjs/nodejs/gpu","format":"cjs","platform":"node","input":"tfjs/tf-node-gpu.ts","output":"dist/tfjs.esm.js","files":1,"inputBytes":167,"outputBytes":612} +2022-09-25 10:14:55 STATE: Compile: {"name":"human/nodejs/gpu","format":"cjs","platform":"node","input":"src/human.ts","output":"dist/human.node-gpu.js","files":75,"inputBytes":658540,"outputBytes":309568} +2022-09-25 10:14:55 STATE: Compile: {"name":"tfjs/nodejs/wasm","format":"cjs","platform":"node","input":"tfjs/tf-node-wasm.ts","output":"dist/tfjs.esm.js","files":1,"inputBytes":206,"outputBytes":664} +2022-09-25 10:14:55 STATE: Compile: {"name":"human/nodejs/wasm","format":"cjs","platform":"node","input":"src/human.ts","output":"dist/human.node-wasm.js","files":75,"inputBytes":658592,"outputBytes":309618} +2022-09-25 10:14:55 STATE: Compile: {"name":"tfjs/browser/version","format":"esm","platform":"browser","input":"tfjs/tf-version.ts","output":"dist/tfjs.version.js","files":1,"inputBytes":1125,"outputBytes":358} +2022-09-25 10:14:55 STATE: Compile: {"name":"tfjs/browser/esm/nobundle","format":"esm","platform":"browser","input":"tfjs/tf-browser.ts","output":"dist/tfjs.esm.js","files":2,"inputBytes":1088,"outputBytes":583} +2022-09-25 10:14:55 STATE: Compile: {"name":"human/browser/esm/nobundle","format":"esm","platform":"browser","input":"src/human.ts","output":"dist/human.esm-nobundle.js","files":75,"inputBytes":658511,"outputBytes":308423} +2022-09-25 10:14:55 STATE: Compile: {"name":"tfjs/browser/esm/custom","format":"esm","platform":"browser","input":"tfjs/tf-custom.ts","output":"dist/tfjs.esm.js","files":11,"inputBytes":1344,"outputBytes":2821914} +2022-09-25 10:14:55 STATE: Compile: {"name":"human/browser/iife/bundle","format":"iife","platform":"browser","input":"src/human.ts","output":"dist/human.js","files":75,"inputBytes":3479842,"outputBytes":1688553} +2022-09-25 10:14:56 STATE: Compile: {"name":"human/browser/esm/bundle","format":"esm","platform":"browser","input":"src/human.ts","output":"dist/human.esm.js","files":75,"inputBytes":3479842,"outputBytes":3110385} +2022-09-25 10:15:00 STATE: Typings: {"input":"src/human.ts","output":"types/lib","files":15} +2022-09-25 10:15:02 STATE: TypeDoc: {"input":"src/human.ts","output":"typedoc","objects":77,"generated":true} +2022-09-25 10:15:02 STATE: Compile: {"name":"demo/typescript","format":"esm","platform":"browser","input":"demo/typescript/index.ts","output":"demo/typescript/index.js","files":1,"inputBytes":6714,"outputBytes":3134} +2022-09-25 10:15:02 STATE: Compile: {"name":"demo/faceid","format":"esm","platform":"browser","input":"demo/faceid/index.ts","output":"demo/faceid/index.js","files":2,"inputBytes":17155,"outputBytes":9175} +2022-09-25 10:15:13 STATE: Lint: {"locations":["*.json","src/**/*.ts","test/**/*.js","demo/**/*.js"],"files":110,"errors":0,"warnings":0} +2022-09-25 10:15:14 STATE: ChangeLog: {"repository":"https://github.com/vladmandic/human","branch":"main","output":"CHANGELOG.md"} +2022-09-25 10:15:14 STATE: Copy: {"input":"tfjs/tfjs.esm.d.ts"} +2022-09-25 10:15:14 INFO:  Done... +2022-09-25 10:15:14 STATE: API-Extractor: {"succeeeded":true,"errors":0,"warnings":193} +2022-09-25 10:15:14 STATE: Copy: {"input":"types/human.d.ts"} +2022-09-25 10:15:14 INFO:  Analyze models: {"folders":8,"result":"models/models.json"} +2022-09-25 10:15:14 STATE: Models {"folder":"./models","models":13} +2022-09-25 10:15:14 STATE: Models {"folder":"../human-models/models","models":42} +2022-09-25 10:15:14 STATE: Models {"folder":"../blazepose/model/","models":4} +2022-09-25 10:15:14 STATE: Models {"folder":"../anti-spoofing/model","models":1} +2022-09-25 10:15:14 STATE: Models {"folder":"../efficientpose/models","models":3} +2022-09-25 10:15:14 STATE: Models {"folder":"../insightface/models","models":5} +2022-09-25 10:15:14 STATE: Models {"folder":"../movenet/models","models":3} +2022-09-25 10:15:14 STATE: Models {"folder":"../nanodet/models","models":4} +2022-09-25 10:15:15 STATE: Models: {"count":57,"totalSize":383017442} +2022-09-25 10:15:15 INFO:  Human Build complete... {"logFile":"test/build.log"} diff --git a/wiki b/wiki index cf9ea492..c90beada 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit cf9ea4929d720dcb4e1b25a6b7c1fb4c5b4d2718 +Subproject commit c90beadaf77a71df5c25c08d878ad2b6913b15dd