Socket
Socket
Sign inDemoInstall

@tensorflow-models/handpose

Package Overview
Dependencies
308
Maintainers
6
Versions
7
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.0.3 to 0.0.4

dist/rotate_webgpu.d.ts

2

dist/hand.d.ts

@@ -21,3 +21,3 @@ import * as tfconv from '@tensorflow/tfjs-converter';

private getBoundingBoxes;
estimateHandBounds(input: tf.Tensor4D): Box;
estimateHandBounds(input: tf.Tensor4D): Promise<Box>;
}

@@ -34,33 +34,40 @@ "use strict";

}
getBoundingBoxes(input) {
return tf.tidy(() => {
const normalizedInput = tf.mul(tf.sub(input, 0.5), 2);
const savedWebglPackDepthwiseConvFlag = tf.env().get('WEBGL_PACK_DEPTHWISECONV');
tf.env().set('WEBGL_PACK_DEPTHWISECONV', true);
const prediction = this.model.predict(normalizedInput).squeeze();
tf.env().set('WEBGL_PACK_DEPTHWISECONV', savedWebglPackDepthwiseConvFlag);
const scores = tf.sigmoid(tf.slice(prediction, [0, 0], [-1, 1])).squeeze();
const rawBoxes = tf.slice(prediction, [0, 1], [-1, 4]);
const boxes = this.normalizeBoxes(rawBoxes);
const savedConsoleWarnFn = console.warn;
console.warn = () => { };
const boxesWithHands = tf.image
.nonMaxSuppression(boxes, scores, 1, this.iouThreshold, this.scoreThreshold)
.arraySync();
console.warn = savedConsoleWarnFn;
if (boxesWithHands.length === 0) {
return null;
}
const boxIndex = boxesWithHands[0];
const matchingBox = tf.slice(boxes, [boxIndex, 0], [1, -1]);
const rawPalmLandmarks = tf.slice(prediction, [boxIndex, 5], [1, 14]);
const palmLandmarks = this.normalizeLandmarks(rawPalmLandmarks, boxIndex).reshape([-1, 2]);
return { boxes: matchingBox, palmLandmarks };
});
async getBoundingBoxes(input) {
const normalizedInput = tf.tidy(() => tf.mul(tf.sub(input, 0.5), 2));
const savedWebglPackDepthwiseConvFlag = tf.env().get('WEBGL_PACK_DEPTHWISECONV');
tf.env().set('WEBGL_PACK_DEPTHWISECONV', true);
const batchedPrediction = this.model.predict(normalizedInput);
tf.env().set('WEBGL_PACK_DEPTHWISECONV', savedWebglPackDepthwiseConvFlag);
const prediction = batchedPrediction.squeeze();
const scores = tf.tidy(() => tf.sigmoid(tf.slice(prediction, [0, 0], [-1, 1])).squeeze());
const rawBoxes = tf.slice(prediction, [0, 1], [-1, 4]);
const boxes = this.normalizeBoxes(rawBoxes);
const savedConsoleWarnFn = console.warn;
console.warn = () => { };
const boxesWithHandsTensor = tf.image.nonMaxSuppression(boxes, scores, 1, this.iouThreshold, this.scoreThreshold);
console.warn = savedConsoleWarnFn;
const boxesWithHands = await boxesWithHandsTensor.array();
const toDispose = [
normalizedInput, batchedPrediction, boxesWithHandsTensor, prediction,
boxes, rawBoxes, scores
];
if (boxesWithHands.length === 0) {
toDispose.forEach(tensor => tensor.dispose());
return null;
}
const boxIndex = boxesWithHands[0];
const matchingBox = tf.slice(boxes, [boxIndex, 0], [1, -1]);
const rawPalmLandmarks = tf.slice(prediction, [boxIndex, 5], [1, 14]);
const palmLandmarks = tf.tidy(() => this.normalizeLandmarks(rawPalmLandmarks, boxIndex).reshape([
-1, 2
]));
toDispose.push(rawPalmLandmarks);
toDispose.forEach(tensor => tensor.dispose());
return { boxes: matchingBox, palmLandmarks };
}
estimateHandBounds(input) {
async estimateHandBounds(input) {
const inputHeight = input.shape[1];
const inputWidth = input.shape[2];
const image = tf.tidy(() => input.resizeBilinear([this.width, this.height]).div(255));
const prediction = this.getBoundingBoxes(image);
const prediction = await this.getBoundingBoxes(image);
if (prediction === null) {

@@ -67,0 +74,0 @@ image.dispose();

@@ -17,2 +17,2 @@ /**

*/
import{util,Tensor,tidy,browser,tensor2d,tensor1d,slice,add,div,mul,sub,concat2d,env,sigmoid,image,getBackend,reshape,backend,buffer}from"@tensorflow/tfjs-core";import{loadGraphModel}from"@tensorflow/tfjs-converter";function getBoxSize(t){return[Math.abs(t.endPoint[0]-t.startPoint[0]),Math.abs(t.endPoint[1]-t.startPoint[1])]}function getBoxCenter(t){return[t.startPoint[0]+(t.endPoint[0]-t.startPoint[0])/2,t.startPoint[1]+(t.endPoint[1]-t.startPoint[1])/2]}function cutBoxFromImageAndResize(t,n,e){const o=n.shape[1],s=n.shape[2],i=[[t.startPoint[1]/o,t.startPoint[0]/s,t.endPoint[1]/o,t.endPoint[0]/s]];return image.cropAndResize(n,i,[0],e)}function scaleBoxCoordinates(t,n){return{startPoint:[t.startPoint[0]*n[0],t.startPoint[1]*n[1]],endPoint:[t.endPoint[0]*n[0],t.endPoint[1]*n[1]],palmLandmarks:t.palmLandmarks.map(t=>{return[t[0]*n[0],t[1]*n[1]]})}}function enlargeBox(t,n=1.5){const e=getBoxCenter(t),o=getBoxSize(t),s=[n*o[0]/2,n*o[1]/2];return{startPoint:[e[0]-s[0],e[1]-s[1]],endPoint:[e[0]+s[0],e[1]+s[1]],palmLandmarks:t.palmLandmarks}}function squarifyBox(t){const n=getBoxCenter(t),e=getBoxSize(t),o=Math.max(...e)/2;return{startPoint:[n[0]-o,n[1]-o],endPoint:[n[0]+o,n[1]+o],palmLandmarks:t.palmLandmarks}}function shiftBox(t,n){const e=[t.endPoint[0]-t.startPoint[0],t.endPoint[1]-t.startPoint[1]],o=[e[0]*n[0],e[1]*n[1]];return{startPoint:[t.startPoint[0]+o[0],t.startPoint[1]+o[1]],endPoint:[t.endPoint[0]+o[0],t.endPoint[1]+o[1]],palmLandmarks:t.palmLandmarks}}class HandDetector{constructor(t,n,e,o,s,i){this.model=t,this.width=n,this.height=e,this.iouThreshold=s,this.scoreThreshold=i,this.anchors=o.map(t=>[t.x_center,t.y_center]),this.anchorsTensor=tensor2d(this.anchors),this.inputSizeTensor=tensor1d([n,e]),this.doubleInputSizeTensor=tensor1d([2*n,2*e])}normalizeBoxes(t){return tidy(()=>{const n=slice(t,[0,0],[-1,2]),e=slice(t,[0,2],[-1,2]),o=add(div(n,this.inputSizeTensor),this.anchorsTensor),s=div(e,this.doubleInputSizeTensor),i=mul(sub(o,s),this.inputSizeTensor),r=mul(add(o,s),this.inputSizeTensor);return concat2d([i,r],1)})}normalizeLandmarks(t,n){return tidy(()=>{const e=add(div(t.reshape([-1,7,2]),this.inputSizeTensor),this.anchors[n]);return mul(e,this.inputSizeTensor)})}getBoundingBoxes(t){return tidy(()=>{const n=mul(sub(t,.5),2),e=env().get("WEBGL_PACK_DEPTHWISECONV");env().set("WEBGL_PACK_DEPTHWISECONV",!0);const o=this.model.predict(n).squeeze();env().set("WEBGL_PACK_DEPTHWISECONV",e);const s=sigmoid(slice(o,[0,0],[-1,1])).squeeze(),i=slice(o,[0,1],[-1,4]),r=this.normalizeBoxes(i),a=console.warn;console.warn=(()=>{});const d=image.nonMaxSuppression(r,s,1,this.iouThreshold,this.scoreThreshold).arraySync();if(console.warn=a,0===d.length)return null;const u=d[0],c=slice(r,[u,0],[1,-1]),h=slice(o,[u,5],[1,14]);return{boxes:c,palmLandmarks:this.normalizeLandmarks(h,u).reshape([-1,2])}})}estimateHandBounds(t){const n=t.shape[1],e=t.shape[2],o=tidy(()=>t.resizeBilinear([this.width,this.height]).div(255)),s=this.getBoundingBoxes(o);if(null===s)return o.dispose(),null;const i=s.boxes.arraySync(),r=i[0].slice(0,2),a=i[0].slice(2,4),d=s.palmLandmarks.arraySync();return o.dispose(),s.boxes.dispose(),s.palmLandmarks.dispose(),scaleBoxCoordinates({startPoint:r,endPoint:a,palmLandmarks:d},[e/this.width,n/this.height])}}const MESH_ANNOTATIONS={thumb:[1,2,3,4],indexFinger:[5,6,7,8],middleFinger:[9,10,11,12],ringFinger:[13,14,15,16],pinky:[17,18,19,20],palmBase:[0]};function rotate(t,n,e,o){const s=backend(),i=buffer(t.shape,t.dtype),[r,a,d,u]=t.shape,c=d*("number"==typeof o?o:o[0]),h=a*("number"==typeof o?o:o[1]),l=Math.sin(-n),m=Math.cos(-n),p=s.readSync(t.dataId);for(let t=0;t<r;t++)for(let n=0;n<a;n++)for(let o=0;o<d;o++)for(let s=0;s<u;s++){const f=[r,n,o,s],_=f[2],P=f[1];let g=(_-c)*m-(P-h)*l,M=(_-c)*l+(P-h)*m;g=Math.round(g+c),M=Math.round(M+h);let T=e;if("number"!=typeof e&&(T=3===s?255:e[s]),g>=0&&g<d&&M>=0&&M<a){T=p[t*d*a*u+M*(d*u)+g*u+s]}const I=t*d*a*u+n*(d*u)+o*u+s;i.values[I]=T}return i.toTensor()}function rotate$1(t,n,e,o){const s=t.shape,i=s[1],r=s[2],a=Math.sin(n),d=Math.cos(n),u=Math.floor(r*("number"==typeof o?o:o[0])),c=Math.floor(i*("number"==typeof o?o:o[1]));let h="";const l={variableNames:["Image"],outputShape:s,userCode:`\n void main() {\n ivec4 coords = getOutputCoords();\n int x = coords[2];\n int y = coords[1];\n int coordX = int(float(x - ${u}) * ${d} -\n float(y - ${c}) * ${a});\n int coordY = int(float(x - ${u}) * ${a} +\n float(y - ${c}) * ${d});\n coordX = int(coordX + ${u});\n coordY = int(coordY + ${c});\n\n ${h="number"==typeof e?`float outputValue = ${e.toFixed(2)};`:`\n vec3 fill = vec3(${e.join(",")});\n float outputValue = fill[coords[3]];`}\n\n if(coordX > 0 && coordX < ${r} && coordY > 0 && coordY < ${i}) {\n outputValue = getImage(coords[0], coordY, coordX, coords[3]);\n }\n\n setOutput(outputValue);\n }`};return backend().compileAndRun(l,[t])}function normalizeRadians(t){return t-2*Math.PI*Math.floor((t+Math.PI)/(2*Math.PI))}function computeRotation(t,n){return normalizeRadians(Math.PI/2-Math.atan2(-(n[1]-t[1]),n[0]-t[0]))}const buildTranslationMatrix=(t,n)=>[[1,0,t],[0,1,n],[0,0,1]];function dot(t,n){let e=0;for(let o=0;o<t.length;o++)e+=t[o]*n[o];return e}function getColumnFrom2DArr(t,n){const e=[];for(let o=0;o<t.length;o++)e.push(t[o][n]);return e}function multiplyTransformMatrices(t,n){const e=[],o=t.length;for(let s=0;s<o;s++){e.push([]);for(let i=0;i<o;i++)e[s].push(dot(t[s],getColumnFrom2DArr(n,i)))}return e}function buildRotationMatrix(t,n){const e=Math.cos(t),o=Math.sin(t),s=[[e,-o,0],[o,e,0],[0,0,1]];return multiplyTransformMatrices(multiplyTransformMatrices(buildTranslationMatrix(n[0],n[1]),s),buildTranslationMatrix(-n[0],-n[1]))}function invertTransformMatrix(t){const n=[[t[0][0],t[1][0]],[t[0][1],t[1][1]]],e=[t[0][2],t[1][2]],o=[-dot(n[0],e),-dot(n[1],e)];return[n[0].concat(o[0]),n[1].concat(o[1]),[0,0,1]]}function rotatePoint(t,n){return[dot(t,n[0]),dot(t,n[1])]}const UPDATE_REGION_OF_INTEREST_IOU_THRESHOLD=.8,PALM_BOX_SHIFT_VECTOR=[0,-.4],PALM_BOX_ENLARGE_FACTOR=3,HAND_BOX_SHIFT_VECTOR=[0,-.1],HAND_BOX_ENLARGE_FACTOR=1.65,PALM_LANDMARK_IDS=[0,5,9,13,17,1,2],PALM_LANDMARKS_INDEX_OF_PALM_BASE=0,PALM_LANDMARKS_INDEX_OF_MIDDLE_FINGER_BASE=2;class HandPipeline{constructor(t,n,e,o,s,i){this.regionsOfInterest=[],this.runsWithoutHandDetector=0,this.boundingBoxDetector=t,this.meshDetector=n,this.maxContinuousChecks=s,this.detectionConfidence=i,this.meshWidth=e,this.meshHeight=o,this.maxHandsNumber=1}getBoxForPalmLandmarks(t,n){const e=t.map(t=>{return rotatePoint([...t,1],n)});return enlargeBox(squarifyBox(shiftBox(this.calculateLandmarksBoundingBox(e),PALM_BOX_SHIFT_VECTOR)),PALM_BOX_ENLARGE_FACTOR)}getBoxForHandLandmarks(t){const n=enlargeBox(squarifyBox(shiftBox(this.calculateLandmarksBoundingBox(t),HAND_BOX_SHIFT_VECTOR)),HAND_BOX_ENLARGE_FACTOR),e=[];for(let n=0;n<PALM_LANDMARK_IDS.length;n++)e.push(t[PALM_LANDMARK_IDS[n]].slice(0,2));return n.palmLandmarks=e,n}transformRawCoords(t,n,e,o){const s=getBoxSize(n),i=[s[0]/this.meshWidth,s[1]/this.meshHeight],r=t.map(t=>[i[0]*(t[0]-this.meshWidth/2),i[1]*(t[1]-this.meshHeight/2),t[2]]),a=buildRotationMatrix(e,[0,0]),d=r.map(t=>{return[...rotatePoint(t,a),t[2]]}),u=invertTransformMatrix(o),c=[...getBoxCenter(n),1],h=[dot(c,u[0]),dot(c,u[1])];return d.map(t=>[t[0]+h[0],t[1]+h[1],t[2]])}async estimateHand(t){const n=this.shouldUpdateRegionsOfInterest();if(!0===n){const n=this.boundingBoxDetector.estimateHandBounds(t);if(null===n)return t.dispose(),this.regionsOfInterest=[],null;this.updateRegionsOfInterest(n,!0),this.runsWithoutHandDetector=0}else this.runsWithoutHandDetector++;const e=this.regionsOfInterest[0],o=computeRotation(e.palmLandmarks[PALM_LANDMARKS_INDEX_OF_PALM_BASE],e.palmLandmarks[PALM_LANDMARKS_INDEX_OF_MIDDLE_FINGER_BASE]),s=getBoxCenter(e),i=[s[0]/t.shape[2],s[1]/t.shape[1]];let r;const a=getBackend();if("webgl"===a)r=rotate$1(t,o,0,i);else{if("cpu"!==a)throw new Error(`Handpose is not yet supported by the ${a} `+"backend - rotation kernel is not defined.");r=rotate(t,o,0,i)}const d=buildRotationMatrix(-o,s);let u;const c=cutBoxFromImageAndResize(u=!0===n?this.getBoxForPalmLandmarks(e.palmLandmarks,d):e,r,[this.meshWidth,this.meshHeight]),h=c.div(255);c.dispose(),r.dispose();const l=env().get("WEBGL_PACK_DEPTHWISECONV");env().set("WEBGL_PACK_DEPTHWISECONV",!0);const[m,p]=this.meshDetector.predict(h);env().set("WEBGL_PACK_DEPTHWISECONV",l),h.dispose();const f=m.dataSync()[0];if(m.dispose(),f<this.detectionConfidence)return p.dispose(),this.regionsOfInterest=[],null;const _=reshape(p,[-1,3]),P=_.arraySync();p.dispose(),_.dispose();const g=this.transformRawCoords(P,u,o,d),M=this.getBoxForHandLandmarks(g);return this.updateRegionsOfInterest(M,!1),{landmarks:g,handInViewConfidence:f,boundingBox:{topLeft:M.startPoint,bottomRight:M.endPoint}}}calculateLandmarksBoundingBox(t){const n=t.map(t=>t[0]),e=t.map(t=>t[1]);return{startPoint:[Math.min(...n),Math.min(...e)],endPoint:[Math.max(...n),Math.max(...e)]}}updateRegionsOfInterest(t,n){if(n)this.regionsOfInterest=[t];else{const n=this.regionsOfInterest[0];let e=0;if(null!=n&&null!=n.startPoint){const[o,s]=t.startPoint,[i,r]=t.endPoint,[a,d]=n.startPoint,[u,c]=n.endPoint,h=Math.max(o,a),l=Math.max(s,d),m=(Math.min(i,u)-h)*(Math.min(r,c)-l);e=m/((i-o)*(r-s)+(u-a)*(c-s)-m)}this.regionsOfInterest[0]=e>UPDATE_REGION_OF_INTEREST_IOU_THRESHOLD?n:t}}shouldUpdateRegionsOfInterest(){return this.regionsOfInterest.length!==this.maxHandsNumber||this.runsWithoutHandDetector>=this.maxContinuousChecks}}async function loadHandDetectorModel(){return loadGraphModel("https://tfhub.dev/mediapipe/tfjs-model/handdetector/1/default/1",{fromTFHub:!0})}const MESH_MODEL_INPUT_WIDTH=256,MESH_MODEL_INPUT_HEIGHT=256;async function loadHandPoseModel(){return loadGraphModel("https://tfhub.dev/mediapipe/tfjs-model/handskeleton/1/default/1",{fromTFHub:!0})}async function loadAnchors(){return util.fetch("https://tfhub.dev/mediapipe/tfjs-model/handskeleton/1/default/1/anchors.json?tfjs-format=file").then(t=>t.json())}async function load({maxContinuousChecks:t=1/0,detectionConfidence:n=.8,iouThreshold:e=.3,scoreThreshold:o=.5}={}){const[s,i,r]=await Promise.all([loadAnchors(),loadHandDetectorModel(),loadHandPoseModel()]),a=new HandDetector(i,MESH_MODEL_INPUT_WIDTH,MESH_MODEL_INPUT_HEIGHT,s,e,o),d=new HandPipeline(a,r,MESH_MODEL_INPUT_WIDTH,MESH_MODEL_INPUT_HEIGHT,t,n);return new HandPose(d)}function getInputTensorDimensions(t){return t instanceof Tensor?[t.shape[0],t.shape[1]]:[t.height,t.width]}function flipHandHorizontal(t,n){const{handInViewConfidence:e,landmarks:o,boundingBox:s}=t;return{handInViewConfidence:e,landmarks:o.map(t=>[n-1-t[0],t[1],t[2]]),boundingBox:{topLeft:[n-1-s.topLeft[0],s.topLeft[1]],bottomRight:[n-1-s.bottomRight[0],s.bottomRight[1]]}}}class HandPose{constructor(t){this.pipeline=t}static getAnnotations(){return MESH_ANNOTATIONS}async estimateHands(t,n=!1){const[,e]=getInputTensorDimensions(t),o=tidy(()=>(t instanceof Tensor||(t=browser.fromPixels(t)),t.toFloat().expandDims(0))),s=await this.pipeline.estimateHand(o);if(o.dispose(),null===s)return[];let i=s;!0===n&&(i=flipHandHorizontal(s,e));const r={};for(const t of Object.keys(MESH_ANNOTATIONS))r[t]=MESH_ANNOTATIONS[t].map(t=>i.landmarks[t]);return[{handInViewConfidence:i.handInViewConfidence,boundingBox:i.boundingBox,landmarks:i.landmarks,annotations:r}]}}export{load,HandPose};
import{util,Tensor,tidy,browser,tensor2d,tensor1d,slice,add,div,mul,sub,concat2d,env,sigmoid,image,getBackend,reshape,backend,buffer}from"@tensorflow/tfjs-core";import{loadGraphModel}from"@tensorflow/tfjs-converter";function getBoxSize(t){return[Math.abs(t.endPoint[0]-t.startPoint[0]),Math.abs(t.endPoint[1]-t.startPoint[1])]}function getBoxCenter(t){return[t.startPoint[0]+(t.endPoint[0]-t.startPoint[0])/2,t.startPoint[1]+(t.endPoint[1]-t.startPoint[1])/2]}function cutBoxFromImageAndResize(t,n,e){const o=n.shape[1],s=n.shape[2],i=[[t.startPoint[1]/o,t.startPoint[0]/s,t.endPoint[1]/o,t.endPoint[0]/s]];return image.cropAndResize(n,i,[0],e)}function scaleBoxCoordinates(t,n){return{startPoint:[t.startPoint[0]*n[0],t.startPoint[1]*n[1]],endPoint:[t.endPoint[0]*n[0],t.endPoint[1]*n[1]],palmLandmarks:t.palmLandmarks.map(t=>{return[t[0]*n[0],t[1]*n[1]]})}}function enlargeBox(t,n=1.5){const e=getBoxCenter(t),o=getBoxSize(t),s=[n*o[0]/2,n*o[1]/2];return{startPoint:[e[0]-s[0],e[1]-s[1]],endPoint:[e[0]+s[0],e[1]+s[1]],palmLandmarks:t.palmLandmarks}}function squarifyBox(t){const n=getBoxCenter(t),e=getBoxSize(t),o=Math.max(...e)/2;return{startPoint:[n[0]-o,n[1]-o],endPoint:[n[0]+o,n[1]+o],palmLandmarks:t.palmLandmarks}}function shiftBox(t,n){const e=[t.endPoint[0]-t.startPoint[0],t.endPoint[1]-t.startPoint[1]],o=[e[0]*n[0],e[1]*n[1]];return{startPoint:[t.startPoint[0]+o[0],t.startPoint[1]+o[1]],endPoint:[t.endPoint[0]+o[0],t.endPoint[1]+o[1]],palmLandmarks:t.palmLandmarks}}class HandDetector{constructor(t,n,e,o,s,i){this.model=t,this.width=n,this.height=e,this.iouThreshold=s,this.scoreThreshold=i,this.anchors=o.map(t=>[t.x_center,t.y_center]),this.anchorsTensor=tensor2d(this.anchors),this.inputSizeTensor=tensor1d([n,e]),this.doubleInputSizeTensor=tensor1d([2*n,2*e])}normalizeBoxes(t){return tidy(()=>{const n=slice(t,[0,0],[-1,2]),e=slice(t,[0,2],[-1,2]),o=add(div(n,this.inputSizeTensor),this.anchorsTensor),s=div(e,this.doubleInputSizeTensor),i=mul(sub(o,s),this.inputSizeTensor),r=mul(add(o,s),this.inputSizeTensor);return concat2d([i,r],1)})}normalizeLandmarks(t,n){return tidy(()=>{const e=add(div(t.reshape([-1,7,2]),this.inputSizeTensor),this.anchors[n]);return mul(e,this.inputSizeTensor)})}async getBoundingBoxes(t){const n=tidy(()=>mul(sub(t,.5),2)),e=env().get("WEBGL_PACK_DEPTHWISECONV");env().set("WEBGL_PACK_DEPTHWISECONV",!0);const o=this.model.predict(n);env().set("WEBGL_PACK_DEPTHWISECONV",e);const s=o.squeeze(),i=tidy(()=>sigmoid(slice(s,[0,0],[-1,1])).squeeze()),r=slice(s,[0,1],[-1,4]),a=this.normalizeBoxes(r),d=console.warn;console.warn=(()=>{});const c=image.nonMaxSuppression(a,i,1,this.iouThreshold,this.scoreThreshold);console.warn=d;const h=await c.array(),u=[n,o,c,s,a,r,i];if(0===h.length)return u.forEach(t=>t.dispose()),null;const l=h[0],m=slice(a,[l,0],[1,-1]),p=slice(s,[l,5],[1,14]),f=tidy(()=>this.normalizeLandmarks(p,l).reshape([-1,2]));return u.push(p),u.forEach(t=>t.dispose()),{boxes:m,palmLandmarks:f}}async estimateHandBounds(t){const n=t.shape[1],e=t.shape[2],o=tidy(()=>t.resizeBilinear([this.width,this.height]).div(255)),s=await this.getBoundingBoxes(o);if(null===s)return o.dispose(),null;const i=s.boxes.arraySync(),r=i[0].slice(0,2),a=i[0].slice(2,4),d=s.palmLandmarks.arraySync();return o.dispose(),s.boxes.dispose(),s.palmLandmarks.dispose(),scaleBoxCoordinates({startPoint:r,endPoint:a,palmLandmarks:d},[e/this.width,n/this.height])}}const MESH_ANNOTATIONS={thumb:[1,2,3,4],indexFinger:[5,6,7,8],middleFinger:[9,10,11,12],ringFinger:[13,14,15,16],pinky:[17,18,19,20],palmBase:[0]};function rotate(t,n,e,o){const s=backend(),i=buffer(t.shape,t.dtype),[r,a,d,c]=t.shape,h=d*("number"==typeof o?o:o[0]),u=a*("number"==typeof o?o:o[1]),l=Math.sin(-n),m=Math.cos(-n),p=s.readSync(t.dataId);for(let t=0;t<r;t++)for(let n=0;n<a;n++)for(let o=0;o<d;o++)for(let s=0;s<c;s++){const f=[r,n,o,s],_=f[2],P=f[1];let g=(_-h)*m-(P-u)*l,M=(_-h)*l+(P-u)*m;g=Math.round(g+h),M=Math.round(M+u);let T=e;if("number"!=typeof e&&(T=3===s?255:e[s]),g>=0&&g<d&&M>=0&&M<a){T=p[t*d*a*c+M*(d*c)+g*c+s]}const E=t*d*a*c+n*(d*c)+o*c+s;i.values[E]=T}return i.toTensor()}function rotate$1(t,n,e,o){const s=t.shape,i=s[1],r=s[2],a=Math.sin(n),d=Math.cos(n),c=Math.floor(r*("number"==typeof o?o:o[0])),h=Math.floor(i*("number"==typeof o?o:o[1]));let u="";const l={variableNames:["Image"],outputShape:s,userCode:`\n void main() {\n ivec4 coords = getOutputCoords();\n int x = coords[2];\n int y = coords[1];\n int coordX = int(float(x - ${c}) * ${d} -\n float(y - ${h}) * ${a});\n int coordY = int(float(x - ${c}) * ${a} +\n float(y - ${h}) * ${d});\n coordX = int(coordX + ${c});\n coordY = int(coordY + ${h});\n\n ${u="number"==typeof e?`float outputValue = ${e.toFixed(2)};`:`\n vec3 fill = vec3(${e.join(",")});\n float outputValue = fill[coords[3]];`}\n\n if(coordX > 0 && coordX < ${r} && coordY > 0 && coordY < ${i}) {\n outputValue = getImage(coords[0], coordY, coordX, coords[3]);\n }\n\n setOutput(outputValue);\n }`};return backend().compileAndRun(l,[t])}function normalizeRadians(t){return t-2*Math.PI*Math.floor((t+Math.PI)/(2*Math.PI))}function computeRotation(t,n){return normalizeRadians(Math.PI/2-Math.atan2(-(n[1]-t[1]),n[0]-t[0]))}const buildTranslationMatrix=(t,n)=>[[1,0,t],[0,1,n],[0,0,1]];function dot(t,n){let e=0;for(let o=0;o<t.length;o++)e+=t[o]*n[o];return e}function getColumnFrom2DArr(t,n){const e=[];for(let o=0;o<t.length;o++)e.push(t[o][n]);return e}function multiplyTransformMatrices(t,n){const e=[],o=t.length;for(let s=0;s<o;s++){e.push([]);for(let i=0;i<o;i++)e[s].push(dot(t[s],getColumnFrom2DArr(n,i)))}return e}function buildRotationMatrix(t,n){const e=Math.cos(t),o=Math.sin(t),s=[[e,-o,0],[o,e,0],[0,0,1]];return multiplyTransformMatrices(multiplyTransformMatrices(buildTranslationMatrix(n[0],n[1]),s),buildTranslationMatrix(-n[0],-n[1]))}function invertTransformMatrix(t){const n=[[t[0][0],t[1][0]],[t[0][1],t[1][1]]],e=[t[0][2],t[1][2]],o=[-dot(n[0],e),-dot(n[1],e)];return[n[0].concat(o[0]),n[1].concat(o[1]),[0,0,1]]}function rotatePoint(t,n){return[dot(t,n[0]),dot(t,n[1])]}const UPDATE_REGION_OF_INTEREST_IOU_THRESHOLD=.8,PALM_BOX_SHIFT_VECTOR=[0,-.4],PALM_BOX_ENLARGE_FACTOR=3,HAND_BOX_SHIFT_VECTOR=[0,-.1],HAND_BOX_ENLARGE_FACTOR=1.65,PALM_LANDMARK_IDS=[0,5,9,13,17,1,2],PALM_LANDMARKS_INDEX_OF_PALM_BASE=0,PALM_LANDMARKS_INDEX_OF_MIDDLE_FINGER_BASE=2;class HandPipeline{constructor(t,n,e,o,s,i){this.regionsOfInterest=[],this.runsWithoutHandDetector=0,this.boundingBoxDetector=t,this.meshDetector=n,this.maxContinuousChecks=s,this.detectionConfidence=i,this.meshWidth=e,this.meshHeight=o,this.maxHandsNumber=1}getBoxForPalmLandmarks(t,n){const e=t.map(t=>{return rotatePoint([...t,1],n)});return enlargeBox(squarifyBox(shiftBox(this.calculateLandmarksBoundingBox(e),PALM_BOX_SHIFT_VECTOR)),PALM_BOX_ENLARGE_FACTOR)}getBoxForHandLandmarks(t){const n=enlargeBox(squarifyBox(shiftBox(this.calculateLandmarksBoundingBox(t),HAND_BOX_SHIFT_VECTOR)),HAND_BOX_ENLARGE_FACTOR),e=[];for(let n=0;n<PALM_LANDMARK_IDS.length;n++)e.push(t[PALM_LANDMARK_IDS[n]].slice(0,2));return n.palmLandmarks=e,n}transformRawCoords(t,n,e,o){const s=getBoxSize(n),i=[s[0]/this.meshWidth,s[1]/this.meshHeight],r=t.map(t=>[i[0]*(t[0]-this.meshWidth/2),i[1]*(t[1]-this.meshHeight/2),t[2]]),a=buildRotationMatrix(e,[0,0]),d=r.map(t=>{return[...rotatePoint(t,a),t[2]]}),c=invertTransformMatrix(o),h=[...getBoxCenter(n),1],u=[dot(h,c[0]),dot(h,c[1])];return d.map(t=>[t[0]+u[0],t[1]+u[1],t[2]])}async estimateHand(t){const n=this.shouldUpdateRegionsOfInterest();if(!0===n){const n=await this.boundingBoxDetector.estimateHandBounds(t);if(null===n)return t.dispose(),this.regionsOfInterest=[],null;this.updateRegionsOfInterest(n,!0),this.runsWithoutHandDetector=0}else this.runsWithoutHandDetector++;const e=this.regionsOfInterest[0],o=computeRotation(e.palmLandmarks[PALM_LANDMARKS_INDEX_OF_PALM_BASE],e.palmLandmarks[PALM_LANDMARKS_INDEX_OF_MIDDLE_FINGER_BASE]),s=getBoxCenter(e),i=[s[0]/t.shape[2],s[1]/t.shape[1]];let r;const a=getBackend();if(a.match("webgl"))r=rotate$1(t,o,0,i);else{if("cpu"!==a&&"tensorflow"!==a)throw new Error(`Handpose is not yet supported by the ${a} `+"backend - rotation kernel is not defined.");r=rotate(t,o,0,i)}const d=buildRotationMatrix(-o,s);let c;const h=cutBoxFromImageAndResize(c=!0===n?this.getBoxForPalmLandmarks(e.palmLandmarks,d):e,r,[this.meshWidth,this.meshHeight]),u=h.div(255);h.dispose(),r.dispose();const l=env().get("WEBGL_PACK_DEPTHWISECONV");env().set("WEBGL_PACK_DEPTHWISECONV",!0);const[m,p]=this.meshDetector.predict(u);env().set("WEBGL_PACK_DEPTHWISECONV",l),u.dispose();const f=m.dataSync()[0];if(m.dispose(),f<this.detectionConfidence)return p.dispose(),this.regionsOfInterest=[],null;const _=reshape(p,[-1,3]),P=_.arraySync();p.dispose(),_.dispose();const g=this.transformRawCoords(P,c,o,d),M=this.getBoxForHandLandmarks(g);return this.updateRegionsOfInterest(M,!1),{landmarks:g,handInViewConfidence:f,boundingBox:{topLeft:M.startPoint,bottomRight:M.endPoint}}}calculateLandmarksBoundingBox(t){const n=t.map(t=>t[0]),e=t.map(t=>t[1]);return{startPoint:[Math.min(...n),Math.min(...e)],endPoint:[Math.max(...n),Math.max(...e)]}}updateRegionsOfInterest(t,n){if(n)this.regionsOfInterest=[t];else{const n=this.regionsOfInterest[0];let e=0;if(null!=n&&null!=n.startPoint){const[o,s]=t.startPoint,[i,r]=t.endPoint,[a,d]=n.startPoint,[c,h]=n.endPoint,u=Math.max(o,a),l=Math.max(s,d),m=(Math.min(i,c)-u)*(Math.min(r,h)-l);e=m/((i-o)*(r-s)+(c-a)*(h-s)-m)}this.regionsOfInterest[0]=e>UPDATE_REGION_OF_INTEREST_IOU_THRESHOLD?n:t}}shouldUpdateRegionsOfInterest(){return this.regionsOfInterest.length!==this.maxHandsNumber||this.runsWithoutHandDetector>=this.maxContinuousChecks}}async function loadHandDetectorModel(){return loadGraphModel("https://tfhub.dev/mediapipe/tfjs-model/handdetector/1/default/1",{fromTFHub:!0})}const MESH_MODEL_INPUT_WIDTH=256,MESH_MODEL_INPUT_HEIGHT=256;async function loadHandPoseModel(){return loadGraphModel("https://tfhub.dev/mediapipe/tfjs-model/handskeleton/1/default/1",{fromTFHub:!0})}async function loadAnchors(){return util.fetch("https://tfhub.dev/mediapipe/tfjs-model/handskeleton/1/default/1/anchors.json?tfjs-format=file").then(t=>t.json())}async function load({maxContinuousChecks:t=1/0,detectionConfidence:n=.8,iouThreshold:e=.3,scoreThreshold:o=.5}={}){const[s,i,r]=await Promise.all([loadAnchors(),loadHandDetectorModel(),loadHandPoseModel()]),a=new HandDetector(i,MESH_MODEL_INPUT_WIDTH,MESH_MODEL_INPUT_HEIGHT,s,e,o),d=new HandPipeline(a,r,MESH_MODEL_INPUT_WIDTH,MESH_MODEL_INPUT_HEIGHT,t,n);return new HandPose(d)}function getInputTensorDimensions(t){return t instanceof Tensor?[t.shape[0],t.shape[1]]:[t.height,t.width]}function flipHandHorizontal(t,n){const{handInViewConfidence:e,landmarks:o,boundingBox:s}=t;return{handInViewConfidence:e,landmarks:o.map(t=>[n-1-t[0],t[1],t[2]]),boundingBox:{topLeft:[n-1-s.topLeft[0],s.topLeft[1]],bottomRight:[n-1-s.bottomRight[0],s.bottomRight[1]]}}}class HandPose{constructor(t){this.pipeline=t}static getAnnotations(){return MESH_ANNOTATIONS}async estimateHands(t,n=!1){const[,e]=getInputTensorDimensions(t),o=tidy(()=>(t instanceof Tensor||(t=browser.fromPixels(t)),t.toFloat().expandDims(0))),s=await this.pipeline.estimateHand(o);if(o.dispose(),null===s)return[];let i=s;!0===n&&(i=flipHandHorizontal(s,e));const r={};for(const t of Object.keys(MESH_ANNOTATIONS))r[t]=MESH_ANNOTATIONS[t].map(t=>i.landmarks[t]);return[{handInViewConfidence:i.handInViewConfidence,boundingBox:i.boundingBox,landmarks:i.landmarks,annotations:r}]}}export{load,HandPose};

@@ -109,33 +109,40 @@ /**

}
getBoundingBoxes(input) {
return tf.tidy(() => {
const normalizedInput = tf.mul(tf.sub(input, 0.5), 2);
const savedWebglPackDepthwiseConvFlag = tf.env().get('WEBGL_PACK_DEPTHWISECONV');
tf.env().set('WEBGL_PACK_DEPTHWISECONV', true);
const prediction = this.model.predict(normalizedInput).squeeze();
tf.env().set('WEBGL_PACK_DEPTHWISECONV', savedWebglPackDepthwiseConvFlag);
const scores = tf.sigmoid(tf.slice(prediction, [0, 0], [-1, 1])).squeeze();
const rawBoxes = tf.slice(prediction, [0, 1], [-1, 4]);
const boxes = this.normalizeBoxes(rawBoxes);
const savedConsoleWarnFn = console.warn;
console.warn = () => { };
const boxesWithHands = tf.image
.nonMaxSuppression(boxes, scores, 1, this.iouThreshold, this.scoreThreshold)
.arraySync();
console.warn = savedConsoleWarnFn;
if (boxesWithHands.length === 0) {
return null;
}
const boxIndex = boxesWithHands[0];
const matchingBox = tf.slice(boxes, [boxIndex, 0], [1, -1]);
const rawPalmLandmarks = tf.slice(prediction, [boxIndex, 5], [1, 14]);
const palmLandmarks = this.normalizeLandmarks(rawPalmLandmarks, boxIndex).reshape([-1, 2]);
return { boxes: matchingBox, palmLandmarks };
});
async getBoundingBoxes(input) {
const normalizedInput = tf.tidy(() => tf.mul(tf.sub(input, 0.5), 2));
const savedWebglPackDepthwiseConvFlag = tf.env().get('WEBGL_PACK_DEPTHWISECONV');
tf.env().set('WEBGL_PACK_DEPTHWISECONV', true);
const batchedPrediction = this.model.predict(normalizedInput);
tf.env().set('WEBGL_PACK_DEPTHWISECONV', savedWebglPackDepthwiseConvFlag);
const prediction = batchedPrediction.squeeze();
const scores = tf.tidy(() => tf.sigmoid(tf.slice(prediction, [0, 0], [-1, 1])).squeeze());
const rawBoxes = tf.slice(prediction, [0, 1], [-1, 4]);
const boxes = this.normalizeBoxes(rawBoxes);
const savedConsoleWarnFn = console.warn;
console.warn = () => { };
const boxesWithHandsTensor = tf.image.nonMaxSuppression(boxes, scores, 1, this.iouThreshold, this.scoreThreshold);
console.warn = savedConsoleWarnFn;
const boxesWithHands = await boxesWithHandsTensor.array();
const toDispose = [
normalizedInput, batchedPrediction, boxesWithHandsTensor, prediction,
boxes, rawBoxes, scores
];
if (boxesWithHands.length === 0) {
toDispose.forEach(tensor => tensor.dispose());
return null;
}
const boxIndex = boxesWithHands[0];
const matchingBox = tf.slice(boxes, [boxIndex, 0], [1, -1]);
const rawPalmLandmarks = tf.slice(prediction, [boxIndex, 5], [1, 14]);
const palmLandmarks = tf.tidy(() => this.normalizeLandmarks(rawPalmLandmarks, boxIndex).reshape([
-1, 2
]));
toDispose.push(rawPalmLandmarks);
toDispose.forEach(tensor => tensor.dispose());
return { boxes: matchingBox, palmLandmarks };
}
estimateHandBounds(input) {
async estimateHandBounds(input) {
const inputHeight = input.shape[1];
const inputWidth = input.shape[2];
const image = tf.tidy(() => input.resizeBilinear([this.width, this.height]).div(255));
const prediction = this.getBoundingBoxes(image);
const prediction = await this.getBoundingBoxes(image);
if (prediction === null) {

@@ -386,3 +393,3 @@ image.dispose();

if (useFreshBox === true) {
const boundingBoxPrediction = this.boundingBoxDetector.estimateHandBounds(image);
const boundingBoxPrediction = await this.boundingBoxDetector.estimateHandBounds(image);
if (boundingBoxPrediction === null) {

@@ -405,6 +412,6 @@ image.dispose();

const backend = tf.getBackend();
if (backend === 'webgl') {
if (backend.match('webgl')) {
rotatedImage = rotate$1(image, angle, 0, palmCenterNormalized);
}
else if (backend === 'cpu') {
else if (backend === 'cpu' || backend === 'tensorflow') {
rotatedImage = rotate(image, angle, 0, palmCenterNormalized);

@@ -411,0 +418,0 @@ }

@@ -17,2 +17,2 @@ /**

*/
!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports,require("@tensorflow/tfjs-core"),require("@tensorflow/tfjs-converter")):"function"==typeof define&&define.amd?define(["exports","@tensorflow/tfjs-core","@tensorflow/tfjs-converter"],n):n(t.handpose={},t.tf,t.tf)}(this,function(t,n,e){"use strict";function o(t){return[Math.abs(t.endPoint[0]-t.startPoint[0]),Math.abs(t.endPoint[1]-t.startPoint[1])]}function s(t){return[t.startPoint[0]+(t.endPoint[0]-t.startPoint[0])/2,t.startPoint[1]+(t.endPoint[1]-t.startPoint[1])/2]}function i(t,n=1.5){const e=s(t),i=o(t),r=[n*i[0]/2,n*i[1]/2];return{startPoint:[e[0]-r[0],e[1]-r[1]],endPoint:[e[0]+r[0],e[1]+r[1]],palmLandmarks:t.palmLandmarks}}function r(t){const n=s(t),e=o(t),i=Math.max(...e)/2;return{startPoint:[n[0]-i,n[1]-i],endPoint:[n[0]+i,n[1]+i],palmLandmarks:t.palmLandmarks}}function a(t,n){const e=[t.endPoint[0]-t.startPoint[0],t.endPoint[1]-t.startPoint[1]],o=[e[0]*n[0],e[1]*n[1]];return{startPoint:[t.startPoint[0]+o[0],t.startPoint[1]+o[1]],endPoint:[t.endPoint[0]+o[0],t.endPoint[1]+o[1]],palmLandmarks:t.palmLandmarks}}class d{constructor(t,e,o,s,i,r){this.model=t,this.width=e,this.height=o,this.iouThreshold=i,this.scoreThreshold=r,this.anchors=s.map(t=>[t.x_center,t.y_center]),this.anchorsTensor=n.tensor2d(this.anchors),this.inputSizeTensor=n.tensor1d([e,o]),this.doubleInputSizeTensor=n.tensor1d([2*e,2*o])}normalizeBoxes(t){return n.tidy(()=>{const e=n.slice(t,[0,0],[-1,2]),o=n.slice(t,[0,2],[-1,2]),s=n.add(n.div(e,this.inputSizeTensor),this.anchorsTensor),i=n.div(o,this.doubleInputSizeTensor),r=n.mul(n.sub(s,i),this.inputSizeTensor),a=n.mul(n.add(s,i),this.inputSizeTensor);return n.concat2d([r,a],1)})}normalizeLandmarks(t,e){return n.tidy(()=>{const o=n.add(n.div(t.reshape([-1,7,2]),this.inputSizeTensor),this.anchors[e]);return n.mul(o,this.inputSizeTensor)})}getBoundingBoxes(t){return n.tidy(()=>{const e=n.mul(n.sub(t,.5),2),o=n.env().get("WEBGL_PACK_DEPTHWISECONV");n.env().set("WEBGL_PACK_DEPTHWISECONV",!0);const s=this.model.predict(e).squeeze();n.env().set("WEBGL_PACK_DEPTHWISECONV",o);const i=n.sigmoid(n.slice(s,[0,0],[-1,1])).squeeze(),r=n.slice(s,[0,1],[-1,4]),a=this.normalizeBoxes(r),d=console.warn;console.warn=(()=>{});const h=n.image.nonMaxSuppression(a,i,1,this.iouThreshold,this.scoreThreshold).arraySync();if(console.warn=d,0===h.length)return null;const c=h[0],u=n.slice(a,[c,0],[1,-1]),l=n.slice(s,[c,5],[1,14]);return{boxes:u,palmLandmarks:this.normalizeLandmarks(l,c).reshape([-1,2])}})}estimateHandBounds(t){const e=t.shape[1],o=t.shape[2],s=n.tidy(()=>t.resizeBilinear([this.width,this.height]).div(255)),i=this.getBoundingBoxes(s);if(null===i)return s.dispose(),null;const r=i.boxes.arraySync(),a=r[0].slice(0,2),d=r[0].slice(2,4),h=i.palmLandmarks.arraySync();return s.dispose(),i.boxes.dispose(),i.palmLandmarks.dispose(),c={startPoint:a,endPoint:d,palmLandmarks:h},u=[o/this.width,e/this.height],{startPoint:[c.startPoint[0]*u[0],c.startPoint[1]*u[1]],endPoint:[c.endPoint[0]*u[0],c.endPoint[1]*u[1]],palmLandmarks:c.palmLandmarks.map(t=>[t[0]*u[0],t[1]*u[1]])};var c,u}}const h={thumb:[1,2,3,4],indexFinger:[5,6,7,8],middleFinger:[9,10,11,12],ringFinger:[13,14,15,16],pinky:[17,18,19,20],palmBase:[0]};function c(t,n){const e=Math.PI/2-Math.atan2(-(n[1]-t[1]),n[0]-t[0]);return(o=e)-2*Math.PI*Math.floor((o+Math.PI)/(2*Math.PI));var o}const u=(t,n)=>[[1,0,t],[0,1,n],[0,0,1]];function l(t,n){let e=0;for(let o=0;o<t.length;o++)e+=t[o]*n[o];return e}function m(t,n){const e=[];for(let o=0;o<t.length;o++)e.push(t[o][n]);return e}function f(t,n){const e=[],o=t.length;for(let s=0;s<o;s++){e.push([]);for(let i=0;i<o;i++)e[s].push(l(t[s],m(n,i)))}return e}function p(t,n){const e=Math.cos(t),o=Math.sin(t),s=[[e,-o,0],[o,e,0],[0,0,1]];return f(f(u(n[0],n[1]),s),u(-n[0],-n[1]))}function P(t,n){return[l(t,n[0]),l(t,n[1])]}const g=.8,b=[0,-.4],k=3,x=[0,-.1],y=1.65,L=[0,5,9,13,17,1,2],B=0,I=2;class M{constructor(t,n,e,o,s,i){this.regionsOfInterest=[],this.runsWithoutHandDetector=0,this.boundingBoxDetector=t,this.meshDetector=n,this.maxContinuousChecks=s,this.detectionConfidence=i,this.meshWidth=e,this.meshHeight=o,this.maxHandsNumber=1}getBoxForPalmLandmarks(t,n){const e=t.map(t=>{return P([...t,1],n)});return i(r(a(this.calculateLandmarksBoundingBox(e),b)),k)}getBoxForHandLandmarks(t){const n=i(r(a(this.calculateLandmarksBoundingBox(t),x)),y),e=[];for(let n=0;n<L.length;n++)e.push(t[L[n]].slice(0,2));return n.palmLandmarks=e,n}transformRawCoords(t,n,e,i){const r=o(n),a=[r[0]/this.meshWidth,r[1]/this.meshHeight],d=t.map(t=>[a[0]*(t[0]-this.meshWidth/2),a[1]*(t[1]-this.meshHeight/2),t[2]]),h=p(e,[0,0]),c=d.map(t=>{return[...P(t,h),t[2]]}),u=function(t){const n=[[t[0][0],t[1][0]],[t[0][1],t[1][1]]],e=[t[0][2],t[1][2]],o=[-l(n[0],e),-l(n[1],e)];return[n[0].concat(o[0]),n[1].concat(o[1]),[0,0,1]]}(i),m=[...s(n),1],f=[l(m,u[0]),l(m,u[1])];return c.map(t=>[t[0]+f[0],t[1]+f[1],t[2]])}async estimateHand(t){const e=this.shouldUpdateRegionsOfInterest();if(!0===e){const n=this.boundingBoxDetector.estimateHandBounds(t);if(null===n)return t.dispose(),this.regionsOfInterest=[],null;this.updateRegionsOfInterest(n,!0),this.runsWithoutHandDetector=0}else this.runsWithoutHandDetector++;const o=this.regionsOfInterest[0],i=c(o.palmLandmarks[B],o.palmLandmarks[I]),r=s(o),a=[r[0]/t.shape[2],r[1]/t.shape[1]];let d;const h=n.getBackend();if("webgl"===h)d=function(t,e,o,s){const i=t.shape,r=i[1],a=i[2],d=Math.sin(e),h=Math.cos(e),c=Math.floor(a*("number"==typeof s?s:s[0])),u=Math.floor(r*("number"==typeof s?s:s[1]));let l="";const m={variableNames:["Image"],outputShape:i,userCode:`\n void main() {\n ivec4 coords = getOutputCoords();\n int x = coords[2];\n int y = coords[1];\n int coordX = int(float(x - ${c}) * ${h} -\n float(y - ${u}) * ${d});\n int coordY = int(float(x - ${c}) * ${d} +\n float(y - ${u}) * ${h});\n coordX = int(coordX + ${c});\n coordY = int(coordY + ${u});\n\n ${l="number"==typeof o?`float outputValue = ${o.toFixed(2)};`:`\n vec3 fill = vec3(${o.join(",")});\n float outputValue = fill[coords[3]];`}\n\n if(coordX > 0 && coordX < ${a} && coordY > 0 && coordY < ${r}) {\n outputValue = getImage(coords[0], coordY, coordX, coords[3]);\n }\n\n setOutput(outputValue);\n }`};return n.backend().compileAndRun(m,[t])}(t,i,0,a);else{if("cpu"!==h)throw new Error(`Handpose is not yet supported by the ${h} `+"backend - rotation kernel is not defined.");d=function(t,e,o,s){const i=n.backend(),r=n.buffer(t.shape,t.dtype),[a,d,h,c]=t.shape,u=h*("number"==typeof s?s:s[0]),l=d*("number"==typeof s?s:s[1]),m=Math.sin(-e),f=Math.cos(-e),p=i.readSync(t.dataId);for(let t=0;t<a;t++)for(let n=0;n<d;n++)for(let e=0;e<h;e++)for(let s=0;s<c;s++){const i=[a,n,e,s],P=i[2],g=i[1];let b=(P-u)*f-(g-l)*m,k=(P-u)*m+(g-l)*f;b=Math.round(b+u),k=Math.round(k+l);let x=o;"number"!=typeof o&&(x=3===s?255:o[s]),b>=0&&b<h&&k>=0&&k<d&&(x=p[t*h*d*c+k*(h*c)+b*c+s]);const y=t*h*d*c+n*(h*c)+e*c+s;r.values[y]=x}return r.toTensor()}(t,i,0,a)}const u=p(-i,r);let l;const m=function(t,e,o){const s=e.shape[1],i=e.shape[2],r=[[t.startPoint[1]/s,t.startPoint[0]/i,t.endPoint[1]/s,t.endPoint[0]/i]];return n.image.cropAndResize(e,r,[0],o)}(l=!0===e?this.getBoxForPalmLandmarks(o.palmLandmarks,u):o,d,[this.meshWidth,this.meshHeight]),f=m.div(255);m.dispose(),d.dispose();const P=n.env().get("WEBGL_PACK_DEPTHWISECONV");n.env().set("WEBGL_PACK_DEPTHWISECONV",!0);const[g,b]=this.meshDetector.predict(f);n.env().set("WEBGL_PACK_DEPTHWISECONV",P),f.dispose();const k=g.dataSync()[0];if(g.dispose(),k<this.detectionConfidence)return b.dispose(),this.regionsOfInterest=[],null;const x=n.reshape(b,[-1,3]),y=x.arraySync();b.dispose(),x.dispose();const L=this.transformRawCoords(y,l,i,u),M=this.getBoxForHandLandmarks(L);return this.updateRegionsOfInterest(M,!1),{landmarks:L,handInViewConfidence:k,boundingBox:{topLeft:M.startPoint,bottomRight:M.endPoint}}}calculateLandmarksBoundingBox(t){const n=t.map(t=>t[0]),e=t.map(t=>t[1]);return{startPoint:[Math.min(...n),Math.min(...e)],endPoint:[Math.max(...n),Math.max(...e)]}}updateRegionsOfInterest(t,n){if(n)this.regionsOfInterest=[t];else{const n=this.regionsOfInterest[0];let e=0;if(null!=n&&null!=n.startPoint){const[o,s]=t.startPoint,[i,r]=t.endPoint,[a,d]=n.startPoint,[h,c]=n.endPoint,u=Math.max(o,a),l=Math.max(s,d),m=(Math.min(i,h)-u)*(Math.min(r,c)-l);e=m/((i-o)*(r-s)+(h-a)*(c-s)-m)}this.regionsOfInterest[0]=e>g?n:t}}shouldUpdateRegionsOfInterest(){return this.regionsOfInterest.length!==this.maxHandsNumber||this.runsWithoutHandDetector>=this.maxContinuousChecks}}const C=256,w=256;class H{constructor(t){this.pipeline=t}static getAnnotations(){return h}async estimateHands(t,e=!1){const[,o]=function(t){return t instanceof n.Tensor?[t.shape[0],t.shape[1]]:[t.height,t.width]}(t),s=n.tidy(()=>(t instanceof n.Tensor||(t=n.browser.fromPixels(t)),t.toFloat().expandDims(0))),i=await this.pipeline.estimateHand(s);if(s.dispose(),null===i)return[];let r=i;!0===e&&(r=function(t,n){const{handInViewConfidence:e,landmarks:o,boundingBox:s}=t;return{handInViewConfidence:e,landmarks:o.map(t=>[n-1-t[0],t[1],t[2]]),boundingBox:{topLeft:[n-1-s.topLeft[0],s.topLeft[1]],bottomRight:[n-1-s.bottomRight[0],s.bottomRight[1]]}}}(i,o));const a={};for(const t of Object.keys(h))a[t]=h[t].map(t=>r.landmarks[t]);return[{handInViewConfidence:r.handInViewConfidence,boundingBox:r.boundingBox,landmarks:r.landmarks,annotations:a}]}}t.load=async function({maxContinuousChecks:t=1/0,detectionConfidence:o=.8,iouThreshold:s=.3,scoreThreshold:i=.5}={}){const[r,a,h]=await Promise.all([async function(){return n.util.fetch("https://tfhub.dev/mediapipe/tfjs-model/handskeleton/1/default/1/anchors.json?tfjs-format=file").then(t=>t.json())}(),async function(){return e.loadGraphModel("https://tfhub.dev/mediapipe/tfjs-model/handdetector/1/default/1",{fromTFHub:!0})}(),async function(){return e.loadGraphModel("https://tfhub.dev/mediapipe/tfjs-model/handskeleton/1/default/1",{fromTFHub:!0})}()]),c=new d(a,C,w,r,s,i),u=new M(c,h,C,w,t,o);return new H(u)},t.HandPose=H,Object.defineProperty(t,"__esModule",{value:!0})});
!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports,require("@tensorflow/tfjs-core"),require("@tensorflow/tfjs-converter")):"function"==typeof define&&define.amd?define(["exports","@tensorflow/tfjs-core","@tensorflow/tfjs-converter"],n):n(t.handpose={},t.tf,t.tf)}(this,function(t,n,e){"use strict";function o(t){return[Math.abs(t.endPoint[0]-t.startPoint[0]),Math.abs(t.endPoint[1]-t.startPoint[1])]}function s(t){return[t.startPoint[0]+(t.endPoint[0]-t.startPoint[0])/2,t.startPoint[1]+(t.endPoint[1]-t.startPoint[1])/2]}function i(t,n=1.5){const e=s(t),i=o(t),r=[n*i[0]/2,n*i[1]/2];return{startPoint:[e[0]-r[0],e[1]-r[1]],endPoint:[e[0]+r[0],e[1]+r[1]],palmLandmarks:t.palmLandmarks}}function r(t){const n=s(t),e=o(t),i=Math.max(...e)/2;return{startPoint:[n[0]-i,n[1]-i],endPoint:[n[0]+i,n[1]+i],palmLandmarks:t.palmLandmarks}}function a(t,n){const e=[t.endPoint[0]-t.startPoint[0],t.endPoint[1]-t.startPoint[1]],o=[e[0]*n[0],e[1]*n[1]];return{startPoint:[t.startPoint[0]+o[0],t.startPoint[1]+o[1]],endPoint:[t.endPoint[0]+o[0],t.endPoint[1]+o[1]],palmLandmarks:t.palmLandmarks}}class d{constructor(t,e,o,s,i,r){this.model=t,this.width=e,this.height=o,this.iouThreshold=i,this.scoreThreshold=r,this.anchors=s.map(t=>[t.x_center,t.y_center]),this.anchorsTensor=n.tensor2d(this.anchors),this.inputSizeTensor=n.tensor1d([e,o]),this.doubleInputSizeTensor=n.tensor1d([2*e,2*o])}normalizeBoxes(t){return n.tidy(()=>{const e=n.slice(t,[0,0],[-1,2]),o=n.slice(t,[0,2],[-1,2]),s=n.add(n.div(e,this.inputSizeTensor),this.anchorsTensor),i=n.div(o,this.doubleInputSizeTensor),r=n.mul(n.sub(s,i),this.inputSizeTensor),a=n.mul(n.add(s,i),this.inputSizeTensor);return n.concat2d([r,a],1)})}normalizeLandmarks(t,e){return n.tidy(()=>{const o=n.add(n.div(t.reshape([-1,7,2]),this.inputSizeTensor),this.anchors[e]);return n.mul(o,this.inputSizeTensor)})}async getBoundingBoxes(t){const e=n.tidy(()=>n.mul(n.sub(t,.5),2)),o=n.env().get("WEBGL_PACK_DEPTHWISECONV");n.env().set("WEBGL_PACK_DEPTHWISECONV",!0);const s=this.model.predict(e);n.env().set("WEBGL_PACK_DEPTHWISECONV",o);const i=s.squeeze(),r=n.tidy(()=>n.sigmoid(n.slice(i,[0,0],[-1,1])).squeeze()),a=n.slice(i,[0,1],[-1,4]),d=this.normalizeBoxes(a),h=console.warn;console.warn=(()=>{});const c=n.image.nonMaxSuppression(d,r,1,this.iouThreshold,this.scoreThreshold);console.warn=h;const u=await c.array(),l=[e,s,c,i,d,a,r];if(0===u.length)return l.forEach(t=>t.dispose()),null;const m=u[0],f=n.slice(d,[m,0],[1,-1]),p=n.slice(i,[m,5],[1,14]),P=n.tidy(()=>this.normalizeLandmarks(p,m).reshape([-1,2]));return l.push(p),l.forEach(t=>t.dispose()),{boxes:f,palmLandmarks:P}}async estimateHandBounds(t){const e=t.shape[1],o=t.shape[2],s=n.tidy(()=>t.resizeBilinear([this.width,this.height]).div(255)),i=await this.getBoundingBoxes(s);if(null===i)return s.dispose(),null;const r=i.boxes.arraySync(),a=r[0].slice(0,2),d=r[0].slice(2,4),h=i.palmLandmarks.arraySync();return s.dispose(),i.boxes.dispose(),i.palmLandmarks.dispose(),c={startPoint:a,endPoint:d,palmLandmarks:h},u=[o/this.width,e/this.height],{startPoint:[c.startPoint[0]*u[0],c.startPoint[1]*u[1]],endPoint:[c.endPoint[0]*u[0],c.endPoint[1]*u[1]],palmLandmarks:c.palmLandmarks.map(t=>[t[0]*u[0],t[1]*u[1]])};var c,u}}const h={thumb:[1,2,3,4],indexFinger:[5,6,7,8],middleFinger:[9,10,11,12],ringFinger:[13,14,15,16],pinky:[17,18,19,20],palmBase:[0]};function c(t,n){const e=Math.PI/2-Math.atan2(-(n[1]-t[1]),n[0]-t[0]);return(o=e)-2*Math.PI*Math.floor((o+Math.PI)/(2*Math.PI));var o}const u=(t,n)=>[[1,0,t],[0,1,n],[0,0,1]];function l(t,n){let e=0;for(let o=0;o<t.length;o++)e+=t[o]*n[o];return e}function m(t,n){const e=[];for(let o=0;o<t.length;o++)e.push(t[o][n]);return e}function f(t,n){const e=[],o=t.length;for(let s=0;s<o;s++){e.push([]);for(let i=0;i<o;i++)e[s].push(l(t[s],m(n,i)))}return e}function p(t,n){const e=Math.cos(t),o=Math.sin(t),s=[[e,-o,0],[o,e,0],[0,0,1]];return f(f(u(n[0],n[1]),s),u(-n[0],-n[1]))}function P(t,n){return[l(t,n[0]),l(t,n[1])]}const g=.8,b=[0,-.4],k=3,x=[0,-.1],y=1.65,L=[0,5,9,13,17,1,2],B=0,I=2;class w{constructor(t,n,e,o,s,i){this.regionsOfInterest=[],this.runsWithoutHandDetector=0,this.boundingBoxDetector=t,this.meshDetector=n,this.maxContinuousChecks=s,this.detectionConfidence=i,this.meshWidth=e,this.meshHeight=o,this.maxHandsNumber=1}getBoxForPalmLandmarks(t,n){const e=t.map(t=>{return P([...t,1],n)});return i(r(a(this.calculateLandmarksBoundingBox(e),b)),k)}getBoxForHandLandmarks(t){const n=i(r(a(this.calculateLandmarksBoundingBox(t),x)),y),e=[];for(let n=0;n<L.length;n++)e.push(t[L[n]].slice(0,2));return n.palmLandmarks=e,n}transformRawCoords(t,n,e,i){const r=o(n),a=[r[0]/this.meshWidth,r[1]/this.meshHeight],d=t.map(t=>[a[0]*(t[0]-this.meshWidth/2),a[1]*(t[1]-this.meshHeight/2),t[2]]),h=p(e,[0,0]),c=d.map(t=>{return[...P(t,h),t[2]]}),u=function(t){const n=[[t[0][0],t[1][0]],[t[0][1],t[1][1]]],e=[t[0][2],t[1][2]],o=[-l(n[0],e),-l(n[1],e)];return[n[0].concat(o[0]),n[1].concat(o[1]),[0,0,1]]}(i),m=[...s(n),1],f=[l(m,u[0]),l(m,u[1])];return c.map(t=>[t[0]+f[0],t[1]+f[1],t[2]])}async estimateHand(t){const e=this.shouldUpdateRegionsOfInterest();if(!0===e){const n=await this.boundingBoxDetector.estimateHandBounds(t);if(null===n)return t.dispose(),this.regionsOfInterest=[],null;this.updateRegionsOfInterest(n,!0),this.runsWithoutHandDetector=0}else this.runsWithoutHandDetector++;const o=this.regionsOfInterest[0],i=c(o.palmLandmarks[B],o.palmLandmarks[I]),r=s(o),a=[r[0]/t.shape[2],r[1]/t.shape[1]];let d;const h=n.getBackend();if(h.match("webgl"))d=function(t,e,o,s){const i=t.shape,r=i[1],a=i[2],d=Math.sin(e),h=Math.cos(e),c=Math.floor(a*("number"==typeof s?s:s[0])),u=Math.floor(r*("number"==typeof s?s:s[1]));let l="";const m={variableNames:["Image"],outputShape:i,userCode:`\n void main() {\n ivec4 coords = getOutputCoords();\n int x = coords[2];\n int y = coords[1];\n int coordX = int(float(x - ${c}) * ${h} -\n float(y - ${u}) * ${d});\n int coordY = int(float(x - ${c}) * ${d} +\n float(y - ${u}) * ${h});\n coordX = int(coordX + ${c});\n coordY = int(coordY + ${u});\n\n ${l="number"==typeof o?`float outputValue = ${o.toFixed(2)};`:`\n vec3 fill = vec3(${o.join(",")});\n float outputValue = fill[coords[3]];`}\n\n if(coordX > 0 && coordX < ${a} && coordY > 0 && coordY < ${r}) {\n outputValue = getImage(coords[0], coordY, coordX, coords[3]);\n }\n\n setOutput(outputValue);\n }`};return n.backend().compileAndRun(m,[t])}(t,i,0,a);else{if("cpu"!==h&&"tensorflow"!==h)throw new Error(`Handpose is not yet supported by the ${h} `+"backend - rotation kernel is not defined.");d=function(t,e,o,s){const i=n.backend(),r=n.buffer(t.shape,t.dtype),[a,d,h,c]=t.shape,u=h*("number"==typeof s?s:s[0]),l=d*("number"==typeof s?s:s[1]),m=Math.sin(-e),f=Math.cos(-e),p=i.readSync(t.dataId);for(let t=0;t<a;t++)for(let n=0;n<d;n++)for(let e=0;e<h;e++)for(let s=0;s<c;s++){const i=[a,n,e,s],P=i[2],g=i[1];let b=(P-u)*f-(g-l)*m,k=(P-u)*m+(g-l)*f;b=Math.round(b+u),k=Math.round(k+l);let x=o;"number"!=typeof o&&(x=3===s?255:o[s]),b>=0&&b<h&&k>=0&&k<d&&(x=p[t*h*d*c+k*(h*c)+b*c+s]);const y=t*h*d*c+n*(h*c)+e*c+s;r.values[y]=x}return r.toTensor()}(t,i,0,a)}const u=p(-i,r);let l;const m=function(t,e,o){const s=e.shape[1],i=e.shape[2],r=[[t.startPoint[1]/s,t.startPoint[0]/i,t.endPoint[1]/s,t.endPoint[0]/i]];return n.image.cropAndResize(e,r,[0],o)}(l=!0===e?this.getBoxForPalmLandmarks(o.palmLandmarks,u):o,d,[this.meshWidth,this.meshHeight]),f=m.div(255);m.dispose(),d.dispose();const P=n.env().get("WEBGL_PACK_DEPTHWISECONV");n.env().set("WEBGL_PACK_DEPTHWISECONV",!0);const[g,b]=this.meshDetector.predict(f);n.env().set("WEBGL_PACK_DEPTHWISECONV",P),f.dispose();const k=g.dataSync()[0];if(g.dispose(),k<this.detectionConfidence)return b.dispose(),this.regionsOfInterest=[],null;const x=n.reshape(b,[-1,3]),y=x.arraySync();b.dispose(),x.dispose();const L=this.transformRawCoords(y,l,i,u),w=this.getBoxForHandLandmarks(L);return this.updateRegionsOfInterest(w,!1),{landmarks:L,handInViewConfidence:k,boundingBox:{topLeft:w.startPoint,bottomRight:w.endPoint}}}calculateLandmarksBoundingBox(t){const n=t.map(t=>t[0]),e=t.map(t=>t[1]);return{startPoint:[Math.min(...n),Math.min(...e)],endPoint:[Math.max(...n),Math.max(...e)]}}updateRegionsOfInterest(t,n){if(n)this.regionsOfInterest=[t];else{const n=this.regionsOfInterest[0];let e=0;if(null!=n&&null!=n.startPoint){const[o,s]=t.startPoint,[i,r]=t.endPoint,[a,d]=n.startPoint,[h,c]=n.endPoint,u=Math.max(o,a),l=Math.max(s,d),m=(Math.min(i,h)-u)*(Math.min(r,c)-l);e=m/((i-o)*(r-s)+(h-a)*(c-s)-m)}this.regionsOfInterest[0]=e>g?n:t}}shouldUpdateRegionsOfInterest(){return this.regionsOfInterest.length!==this.maxHandsNumber||this.runsWithoutHandDetector>=this.maxContinuousChecks}}const M=256,C=256;class H{constructor(t){this.pipeline=t}static getAnnotations(){return h}async estimateHands(t,e=!1){const[,o]=function(t){return t instanceof n.Tensor?[t.shape[0],t.shape[1]]:[t.height,t.width]}(t),s=n.tidy(()=>(t instanceof n.Tensor||(t=n.browser.fromPixels(t)),t.toFloat().expandDims(0))),i=await this.pipeline.estimateHand(s);if(s.dispose(),null===i)return[];let r=i;!0===e&&(r=function(t,n){const{handInViewConfidence:e,landmarks:o,boundingBox:s}=t;return{handInViewConfidence:e,landmarks:o.map(t=>[n-1-t[0],t[1],t[2]]),boundingBox:{topLeft:[n-1-s.topLeft[0],s.topLeft[1]],bottomRight:[n-1-s.bottomRight[0],s.bottomRight[1]]}}}(i,o));const a={};for(const t of Object.keys(h))a[t]=h[t].map(t=>r.landmarks[t]);return[{handInViewConfidence:r.handInViewConfidence,boundingBox:r.boundingBox,landmarks:r.landmarks,annotations:a}]}}t.load=async function({maxContinuousChecks:t=1/0,detectionConfidence:o=.8,iouThreshold:s=.3,scoreThreshold:i=.5}={}){const[r,a,h]=await Promise.all([async function(){return n.util.fetch("https://tfhub.dev/mediapipe/tfjs-model/handskeleton/1/default/1/anchors.json?tfjs-format=file").then(t=>t.json())}(),async function(){return e.loadGraphModel("https://tfhub.dev/mediapipe/tfjs-model/handdetector/1/default/1",{fromTFHub:!0})}(),async function(){return e.loadGraphModel("https://tfhub.dev/mediapipe/tfjs-model/handskeleton/1/default/1",{fromTFHub:!0})}()]),c=new d(a,M,C,r,s,i),u=new w(c,h,M,C,t,o);return new H(u)},t.HandPose=H,Object.defineProperty(t,"__esModule",{value:!0})});

@@ -76,3 +76,3 @@ "use strict";

if (useFreshBox === true) {
const boundingBoxPrediction = this.boundingBoxDetector.estimateHandBounds(image);
const boundingBoxPrediction = await this.boundingBoxDetector.estimateHandBounds(image);
if (boundingBoxPrediction === null) {

@@ -95,6 +95,6 @@ image.dispose();

const backend = tf.getBackend();
if (backend === 'webgl') {
if (backend.match('webgl')) {
rotatedImage = rotate_gpu_1.rotate(image, angle, 0, palmCenterNormalized);
}
else if (backend === 'cpu') {
else if (backend === 'cpu' || backend === 'tensorflow') {
rotatedImage = rotate_cpu_1.rotate(image, angle, 0, palmCenterNormalized);

@@ -101,0 +101,0 @@ }

{
"name": "@tensorflow-models/handpose",
"version": "0.0.3",
"version": "0.0.4",
"description": "Pretrained hand detection model",

@@ -16,8 +16,8 @@ "main": "dist/index.js",

"peerDependencies": {
"@tensorflow/tfjs-core": "^1.6.1",
"@tensorflow/tfjs-converter": "^1.6.1"
"@tensorflow/tfjs-converter": "^1.7.0",
"@tensorflow/tfjs-core": "^1.7.0"
},
"devDependencies": {
"@tensorflow/tfjs-core": "^1.6.1",
"@tensorflow/tfjs-converter": "^1.6.1",
"@tensorflow/tfjs-converter": "^1.7.0",
"@tensorflow/tfjs-core": "^1.7.0",
"@types/jasmine": "~2.5.53",

@@ -24,0 +24,0 @@ "jasmine": "~3.2.0",

# MediaPipe Handpose
Note: this model can only detect a maximum of one hand in the input - multi-hand detection is coming in a future release.
MediaPipe Handpose is a lightweight ML pipeline consisting of two models: A palm detector and a hand-skeleton finger tracking model. It predicts 21 2D hand keypoints per detected hand. For more details, please read our Google AI [blogpost](https://ai.googleblog.com/2019/08/on-device-real-time-hand-tracking-with.html).
MediaPipe Handpose is a lightweight ML pipeline consisting of two models: A palm detector and a hand-skeleton finger tracking model. It predicts 21 3D hand keypoints per detected hand. For more details, please read our Google AI [blogpost](https://ai.googleblog.com/2019/08/on-device-real-time-hand-tracking-with.html).

@@ -36,3 +37,3 @@ <img src="demo/demo.gif" alt="demo" style="width:640px" />

```js
import * as handpose from '@tensorflow-models/handpose';
const handpose = require('@tensorflow-models/handpose');
```

@@ -39,0 +40,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc