implement finger poses in hand detection and gestures

pull/193/head
Vladimir Mandic 2021-08-20 20:43:03 -04:00
parent 348176b180
commit a6ea8f5ca3
33 changed files with 2631 additions and 966 deletions

View File

@ -9,7 +9,7 @@ Repository: **<git+https://github.com/vladmandic/human.git>**
## Changelog
### **HEAD -> main** 2021/08/19 mandic00@live.com
### **HEAD -> main** 2021/08/20 mandic00@live.com
### **2.1.4** 2021/08/19 mandic00@live.com

View File

@ -31,23 +31,26 @@
"keypoints": [{"score":0.7,"part":"nose","positionRaw":[0.5763444304466248,0.11134015768766403],"position":[576,145]},{"score":0.61,"part":"leftEye","positionRaw":[0.5944706201553345,0.10566485673189163],"position":[594,137]},{"score":0.82,"part":"rightEye","positionRaw":[0.5578470230102539,0.09248857945203781],"position":[558,120]},{"score":0.23,"part":"leftEar","positionRaw":[0.6052790880203247,0.12891994416713715],"position":[605,168]},{"score":0.41,"part":"rightEar","positionRaw":[0.5136336088180542,0.10578916221857071],"position":[514,138]},{"score":0.54,"part":"leftShoulder","positionRaw":[0.5964832305908203,0.21158094704151154],"position":[596,275]},{"score":0.61,"part":"rightShoulder","positionRaw":[0.46281856298446655,0.17411141097545624],"position":[463,226]},{"score":0.5,"part":"leftElbow","positionRaw":[0.6168122291564941,0.3124275207519531],"position":[617,406]},{"score":0.66,"part":"rightElbow","positionRaw":[0.3865360915660858,0.07774468511343002],"position":[387,101]},{"score":0.47,"part":"leftWrist","positionRaw":[0.6024561524391174,0.21729949116706848],"position":[602,282]},{"score":0.24,"part":"rightWrist","positionRaw":[0.460940420627594,0.10492551326751709],"position":[461,136]},{"score":0.65,"part":"leftHip","positionRaw":[0.5450115203857422,0.4253421127796173],"position":[545,553]},{"score":0.64,"part":"rightHip","positionRaw":[0.43963193893432617,0.42362260818481445],"position":[440,551]},{"score":0.5,"part":"leftKnee","positionRaw":[0.5384384989738464,0.6577324271202087],"position":[538,855]},{"score":0.61,"part":"rightKnee","positionRaw":[0.5079054832458496,0.6598895192146301],"position":[508,858]},{"score":0.46,"part":"leftAnkle","positionRaw":[0.5350232720375061,0.8530999422073364],"position":[535,1109]},{"score":0.51,"part":"rightAnkle","positionRaw":[0.5480659008026123,0.8436547517776489],"position":[548,1097]}]
}
],
"hand":
[
{
"id": 0,
"score": 0.89,
"box": [553,215,86,66],
"boxRaw": [0.553,0.16538461538461538,0.086,0.05076923076923077],
"keypoints": [[639,281,0],[608,278,12],[587,269,17],[569,268,17],[553,270,17],[598,224,17],[574,219,19],[561,227,18],[554,236,16],[603,219,9],[577,215,11],[564,223,11],[557,232,10],[606,222,2],[582,216,3],[569,223,4],[564,232,4],[607,230,-4],[587,225,-2],[577,228,-2],[571,233,-1]],
"annotations": {"thumb":[[608,278,12],[587,269,17],[569,268,17],[553,270,17]],"indexFinger":[[598,224,17],[574,219,19],[561,227,18],[554,236,16]],"middleFinger":[[603,219,9],[577,215,11],[564,223,11],[557,232,10]],"ringFinger":[[606,222,2],[582,216,3],[569,223,4],[564,232,4]],"pinky":[[607,230,-4],[587,225,-2],[577,228,-2],[571,233,-1]],"palmBase":[[639,281,0]]}
"id": 0,
"score": 0.97,
"box": [215,152,252,439],
"boxRaw": [0.16796875,0.2111111111111111,0.196875,0.6097222222222223],
"keypoints": [[296,530,0],[350,544,-68],[392,545,-114],[427,563,-147],[467,591,-184],[341,381,-113],[353,292,-143],[377,236,-155],[397,202,-162],[291,346,-91],[306,239,-123],[331,185,-136],[355,152,-144],[252,332,-70],[258,240,-94],[276,193,-105],[294,162,-111],[221,332,-52],[215,262,-70],[219,224,-78],[230,195,-82]],
"annotations": {"thumb":[[350,544,-68],[392,545,-114],[427,563,-147],[467,591,-184]],"index":[[341,381,-113],[353,292,-143],[377,236,-155],[397,202,-162]],"middle":[[291,346,-91],[306,239,-123],[331,185,-136],[355,152,-144]],"ring":[[252,332,-70],[258,240,-94],[276,193,-105],[294,162,-111]],"pinky":[[221,332,-52],[215,262,-70],[219,224,-78],[230,195,-82]],"palm":[[296,530,0]]},
"landmarks": {"thumb":{"curl":"none","direction":"horizontalRight"},"index":{"curl":"none","direction":"diagonalUpRight"},"middle":{"curl":"none","direction":"verticalUp"},"ring":{"curl":"none","direction":"verticalUp"},"pinky":{"curl":"none","direction":"verticalUp"}}
},
{
"id": 1,
"score": 0.97,
"box": [539,213,84,113],
"boxRaw": [0.539,0.16384615384615384,0.084,0.08692307692307692],
"keypoints": [[623,326,0],[594,297,16],[579,272,21],[561,255,22],[544,241,23],[608,241,13],[582,216,12],[558,215,11],[543,221,10],[610,240,2],[579,213,0],[552,217,-3],[539,227,-3],[608,244,-7],[580,220,-10],[556,223,-11],[545,233,-11],[602,252,-16],[579,234,-18],[561,234,-18],[551,240,-17]],
"annotations": {"thumb":[[594,297,16],[579,272,21],[561,255,22],[544,241,23]],"indexFinger":[[608,241,13],[582,216,12],[558,215,11],[543,221,10]],"middleFinger":[[610,240,2],[579,213,0],[552,217,-3],[539,227,-3]],"ringFinger":[[608,244,-7],[580,220,-10],[556,223,-11],[545,233,-11]],"pinky":[[602,252,-16],[579,234,-18],[561,234,-18],[551,240,-17]],"palmBase":[[623,326,0]]}
"id": 1,
"score": 0.18,
"box": [611,209,99,280],
"boxRaw": [0.47734375,0.2902777777777778,0.07734375,0.3888888888888889],
"keypoints": [[674,209,0],[647,284,-11],[645,347,-15],[642,399,-16],[642,431,-16],[700,404,-11],[677,482,-14],[639,489,-15],[611,471,-15],[709,409,-6],[684,483,-8],[643,484,-8],[616,462,-8],[710,404,-3],[687,468,-4],[651,468,-3],[628,449,-2],[707,393,-1],[690,439,-3],[662,446,-4],[642,437,-4]],
"annotations": {"thumb":[[647,284,-11],[645,347,-15],[642,399,-16],[642,431,-16]],"index":[[700,404,-11],[677,482,-14],[639,489,-15],[611,471,-15]],"middle":[[709,409,-6],[684,483,-8],[643,484,-8],[616,462,-8]],"ring":[[710,404,-3],[687,468,-4],[651,468,-3],[628,449,-2]],"pinky":[[707,393,-1],[690,439,-3],[662,446,-4],[642,437,-4]],"palm":[[674,209,0]]},
"landmarks": {"thumb":{"curl":"none","direction":"verticalDown"},"index":{"curl":"half","direction":"verticalDown"},"middle":{"curl":"half","direction":"verticalDown"},"ring":{"curl":"half","direction":"verticalDown"},"pinky":{"curl":"half","direction":"verticalDown"}}
}
],
"gesture":
@ -62,7 +65,7 @@
},
{
"hand": 0,
"gesture": "pinky forward middlefinger up"
"gesture": "thumbs up"
},
{
"hand": 1,

View File

@ -40,7 +40,7 @@ let userConfig = {
enabled: false,
flip: false,
},
face: { enabled: true,
face: { enabled: false,
detector: { return: false, rotation: true },
mesh: { enabled: true },
iris: { enabled: true },
@ -48,7 +48,7 @@ let userConfig = {
emotion: { enabled: false },
},
object: { enabled: false },
gesture: { enabled: false },
gesture: { enabled: true },
hand: { enabled: false },
body: { enabled: false },
// body: { enabled: true, modelPath: 'movenet-multipose.json' },

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

678
dist/human.esm.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

678
dist/human.js vendored

File diff suppressed because one or more lines are too long

439
dist/human.node-gpu.js vendored
View File

@ -4595,14 +4595,14 @@ function scoreIsMaximumInLocalWindow(keypointId, score3, heatmapY, heatmapX, sco
}
return localMaximum;
}
function buildPartWithScoreQueue(minConfidence, scores) {
function buildPartWithScoreQueue(minConfidence2, scores) {
const [height, width, numKeypoints] = scores.shape;
const queue = new MaxHeap(height * width * numKeypoints, ({ score: score3 }) => score3);
for (let heatmapY = 0; heatmapY < height; ++heatmapY) {
for (let heatmapX = 0; heatmapX < width; ++heatmapX) {
for (let keypointId = 0; keypointId < numKeypoints; ++keypointId) {
const score3 = scores.get(heatmapY, heatmapX, keypointId);
if (score3 < minConfidence)
if (score3 < minConfidence2)
continue;
if (scoreIsMaximumInLocalWindow(keypointId, score3, heatmapY, heatmapX, scores))
queue.enqueue({ score: score3, part: { heatmapY, heatmapX, id: keypointId } });
@ -4628,19 +4628,19 @@ function getInstanceScore(existingPoses, keypoints3) {
}, 0);
return notOverlappedKeypointScores / keypoints3.length;
}
function decode(offsets, scores, displacementsFwd, displacementsBwd, maxDetected, minConfidence) {
function decode(offsets, scores, displacementsFwd, displacementsBwd, maxDetected, minConfidence2) {
const poses2 = [];
const queue = buildPartWithScoreQueue(minConfidence, scores);
const queue = buildPartWithScoreQueue(minConfidence2, scores);
while (poses2.length < maxDetected && !queue.empty()) {
const root = queue.dequeue();
const rootImageCoords = getImageCoords(root.part, outputStride, offsets);
if (withinRadius(poses2, rootImageCoords, root.part.id))
continue;
let keypoints3 = decodePose(root, scores, offsets, displacementsFwd, displacementsBwd);
keypoints3 = keypoints3.filter((a) => a.score > minConfidence);
keypoints3 = keypoints3.filter((a) => a.score > minConfidence2);
const score3 = getInstanceScore(poses2, keypoints3);
const box6 = getBoundingBox(keypoints3);
if (score3 > minConfidence)
if (score3 > minConfidence2)
poses2.push({ keypoints: keypoints3, box: box6, score: Math.round(100 * score3) / 100 });
}
return poses2;
@ -7959,14 +7959,383 @@ var HandPipeline = class {
}
};
// src/fingerpose/description.ts
var Finger = {
thumb: 0,
index: 1,
middle: 2,
ring: 3,
pinky: 4,
all: [0, 1, 2, 3, 4],
nameMapping: { 0: "thumb", 1: "index", 2: "middle", 3: "ring", 4: "pinky" },
pointsMapping: {
0: [[0, 1], [1, 2], [2, 3], [3, 4]],
1: [[0, 5], [5, 6], [6, 7], [7, 8]],
2: [[0, 9], [9, 10], [10, 11], [11, 12]],
3: [[0, 13], [13, 14], [14, 15], [15, 16]],
4: [[0, 17], [17, 18], [18, 19], [19, 20]]
},
getName: (value) => Finger.nameMapping[value],
getPoints: (value) => Finger.pointsMapping[value]
};
var FingerCurl = {
none: 0,
half: 1,
full: 2,
nameMapping: { 0: "none", 1: "half", 2: "full" },
getName: (value) => FingerCurl.nameMapping[value]
};
var FingerDirection = {
verticalUp: 0,
verticalDown: 1,
horizontalLeft: 2,
horizontalRight: 3,
diagonalUpRight: 4,
diagonalUpLeft: 5,
diagonalDownRight: 6,
diagonalDownLeft: 7,
nameMapping: { 0: "verticalUp", 1: "verticalDown", 2: "horizontalLeft", 3: "horizontalRight", 4: "diagonalUpRight", 5: "diagonalUpLeft", 6: "diagonalDownRight", 7: "diagonalDownLeft" },
getName: (value) => FingerDirection.nameMapping[value]
};
// src/fingerpose/estimator.ts
var options = {
HALF_CURL_START_LIMIT: 60,
NO_CURL_START_LIMIT: 130,
DISTANCE_VOTE_POWER: 1.1,
SINGLE_ANGLE_VOTE_POWER: 0.9,
TOTAL_ANGLE_VOTE_POWER: 1.6
};
function calculateSlope(point1x, point1y, point2x, point2y) {
const value = (point1y - point2y) / (point1x - point2x);
let slope = Math.atan(value) * 180 / Math.PI;
if (slope <= 0)
slope = -slope;
else if (slope > 0)
slope = 180 - slope;
return slope;
}
function getSlopes(point1, point2) {
const slopeXY = calculateSlope(point1[0], point1[1], point2[0], point2[1]);
if (point1.length === 2)
return slopeXY;
const slopeYZ = calculateSlope(point1[1], point1[2], point2[1], point2[2]);
return [slopeXY, slopeYZ];
}
function angleOrientationAt(angle, weightageAt = 1) {
let isVertical = 0;
let isDiagonal = 0;
let isHorizontal = 0;
if (angle >= 75 && angle <= 105)
isVertical = 1 * weightageAt;
else if (angle >= 25 && angle <= 155)
isDiagonal = 1 * weightageAt;
else
isHorizontal = 1 * weightageAt;
return [isVertical, isDiagonal, isHorizontal];
}
function estimateFingerCurl(startPoint, midPoint, endPoint) {
const start_mid_x_dist = startPoint[0] - midPoint[0];
const start_end_x_dist = startPoint[0] - endPoint[0];
const mid_end_x_dist = midPoint[0] - endPoint[0];
const start_mid_y_dist = startPoint[1] - midPoint[1];
const start_end_y_dist = startPoint[1] - endPoint[1];
const mid_end_y_dist = midPoint[1] - endPoint[1];
const start_mid_z_dist = startPoint[2] - midPoint[2];
const start_end_z_dist = startPoint[2] - endPoint[2];
const mid_end_z_dist = midPoint[2] - endPoint[2];
const start_mid_dist = Math.sqrt(start_mid_x_dist * start_mid_x_dist + start_mid_y_dist * start_mid_y_dist + start_mid_z_dist * start_mid_z_dist);
const start_end_dist = Math.sqrt(start_end_x_dist * start_end_x_dist + start_end_y_dist * start_end_y_dist + start_end_z_dist * start_end_z_dist);
const mid_end_dist = Math.sqrt(mid_end_x_dist * mid_end_x_dist + mid_end_y_dist * mid_end_y_dist + mid_end_z_dist * mid_end_z_dist);
let cos_in = (mid_end_dist * mid_end_dist + start_mid_dist * start_mid_dist - start_end_dist * start_end_dist) / (2 * mid_end_dist * start_mid_dist);
if (cos_in > 1)
cos_in = 1;
else if (cos_in < -1)
cos_in = -1;
let angleOfCurve = Math.acos(cos_in);
angleOfCurve = 57.2958 * angleOfCurve % 180;
let fingerCurl;
if (angleOfCurve > options.NO_CURL_START_LIMIT)
fingerCurl = FingerCurl.none;
else if (angleOfCurve > options.HALF_CURL_START_LIMIT)
fingerCurl = FingerCurl.half;
else
fingerCurl = FingerCurl.full;
return fingerCurl;
}
function estimateHorizontalDirection(start_end_x_dist, start_mid_x_dist, mid_end_x_dist, max_dist_x) {
let estimatedDirection;
if (max_dist_x === Math.abs(start_end_x_dist)) {
if (start_end_x_dist > 0)
estimatedDirection = FingerDirection.horizontalLeft;
else
estimatedDirection = FingerDirection.horizontalRight;
} else if (max_dist_x === Math.abs(start_mid_x_dist)) {
if (start_mid_x_dist > 0)
estimatedDirection = FingerDirection.horizontalLeft;
else
estimatedDirection = FingerDirection.horizontalRight;
} else {
if (mid_end_x_dist > 0)
estimatedDirection = FingerDirection.horizontalLeft;
else
estimatedDirection = FingerDirection.horizontalRight;
}
return estimatedDirection;
}
function estimateVerticalDirection(start_end_y_dist, start_mid_y_dist, mid_end_y_dist, max_dist_y) {
let estimatedDirection;
if (max_dist_y === Math.abs(start_end_y_dist)) {
if (start_end_y_dist < 0)
estimatedDirection = FingerDirection.verticalDown;
else
estimatedDirection = FingerDirection.verticalUp;
} else if (max_dist_y === Math.abs(start_mid_y_dist)) {
if (start_mid_y_dist < 0)
estimatedDirection = FingerDirection.verticalDown;
else
estimatedDirection = FingerDirection.verticalUp;
} else {
if (mid_end_y_dist < 0)
estimatedDirection = FingerDirection.verticalDown;
else
estimatedDirection = FingerDirection.verticalUp;
}
return estimatedDirection;
}
function estimateDiagonalDirection(start_end_y_dist, start_mid_y_dist, mid_end_y_dist, max_dist_y, start_end_x_dist, start_mid_x_dist, mid_end_x_dist, max_dist_x) {
let estimatedDirection;
const reqd_vertical_direction = estimateVerticalDirection(start_end_y_dist, start_mid_y_dist, mid_end_y_dist, max_dist_y);
const reqd_horizontal_direction = estimateHorizontalDirection(start_end_x_dist, start_mid_x_dist, mid_end_x_dist, max_dist_x);
if (reqd_vertical_direction === FingerDirection.verticalUp) {
if (reqd_horizontal_direction === FingerDirection.horizontalLeft)
estimatedDirection = FingerDirection.diagonalUpLeft;
else
estimatedDirection = FingerDirection.diagonalUpRight;
} else {
if (reqd_horizontal_direction === FingerDirection.horizontalLeft)
estimatedDirection = FingerDirection.diagonalDownLeft;
else
estimatedDirection = FingerDirection.diagonalDownRight;
}
return estimatedDirection;
}
function calculateFingerDirection(startPoint, midPoint, endPoint, fingerSlopes) {
const start_mid_x_dist = startPoint[0] - midPoint[0];
const start_end_x_dist = startPoint[0] - endPoint[0];
const mid_end_x_dist = midPoint[0] - endPoint[0];
const start_mid_y_dist = startPoint[1] - midPoint[1];
const start_end_y_dist = startPoint[1] - endPoint[1];
const mid_end_y_dist = midPoint[1] - endPoint[1];
const max_dist_x = Math.max(Math.abs(start_mid_x_dist), Math.abs(start_end_x_dist), Math.abs(mid_end_x_dist));
const max_dist_y = Math.max(Math.abs(start_mid_y_dist), Math.abs(start_end_y_dist), Math.abs(mid_end_y_dist));
let voteVertical = 0;
let voteDiagonal = 0;
let voteHorizontal = 0;
const start_end_x_y_dist_ratio = max_dist_y / (max_dist_x + 1e-5);
if (start_end_x_y_dist_ratio > 1.5)
voteVertical += options.DISTANCE_VOTE_POWER;
else if (start_end_x_y_dist_ratio > 0.66)
voteDiagonal += options.DISTANCE_VOTE_POWER;
else
voteHorizontal += options.DISTANCE_VOTE_POWER;
const start_mid_dist = Math.sqrt(start_mid_x_dist * start_mid_x_dist + start_mid_y_dist * start_mid_y_dist);
const start_end_dist = Math.sqrt(start_end_x_dist * start_end_x_dist + start_end_y_dist * start_end_y_dist);
const mid_end_dist = Math.sqrt(mid_end_x_dist * mid_end_x_dist + mid_end_y_dist * mid_end_y_dist);
const max_dist = Math.max(start_mid_dist, start_end_dist, mid_end_dist);
let calc_start_point_x = startPoint[0];
let calc_start_point_y = startPoint[1];
let calc_end_point_x = endPoint[0];
let calc_end_point_y = endPoint[1];
if (max_dist === start_mid_dist) {
calc_end_point_x = endPoint[0];
calc_end_point_y = endPoint[1];
} else if (max_dist === mid_end_dist) {
calc_start_point_x = midPoint[0];
calc_start_point_y = midPoint[1];
}
const calcStartPoint = [calc_start_point_x, calc_start_point_y];
const calcEndPoint = [calc_end_point_x, calc_end_point_y];
const totalAngle = getSlopes(calcStartPoint, calcEndPoint);
const votes = angleOrientationAt(totalAngle, options.TOTAL_ANGLE_VOTE_POWER);
voteVertical += votes[0];
voteDiagonal += votes[1];
voteHorizontal += votes[2];
for (const fingerSlope of fingerSlopes) {
const fingerVotes = angleOrientationAt(fingerSlope, options.SINGLE_ANGLE_VOTE_POWER);
voteVertical += fingerVotes[0];
voteDiagonal += fingerVotes[1];
voteHorizontal += fingerVotes[2];
}
let estimatedDirection;
if (voteVertical === Math.max(voteVertical, voteDiagonal, voteHorizontal)) {
estimatedDirection = estimateVerticalDirection(start_end_y_dist, start_mid_y_dist, mid_end_y_dist, max_dist_y);
} else if (voteHorizontal === Math.max(voteDiagonal, voteHorizontal)) {
estimatedDirection = estimateHorizontalDirection(start_end_x_dist, start_mid_x_dist, mid_end_x_dist, max_dist_x);
} else {
estimatedDirection = estimateDiagonalDirection(start_end_y_dist, start_mid_y_dist, mid_end_y_dist, max_dist_y, start_end_x_dist, start_mid_x_dist, mid_end_x_dist, max_dist_x);
}
return estimatedDirection;
}
function estimate(landmarks) {
const slopesXY = [];
const slopesYZ = [];
for (const finger of Finger.all) {
const points = Finger.getPoints(finger);
const slopeAtXY = [];
const slopeAtYZ = [];
for (const point2 of points) {
const point1 = landmarks[point2[0]];
const point22 = landmarks[point2[1]];
const slopes = getSlopes(point1, point22);
const slopeXY = slopes[0];
const slopeYZ = slopes[1];
slopeAtXY.push(slopeXY);
slopeAtYZ.push(slopeYZ);
}
slopesXY.push(slopeAtXY);
slopesYZ.push(slopeAtYZ);
}
const fingerCurls = [];
const fingerDirections = [];
for (const finger of Finger.all) {
const pointIndexAt = finger === Finger.thumb ? 1 : 0;
const fingerPointsAt = Finger.getPoints(finger);
const startPoint = landmarks[fingerPointsAt[pointIndexAt][0]];
const midPoint = landmarks[fingerPointsAt[pointIndexAt + 1][1]];
const endPoint = landmarks[fingerPointsAt[3][1]];
const fingerCurled = estimateFingerCurl(startPoint, midPoint, endPoint);
const fingerPosition = calculateFingerDirection(startPoint, midPoint, endPoint, slopesXY[finger].slice(pointIndexAt));
fingerCurls[finger] = fingerCurled;
fingerDirections[finger] = fingerPosition;
}
return { curls: fingerCurls, directions: fingerDirections };
}
// src/fingerpose/gesture.ts
var Gesture = class {
constructor(name) {
this.name = name;
this.curls = {};
this.directions = {};
this.weights = [1, 1, 1, 1, 1];
this.weightsRelative = [1, 1, 1, 1, 1];
}
addCurl(finger, curl, confidence) {
if (typeof this.curls[finger] === "undefined")
this.curls[finger] = [];
this.curls[finger].push([curl, confidence]);
}
addDirection(finger, position, confidence) {
if (!this.directions[finger])
this.directions[finger] = [];
this.directions[finger].push([position, confidence]);
}
setWeight(finger, weight) {
this.weights[finger] = weight;
const total = this.weights.reduce((a, b) => a + b, 0);
this.weightsRelative = this.weights.map((el) => el * 5 / total);
}
matchAgainst(detectedCurls, detectedDirections) {
let confidence = 0;
for (const fingerIdx in detectedCurls) {
const detectedCurl = detectedCurls[fingerIdx];
const expectedCurls = this.curls[fingerIdx];
if (typeof expectedCurls === "undefined") {
confidence += this.weightsRelative[fingerIdx];
continue;
}
for (const [expectedCurl, score3] of expectedCurls) {
if (detectedCurl === expectedCurl) {
confidence += score3 * this.weightsRelative[fingerIdx];
break;
}
}
}
for (const fingerIdx in detectedDirections) {
const detectedDirection = detectedDirections[fingerIdx];
const expectedDirections = this.directions[fingerIdx];
if (typeof expectedDirections === "undefined") {
confidence += this.weightsRelative[fingerIdx];
continue;
}
for (const [expectedDirection, score3] of expectedDirections) {
if (detectedDirection === expectedDirection) {
confidence += score3 * this.weightsRelative[fingerIdx];
break;
}
}
}
return confidence / 10;
}
};
// src/fingerpose/gestures.ts
var ThumbsUp = new Gesture("thumbs up");
ThumbsUp.addCurl(Finger.thumb, FingerCurl.none, 1);
ThumbsUp.addDirection(Finger.thumb, FingerDirection.verticalUp, 1);
ThumbsUp.addDirection(Finger.thumb, FingerDirection.diagonalUpLeft, 0.25);
ThumbsUp.addDirection(Finger.thumb, FingerDirection.diagonalUpRight, 0.25);
for (const finger of [Finger.index, Finger.middle, Finger.ring, Finger.pinky]) {
ThumbsUp.addCurl(finger, FingerCurl.full, 1);
ThumbsUp.addDirection(finger, FingerDirection.horizontalLeft, 1);
ThumbsUp.addDirection(finger, FingerDirection.horizontalRight, 1);
}
var Victory = new Gesture("victory");
Victory.addCurl(Finger.thumb, FingerCurl.half, 0.5);
Victory.addCurl(Finger.thumb, FingerCurl.none, 0.5);
Victory.addDirection(Finger.thumb, FingerDirection.verticalUp, 1);
Victory.addDirection(Finger.thumb, FingerDirection.diagonalUpLeft, 1);
Victory.addCurl(Finger.index, FingerCurl.none, 1);
Victory.addDirection(Finger.index, FingerDirection.verticalUp, 0.75);
Victory.addDirection(Finger.index, FingerDirection.diagonalUpLeft, 1);
Victory.addCurl(Finger.middle, FingerCurl.none, 1);
Victory.addDirection(Finger.middle, FingerDirection.verticalUp, 1);
Victory.addDirection(Finger.middle, FingerDirection.diagonalUpLeft, 0.75);
Victory.addCurl(Finger.ring, FingerCurl.full, 1);
Victory.addDirection(Finger.ring, FingerDirection.verticalUp, 0.2);
Victory.addDirection(Finger.ring, FingerDirection.diagonalUpLeft, 1);
Victory.addDirection(Finger.ring, FingerDirection.horizontalLeft, 0.2);
Victory.addCurl(Finger.pinky, FingerCurl.full, 1);
Victory.addDirection(Finger.pinky, FingerDirection.verticalUp, 0.2);
Victory.addDirection(Finger.pinky, FingerDirection.diagonalUpLeft, 1);
Victory.addDirection(Finger.pinky, FingerDirection.horizontalLeft, 0.2);
Victory.setWeight(Finger.index, 2);
Victory.setWeight(Finger.middle, 2);
var gestures_default = [ThumbsUp, Victory];
// src/fingerpose/fingerpose.ts
var minConfidence = 0.7;
function analyze(keypoints3) {
const estimatorRes = estimate(keypoints3);
const landmarks = {};
for (const fingerIdx of Finger.all) {
landmarks[Finger.getName(fingerIdx)] = {
curl: FingerCurl.getName(estimatorRes.curls[fingerIdx]),
direction: FingerDirection.getName(estimatorRes.directions[fingerIdx])
};
}
return landmarks;
}
function match2(keypoints3) {
const estimatorRes = estimate(keypoints3);
const poses2 = [];
for (const gesture3 of gestures_default) {
const confidence = gesture3.matchAgainst(estimatorRes.curls, estimatorRes.directions);
if (confidence >= minConfidence)
poses2.push({ name: gesture3.name, confidence });
}
return poses2;
}
// src/handpose/handpose.ts
var meshAnnotations = {
thumb: [1, 2, 3, 4],
indexFinger: [5, 6, 7, 8],
middleFinger: [9, 10, 11, 12],
ringFinger: [13, 14, 15, 16],
index: [5, 6, 7, 8],
middle: [9, 10, 11, 12],
ring: [13, 14, 15, 16],
pinky: [17, 18, 19, 20],
palmBase: [0]
palm: [0]
};
var handDetectorModel;
var handPoseModel;
@ -8014,7 +8383,16 @@ async function predict5(input, config3) {
(predictions[i].box.bottomRight[1] - predictions[i].box.topLeft[1]) / (input.shape[1] || 0)
];
}
hands.push({ id: i, score: Math.round(100 * predictions[i].confidence) / 100, box: box6, boxRaw: boxRaw3, keypoints: keypoints3, annotations: annotations3 });
const landmarks = analyze(keypoints3);
hands.push({
id: i,
score: Math.round(100 * predictions[i].confidence) / 100,
box: box6,
boxRaw: boxRaw3,
keypoints: keypoints3,
annotations: annotations3,
landmarks
});
}
return hands;
}
@ -8706,9 +9084,9 @@ var tf18 = __toModule(require_tfjs_esm());
function GLProgram(gl, vertexSource, fragmentSource) {
const _collect = function(source, prefix, collection) {
const r = new RegExp("\\b" + prefix + " \\w+ (\\w+)", "ig");
source.replace(r, (match2, name) => {
source.replace(r, (match3, name) => {
collection[name] = 0;
return match2;
return match3;
});
};
const _compile = function(source, type) {
@ -10013,6 +10391,9 @@ var hand = (res) => {
const highest = fingers.reduce((best, a) => best.position[1] < a.position[1] ? best : a);
gestures.push({ hand: i, gesture: `${highest.name} up` });
}
const poses2 = match2(res[i]["keypoints"]);
for (const pose of poses2)
gestures.push({ hand: i, gesture: pose.name });
}
return gestures;
};
@ -10027,10 +10408,10 @@ __export(draw_exports, {
gesture: () => gesture,
hand: () => hand2,
object: () => object,
options: () => options,
options: () => options2,
person: () => person
});
var options = {
var options2 = {
color: "rgba(173, 216, 230, 0.6)",
labelColor: "rgba(173, 216, 230, 1)",
shadowColor: "black",
@ -10115,7 +10496,7 @@ function curves(ctx, points = [], localOptions) {
}
}
async function gesture(inCanvas2, result, drawOptions) {
const localOptions = mergeDeep(options, drawOptions);
const localOptions = mergeDeep(options2, drawOptions);
if (!result || !inCanvas2)
return;
if (!(inCanvas2 instanceof HTMLCanvasElement))
@ -10145,7 +10526,7 @@ async function gesture(inCanvas2, result, drawOptions) {
}
async function face2(inCanvas2, result, drawOptions) {
var _a, _b, _c, _d;
const localOptions = mergeDeep(options, drawOptions);
const localOptions = mergeDeep(options2, drawOptions);
if (!result || !inCanvas2)
return;
if (!(inCanvas2 instanceof HTMLCanvasElement))
@ -10255,7 +10636,7 @@ async function face2(inCanvas2, result, drawOptions) {
}
async function body2(inCanvas2, result, drawOptions) {
var _a;
const localOptions = mergeDeep(options, drawOptions);
const localOptions = mergeDeep(options2, drawOptions);
if (!result || !inCanvas2)
return;
if (!(inCanvas2 instanceof HTMLCanvasElement))
@ -10387,7 +10768,7 @@ async function body2(inCanvas2, result, drawOptions) {
}
}
async function hand2(inCanvas2, result, drawOptions) {
const localOptions = mergeDeep(options, drawOptions);
const localOptions = mergeDeep(options2, drawOptions);
if (!result || !inCanvas2)
return;
if (!(inCanvas2 instanceof HTMLCanvasElement))
@ -10426,12 +10807,12 @@ async function hand2(inCanvas2, result, drawOptions) {
ctx.fillText(title, part[part.length - 1][0] + 4, part[part.length - 1][1] + 4);
};
ctx.font = localOptions.font;
addHandLabel(h.annotations["indexFinger"], "index");
addHandLabel(h.annotations["middleFinger"], "middle");
addHandLabel(h.annotations["ringFinger"], "ring");
addHandLabel(h.annotations["index"], "index");
addHandLabel(h.annotations["middle"], "middle");
addHandLabel(h.annotations["ring"], "ring");
addHandLabel(h.annotations["pinky"], "pinky");
addHandLabel(h.annotations["thumb"], "thumb");
addHandLabel(h.annotations["palmBase"], "palm");
addHandLabel(h.annotations["palm"], "palm");
}
if (localOptions.drawPolygons) {
const addHandLine = (part) => {
@ -10446,16 +10827,16 @@ async function hand2(inCanvas2, result, drawOptions) {
}
};
ctx.lineWidth = localOptions.lineWidth;
addHandLine(h.annotations["indexFinger"]);
addHandLine(h.annotations["middleFinger"]);
addHandLine(h.annotations["ringFinger"]);
addHandLine(h.annotations["index"]);
addHandLine(h.annotations["middle"]);
addHandLine(h.annotations["ring"]);
addHandLine(h.annotations["pinky"]);
addHandLine(h.annotations["thumb"]);
}
}
}
async function object(inCanvas2, result, drawOptions) {
const localOptions = mergeDeep(options, drawOptions);
const localOptions = mergeDeep(options2, drawOptions);
if (!result || !inCanvas2)
return;
if (!(inCanvas2 instanceof HTMLCanvasElement))
@ -10484,7 +10865,7 @@ async function object(inCanvas2, result, drawOptions) {
}
}
async function person(inCanvas2, result, drawOptions) {
const localOptions = mergeDeep(options, drawOptions);
const localOptions = mergeDeep(options2, drawOptions);
if (!result || !inCanvas2)
return;
if (!(inCanvas2 instanceof HTMLCanvasElement))
@ -10522,7 +10903,7 @@ async function canvas(inCanvas2, outCanvas2) {
}
async function all(inCanvas2, result, drawOptions) {
const timestamp = now();
const localOptions = mergeDeep(options, drawOptions);
const localOptions = mergeDeep(options2, drawOptions);
if (!result || !inCanvas2)
return null;
if (!(inCanvas2 instanceof HTMLCanvasElement))

View File

@ -4596,14 +4596,14 @@ function scoreIsMaximumInLocalWindow(keypointId, score3, heatmapY, heatmapX, sco
}
return localMaximum;
}
function buildPartWithScoreQueue(minConfidence, scores) {
function buildPartWithScoreQueue(minConfidence2, scores) {
const [height, width, numKeypoints] = scores.shape;
const queue = new MaxHeap(height * width * numKeypoints, ({ score: score3 }) => score3);
for (let heatmapY = 0; heatmapY < height; ++heatmapY) {
for (let heatmapX = 0; heatmapX < width; ++heatmapX) {
for (let keypointId = 0; keypointId < numKeypoints; ++keypointId) {
const score3 = scores.get(heatmapY, heatmapX, keypointId);
if (score3 < minConfidence)
if (score3 < minConfidence2)
continue;
if (scoreIsMaximumInLocalWindow(keypointId, score3, heatmapY, heatmapX, scores))
queue.enqueue({ score: score3, part: { heatmapY, heatmapX, id: keypointId } });
@ -4629,19 +4629,19 @@ function getInstanceScore(existingPoses, keypoints3) {
}, 0);
return notOverlappedKeypointScores / keypoints3.length;
}
function decode(offsets, scores, displacementsFwd, displacementsBwd, maxDetected, minConfidence) {
function decode(offsets, scores, displacementsFwd, displacementsBwd, maxDetected, minConfidence2) {
const poses2 = [];
const queue = buildPartWithScoreQueue(minConfidence, scores);
const queue = buildPartWithScoreQueue(minConfidence2, scores);
while (poses2.length < maxDetected && !queue.empty()) {
const root = queue.dequeue();
const rootImageCoords = getImageCoords(root.part, outputStride, offsets);
if (withinRadius(poses2, rootImageCoords, root.part.id))
continue;
let keypoints3 = decodePose(root, scores, offsets, displacementsFwd, displacementsBwd);
keypoints3 = keypoints3.filter((a) => a.score > minConfidence);
keypoints3 = keypoints3.filter((a) => a.score > minConfidence2);
const score3 = getInstanceScore(poses2, keypoints3);
const box6 = getBoundingBox(keypoints3);
if (score3 > minConfidence)
if (score3 > minConfidence2)
poses2.push({ keypoints: keypoints3, box: box6, score: Math.round(100 * score3) / 100 });
}
return poses2;
@ -7960,14 +7960,383 @@ var HandPipeline = class {
}
};
// src/fingerpose/description.ts
var Finger = {
thumb: 0,
index: 1,
middle: 2,
ring: 3,
pinky: 4,
all: [0, 1, 2, 3, 4],
nameMapping: { 0: "thumb", 1: "index", 2: "middle", 3: "ring", 4: "pinky" },
pointsMapping: {
0: [[0, 1], [1, 2], [2, 3], [3, 4]],
1: [[0, 5], [5, 6], [6, 7], [7, 8]],
2: [[0, 9], [9, 10], [10, 11], [11, 12]],
3: [[0, 13], [13, 14], [14, 15], [15, 16]],
4: [[0, 17], [17, 18], [18, 19], [19, 20]]
},
getName: (value) => Finger.nameMapping[value],
getPoints: (value) => Finger.pointsMapping[value]
};
var FingerCurl = {
none: 0,
half: 1,
full: 2,
nameMapping: { 0: "none", 1: "half", 2: "full" },
getName: (value) => FingerCurl.nameMapping[value]
};
var FingerDirection = {
verticalUp: 0,
verticalDown: 1,
horizontalLeft: 2,
horizontalRight: 3,
diagonalUpRight: 4,
diagonalUpLeft: 5,
diagonalDownRight: 6,
diagonalDownLeft: 7,
nameMapping: { 0: "verticalUp", 1: "verticalDown", 2: "horizontalLeft", 3: "horizontalRight", 4: "diagonalUpRight", 5: "diagonalUpLeft", 6: "diagonalDownRight", 7: "diagonalDownLeft" },
getName: (value) => FingerDirection.nameMapping[value]
};
// src/fingerpose/estimator.ts
var options = {
HALF_CURL_START_LIMIT: 60,
NO_CURL_START_LIMIT: 130,
DISTANCE_VOTE_POWER: 1.1,
SINGLE_ANGLE_VOTE_POWER: 0.9,
TOTAL_ANGLE_VOTE_POWER: 1.6
};
function calculateSlope(point1x, point1y, point2x, point2y) {
const value = (point1y - point2y) / (point1x - point2x);
let slope = Math.atan(value) * 180 / Math.PI;
if (slope <= 0)
slope = -slope;
else if (slope > 0)
slope = 180 - slope;
return slope;
}
function getSlopes(point1, point2) {
const slopeXY = calculateSlope(point1[0], point1[1], point2[0], point2[1]);
if (point1.length === 2)
return slopeXY;
const slopeYZ = calculateSlope(point1[1], point1[2], point2[1], point2[2]);
return [slopeXY, slopeYZ];
}
function angleOrientationAt(angle, weightageAt = 1) {
let isVertical = 0;
let isDiagonal = 0;
let isHorizontal = 0;
if (angle >= 75 && angle <= 105)
isVertical = 1 * weightageAt;
else if (angle >= 25 && angle <= 155)
isDiagonal = 1 * weightageAt;
else
isHorizontal = 1 * weightageAt;
return [isVertical, isDiagonal, isHorizontal];
}
function estimateFingerCurl(startPoint, midPoint, endPoint) {
const start_mid_x_dist = startPoint[0] - midPoint[0];
const start_end_x_dist = startPoint[0] - endPoint[0];
const mid_end_x_dist = midPoint[0] - endPoint[0];
const start_mid_y_dist = startPoint[1] - midPoint[1];
const start_end_y_dist = startPoint[1] - endPoint[1];
const mid_end_y_dist = midPoint[1] - endPoint[1];
const start_mid_z_dist = startPoint[2] - midPoint[2];
const start_end_z_dist = startPoint[2] - endPoint[2];
const mid_end_z_dist = midPoint[2] - endPoint[2];
const start_mid_dist = Math.sqrt(start_mid_x_dist * start_mid_x_dist + start_mid_y_dist * start_mid_y_dist + start_mid_z_dist * start_mid_z_dist);
const start_end_dist = Math.sqrt(start_end_x_dist * start_end_x_dist + start_end_y_dist * start_end_y_dist + start_end_z_dist * start_end_z_dist);
const mid_end_dist = Math.sqrt(mid_end_x_dist * mid_end_x_dist + mid_end_y_dist * mid_end_y_dist + mid_end_z_dist * mid_end_z_dist);
let cos_in = (mid_end_dist * mid_end_dist + start_mid_dist * start_mid_dist - start_end_dist * start_end_dist) / (2 * mid_end_dist * start_mid_dist);
if (cos_in > 1)
cos_in = 1;
else if (cos_in < -1)
cos_in = -1;
let angleOfCurve = Math.acos(cos_in);
angleOfCurve = 57.2958 * angleOfCurve % 180;
let fingerCurl;
if (angleOfCurve > options.NO_CURL_START_LIMIT)
fingerCurl = FingerCurl.none;
else if (angleOfCurve > options.HALF_CURL_START_LIMIT)
fingerCurl = FingerCurl.half;
else
fingerCurl = FingerCurl.full;
return fingerCurl;
}
function estimateHorizontalDirection(start_end_x_dist, start_mid_x_dist, mid_end_x_dist, max_dist_x) {
let estimatedDirection;
if (max_dist_x === Math.abs(start_end_x_dist)) {
if (start_end_x_dist > 0)
estimatedDirection = FingerDirection.horizontalLeft;
else
estimatedDirection = FingerDirection.horizontalRight;
} else if (max_dist_x === Math.abs(start_mid_x_dist)) {
if (start_mid_x_dist > 0)
estimatedDirection = FingerDirection.horizontalLeft;
else
estimatedDirection = FingerDirection.horizontalRight;
} else {
if (mid_end_x_dist > 0)
estimatedDirection = FingerDirection.horizontalLeft;
else
estimatedDirection = FingerDirection.horizontalRight;
}
return estimatedDirection;
}
function estimateVerticalDirection(start_end_y_dist, start_mid_y_dist, mid_end_y_dist, max_dist_y) {
let estimatedDirection;
if (max_dist_y === Math.abs(start_end_y_dist)) {
if (start_end_y_dist < 0)
estimatedDirection = FingerDirection.verticalDown;
else
estimatedDirection = FingerDirection.verticalUp;
} else if (max_dist_y === Math.abs(start_mid_y_dist)) {
if (start_mid_y_dist < 0)
estimatedDirection = FingerDirection.verticalDown;
else
estimatedDirection = FingerDirection.verticalUp;
} else {
if (mid_end_y_dist < 0)
estimatedDirection = FingerDirection.verticalDown;
else
estimatedDirection = FingerDirection.verticalUp;
}
return estimatedDirection;
}
function estimateDiagonalDirection(start_end_y_dist, start_mid_y_dist, mid_end_y_dist, max_dist_y, start_end_x_dist, start_mid_x_dist, mid_end_x_dist, max_dist_x) {
let estimatedDirection;
const reqd_vertical_direction = estimateVerticalDirection(start_end_y_dist, start_mid_y_dist, mid_end_y_dist, max_dist_y);
const reqd_horizontal_direction = estimateHorizontalDirection(start_end_x_dist, start_mid_x_dist, mid_end_x_dist, max_dist_x);
if (reqd_vertical_direction === FingerDirection.verticalUp) {
if (reqd_horizontal_direction === FingerDirection.horizontalLeft)
estimatedDirection = FingerDirection.diagonalUpLeft;
else
estimatedDirection = FingerDirection.diagonalUpRight;
} else {
if (reqd_horizontal_direction === FingerDirection.horizontalLeft)
estimatedDirection = FingerDirection.diagonalDownLeft;
else
estimatedDirection = FingerDirection.diagonalDownRight;
}
return estimatedDirection;
}
function calculateFingerDirection(startPoint, midPoint, endPoint, fingerSlopes) {
const start_mid_x_dist = startPoint[0] - midPoint[0];
const start_end_x_dist = startPoint[0] - endPoint[0];
const mid_end_x_dist = midPoint[0] - endPoint[0];
const start_mid_y_dist = startPoint[1] - midPoint[1];
const start_end_y_dist = startPoint[1] - endPoint[1];
const mid_end_y_dist = midPoint[1] - endPoint[1];
const max_dist_x = Math.max(Math.abs(start_mid_x_dist), Math.abs(start_end_x_dist), Math.abs(mid_end_x_dist));
const max_dist_y = Math.max(Math.abs(start_mid_y_dist), Math.abs(start_end_y_dist), Math.abs(mid_end_y_dist));
let voteVertical = 0;
let voteDiagonal = 0;
let voteHorizontal = 0;
const start_end_x_y_dist_ratio = max_dist_y / (max_dist_x + 1e-5);
if (start_end_x_y_dist_ratio > 1.5)
voteVertical += options.DISTANCE_VOTE_POWER;
else if (start_end_x_y_dist_ratio > 0.66)
voteDiagonal += options.DISTANCE_VOTE_POWER;
else
voteHorizontal += options.DISTANCE_VOTE_POWER;
const start_mid_dist = Math.sqrt(start_mid_x_dist * start_mid_x_dist + start_mid_y_dist * start_mid_y_dist);
const start_end_dist = Math.sqrt(start_end_x_dist * start_end_x_dist + start_end_y_dist * start_end_y_dist);
const mid_end_dist = Math.sqrt(mid_end_x_dist * mid_end_x_dist + mid_end_y_dist * mid_end_y_dist);
const max_dist = Math.max(start_mid_dist, start_end_dist, mid_end_dist);
let calc_start_point_x = startPoint[0];
let calc_start_point_y = startPoint[1];
let calc_end_point_x = endPoint[0];
let calc_end_point_y = endPoint[1];
if (max_dist === start_mid_dist) {
calc_end_point_x = endPoint[0];
calc_end_point_y = endPoint[1];
} else if (max_dist === mid_end_dist) {
calc_start_point_x = midPoint[0];
calc_start_point_y = midPoint[1];
}
const calcStartPoint = [calc_start_point_x, calc_start_point_y];
const calcEndPoint = [calc_end_point_x, calc_end_point_y];
const totalAngle = getSlopes(calcStartPoint, calcEndPoint);
const votes = angleOrientationAt(totalAngle, options.TOTAL_ANGLE_VOTE_POWER);
voteVertical += votes[0];
voteDiagonal += votes[1];
voteHorizontal += votes[2];
for (const fingerSlope of fingerSlopes) {
const fingerVotes = angleOrientationAt(fingerSlope, options.SINGLE_ANGLE_VOTE_POWER);
voteVertical += fingerVotes[0];
voteDiagonal += fingerVotes[1];
voteHorizontal += fingerVotes[2];
}
let estimatedDirection;
if (voteVertical === Math.max(voteVertical, voteDiagonal, voteHorizontal)) {
estimatedDirection = estimateVerticalDirection(start_end_y_dist, start_mid_y_dist, mid_end_y_dist, max_dist_y);
} else if (voteHorizontal === Math.max(voteDiagonal, voteHorizontal)) {
estimatedDirection = estimateHorizontalDirection(start_end_x_dist, start_mid_x_dist, mid_end_x_dist, max_dist_x);
} else {
estimatedDirection = estimateDiagonalDirection(start_end_y_dist, start_mid_y_dist, mid_end_y_dist, max_dist_y, start_end_x_dist, start_mid_x_dist, mid_end_x_dist, max_dist_x);
}
return estimatedDirection;
}
function estimate(landmarks) {
const slopesXY = [];
const slopesYZ = [];
for (const finger of Finger.all) {
const points = Finger.getPoints(finger);
const slopeAtXY = [];
const slopeAtYZ = [];
for (const point2 of points) {
const point1 = landmarks[point2[0]];
const point22 = landmarks[point2[1]];
const slopes = getSlopes(point1, point22);
const slopeXY = slopes[0];
const slopeYZ = slopes[1];
slopeAtXY.push(slopeXY);
slopeAtYZ.push(slopeYZ);
}
slopesXY.push(slopeAtXY);
slopesYZ.push(slopeAtYZ);
}
const fingerCurls = [];
const fingerDirections = [];
for (const finger of Finger.all) {
const pointIndexAt = finger === Finger.thumb ? 1 : 0;
const fingerPointsAt = Finger.getPoints(finger);
const startPoint = landmarks[fingerPointsAt[pointIndexAt][0]];
const midPoint = landmarks[fingerPointsAt[pointIndexAt + 1][1]];
const endPoint = landmarks[fingerPointsAt[3][1]];
const fingerCurled = estimateFingerCurl(startPoint, midPoint, endPoint);
const fingerPosition = calculateFingerDirection(startPoint, midPoint, endPoint, slopesXY[finger].slice(pointIndexAt));
fingerCurls[finger] = fingerCurled;
fingerDirections[finger] = fingerPosition;
}
return { curls: fingerCurls, directions: fingerDirections };
}
// src/fingerpose/gesture.ts
var Gesture = class {
constructor(name) {
this.name = name;
this.curls = {};
this.directions = {};
this.weights = [1, 1, 1, 1, 1];
this.weightsRelative = [1, 1, 1, 1, 1];
}
addCurl(finger, curl, confidence) {
if (typeof this.curls[finger] === "undefined")
this.curls[finger] = [];
this.curls[finger].push([curl, confidence]);
}
addDirection(finger, position, confidence) {
if (!this.directions[finger])
this.directions[finger] = [];
this.directions[finger].push([position, confidence]);
}
setWeight(finger, weight) {
this.weights[finger] = weight;
const total = this.weights.reduce((a, b) => a + b, 0);
this.weightsRelative = this.weights.map((el) => el * 5 / total);
}
matchAgainst(detectedCurls, detectedDirections) {
let confidence = 0;
for (const fingerIdx in detectedCurls) {
const detectedCurl = detectedCurls[fingerIdx];
const expectedCurls = this.curls[fingerIdx];
if (typeof expectedCurls === "undefined") {
confidence += this.weightsRelative[fingerIdx];
continue;
}
for (const [expectedCurl, score3] of expectedCurls) {
if (detectedCurl === expectedCurl) {
confidence += score3 * this.weightsRelative[fingerIdx];
break;
}
}
}
for (const fingerIdx in detectedDirections) {
const detectedDirection = detectedDirections[fingerIdx];
const expectedDirections = this.directions[fingerIdx];
if (typeof expectedDirections === "undefined") {
confidence += this.weightsRelative[fingerIdx];
continue;
}
for (const [expectedDirection, score3] of expectedDirections) {
if (detectedDirection === expectedDirection) {
confidence += score3 * this.weightsRelative[fingerIdx];
break;
}
}
}
return confidence / 10;
}
};
// src/fingerpose/gestures.ts
var ThumbsUp = new Gesture("thumbs up");
ThumbsUp.addCurl(Finger.thumb, FingerCurl.none, 1);
ThumbsUp.addDirection(Finger.thumb, FingerDirection.verticalUp, 1);
ThumbsUp.addDirection(Finger.thumb, FingerDirection.diagonalUpLeft, 0.25);
ThumbsUp.addDirection(Finger.thumb, FingerDirection.diagonalUpRight, 0.25);
for (const finger of [Finger.index, Finger.middle, Finger.ring, Finger.pinky]) {
ThumbsUp.addCurl(finger, FingerCurl.full, 1);
ThumbsUp.addDirection(finger, FingerDirection.horizontalLeft, 1);
ThumbsUp.addDirection(finger, FingerDirection.horizontalRight, 1);
}
var Victory = new Gesture("victory");
Victory.addCurl(Finger.thumb, FingerCurl.half, 0.5);
Victory.addCurl(Finger.thumb, FingerCurl.none, 0.5);
Victory.addDirection(Finger.thumb, FingerDirection.verticalUp, 1);
Victory.addDirection(Finger.thumb, FingerDirection.diagonalUpLeft, 1);
Victory.addCurl(Finger.index, FingerCurl.none, 1);
Victory.addDirection(Finger.index, FingerDirection.verticalUp, 0.75);
Victory.addDirection(Finger.index, FingerDirection.diagonalUpLeft, 1);
Victory.addCurl(Finger.middle, FingerCurl.none, 1);
Victory.addDirection(Finger.middle, FingerDirection.verticalUp, 1);
Victory.addDirection(Finger.middle, FingerDirection.diagonalUpLeft, 0.75);
Victory.addCurl(Finger.ring, FingerCurl.full, 1);
Victory.addDirection(Finger.ring, FingerDirection.verticalUp, 0.2);
Victory.addDirection(Finger.ring, FingerDirection.diagonalUpLeft, 1);
Victory.addDirection(Finger.ring, FingerDirection.horizontalLeft, 0.2);
Victory.addCurl(Finger.pinky, FingerCurl.full, 1);
Victory.addDirection(Finger.pinky, FingerDirection.verticalUp, 0.2);
Victory.addDirection(Finger.pinky, FingerDirection.diagonalUpLeft, 1);
Victory.addDirection(Finger.pinky, FingerDirection.horizontalLeft, 0.2);
Victory.setWeight(Finger.index, 2);
Victory.setWeight(Finger.middle, 2);
var gestures_default = [ThumbsUp, Victory];
// src/fingerpose/fingerpose.ts
var minConfidence = 0.7;
function analyze(keypoints3) {
const estimatorRes = estimate(keypoints3);
const landmarks = {};
for (const fingerIdx of Finger.all) {
landmarks[Finger.getName(fingerIdx)] = {
curl: FingerCurl.getName(estimatorRes.curls[fingerIdx]),
direction: FingerDirection.getName(estimatorRes.directions[fingerIdx])
};
}
return landmarks;
}
function match2(keypoints3) {
const estimatorRes = estimate(keypoints3);
const poses2 = [];
for (const gesture3 of gestures_default) {
const confidence = gesture3.matchAgainst(estimatorRes.curls, estimatorRes.directions);
if (confidence >= minConfidence)
poses2.push({ name: gesture3.name, confidence });
}
return poses2;
}
// src/handpose/handpose.ts
var meshAnnotations = {
thumb: [1, 2, 3, 4],
indexFinger: [5, 6, 7, 8],
middleFinger: [9, 10, 11, 12],
ringFinger: [13, 14, 15, 16],
index: [5, 6, 7, 8],
middle: [9, 10, 11, 12],
ring: [13, 14, 15, 16],
pinky: [17, 18, 19, 20],
palmBase: [0]
palm: [0]
};
var handDetectorModel;
var handPoseModel;
@ -8015,7 +8384,16 @@ async function predict5(input, config3) {
(predictions[i].box.bottomRight[1] - predictions[i].box.topLeft[1]) / (input.shape[1] || 0)
];
}
hands.push({ id: i, score: Math.round(100 * predictions[i].confidence) / 100, box: box6, boxRaw: boxRaw3, keypoints: keypoints3, annotations: annotations3 });
const landmarks = analyze(keypoints3);
hands.push({
id: i,
score: Math.round(100 * predictions[i].confidence) / 100,
box: box6,
boxRaw: boxRaw3,
keypoints: keypoints3,
annotations: annotations3,
landmarks
});
}
return hands;
}
@ -8707,9 +9085,9 @@ var tf18 = __toModule(require_tfjs_esm());
function GLProgram(gl, vertexSource, fragmentSource) {
const _collect = function(source, prefix, collection) {
const r = new RegExp("\\b" + prefix + " \\w+ (\\w+)", "ig");
source.replace(r, (match2, name) => {
source.replace(r, (match3, name) => {
collection[name] = 0;
return match2;
return match3;
});
};
const _compile = function(source, type) {
@ -10014,6 +10392,9 @@ var hand = (res) => {
const highest = fingers.reduce((best, a) => best.position[1] < a.position[1] ? best : a);
gestures.push({ hand: i, gesture: `${highest.name} up` });
}
const poses2 = match2(res[i]["keypoints"]);
for (const pose of poses2)
gestures.push({ hand: i, gesture: pose.name });
}
return gestures;
};
@ -10028,10 +10409,10 @@ __export(draw_exports, {
gesture: () => gesture,
hand: () => hand2,
object: () => object,
options: () => options,
options: () => options2,
person: () => person
});
var options = {
var options2 = {
color: "rgba(173, 216, 230, 0.6)",
labelColor: "rgba(173, 216, 230, 1)",
shadowColor: "black",
@ -10116,7 +10497,7 @@ function curves(ctx, points = [], localOptions) {
}
}
async function gesture(inCanvas2, result, drawOptions) {
const localOptions = mergeDeep(options, drawOptions);
const localOptions = mergeDeep(options2, drawOptions);
if (!result || !inCanvas2)
return;
if (!(inCanvas2 instanceof HTMLCanvasElement))
@ -10146,7 +10527,7 @@ async function gesture(inCanvas2, result, drawOptions) {
}
async function face2(inCanvas2, result, drawOptions) {
var _a, _b, _c, _d;
const localOptions = mergeDeep(options, drawOptions);
const localOptions = mergeDeep(options2, drawOptions);
if (!result || !inCanvas2)
return;
if (!(inCanvas2 instanceof HTMLCanvasElement))
@ -10256,7 +10637,7 @@ async function face2(inCanvas2, result, drawOptions) {
}
async function body2(inCanvas2, result, drawOptions) {
var _a;
const localOptions = mergeDeep(options, drawOptions);
const localOptions = mergeDeep(options2, drawOptions);
if (!result || !inCanvas2)
return;
if (!(inCanvas2 instanceof HTMLCanvasElement))
@ -10388,7 +10769,7 @@ async function body2(inCanvas2, result, drawOptions) {
}
}
async function hand2(inCanvas2, result, drawOptions) {
const localOptions = mergeDeep(options, drawOptions);
const localOptions = mergeDeep(options2, drawOptions);
if (!result || !inCanvas2)
return;
if (!(inCanvas2 instanceof HTMLCanvasElement))
@ -10427,12 +10808,12 @@ async function hand2(inCanvas2, result, drawOptions) {
ctx.fillText(title, part[part.length - 1][0] + 4, part[part.length - 1][1] + 4);
};
ctx.font = localOptions.font;
addHandLabel(h.annotations["indexFinger"], "index");
addHandLabel(h.annotations["middleFinger"], "middle");
addHandLabel(h.annotations["ringFinger"], "ring");
addHandLabel(h.annotations["index"], "index");
addHandLabel(h.annotations["middle"], "middle");
addHandLabel(h.annotations["ring"], "ring");
addHandLabel(h.annotations["pinky"], "pinky");
addHandLabel(h.annotations["thumb"], "thumb");
addHandLabel(h.annotations["palmBase"], "palm");
addHandLabel(h.annotations["palm"], "palm");
}
if (localOptions.drawPolygons) {
const addHandLine = (part) => {
@ -10447,16 +10828,16 @@ async function hand2(inCanvas2, result, drawOptions) {
}
};
ctx.lineWidth = localOptions.lineWidth;
addHandLine(h.annotations["indexFinger"]);
addHandLine(h.annotations["middleFinger"]);
addHandLine(h.annotations["ringFinger"]);
addHandLine(h.annotations["index"]);
addHandLine(h.annotations["middle"]);
addHandLine(h.annotations["ring"]);
addHandLine(h.annotations["pinky"]);
addHandLine(h.annotations["thumb"]);
}
}
}
async function object(inCanvas2, result, drawOptions) {
const localOptions = mergeDeep(options, drawOptions);
const localOptions = mergeDeep(options2, drawOptions);
if (!result || !inCanvas2)
return;
if (!(inCanvas2 instanceof HTMLCanvasElement))
@ -10485,7 +10866,7 @@ async function object(inCanvas2, result, drawOptions) {
}
}
async function person(inCanvas2, result, drawOptions) {
const localOptions = mergeDeep(options, drawOptions);
const localOptions = mergeDeep(options2, drawOptions);
if (!result || !inCanvas2)
return;
if (!(inCanvas2 instanceof HTMLCanvasElement))
@ -10523,7 +10904,7 @@ async function canvas(inCanvas2, outCanvas2) {
}
async function all(inCanvas2, result, drawOptions) {
const timestamp = now();
const localOptions = mergeDeep(options, drawOptions);
const localOptions = mergeDeep(options2, drawOptions);
if (!result || !inCanvas2)
return null;
if (!(inCanvas2 instanceof HTMLCanvasElement))

439
dist/human.node.js vendored
View File

@ -4595,14 +4595,14 @@ function scoreIsMaximumInLocalWindow(keypointId, score3, heatmapY, heatmapX, sco
}
return localMaximum;
}
function buildPartWithScoreQueue(minConfidence, scores) {
function buildPartWithScoreQueue(minConfidence2, scores) {
const [height, width, numKeypoints] = scores.shape;
const queue = new MaxHeap(height * width * numKeypoints, ({ score: score3 }) => score3);
for (let heatmapY = 0; heatmapY < height; ++heatmapY) {
for (let heatmapX = 0; heatmapX < width; ++heatmapX) {
for (let keypointId = 0; keypointId < numKeypoints; ++keypointId) {
const score3 = scores.get(heatmapY, heatmapX, keypointId);
if (score3 < minConfidence)
if (score3 < minConfidence2)
continue;
if (scoreIsMaximumInLocalWindow(keypointId, score3, heatmapY, heatmapX, scores))
queue.enqueue({ score: score3, part: { heatmapY, heatmapX, id: keypointId } });
@ -4628,19 +4628,19 @@ function getInstanceScore(existingPoses, keypoints3) {
}, 0);
return notOverlappedKeypointScores / keypoints3.length;
}
function decode(offsets, scores, displacementsFwd, displacementsBwd, maxDetected, minConfidence) {
function decode(offsets, scores, displacementsFwd, displacementsBwd, maxDetected, minConfidence2) {
const poses2 = [];
const queue = buildPartWithScoreQueue(minConfidence, scores);
const queue = buildPartWithScoreQueue(minConfidence2, scores);
while (poses2.length < maxDetected && !queue.empty()) {
const root = queue.dequeue();
const rootImageCoords = getImageCoords(root.part, outputStride, offsets);
if (withinRadius(poses2, rootImageCoords, root.part.id))
continue;
let keypoints3 = decodePose(root, scores, offsets, displacementsFwd, displacementsBwd);
keypoints3 = keypoints3.filter((a) => a.score > minConfidence);
keypoints3 = keypoints3.filter((a) => a.score > minConfidence2);
const score3 = getInstanceScore(poses2, keypoints3);
const box6 = getBoundingBox(keypoints3);
if (score3 > minConfidence)
if (score3 > minConfidence2)
poses2.push({ keypoints: keypoints3, box: box6, score: Math.round(100 * score3) / 100 });
}
return poses2;
@ -7959,14 +7959,383 @@ var HandPipeline = class {
}
};
// src/fingerpose/description.ts
var Finger = {
thumb: 0,
index: 1,
middle: 2,
ring: 3,
pinky: 4,
all: [0, 1, 2, 3, 4],
nameMapping: { 0: "thumb", 1: "index", 2: "middle", 3: "ring", 4: "pinky" },
pointsMapping: {
0: [[0, 1], [1, 2], [2, 3], [3, 4]],
1: [[0, 5], [5, 6], [6, 7], [7, 8]],
2: [[0, 9], [9, 10], [10, 11], [11, 12]],
3: [[0, 13], [13, 14], [14, 15], [15, 16]],
4: [[0, 17], [17, 18], [18, 19], [19, 20]]
},
getName: (value) => Finger.nameMapping[value],
getPoints: (value) => Finger.pointsMapping[value]
};
var FingerCurl = {
none: 0,
half: 1,
full: 2,
nameMapping: { 0: "none", 1: "half", 2: "full" },
getName: (value) => FingerCurl.nameMapping[value]
};
var FingerDirection = {
verticalUp: 0,
verticalDown: 1,
horizontalLeft: 2,
horizontalRight: 3,
diagonalUpRight: 4,
diagonalUpLeft: 5,
diagonalDownRight: 6,
diagonalDownLeft: 7,
nameMapping: { 0: "verticalUp", 1: "verticalDown", 2: "horizontalLeft", 3: "horizontalRight", 4: "diagonalUpRight", 5: "diagonalUpLeft", 6: "diagonalDownRight", 7: "diagonalDownLeft" },
getName: (value) => FingerDirection.nameMapping[value]
};
// src/fingerpose/estimator.ts
var options = {
HALF_CURL_START_LIMIT: 60,
NO_CURL_START_LIMIT: 130,
DISTANCE_VOTE_POWER: 1.1,
SINGLE_ANGLE_VOTE_POWER: 0.9,
TOTAL_ANGLE_VOTE_POWER: 1.6
};
function calculateSlope(point1x, point1y, point2x, point2y) {
const value = (point1y - point2y) / (point1x - point2x);
let slope = Math.atan(value) * 180 / Math.PI;
if (slope <= 0)
slope = -slope;
else if (slope > 0)
slope = 180 - slope;
return slope;
}
function getSlopes(point1, point2) {
const slopeXY = calculateSlope(point1[0], point1[1], point2[0], point2[1]);
if (point1.length === 2)
return slopeXY;
const slopeYZ = calculateSlope(point1[1], point1[2], point2[1], point2[2]);
return [slopeXY, slopeYZ];
}
function angleOrientationAt(angle, weightageAt = 1) {
let isVertical = 0;
let isDiagonal = 0;
let isHorizontal = 0;
if (angle >= 75 && angle <= 105)
isVertical = 1 * weightageAt;
else if (angle >= 25 && angle <= 155)
isDiagonal = 1 * weightageAt;
else
isHorizontal = 1 * weightageAt;
return [isVertical, isDiagonal, isHorizontal];
}
function estimateFingerCurl(startPoint, midPoint, endPoint) {
const start_mid_x_dist = startPoint[0] - midPoint[0];
const start_end_x_dist = startPoint[0] - endPoint[0];
const mid_end_x_dist = midPoint[0] - endPoint[0];
const start_mid_y_dist = startPoint[1] - midPoint[1];
const start_end_y_dist = startPoint[1] - endPoint[1];
const mid_end_y_dist = midPoint[1] - endPoint[1];
const start_mid_z_dist = startPoint[2] - midPoint[2];
const start_end_z_dist = startPoint[2] - endPoint[2];
const mid_end_z_dist = midPoint[2] - endPoint[2];
const start_mid_dist = Math.sqrt(start_mid_x_dist * start_mid_x_dist + start_mid_y_dist * start_mid_y_dist + start_mid_z_dist * start_mid_z_dist);
const start_end_dist = Math.sqrt(start_end_x_dist * start_end_x_dist + start_end_y_dist * start_end_y_dist + start_end_z_dist * start_end_z_dist);
const mid_end_dist = Math.sqrt(mid_end_x_dist * mid_end_x_dist + mid_end_y_dist * mid_end_y_dist + mid_end_z_dist * mid_end_z_dist);
let cos_in = (mid_end_dist * mid_end_dist + start_mid_dist * start_mid_dist - start_end_dist * start_end_dist) / (2 * mid_end_dist * start_mid_dist);
if (cos_in > 1)
cos_in = 1;
else if (cos_in < -1)
cos_in = -1;
let angleOfCurve = Math.acos(cos_in);
angleOfCurve = 57.2958 * angleOfCurve % 180;
let fingerCurl;
if (angleOfCurve > options.NO_CURL_START_LIMIT)
fingerCurl = FingerCurl.none;
else if (angleOfCurve > options.HALF_CURL_START_LIMIT)
fingerCurl = FingerCurl.half;
else
fingerCurl = FingerCurl.full;
return fingerCurl;
}
function estimateHorizontalDirection(start_end_x_dist, start_mid_x_dist, mid_end_x_dist, max_dist_x) {
let estimatedDirection;
if (max_dist_x === Math.abs(start_end_x_dist)) {
if (start_end_x_dist > 0)
estimatedDirection = FingerDirection.horizontalLeft;
else
estimatedDirection = FingerDirection.horizontalRight;
} else if (max_dist_x === Math.abs(start_mid_x_dist)) {
if (start_mid_x_dist > 0)
estimatedDirection = FingerDirection.horizontalLeft;
else
estimatedDirection = FingerDirection.horizontalRight;
} else {
if (mid_end_x_dist > 0)
estimatedDirection = FingerDirection.horizontalLeft;
else
estimatedDirection = FingerDirection.horizontalRight;
}
return estimatedDirection;
}
function estimateVerticalDirection(start_end_y_dist, start_mid_y_dist, mid_end_y_dist, max_dist_y) {
let estimatedDirection;
if (max_dist_y === Math.abs(start_end_y_dist)) {
if (start_end_y_dist < 0)
estimatedDirection = FingerDirection.verticalDown;
else
estimatedDirection = FingerDirection.verticalUp;
} else if (max_dist_y === Math.abs(start_mid_y_dist)) {
if (start_mid_y_dist < 0)
estimatedDirection = FingerDirection.verticalDown;
else
estimatedDirection = FingerDirection.verticalUp;
} else {
if (mid_end_y_dist < 0)
estimatedDirection = FingerDirection.verticalDown;
else
estimatedDirection = FingerDirection.verticalUp;
}
return estimatedDirection;
}
function estimateDiagonalDirection(start_end_y_dist, start_mid_y_dist, mid_end_y_dist, max_dist_y, start_end_x_dist, start_mid_x_dist, mid_end_x_dist, max_dist_x) {
let estimatedDirection;
const reqd_vertical_direction = estimateVerticalDirection(start_end_y_dist, start_mid_y_dist, mid_end_y_dist, max_dist_y);
const reqd_horizontal_direction = estimateHorizontalDirection(start_end_x_dist, start_mid_x_dist, mid_end_x_dist, max_dist_x);
if (reqd_vertical_direction === FingerDirection.verticalUp) {
if (reqd_horizontal_direction === FingerDirection.horizontalLeft)
estimatedDirection = FingerDirection.diagonalUpLeft;
else
estimatedDirection = FingerDirection.diagonalUpRight;
} else {
if (reqd_horizontal_direction === FingerDirection.horizontalLeft)
estimatedDirection = FingerDirection.diagonalDownLeft;
else
estimatedDirection = FingerDirection.diagonalDownRight;
}
return estimatedDirection;
}
function calculateFingerDirection(startPoint, midPoint, endPoint, fingerSlopes) {
const start_mid_x_dist = startPoint[0] - midPoint[0];
const start_end_x_dist = startPoint[0] - endPoint[0];
const mid_end_x_dist = midPoint[0] - endPoint[0];
const start_mid_y_dist = startPoint[1] - midPoint[1];
const start_end_y_dist = startPoint[1] - endPoint[1];
const mid_end_y_dist = midPoint[1] - endPoint[1];
const max_dist_x = Math.max(Math.abs(start_mid_x_dist), Math.abs(start_end_x_dist), Math.abs(mid_end_x_dist));
const max_dist_y = Math.max(Math.abs(start_mid_y_dist), Math.abs(start_end_y_dist), Math.abs(mid_end_y_dist));
let voteVertical = 0;
let voteDiagonal = 0;
let voteHorizontal = 0;
const start_end_x_y_dist_ratio = max_dist_y / (max_dist_x + 1e-5);
if (start_end_x_y_dist_ratio > 1.5)
voteVertical += options.DISTANCE_VOTE_POWER;
else if (start_end_x_y_dist_ratio > 0.66)
voteDiagonal += options.DISTANCE_VOTE_POWER;
else
voteHorizontal += options.DISTANCE_VOTE_POWER;
const start_mid_dist = Math.sqrt(start_mid_x_dist * start_mid_x_dist + start_mid_y_dist * start_mid_y_dist);
const start_end_dist = Math.sqrt(start_end_x_dist * start_end_x_dist + start_end_y_dist * start_end_y_dist);
const mid_end_dist = Math.sqrt(mid_end_x_dist * mid_end_x_dist + mid_end_y_dist * mid_end_y_dist);
const max_dist = Math.max(start_mid_dist, start_end_dist, mid_end_dist);
let calc_start_point_x = startPoint[0];
let calc_start_point_y = startPoint[1];
let calc_end_point_x = endPoint[0];
let calc_end_point_y = endPoint[1];
if (max_dist === start_mid_dist) {
calc_end_point_x = endPoint[0];
calc_end_point_y = endPoint[1];
} else if (max_dist === mid_end_dist) {
calc_start_point_x = midPoint[0];
calc_start_point_y = midPoint[1];
}
const calcStartPoint = [calc_start_point_x, calc_start_point_y];
const calcEndPoint = [calc_end_point_x, calc_end_point_y];
const totalAngle = getSlopes(calcStartPoint, calcEndPoint);
const votes = angleOrientationAt(totalAngle, options.TOTAL_ANGLE_VOTE_POWER);
voteVertical += votes[0];
voteDiagonal += votes[1];
voteHorizontal += votes[2];
for (const fingerSlope of fingerSlopes) {
const fingerVotes = angleOrientationAt(fingerSlope, options.SINGLE_ANGLE_VOTE_POWER);
voteVertical += fingerVotes[0];
voteDiagonal += fingerVotes[1];
voteHorizontal += fingerVotes[2];
}
let estimatedDirection;
if (voteVertical === Math.max(voteVertical, voteDiagonal, voteHorizontal)) {
estimatedDirection = estimateVerticalDirection(start_end_y_dist, start_mid_y_dist, mid_end_y_dist, max_dist_y);
} else if (voteHorizontal === Math.max(voteDiagonal, voteHorizontal)) {
estimatedDirection = estimateHorizontalDirection(start_end_x_dist, start_mid_x_dist, mid_end_x_dist, max_dist_x);
} else {
estimatedDirection = estimateDiagonalDirection(start_end_y_dist, start_mid_y_dist, mid_end_y_dist, max_dist_y, start_end_x_dist, start_mid_x_dist, mid_end_x_dist, max_dist_x);
}
return estimatedDirection;
}
function estimate(landmarks) {
const slopesXY = [];
const slopesYZ = [];
for (const finger of Finger.all) {
const points = Finger.getPoints(finger);
const slopeAtXY = [];
const slopeAtYZ = [];
for (const point2 of points) {
const point1 = landmarks[point2[0]];
const point22 = landmarks[point2[1]];
const slopes = getSlopes(point1, point22);
const slopeXY = slopes[0];
const slopeYZ = slopes[1];
slopeAtXY.push(slopeXY);
slopeAtYZ.push(slopeYZ);
}
slopesXY.push(slopeAtXY);
slopesYZ.push(slopeAtYZ);
}
const fingerCurls = [];
const fingerDirections = [];
for (const finger of Finger.all) {
const pointIndexAt = finger === Finger.thumb ? 1 : 0;
const fingerPointsAt = Finger.getPoints(finger);
const startPoint = landmarks[fingerPointsAt[pointIndexAt][0]];
const midPoint = landmarks[fingerPointsAt[pointIndexAt + 1][1]];
const endPoint = landmarks[fingerPointsAt[3][1]];
const fingerCurled = estimateFingerCurl(startPoint, midPoint, endPoint);
const fingerPosition = calculateFingerDirection(startPoint, midPoint, endPoint, slopesXY[finger].slice(pointIndexAt));
fingerCurls[finger] = fingerCurled;
fingerDirections[finger] = fingerPosition;
}
return { curls: fingerCurls, directions: fingerDirections };
}
// src/fingerpose/gesture.ts
var Gesture = class {
constructor(name) {
this.name = name;
this.curls = {};
this.directions = {};
this.weights = [1, 1, 1, 1, 1];
this.weightsRelative = [1, 1, 1, 1, 1];
}
addCurl(finger, curl, confidence) {
if (typeof this.curls[finger] === "undefined")
this.curls[finger] = [];
this.curls[finger].push([curl, confidence]);
}
addDirection(finger, position, confidence) {
if (!this.directions[finger])
this.directions[finger] = [];
this.directions[finger].push([position, confidence]);
}
setWeight(finger, weight) {
this.weights[finger] = weight;
const total = this.weights.reduce((a, b) => a + b, 0);
this.weightsRelative = this.weights.map((el) => el * 5 / total);
}
matchAgainst(detectedCurls, detectedDirections) {
let confidence = 0;
for (const fingerIdx in detectedCurls) {
const detectedCurl = detectedCurls[fingerIdx];
const expectedCurls = this.curls[fingerIdx];
if (typeof expectedCurls === "undefined") {
confidence += this.weightsRelative[fingerIdx];
continue;
}
for (const [expectedCurl, score3] of expectedCurls) {
if (detectedCurl === expectedCurl) {
confidence += score3 * this.weightsRelative[fingerIdx];
break;
}
}
}
for (const fingerIdx in detectedDirections) {
const detectedDirection = detectedDirections[fingerIdx];
const expectedDirections = this.directions[fingerIdx];
if (typeof expectedDirections === "undefined") {
confidence += this.weightsRelative[fingerIdx];
continue;
}
for (const [expectedDirection, score3] of expectedDirections) {
if (detectedDirection === expectedDirection) {
confidence += score3 * this.weightsRelative[fingerIdx];
break;
}
}
}
return confidence / 10;
}
};
// src/fingerpose/gestures.ts
var ThumbsUp = new Gesture("thumbs up");
ThumbsUp.addCurl(Finger.thumb, FingerCurl.none, 1);
ThumbsUp.addDirection(Finger.thumb, FingerDirection.verticalUp, 1);
ThumbsUp.addDirection(Finger.thumb, FingerDirection.diagonalUpLeft, 0.25);
ThumbsUp.addDirection(Finger.thumb, FingerDirection.diagonalUpRight, 0.25);
for (const finger of [Finger.index, Finger.middle, Finger.ring, Finger.pinky]) {
ThumbsUp.addCurl(finger, FingerCurl.full, 1);
ThumbsUp.addDirection(finger, FingerDirection.horizontalLeft, 1);
ThumbsUp.addDirection(finger, FingerDirection.horizontalRight, 1);
}
var Victory = new Gesture("victory");
Victory.addCurl(Finger.thumb, FingerCurl.half, 0.5);
Victory.addCurl(Finger.thumb, FingerCurl.none, 0.5);
Victory.addDirection(Finger.thumb, FingerDirection.verticalUp, 1);
Victory.addDirection(Finger.thumb, FingerDirection.diagonalUpLeft, 1);
Victory.addCurl(Finger.index, FingerCurl.none, 1);
Victory.addDirection(Finger.index, FingerDirection.verticalUp, 0.75);
Victory.addDirection(Finger.index, FingerDirection.diagonalUpLeft, 1);
Victory.addCurl(Finger.middle, FingerCurl.none, 1);
Victory.addDirection(Finger.middle, FingerDirection.verticalUp, 1);
Victory.addDirection(Finger.middle, FingerDirection.diagonalUpLeft, 0.75);
Victory.addCurl(Finger.ring, FingerCurl.full, 1);
Victory.addDirection(Finger.ring, FingerDirection.verticalUp, 0.2);
Victory.addDirection(Finger.ring, FingerDirection.diagonalUpLeft, 1);
Victory.addDirection(Finger.ring, FingerDirection.horizontalLeft, 0.2);
Victory.addCurl(Finger.pinky, FingerCurl.full, 1);
Victory.addDirection(Finger.pinky, FingerDirection.verticalUp, 0.2);
Victory.addDirection(Finger.pinky, FingerDirection.diagonalUpLeft, 1);
Victory.addDirection(Finger.pinky, FingerDirection.horizontalLeft, 0.2);
Victory.setWeight(Finger.index, 2);
Victory.setWeight(Finger.middle, 2);
var gestures_default = [ThumbsUp, Victory];
// src/fingerpose/fingerpose.ts
var minConfidence = 0.7;
function analyze(keypoints3) {
const estimatorRes = estimate(keypoints3);
const landmarks = {};
for (const fingerIdx of Finger.all) {
landmarks[Finger.getName(fingerIdx)] = {
curl: FingerCurl.getName(estimatorRes.curls[fingerIdx]),
direction: FingerDirection.getName(estimatorRes.directions[fingerIdx])
};
}
return landmarks;
}
function match2(keypoints3) {
const estimatorRes = estimate(keypoints3);
const poses2 = [];
for (const gesture3 of gestures_default) {
const confidence = gesture3.matchAgainst(estimatorRes.curls, estimatorRes.directions);
if (confidence >= minConfidence)
poses2.push({ name: gesture3.name, confidence });
}
return poses2;
}
// src/handpose/handpose.ts
var meshAnnotations = {
thumb: [1, 2, 3, 4],
indexFinger: [5, 6, 7, 8],
middleFinger: [9, 10, 11, 12],
ringFinger: [13, 14, 15, 16],
index: [5, 6, 7, 8],
middle: [9, 10, 11, 12],
ring: [13, 14, 15, 16],
pinky: [17, 18, 19, 20],
palmBase: [0]
palm: [0]
};
var handDetectorModel;
var handPoseModel;
@ -8014,7 +8383,16 @@ async function predict5(input, config3) {
(predictions[i].box.bottomRight[1] - predictions[i].box.topLeft[1]) / (input.shape[1] || 0)
];
}
hands.push({ id: i, score: Math.round(100 * predictions[i].confidence) / 100, box: box6, boxRaw: boxRaw3, keypoints: keypoints3, annotations: annotations3 });
const landmarks = analyze(keypoints3);
hands.push({
id: i,
score: Math.round(100 * predictions[i].confidence) / 100,
box: box6,
boxRaw: boxRaw3,
keypoints: keypoints3,
annotations: annotations3,
landmarks
});
}
return hands;
}
@ -8706,9 +9084,9 @@ var tf18 = __toModule(require_tfjs_esm());
function GLProgram(gl, vertexSource, fragmentSource) {
const _collect = function(source, prefix, collection) {
const r = new RegExp("\\b" + prefix + " \\w+ (\\w+)", "ig");
source.replace(r, (match2, name) => {
source.replace(r, (match3, name) => {
collection[name] = 0;
return match2;
return match3;
});
};
const _compile = function(source, type) {
@ -10013,6 +10391,9 @@ var hand = (res) => {
const highest = fingers.reduce((best, a) => best.position[1] < a.position[1] ? best : a);
gestures.push({ hand: i, gesture: `${highest.name} up` });
}
const poses2 = match2(res[i]["keypoints"]);
for (const pose of poses2)
gestures.push({ hand: i, gesture: pose.name });
}
return gestures;
};
@ -10027,10 +10408,10 @@ __export(draw_exports, {
gesture: () => gesture,
hand: () => hand2,
object: () => object,
options: () => options,
options: () => options2,
person: () => person
});
var options = {
var options2 = {
color: "rgba(173, 216, 230, 0.6)",
labelColor: "rgba(173, 216, 230, 1)",
shadowColor: "black",
@ -10115,7 +10496,7 @@ function curves(ctx, points = [], localOptions) {
}
}
async function gesture(inCanvas2, result, drawOptions) {
const localOptions = mergeDeep(options, drawOptions);
const localOptions = mergeDeep(options2, drawOptions);
if (!result || !inCanvas2)
return;
if (!(inCanvas2 instanceof HTMLCanvasElement))
@ -10145,7 +10526,7 @@ async function gesture(inCanvas2, result, drawOptions) {
}
async function face2(inCanvas2, result, drawOptions) {
var _a, _b, _c, _d;
const localOptions = mergeDeep(options, drawOptions);
const localOptions = mergeDeep(options2, drawOptions);
if (!result || !inCanvas2)
return;
if (!(inCanvas2 instanceof HTMLCanvasElement))
@ -10255,7 +10636,7 @@ async function face2(inCanvas2, result, drawOptions) {
}
async function body2(inCanvas2, result, drawOptions) {
var _a;
const localOptions = mergeDeep(options, drawOptions);
const localOptions = mergeDeep(options2, drawOptions);
if (!result || !inCanvas2)
return;
if (!(inCanvas2 instanceof HTMLCanvasElement))
@ -10387,7 +10768,7 @@ async function body2(inCanvas2, result, drawOptions) {
}
}
async function hand2(inCanvas2, result, drawOptions) {
const localOptions = mergeDeep(options, drawOptions);
const localOptions = mergeDeep(options2, drawOptions);
if (!result || !inCanvas2)
return;
if (!(inCanvas2 instanceof HTMLCanvasElement))
@ -10426,12 +10807,12 @@ async function hand2(inCanvas2, result, drawOptions) {
ctx.fillText(title, part[part.length - 1][0] + 4, part[part.length - 1][1] + 4);
};
ctx.font = localOptions.font;
addHandLabel(h.annotations["indexFinger"], "index");
addHandLabel(h.annotations["middleFinger"], "middle");
addHandLabel(h.annotations["ringFinger"], "ring");
addHandLabel(h.annotations["index"], "index");
addHandLabel(h.annotations["middle"], "middle");
addHandLabel(h.annotations["ring"], "ring");
addHandLabel(h.annotations["pinky"], "pinky");
addHandLabel(h.annotations["thumb"], "thumb");
addHandLabel(h.annotations["palmBase"], "palm");
addHandLabel(h.annotations["palm"], "palm");
}
if (localOptions.drawPolygons) {
const addHandLine = (part) => {
@ -10446,16 +10827,16 @@ async function hand2(inCanvas2, result, drawOptions) {
}
};
ctx.lineWidth = localOptions.lineWidth;
addHandLine(h.annotations["indexFinger"]);
addHandLine(h.annotations["middleFinger"]);
addHandLine(h.annotations["ringFinger"]);
addHandLine(h.annotations["index"]);
addHandLine(h.annotations["middle"]);
addHandLine(h.annotations["ring"]);
addHandLine(h.annotations["pinky"]);
addHandLine(h.annotations["thumb"]);
}
}
}
async function object(inCanvas2, result, drawOptions) {
const localOptions = mergeDeep(options, drawOptions);
const localOptions = mergeDeep(options2, drawOptions);
if (!result || !inCanvas2)
return;
if (!(inCanvas2 instanceof HTMLCanvasElement))
@ -10484,7 +10865,7 @@ async function object(inCanvas2, result, drawOptions) {
}
}
async function person(inCanvas2, result, drawOptions) {
const localOptions = mergeDeep(options, drawOptions);
const localOptions = mergeDeep(options2, drawOptions);
if (!result || !inCanvas2)
return;
if (!(inCanvas2 instanceof HTMLCanvasElement))
@ -10522,7 +10903,7 @@ async function canvas(inCanvas2, outCanvas2) {
}
async function all(inCanvas2, result, drawOptions) {
const timestamp = now();
const localOptions = mergeDeep(options, drawOptions);
const localOptions = mergeDeep(options2, drawOptions);
if (!result || !inCanvas2)
return null;
if (!(inCanvas2 instanceof HTMLCanvasElement))

View File

@ -1,22 +1,22 @@
2021-08-20 09:03:11 INFO:  @vladmandic/human version 2.1.4
2021-08-20 09:03:11 INFO:  User: vlado Platform: linux Arch: x64 Node: v16.5.0
2021-08-20 09:03:11 INFO:  Toolchain: {"tfjs":"3.8.0","esbuild":"0.12.21","typescript":"4.3.5","typedoc":"0.21.6","eslint":"7.32.0"}
2021-08-20 09:03:11 INFO:  Clean: ["dist/*","types/*","typedoc/*"]
2021-08-20 09:03:11 INFO:  Build: file startup all type: production config: {"minifyWhitespace":true,"minifyIdentifiers":true,"minifySyntax":true}
2021-08-20 09:03:12 STATE: target: node type: tfjs: {"imports":1,"importBytes":102,"outputBytes":1303,"outputFiles":"dist/tfjs.esm.js"}
2021-08-20 09:03:12 STATE: target: node type: node: {"imports":42,"importBytes":438886,"outputBytes":381429,"outputFiles":"dist/human.node.js"}
2021-08-20 09:03:12 STATE: target: nodeGPU type: tfjs: {"imports":1,"importBytes":110,"outputBytes":1311,"outputFiles":"dist/tfjs.esm.js"}
2021-08-20 09:03:12 STATE: target: nodeGPU type: node: {"imports":42,"importBytes":438894,"outputBytes":381433,"outputFiles":"dist/human.node-gpu.js"}
2021-08-20 09:03:12 STATE: target: nodeWASM type: tfjs: {"imports":1,"importBytes":149,"outputBytes":1378,"outputFiles":"dist/tfjs.esm.js"}
2021-08-20 09:03:12 STATE: target: nodeWASM type: node: {"imports":42,"importBytes":438961,"outputBytes":381505,"outputFiles":"dist/human.node-wasm.js"}
2021-08-20 09:03:12 STATE: target: browserNoBundle type: tfjs: {"imports":1,"importBytes":2168,"outputBytes":1242,"outputFiles":"dist/tfjs.esm.js"}
2021-08-20 09:03:12 STATE: target: browserNoBundle type: esm: {"imports":42,"importBytes":438825,"outputBytes":249942,"outputFiles":"dist/human.esm-nobundle.js"}
2021-08-20 09:03:12 STATE: target: browserBundle type: tfjs: {"modules":1170,"moduleBytes":4145868,"imports":7,"importBytes":2168,"outputBytes":2334701,"outputFiles":"dist/tfjs.esm.js"}
2021-08-20 09:03:13 STATE: target: browserBundle type: iife: {"imports":42,"importBytes":2772284,"outputBytes":1380316,"outputFiles":"dist/human.js"}
2021-08-20 09:03:13 STATE: target: browserBundle type: esm: {"imports":42,"importBytes":2772284,"outputBytes":1380308,"outputFiles":"dist/human.esm.js"}
2021-08-20 09:03:13 INFO:  Running Linter: ["server/","src/","tfjs/","test/","demo/"]
2021-08-20 09:03:37 INFO:  Linter complete: files: 77 errors: 0 warnings: 0
2021-08-20 09:03:37 INFO:  Generate ChangeLog: ["/home/vlado/dev/human/CHANGELOG.md"]
2021-08-20 09:03:37 INFO:  Generate Typings: ["src/human.ts"] outDir: ["types"]
2021-08-20 09:03:51 INFO:  Generate TypeDocs: ["src/human.ts"] outDir: ["typedoc"]
2021-08-20 09:04:05 INFO:  Documentation generated at /home/vlado/dev/human/typedoc 1
2021-08-20 20:41:20 INFO:  @vladmandic/human version 2.1.4
2021-08-20 20:41:20 INFO:  User: vlado Platform: linux Arch: x64 Node: v16.5.0
2021-08-20 20:41:20 INFO:  Toolchain: {"tfjs":"3.8.0","esbuild":"0.12.21","typescript":"4.3.5","typedoc":"0.21.6","eslint":"7.32.0"}
2021-08-20 20:41:20 INFO:  Clean: ["dist/*","types/*","typedoc/*"]
2021-08-20 20:41:20 INFO:  Build: file startup all type: production config: {"minifyWhitespace":true,"minifyIdentifiers":true,"minifySyntax":true}
2021-08-20 20:41:20 STATE: target: node type: tfjs: {"imports":1,"importBytes":102,"outputBytes":1303,"outputFiles":"dist/tfjs.esm.js"}
2021-08-20 20:41:20 STATE: target: node type: node: {"imports":47,"importBytes":456494,"outputBytes":396866,"outputFiles":"dist/human.node.js"}
2021-08-20 20:41:20 STATE: target: nodeGPU type: tfjs: {"imports":1,"importBytes":110,"outputBytes":1311,"outputFiles":"dist/tfjs.esm.js"}
2021-08-20 20:41:20 STATE: target: nodeGPU type: node: {"imports":47,"importBytes":456502,"outputBytes":396870,"outputFiles":"dist/human.node-gpu.js"}
2021-08-20 20:41:20 STATE: target: nodeWASM type: tfjs: {"imports":1,"importBytes":149,"outputBytes":1378,"outputFiles":"dist/tfjs.esm.js"}
2021-08-20 20:41:20 STATE: target: nodeWASM type: node: {"imports":47,"importBytes":456569,"outputBytes":396942,"outputFiles":"dist/human.node-wasm.js"}
2021-08-20 20:41:20 STATE: target: browserNoBundle type: tfjs: {"imports":1,"importBytes":2168,"outputBytes":1242,"outputFiles":"dist/tfjs.esm.js"}
2021-08-20 20:41:20 STATE: target: browserNoBundle type: esm: {"imports":47,"importBytes":456433,"outputBytes":255552,"outputFiles":"dist/human.esm-nobundle.js"}
2021-08-20 20:41:21 STATE: target: browserBundle type: tfjs: {"modules":1170,"moduleBytes":4145868,"imports":7,"importBytes":2168,"outputBytes":2334701,"outputFiles":"dist/tfjs.esm.js"}
2021-08-20 20:41:21 STATE: target: browserBundle type: iife: {"imports":47,"importBytes":2789892,"outputBytes":1386025,"outputFiles":"dist/human.js"}
2021-08-20 20:41:21 STATE: target: browserBundle type: esm: {"imports":47,"importBytes":2789892,"outputBytes":1386017,"outputFiles":"dist/human.esm.js"}
2021-08-20 20:41:21 INFO:  Running Linter: ["server/","src/","tfjs/","test/","demo/"]
2021-08-20 20:41:44 INFO:  Linter complete: files: 82 errors: 0 warnings: 0
2021-08-20 20:41:44 INFO:  Generate ChangeLog: ["/home/vlado/dev/human/CHANGELOG.md"]
2021-08-20 20:41:44 INFO:  Generate Typings: ["src/human.ts"] outDir: ["types"]
2021-08-20 20:41:58 INFO:  Generate TypeDocs: ["src/human.ts"] outDir: ["typedoc"]
2021-08-20 20:42:12 INFO:  Documentation generated at /home/vlado/dev/human/typedoc 1

View File

@ -414,12 +414,12 @@ export async function hand(inCanvas: HTMLCanvasElement, result: Array<Hand>, dra
ctx.fillText(title, part[part.length - 1][0] + 4, part[part.length - 1][1] + 4);
};
ctx.font = localOptions.font;
addHandLabel(h.annotations['indexFinger'], 'index');
addHandLabel(h.annotations['middleFinger'], 'middle');
addHandLabel(h.annotations['ringFinger'], 'ring');
addHandLabel(h.annotations['index'], 'index');
addHandLabel(h.annotations['middle'], 'middle');
addHandLabel(h.annotations['ring'], 'ring');
addHandLabel(h.annotations['pinky'], 'pinky');
addHandLabel(h.annotations['thumb'], 'thumb');
addHandLabel(h.annotations['palmBase'], 'palm');
addHandLabel(h.annotations['palm'], 'palm');
}
if (localOptions.drawPolygons) {
const addHandLine = (part) => {
@ -433,12 +433,12 @@ export async function hand(inCanvas: HTMLCanvasElement, result: Array<Hand>, dra
}
};
ctx.lineWidth = localOptions.lineWidth;
addHandLine(h.annotations['indexFinger']);
addHandLine(h.annotations['middleFinger']);
addHandLine(h.annotations['ringFinger']);
addHandLine(h.annotations['index']);
addHandLine(h.annotations['middle']);
addHandLine(h.annotations['ring']);
addHandLine(h.annotations['pinky']);
addHandLine(h.annotations['thumb']);
// addPart(h.annotations.palmBase);
// addPart(h.annotations.palm);
}
}
}

View File

@ -0,0 +1,48 @@
const Finger = {
thumb: 0,
index: 1,
middle: 2,
ring: 3,
pinky: 4,
all: [0, 1, 2, 3, 4], // just for convenience
nameMapping: { 0: 'thumb', 1: 'index', 2: 'middle', 3: 'ring', 4: 'pinky' },
// Describes mapping of joints based on the 21 points returned by handpose.
// [0] Palm
// [1-4] Thumb
// [5-8] Index
// [9-12] Middle
// [13-16] Ring
// [17-20] Pinky
pointsMapping: {
0: [[0, 1], [1, 2], [2, 3], [3, 4]],
1: [[0, 5], [5, 6], [6, 7], [7, 8]],
2: [[0, 9], [9, 10], [10, 11], [11, 12]],
3: [[0, 13], [13, 14], [14, 15], [15, 16]],
4: [[0, 17], [17, 18], [18, 19], [19, 20]],
},
getName: (value) => Finger.nameMapping[value],
getPoints: (value) => Finger.pointsMapping[value],
};
const FingerCurl = {
none: 0,
half: 1,
full: 2,
nameMapping: { 0: 'none', 1: 'half', 2: 'full' },
getName: (value) => FingerCurl.nameMapping[value],
};
const FingerDirection = {
verticalUp: 0,
verticalDown: 1,
horizontalLeft: 2,
horizontalRight: 3,
diagonalUpRight: 4,
diagonalUpLeft: 5,
diagonalDownRight: 6,
diagonalDownLeft: 7,
nameMapping: { 0: 'verticalUp', 1: 'verticalDown', 2: 'horizontalLeft', 3: 'horizontalRight', 4: 'diagonalUpRight', 5: 'diagonalUpLeft', 6: 'diagonalDownRight', 7: 'diagonalDownLeft' },
getName: (value) => FingerDirection.nameMapping[value],
};
export { Finger, FingerCurl, FingerDirection };

205
src/fingerpose/estimator.ts Normal file
View File

@ -0,0 +1,205 @@
import { Finger, FingerCurl, FingerDirection } from './description';
const options = {
// curl estimation
HALF_CURL_START_LIMIT: 60.0,
NO_CURL_START_LIMIT: 130.0,
// direction estimation
DISTANCE_VOTE_POWER: 1.1,
SINGLE_ANGLE_VOTE_POWER: 0.9,
TOTAL_ANGLE_VOTE_POWER: 1.6,
};
function calculateSlope(point1x, point1y, point2x, point2y) {
const value = (point1y - point2y) / (point1x - point2x);
let slope = Math.atan(value) * 180 / Math.PI;
if (slope <= 0) slope = -slope;
else if (slope > 0) slope = 180 - slope;
return slope;
}
// point1, point2 are 2d or 3d point arrays (xy[z])
// returns either a single scalar (2d) or array of two slopes (3d)
function getSlopes(point1, point2) {
const slopeXY = calculateSlope(point1[0], point1[1], point2[0], point2[1]);
if (point1.length === 2) return slopeXY;
const slopeYZ = calculateSlope(point1[1], point1[2], point2[1], point2[2]);
return [slopeXY, slopeYZ];
}
function angleOrientationAt(angle, weightageAt = 1.0) {
let isVertical = 0;
let isDiagonal = 0;
let isHorizontal = 0;
if (angle >= 75.0 && angle <= 105.0) isVertical = 1 * weightageAt;
else if (angle >= 25.0 && angle <= 155.0) isDiagonal = 1 * weightageAt;
else isHorizontal = 1 * weightageAt;
return [isVertical, isDiagonal, isHorizontal];
}
function estimateFingerCurl(startPoint, midPoint, endPoint) {
const start_mid_x_dist = startPoint[0] - midPoint[0];
const start_end_x_dist = startPoint[0] - endPoint[0];
const mid_end_x_dist = midPoint[0] - endPoint[0];
const start_mid_y_dist = startPoint[1] - midPoint[1];
const start_end_y_dist = startPoint[1] - endPoint[1];
const mid_end_y_dist = midPoint[1] - endPoint[1];
const start_mid_z_dist = startPoint[2] - midPoint[2];
const start_end_z_dist = startPoint[2] - endPoint[2];
const mid_end_z_dist = midPoint[2] - endPoint[2];
const start_mid_dist = Math.sqrt(start_mid_x_dist * start_mid_x_dist + start_mid_y_dist * start_mid_y_dist + start_mid_z_dist * start_mid_z_dist);
const start_end_dist = Math.sqrt(start_end_x_dist * start_end_x_dist + start_end_y_dist * start_end_y_dist + start_end_z_dist * start_end_z_dist);
const mid_end_dist = Math.sqrt(mid_end_x_dist * mid_end_x_dist + mid_end_y_dist * mid_end_y_dist + mid_end_z_dist * mid_end_z_dist);
let cos_in = (mid_end_dist * mid_end_dist + start_mid_dist * start_mid_dist - start_end_dist * start_end_dist) / (2 * mid_end_dist * start_mid_dist);
if (cos_in > 1.0) cos_in = 1.0;
else if (cos_in < -1.0) cos_in = -1.0;
let angleOfCurve = Math.acos(cos_in);
angleOfCurve = (57.2958 * angleOfCurve) % 180;
let fingerCurl;
if (angleOfCurve > options.NO_CURL_START_LIMIT) fingerCurl = FingerCurl.none;
else if (angleOfCurve > options.HALF_CURL_START_LIMIT) fingerCurl = FingerCurl.half;
else fingerCurl = FingerCurl.full;
return fingerCurl;
}
function estimateHorizontalDirection(start_end_x_dist, start_mid_x_dist, mid_end_x_dist, max_dist_x) {
let estimatedDirection;
if (max_dist_x === Math.abs(start_end_x_dist)) {
if (start_end_x_dist > 0) estimatedDirection = FingerDirection.horizontalLeft;
else estimatedDirection = FingerDirection.horizontalRight;
} else if (max_dist_x === Math.abs(start_mid_x_dist)) {
if (start_mid_x_dist > 0) estimatedDirection = FingerDirection.horizontalLeft;
else estimatedDirection = FingerDirection.horizontalRight;
} else {
if (mid_end_x_dist > 0) estimatedDirection = FingerDirection.horizontalLeft;
else estimatedDirection = FingerDirection.horizontalRight;
}
return estimatedDirection;
}
function estimateVerticalDirection(start_end_y_dist, start_mid_y_dist, mid_end_y_dist, max_dist_y) {
let estimatedDirection;
if (max_dist_y === Math.abs(start_end_y_dist)) {
if (start_end_y_dist < 0) estimatedDirection = FingerDirection.verticalDown;
else estimatedDirection = FingerDirection.verticalUp;
} else if (max_dist_y === Math.abs(start_mid_y_dist)) {
if (start_mid_y_dist < 0) estimatedDirection = FingerDirection.verticalDown;
else estimatedDirection = FingerDirection.verticalUp;
} else {
if (mid_end_y_dist < 0) estimatedDirection = FingerDirection.verticalDown;
else estimatedDirection = FingerDirection.verticalUp;
}
return estimatedDirection;
}
function estimateDiagonalDirection(start_end_y_dist, start_mid_y_dist, mid_end_y_dist, max_dist_y, start_end_x_dist, start_mid_x_dist, mid_end_x_dist, max_dist_x) {
let estimatedDirection;
const reqd_vertical_direction = estimateVerticalDirection(start_end_y_dist, start_mid_y_dist, mid_end_y_dist, max_dist_y);
const reqd_horizontal_direction = estimateHorizontalDirection(start_end_x_dist, start_mid_x_dist, mid_end_x_dist, max_dist_x);
if (reqd_vertical_direction === FingerDirection.verticalUp) {
if (reqd_horizontal_direction === FingerDirection.horizontalLeft) estimatedDirection = FingerDirection.diagonalUpLeft;
else estimatedDirection = FingerDirection.diagonalUpRight;
} else {
if (reqd_horizontal_direction === FingerDirection.horizontalLeft) estimatedDirection = FingerDirection.diagonalDownLeft;
else estimatedDirection = FingerDirection.diagonalDownRight;
}
return estimatedDirection;
}
function calculateFingerDirection(startPoint, midPoint, endPoint, fingerSlopes) {
const start_mid_x_dist = startPoint[0] - midPoint[0];
const start_end_x_dist = startPoint[0] - endPoint[0];
const mid_end_x_dist = midPoint[0] - endPoint[0];
const start_mid_y_dist = startPoint[1] - midPoint[1];
const start_end_y_dist = startPoint[1] - endPoint[1];
const mid_end_y_dist = midPoint[1] - endPoint[1];
const max_dist_x = Math.max(Math.abs(start_mid_x_dist), Math.abs(start_end_x_dist), Math.abs(mid_end_x_dist));
const max_dist_y = Math.max(Math.abs(start_mid_y_dist), Math.abs(start_end_y_dist), Math.abs(mid_end_y_dist));
let voteVertical = 0.0;
let voteDiagonal = 0.0;
let voteHorizontal = 0.0;
const start_end_x_y_dist_ratio = max_dist_y / (max_dist_x + 0.00001);
if (start_end_x_y_dist_ratio > 1.5) voteVertical += options.DISTANCE_VOTE_POWER;
else if (start_end_x_y_dist_ratio > 0.66) voteDiagonal += options.DISTANCE_VOTE_POWER;
else voteHorizontal += options.DISTANCE_VOTE_POWER;
const start_mid_dist = Math.sqrt(start_mid_x_dist * start_mid_x_dist + start_mid_y_dist * start_mid_y_dist);
const start_end_dist = Math.sqrt(start_end_x_dist * start_end_x_dist + start_end_y_dist * start_end_y_dist);
const mid_end_dist = Math.sqrt(mid_end_x_dist * mid_end_x_dist + mid_end_y_dist * mid_end_y_dist);
const max_dist = Math.max(start_mid_dist, start_end_dist, mid_end_dist);
let calc_start_point_x = startPoint[0];
let calc_start_point_y = startPoint[1];
let calc_end_point_x = endPoint[0];
let calc_end_point_y = endPoint[1];
if (max_dist === start_mid_dist) {
calc_end_point_x = endPoint[0];
calc_end_point_y = endPoint[1];
} else if (max_dist === mid_end_dist) {
calc_start_point_x = midPoint[0];
calc_start_point_y = midPoint[1];
}
const calcStartPoint = [calc_start_point_x, calc_start_point_y];
const calcEndPoint = [calc_end_point_x, calc_end_point_y];
const totalAngle = getSlopes(calcStartPoint, calcEndPoint);
const votes = angleOrientationAt(totalAngle, options.TOTAL_ANGLE_VOTE_POWER);
voteVertical += votes[0];
voteDiagonal += votes[1];
voteHorizontal += votes[2];
for (const fingerSlope of fingerSlopes) {
const fingerVotes = angleOrientationAt(fingerSlope, options.SINGLE_ANGLE_VOTE_POWER);
voteVertical += fingerVotes[0];
voteDiagonal += fingerVotes[1];
voteHorizontal += fingerVotes[2];
}
// in case of tie, highest preference goes to Vertical,
// followed by horizontal and then diagonal
let estimatedDirection;
if (voteVertical === Math.max(voteVertical, voteDiagonal, voteHorizontal)) {
estimatedDirection = estimateVerticalDirection(start_end_y_dist, start_mid_y_dist, mid_end_y_dist, max_dist_y);
} else if (voteHorizontal === Math.max(voteDiagonal, voteHorizontal)) {
estimatedDirection = estimateHorizontalDirection(start_end_x_dist, start_mid_x_dist, mid_end_x_dist, max_dist_x);
} else {
estimatedDirection = estimateDiagonalDirection(start_end_y_dist, start_mid_y_dist, mid_end_y_dist, max_dist_y, start_end_x_dist, start_mid_x_dist, mid_end_x_dist, max_dist_x);
}
return estimatedDirection;
}
export function estimate(landmarks) {
// step 1: calculate slopes
const slopesXY: Array<number[]> = [];
const slopesYZ: Array<number[]> = [];
for (const finger of Finger.all) {
const points = Finger.getPoints(finger);
const slopeAtXY: Array<number> = [];
const slopeAtYZ: Array<number> = [];
for (const point of points) {
const point1 = landmarks[point[0]];
const point2 = landmarks[point[1]];
// calculate single slope
const slopes = getSlopes(point1, point2);
const slopeXY = slopes[0];
const slopeYZ = slopes[1];
slopeAtXY.push(slopeXY);
slopeAtYZ.push(slopeYZ);
}
slopesXY.push(slopeAtXY);
slopesYZ.push(slopeAtYZ);
}
// step 2: calculate orientations
const fingerCurls: Array<number> = [];
const fingerDirections: Array<number> = [];
for (const finger of Finger.all) {
// start finger predictions from palm - except for thumb
const pointIndexAt = (finger === Finger.thumb) ? 1 : 0;
const fingerPointsAt = Finger.getPoints(finger);
const startPoint = landmarks[fingerPointsAt[pointIndexAt][0]];
const midPoint = landmarks[fingerPointsAt[pointIndexAt + 1][1]];
const endPoint = landmarks[fingerPointsAt[3][1]];
// check if finger is curled
const fingerCurled = estimateFingerCurl(startPoint, midPoint, endPoint);
const fingerPosition = calculateFingerDirection(startPoint, midPoint, endPoint, slopesXY[finger].slice(pointIndexAt));
fingerCurls[finger] = fingerCurled;
fingerDirections[finger] = fingerPosition;
}
return { curls: fingerCurls, directions: fingerDirections };
}

View File

@ -0,0 +1,31 @@
// based on <https://github.com/andypotato/fingerpose>
import * as estimator from './estimator';
import { Finger, FingerCurl, FingerDirection } from './description';
import Gestures from './gestures';
const minConfidence = 0.7;
export function analyze(keypoints) { // get estimations of curl / direction for each finger
const estimatorRes = estimator.estimate(keypoints);
const landmarks = {};
for (const fingerIdx of Finger.all) {
landmarks[Finger.getName(fingerIdx)] = {
curl: FingerCurl.getName(estimatorRes.curls[fingerIdx]),
direction: FingerDirection.getName(estimatorRes.directions[fingerIdx]),
};
}
// console.log('finger landmarks', landmarks);
return landmarks;
}
export function match(keypoints) { // compare gesture description to each known gesture
const estimatorRes = estimator.estimate(keypoints);
const poses: Array<{ name: string, confidence: number }> = [];
for (const gesture of Gestures) {
const confidence = gesture.matchAgainst(estimatorRes.curls, estimatorRes.directions);
if (confidence >= minConfidence) poses.push({ name: gesture.name, confidence });
}
// console.log('finger poses', poses);
return poses;
}

75
src/fingerpose/gesture.ts Normal file
View File

@ -0,0 +1,75 @@
export default class Gesture {
name;
curls;
directions;
weights;
weightsRelative;
constructor(name) {
// name (should be unique)
this.name = name;
this.curls = {};
this.directions = {};
this.weights = [1.0, 1.0, 1.0, 1.0, 1.0];
this.weightsRelative = [1.0, 1.0, 1.0, 1.0, 1.0];
}
addCurl(finger, curl, confidence) {
if (typeof this.curls[finger] === 'undefined') this.curls[finger] = [];
this.curls[finger].push([curl, confidence]);
}
addDirection(finger, position, confidence) {
if (!this.directions[finger]) this.directions[finger] = [];
this.directions[finger].push([position, confidence]);
}
setWeight(finger, weight) {
this.weights[finger] = weight;
// recalculate relative weights
const total = this.weights.reduce((a, b) => a + b, 0);
this.weightsRelative = this.weights.map((el) => el * 5 / total);
}
matchAgainst(detectedCurls, detectedDirections) {
let confidence = 0.0;
// look at the detected curl of each finger and compare with
// the expected curl of this finger inside current gesture
for (const fingerIdx in detectedCurls) {
const detectedCurl = detectedCurls[fingerIdx];
const expectedCurls = this.curls[fingerIdx];
if (typeof expectedCurls === 'undefined') {
// no curl description available for this finger
// add default confidence of "1"
confidence += this.weightsRelative[fingerIdx];
continue;
}
// compare to each possible curl of this specific finger
for (const [expectedCurl, score] of expectedCurls) {
if (detectedCurl === expectedCurl) {
confidence += score * this.weightsRelative[fingerIdx];
break;
}
}
}
// same for detected direction of each finger
for (const fingerIdx in detectedDirections) {
const detectedDirection = detectedDirections[fingerIdx];
const expectedDirections = this.directions[fingerIdx];
if (typeof expectedDirections === 'undefined') {
// no direction description available for this finger
// add default confidence of "1"
confidence += this.weightsRelative[fingerIdx];
continue;
}
// compare to each possible direction of this specific finger
for (const [expectedDirection, score] of expectedDirections) {
if (detectedDirection === expectedDirection) {
confidence += score * this.weightsRelative[fingerIdx];
break;
}
}
}
return confidence / 10;
}
}

View File

@ -0,0 +1,39 @@
import { Finger, FingerCurl, FingerDirection } from './description';
import Gesture from './gesture';
// describe thumbs up gesture 👍
const ThumbsUp = new Gesture('thumbs up');
ThumbsUp.addCurl(Finger.thumb, FingerCurl.none, 1.0);
ThumbsUp.addDirection(Finger.thumb, FingerDirection.verticalUp, 1.0);
ThumbsUp.addDirection(Finger.thumb, FingerDirection.diagonalUpLeft, 0.25);
ThumbsUp.addDirection(Finger.thumb, FingerDirection.diagonalUpRight, 0.25);
for (const finger of [Finger.index, Finger.middle, Finger.ring, Finger.pinky]) {
ThumbsUp.addCurl(finger, FingerCurl.full, 1.0);
ThumbsUp.addDirection(finger, FingerDirection.horizontalLeft, 1.0);
ThumbsUp.addDirection(finger, FingerDirection.horizontalRight, 1.0);
}
// describe Victory gesture ✌️
const Victory = new Gesture('victory');
Victory.addCurl(Finger.thumb, FingerCurl.half, 0.5);
Victory.addCurl(Finger.thumb, FingerCurl.none, 0.5);
Victory.addDirection(Finger.thumb, FingerDirection.verticalUp, 1.0);
Victory.addDirection(Finger.thumb, FingerDirection.diagonalUpLeft, 1.0);
Victory.addCurl(Finger.index, FingerCurl.none, 1.0);
Victory.addDirection(Finger.index, FingerDirection.verticalUp, 0.75);
Victory.addDirection(Finger.index, FingerDirection.diagonalUpLeft, 1.0);
Victory.addCurl(Finger.middle, FingerCurl.none, 1.0);
Victory.addDirection(Finger.middle, FingerDirection.verticalUp, 1.0);
Victory.addDirection(Finger.middle, FingerDirection.diagonalUpLeft, 0.75);
Victory.addCurl(Finger.ring, FingerCurl.full, 1.0);
Victory.addDirection(Finger.ring, FingerDirection.verticalUp, 0.2);
Victory.addDirection(Finger.ring, FingerDirection.diagonalUpLeft, 1.0);
Victory.addDirection(Finger.ring, FingerDirection.horizontalLeft, 0.2);
Victory.addCurl(Finger.pinky, FingerCurl.full, 1.0);
Victory.addDirection(Finger.pinky, FingerDirection.verticalUp, 0.2);
Victory.addDirection(Finger.pinky, FingerDirection.diagonalUpLeft, 1.0);
Victory.addDirection(Finger.pinky, FingerDirection.horizontalLeft, 0.2);
Victory.setWeight(Finger.index, 2);
Victory.setWeight(Finger.middle, 2);
export default [ThumbsUp, Victory];

View File

@ -3,6 +3,7 @@
*/
import { Gesture } from '../result';
import * as fingerPose from '../fingerpose/fingerpose';
/**
* @typedef FaceGesture
@ -33,8 +34,10 @@ export type BodyGesture =
* @typedef BodyGesture
*/
export type HandGesture =
`${'thumb' | 'index finger' | 'middle finger' | 'ring finger' | 'pinky'} forward`
| `${'thumb' | 'index finger' | 'middle finger' | 'ring finger' | 'pinky'} up`;
`${'thumb' | 'index' | 'middle' | 'ring' | 'pinky'} forward`
| `${'thumb' | 'index' | 'middle' | 'ring' | 'pinky'} up`
| 'victory'
| 'thumbs up';
export const body = (res): Gesture[] => {
if (!res) return [];
@ -129,6 +132,8 @@ export const hand = (res): Gesture[] => {
const highest = fingers.reduce((best, a) => (best.position[1] < a.position[1] ? best : a));
gestures.push({ hand: i, gesture: `${highest.name} up` as HandGesture });
}
const poses = fingerPose.match(res[i]['keypoints']);
for (const pose of poses) gestures.push({ hand: i, gesture: pose.name as HandGesture });
}
return gestures;
};

View File

@ -6,17 +6,18 @@ import { log, join } from '../helpers';
import * as tf from '../../dist/tfjs.esm.js';
import * as handdetector from './handdetector';
import * as handpipeline from './handpipeline';
import * as fingerPose from '../fingerpose/fingerpose';
import { Hand } from '../result';
import { Tensor, GraphModel } from '../tfjs/types';
import { Config } from '../config';
const meshAnnotations = {
thumb: [1, 2, 3, 4],
indexFinger: [5, 6, 7, 8],
middleFinger: [9, 10, 11, 12],
ringFinger: [13, 14, 15, 16],
index: [5, 6, 7, 8],
middle: [9, 10, 11, 12],
ring: [13, 14, 15, 16],
pinky: [17, 18, 19, 20],
palmBase: [0],
palm: [0],
};
let handDetectorModel: GraphModel | null;
@ -64,7 +65,16 @@ export async function predict(input: Tensor, config: Config): Promise<Hand[]> {
(predictions[i].box.bottomRight[1] - predictions[i].box.topLeft[1]) / (input.shape[1] || 0),
];
}
hands.push({ id: i, score: Math.round(100 * predictions[i].confidence) / 100, box, boxRaw, keypoints, annotations });
const landmarks = fingerPose.analyze(keypoints);
hands.push({
id: i,
score: Math.round(100 * predictions[i].confidence) / 100,
box,
boxRaw,
keypoints,
annotations: annotations as Hand['annotations'],
landmarks: landmarks as Hand['landmarks'],
});
}
return hands;
}

View File

@ -68,7 +68,7 @@ export function calc(newResult: Result): Result {
annotations[key] = newResult.hand[i].annotations[key]
.map((val, j) => val.map((coord, k) => ((bufferedFactor - 1) * bufferedResult.hand[i].annotations[key][j][k] + coord) / bufferedFactor));
}
bufferedResult.hand[i] = { ...newResult.hand[i], box, boxRaw, keypoints, annotations }; // shallow clone plus updated values
bufferedResult.hand[i] = { ...newResult.hand[i], box, boxRaw, keypoints, annotations: annotations as Hand['annotations'] }; // shallow clone plus updated values
}
}

View File

@ -90,8 +90,9 @@ export interface Body {
* - score: detection confidence score as value
* - box: bounding box: x, y, width, height normalized to input image resolution
* - boxRaw: bounding box: x, y, width, height normalized to 0..1
* - landmarks: landmarks as array of [x, y, z] points of hand, normalized to image resolution
* - annotations: annotated landmarks for each hand part
* - keypoints: keypoints as array of [x, y, z] points of hand, normalized to image resolution
* - annotations: annotated landmarks for each hand part with keypoints
* - landmarks: annotated landmarks for eachb hand part with logical curl and direction strings
*/
export interface Hand {
id: number,
@ -99,7 +100,14 @@ export interface Hand {
box: [number, number, number, number],
boxRaw: [number, number, number, number],
keypoints: Array<[number, number, number]>,
annotations: Record<string, Array<[number, number, number]>>,
annotations: Record<
'index' | 'middle' | 'pinky' | 'ring' | 'thumb' | 'palm',
Array<[number, number, number]>
>,
landmarks: Record<
'index' | 'middle' | 'pinky' | 'ring' | 'thumb',
{ curl: 'none' | 'half' | 'full', direction: 'verticalUp' | 'verticalDown' | 'horizontalLeft' | 'horizontalRight' | 'diagonalUpRight' | 'diagonalUpLeft' | 'diagonalDownRight' | 'diagonalDownLeft' }
>,
}
/** Object results

View File

@ -1,120 +1,120 @@
2021-08-20 09:04:08 INFO:  @vladmandic/human version 2.1.4
2021-08-20 09:04:08 INFO:  User: vlado Platform: linux Arch: x64 Node: v16.5.0
2021-08-20 09:04:08 INFO:  tests: ["test-node.js","test-node-gpu.js","test-node-wasm.js"]
2021-08-20 09:04:08 INFO:  test-node.js start
2021-08-20 09:04:08 STATE: test-node.js passed: create human
2021-08-20 09:04:08 INFO:  test-node.js human version: 2.1.4
2021-08-20 09:04:08 INFO:  test-node.js platform: linux x64 agent: NodeJS v16.5.0
2021-08-20 09:04:08 INFO:  test-node.js tfjs version: 3.8.0
2021-08-20 09:04:09 STATE: test-node.js passed: set backend: tensorflow
2021-08-20 09:04:09 STATE: test-node.js passed: load models
2021-08-20 09:04:09 STATE: test-node.js result: defined models: 14 loaded models: 7
2021-08-20 09:04:09 STATE: test-node.js passed: warmup: none default
2021-08-20 09:04:10 STATE: test-node.js passed: warmup: face default
2021-08-20 09:04:10 DATA:  test-node.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":23.6,"gender":"female"} {"score":0.82,"class":"person"} {"score":0.42,"keypoints":4}
2021-08-20 09:04:10 DATA:  test-node.js result: performance: load: 372 total: 1090
2021-08-20 09:04:11 STATE: test-node.js passed: warmup: body default
2021-08-20 09:04:11 DATA:  test-node.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":29.5,"gender":"female"} {"score":0.72,"class":"person"} {"score":0.92,"keypoints":17}
2021-08-20 09:04:11 DATA:  test-node.js result: performance: load: 372 total: 1054
2021-08-20 09:04:11 INFO:  test-node.js test body variants
2021-08-20 09:04:12 STATE: test-node.js passed: load image: samples/ai-body.jpg [1,1200,1200,3]
2021-08-20 09:04:13 STATE: test-node.js passed: detect: samples/ai-body.jpg posenet
2021-08-20 09:04:13 DATA:  test-node.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":29.5,"gender":"female"} {"score":0.72,"class":"person"} {"score":0.96,"keypoints":16}
2021-08-20 09:04:13 DATA:  test-node.js result: performance: load: 372 total: 687
2021-08-20 09:04:13 STATE: test-node.js passed: load image: samples/ai-body.jpg [1,1200,1200,3]
2021-08-20 09:04:14 STATE: test-node.js passed: detect: samples/ai-body.jpg movenet
2021-08-20 09:04:14 DATA:  test-node.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":29.5,"gender":"female"} {"score":0.72,"class":"person"} {"score":0.92,"keypoints":17}
2021-08-20 09:04:14 DATA:  test-node.js result: performance: load: 372 total: 193
2021-08-20 09:04:14 STATE: test-node.js passed: detect: random default
2021-08-20 09:04:14 DATA:  test-node.js result: face: 0 body: 1 hand: 0 gesture: 0 object: 0 person: 0 {} {} {"score":0,"keypoints":0}
2021-08-20 09:04:14 DATA:  test-node.js result: performance: load: 372 total: 591
2021-08-20 09:04:14 INFO:  test-node.js test: first instance
2021-08-20 09:04:15 STATE: test-node.js passed: load image: samples/ai-upper.jpg [1,720,688,3]
2021-08-20 09:04:16 STATE: test-node.js passed: detect: samples/ai-upper.jpg default
2021-08-20 09:04:16 DATA:  test-node.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":29.5,"gender":"female"} {"score":0.71,"class":"person"} {"score":0.69,"keypoints":10}
2021-08-20 09:04:16 DATA:  test-node.js result: performance: load: 372 total: 975
2021-08-20 09:04:16 INFO:  test-node.js test: second instance
2021-08-20 09:04:16 STATE: test-node.js passed: load image: samples/ai-upper.jpg [1,720,688,3]
2021-08-20 09:04:17 STATE: test-node.js passed: detect: samples/ai-upper.jpg default
2021-08-20 09:04:17 DATA:  test-node.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":29.5,"gender":"female"} {"score":0.71,"class":"person"} {"score":0.69,"keypoints":10}
2021-08-20 09:04:17 DATA:  test-node.js result: performance: load: 3 total: 902
2021-08-20 09:04:17 INFO:  test-node.js test: concurrent
2021-08-20 09:04:17 STATE: test-node.js passed: load image: samples/ai-face.jpg [1,256,256,3]
2021-08-20 09:04:17 STATE: test-node.js passed: load image: samples/ai-face.jpg [1,256,256,3]
2021-08-20 09:04:18 STATE: test-node.js passed: load image: samples/ai-body.jpg [1,1200,1200,3]
2021-08-20 09:04:19 STATE: test-node.js passed: load image: samples/ai-body.jpg [1,1200,1200,3]
2021-08-20 09:04:23 STATE: test-node.js passed: detect: samples/ai-face.jpg default
2021-08-20 09:04:23 DATA:  test-node.js result: face: 1 body: 1 hand: 0 gesture: 4 object: 1 person: 1 {"age":23.6,"gender":"female"} {"score":0.82,"class":"person"} {"score":0.47,"keypoints":17}
2021-08-20 09:04:23 DATA:  test-node.js result: performance: load: 372 total: 4140
2021-08-20 09:04:23 STATE: test-node.js passed: detect: samples/ai-face.jpg default
2021-08-20 09:04:23 DATA:  test-node.js result: face: 1 body: 1 hand: 0 gesture: 5 object: 1 person: 1 {"age":23.6,"gender":"female"} {"score":0.82,"class":"person"} {"score":0.47,"keypoints":17}
2021-08-20 09:04:23 DATA:  test-node.js result: performance: load: 3 total: 4140
2021-08-20 09:04:23 STATE: test-node.js passed: detect: samples/ai-body.jpg default
2021-08-20 09:04:23 DATA:  test-node.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":28.5,"gender":"female"} {"score":0.72,"class":"person"} {"score":0.92,"keypoints":17}
2021-08-20 09:04:23 DATA:  test-node.js result: performance: load: 372 total: 4140
2021-08-20 09:04:23 STATE: test-node.js passed: detect: samples/ai-body.jpg default
2021-08-20 09:04:23 DATA:  test-node.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":28.5,"gender":"female"} {"score":0.72,"class":"person"} {"score":0.92,"keypoints":17}
2021-08-20 09:04:23 DATA:  test-node.js result: performance: load: 3 total: 4140
2021-08-20 09:04:23 INFO:  test-node.js test complete: 14355 ms
2021-08-20 09:04:23 INFO:  test-node-gpu.js start
2021-08-20 09:04:23 WARN:  test-node-gpu.js stderr: 2021-08-20 09:04:23.693064: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libcudart.so.11.0'; dlerror: libcudart.so.11.0: cannot open shared object file: No such file or directory
2021-08-20 09:04:23 WARN:  test-node-gpu.js stderr: 2021-08-20 09:04:23.741809: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libcuda.so.1'; dlerror: libcuda.so.1: cannot open shared object file: No such file or directory
2021-08-20 09:04:23 WARN:  test-node-gpu.js stderr: 2021-08-20 09:04:23.741844: I tensorflow/stream_executor/cuda/cuda_diagnostics.cc:156] kernel driver does not appear to be running on this host (wyse): /proc/driver/nvidia/version does not exist
2021-08-20 09:04:23 STATE: test-node-gpu.js passed: create human
2021-08-20 09:04:23 INFO:  test-node-gpu.js human version: 2.1.4
2021-08-20 09:04:23 INFO:  test-node-gpu.js platform: linux x64 agent: NodeJS v16.5.0
2021-08-20 09:04:23 INFO:  test-node-gpu.js tfjs version: 3.8.0
2021-08-20 09:04:24 STATE: test-node-gpu.js passed: set backend: tensorflow
2021-08-20 09:04:24 STATE: test-node-gpu.js passed: load models
2021-08-20 09:04:24 STATE: test-node-gpu.js result: defined models: 14 loaded models: 7
2021-08-20 09:04:24 STATE: test-node-gpu.js passed: warmup: none default
2021-08-20 09:04:25 STATE: test-node-gpu.js passed: warmup: face default
2021-08-20 09:04:25 DATA:  test-node-gpu.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":23.6,"gender":"female"} {"score":0.82,"class":"person"} {"score":0.42,"keypoints":4}
2021-08-20 09:04:25 DATA:  test-node-gpu.js result: performance: load: 277 total: 1107
2021-08-20 09:04:26 STATE: test-node-gpu.js passed: warmup: body default
2021-08-20 09:04:26 DATA:  test-node-gpu.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":29.5,"gender":"female"} {"score":0.72,"class":"person"} {"score":0.92,"keypoints":17}
2021-08-20 09:04:26 DATA:  test-node-gpu.js result: performance: load: 277 total: 1083
2021-08-20 09:04:26 INFO:  test-node-gpu.js test body variants
2021-08-20 09:04:27 STATE: test-node-gpu.js passed: load image: samples/ai-body.jpg [1,1200,1200,3]
2021-08-20 09:04:27 STATE: test-node-gpu.js passed: detect: samples/ai-body.jpg posenet
2021-08-20 09:04:27 DATA:  test-node-gpu.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":29.5,"gender":"female"} {"score":0.72,"class":"person"} {"score":0.96,"keypoints":16}
2021-08-20 09:04:27 DATA:  test-node-gpu.js result: performance: load: 277 total: 728
2021-08-20 09:04:28 STATE: test-node-gpu.js passed: load image: samples/ai-body.jpg [1,1200,1200,3]
2021-08-20 09:04:28 STATE: test-node-gpu.js passed: detect: samples/ai-body.jpg movenet
2021-08-20 09:04:28 DATA:  test-node-gpu.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":29.5,"gender":"female"} {"score":0.72,"class":"person"} {"score":0.92,"keypoints":17}
2021-08-20 09:04:28 DATA:  test-node-gpu.js result: performance: load: 277 total: 191
2021-08-20 09:04:29 STATE: test-node-gpu.js passed: detect: random default
2021-08-20 09:04:29 DATA:  test-node-gpu.js result: face: 0 body: 1 hand: 0 gesture: 0 object: 0 person: 0 {} {} {"score":0,"keypoints":0}
2021-08-20 09:04:29 DATA:  test-node-gpu.js result: performance: load: 277 total: 608
2021-08-20 09:04:29 INFO:  test-node-gpu.js test: first instance
2021-08-20 09:04:29 STATE: test-node-gpu.js passed: load image: samples/ai-upper.jpg [1,720,688,3]
2021-08-20 09:04:30 STATE: test-node-gpu.js passed: detect: samples/ai-upper.jpg default
2021-08-20 09:04:30 DATA:  test-node-gpu.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":29.5,"gender":"female"} {"score":0.71,"class":"person"} {"score":0.69,"keypoints":10}
2021-08-20 09:04:30 DATA:  test-node-gpu.js result: performance: load: 277 total: 915
2021-08-20 09:04:30 INFO:  test-node-gpu.js test: second instance
2021-08-20 09:04:31 STATE: test-node-gpu.js passed: load image: samples/ai-upper.jpg [1,720,688,3]
2021-08-20 09:04:32 STATE: test-node-gpu.js passed: detect: samples/ai-upper.jpg default
2021-08-20 09:04:32 DATA:  test-node-gpu.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":29.5,"gender":"female"} {"score":0.71,"class":"person"} {"score":0.69,"keypoints":10}
2021-08-20 09:04:32 DATA:  test-node-gpu.js result: performance: load: 5 total: 982
2021-08-20 09:04:32 INFO:  test-node-gpu.js test: concurrent
2021-08-20 09:04:32 STATE: test-node-gpu.js passed: load image: samples/ai-face.jpg [1,256,256,3]
2021-08-20 09:04:32 STATE: test-node-gpu.js passed: load image: samples/ai-face.jpg [1,256,256,3]
2021-08-20 09:04:32 STATE: test-node-gpu.js passed: load image: samples/ai-body.jpg [1,1200,1200,3]
2021-08-20 09:04:33 STATE: test-node-gpu.js passed: load image: samples/ai-body.jpg [1,1200,1200,3]
2021-08-20 09:04:38 STATE: test-node-gpu.js passed: detect: samples/ai-face.jpg default
2021-08-20 09:04:38 DATA:  test-node-gpu.js result: face: 1 body: 1 hand: 0 gesture: 4 object: 1 person: 1 {"age":23.6,"gender":"female"} {"score":0.82,"class":"person"} {"score":0.47,"keypoints":17}
2021-08-20 09:04:38 DATA:  test-node-gpu.js result: performance: load: 277 total: 4187
2021-08-20 09:04:38 STATE: test-node-gpu.js passed: detect: samples/ai-face.jpg default
2021-08-20 09:04:38 DATA:  test-node-gpu.js result: face: 1 body: 1 hand: 0 gesture: 5 object: 1 person: 1 {"age":23.6,"gender":"female"} {"score":0.82,"class":"person"} {"score":0.47,"keypoints":17}
2021-08-20 09:04:38 DATA:  test-node-gpu.js result: performance: load: 5 total: 4187
2021-08-20 09:04:38 STATE: test-node-gpu.js passed: detect: samples/ai-body.jpg default
2021-08-20 09:04:38 DATA:  test-node-gpu.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":28.5,"gender":"female"} {"score":0.72,"class":"person"} {"score":0.92,"keypoints":17}
2021-08-20 09:04:38 DATA:  test-node-gpu.js result: performance: load: 277 total: 4187
2021-08-20 09:04:38 STATE: test-node-gpu.js passed: detect: samples/ai-body.jpg default
2021-08-20 09:04:38 DATA:  test-node-gpu.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":28.5,"gender":"female"} {"score":0.72,"class":"person"} {"score":0.92,"keypoints":17}
2021-08-20 09:04:38 DATA:  test-node-gpu.js result: performance: load: 5 total: 4187
2021-08-20 09:04:38 INFO:  test-node-gpu.js test complete: 14218 ms
2021-08-20 09:04:38 INFO:  test-node-wasm.js start
2021-08-20 09:04:38 ERROR: test-node-wasm.js failed: model server: request to http://localhost:10030/models/ failed, reason: connect ECONNREFUSED 127.0.0.1:10030
2021-08-20 09:04:38 ERROR: test-node-wasm.js aborting test
2021-08-20 09:04:38 INFO:  status: {"passed":46,"failed":1}
2021-08-20 20:42:17 INFO:  @vladmandic/human version 2.1.4
2021-08-20 20:42:17 INFO:  User: vlado Platform: linux Arch: x64 Node: v16.5.0
2021-08-20 20:42:17 INFO:  tests: ["test-node.js","test-node-gpu.js","test-node-wasm.js"]
2021-08-20 20:42:17 INFO:  test-node.js start
2021-08-20 20:42:19 STATE: test-node.js passed: create human
2021-08-20 20:42:19 INFO:  test-node.js human version: 2.1.4
2021-08-20 20:42:19 INFO:  test-node.js platform: linux x64 agent: NodeJS v16.5.0
2021-08-20 20:42:19 INFO:  test-node.js tfjs version: 3.8.0
2021-08-20 20:42:19 STATE: test-node.js passed: set backend: tensorflow
2021-08-20 20:42:19 STATE: test-node.js passed: load models
2021-08-20 20:42:19 STATE: test-node.js result: defined models: 14 loaded models: 7
2021-08-20 20:42:19 STATE: test-node.js passed: warmup: none default
2021-08-20 20:42:21 STATE: test-node.js passed: warmup: face default
2021-08-20 20:42:21 DATA:  test-node.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":23.6,"gender":"female"} {"score":0.82,"class":"person"} {"score":0.42,"keypoints":4}
2021-08-20 20:42:21 DATA:  test-node.js result: performance: load: 343 total: 1358
2021-08-20 20:42:22 STATE: test-node.js passed: warmup: body default
2021-08-20 20:42:22 DATA:  test-node.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":29.5,"gender":"female"} {"score":0.72,"class":"person"} {"score":0.92,"keypoints":17}
2021-08-20 20:42:22 DATA:  test-node.js result: performance: load: 343 total: 1140
2021-08-20 20:42:22 INFO:  test-node.js test body variants
2021-08-20 20:42:23 STATE: test-node.js passed: load image: samples/ai-body.jpg [1,1200,1200,3]
2021-08-20 20:42:24 STATE: test-node.js passed: detect: samples/ai-body.jpg posenet
2021-08-20 20:42:24 DATA:  test-node.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":29.5,"gender":"female"} {"score":0.72,"class":"person"} {"score":0.96,"keypoints":16}
2021-08-20 20:42:24 DATA:  test-node.js result: performance: load: 343 total: 695
2021-08-20 20:42:24 STATE: test-node.js passed: load image: samples/ai-body.jpg [1,1200,1200,3]
2021-08-20 20:42:25 STATE: test-node.js passed: detect: samples/ai-body.jpg movenet
2021-08-20 20:42:25 DATA:  test-node.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":29.5,"gender":"female"} {"score":0.72,"class":"person"} {"score":0.92,"keypoints":17}
2021-08-20 20:42:25 DATA:  test-node.js result: performance: load: 343 total: 257
2021-08-20 20:42:26 STATE: test-node.js passed: detect: random default
2021-08-20 20:42:26 DATA:  test-node.js result: face: 0 body: 1 hand: 0 gesture: 0 object: 0 person: 0 {} {} {"score":0,"keypoints":0}
2021-08-20 20:42:26 DATA:  test-node.js result: performance: load: 343 total: 704
2021-08-20 20:42:26 INFO:  test-node.js test: first instance
2021-08-20 20:42:26 STATE: test-node.js passed: load image: samples/ai-upper.jpg [1,720,688,3]
2021-08-20 20:42:27 STATE: test-node.js passed: detect: samples/ai-upper.jpg default
2021-08-20 20:42:27 DATA:  test-node.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":29.5,"gender":"female"} {"score":0.71,"class":"person"} {"score":0.69,"keypoints":10}
2021-08-20 20:42:27 DATA:  test-node.js result: performance: load: 343 total: 953
2021-08-20 20:42:27 INFO:  test-node.js test: second instance
2021-08-20 20:42:27 STATE: test-node.js passed: load image: samples/ai-upper.jpg [1,720,688,3]
2021-08-20 20:42:28 STATE: test-node.js passed: detect: samples/ai-upper.jpg default
2021-08-20 20:42:28 DATA:  test-node.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":29.5,"gender":"female"} {"score":0.71,"class":"person"} {"score":0.69,"keypoints":10}
2021-08-20 20:42:28 DATA:  test-node.js result: performance: load: 4 total: 940
2021-08-20 20:42:28 INFO:  test-node.js test: concurrent
2021-08-20 20:42:28 STATE: test-node.js passed: load image: samples/ai-face.jpg [1,256,256,3]
2021-08-20 20:42:28 STATE: test-node.js passed: load image: samples/ai-face.jpg [1,256,256,3]
2021-08-20 20:42:29 STATE: test-node.js passed: load image: samples/ai-body.jpg [1,1200,1200,3]
2021-08-20 20:42:30 STATE: test-node.js passed: load image: samples/ai-body.jpg [1,1200,1200,3]
2021-08-20 20:42:34 STATE: test-node.js passed: detect: samples/ai-face.jpg default
2021-08-20 20:42:34 DATA:  test-node.js result: face: 1 body: 1 hand: 0 gesture: 4 object: 1 person: 1 {"age":23.6,"gender":"female"} {"score":0.82,"class":"person"} {"score":0.47,"keypoints":17}
2021-08-20 20:42:34 DATA:  test-node.js result: performance: load: 343 total: 4377
2021-08-20 20:42:34 STATE: test-node.js passed: detect: samples/ai-face.jpg default
2021-08-20 20:42:34 DATA:  test-node.js result: face: 1 body: 1 hand: 0 gesture: 5 object: 1 person: 1 {"age":23.6,"gender":"female"} {"score":0.82,"class":"person"} {"score":0.47,"keypoints":17}
2021-08-20 20:42:34 DATA:  test-node.js result: performance: load: 4 total: 4377
2021-08-20 20:42:34 STATE: test-node.js passed: detect: samples/ai-body.jpg default
2021-08-20 20:42:34 DATA:  test-node.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":28.5,"gender":"female"} {"score":0.72,"class":"person"} {"score":0.92,"keypoints":17}
2021-08-20 20:42:34 DATA:  test-node.js result: performance: load: 343 total: 4377
2021-08-20 20:42:34 STATE: test-node.js passed: detect: samples/ai-body.jpg default
2021-08-20 20:42:34 DATA:  test-node.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":28.5,"gender":"female"} {"score":0.72,"class":"person"} {"score":0.92,"keypoints":17}
2021-08-20 20:42:34 DATA:  test-node.js result: performance: load: 4 total: 4377
2021-08-20 20:42:34 INFO:  test-node.js test complete: 15280 ms
2021-08-20 20:42:34 INFO:  test-node-gpu.js start
2021-08-20 20:42:35 WARN:  test-node-gpu.js stderr: 2021-08-20 20:42:35.983213: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libcudart.so.11.0'; dlerror: libcudart.so.11.0: cannot open shared object file: No such file or directory
2021-08-20 20:42:36 WARN:  test-node-gpu.js stderr: 2021-08-20 20:42:36.281966: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libcuda.so.1'; dlerror: libcuda.so.1: cannot open shared object file: No such file or directory
2021-08-20 20:42:36 WARN:  test-node-gpu.js stderr: 2021-08-20 20:42:36.282070: I tensorflow/stream_executor/cuda/cuda_diagnostics.cc:156] kernel driver does not appear to be running on this host (wyse): /proc/driver/nvidia/version does not exist
2021-08-20 20:42:36 STATE: test-node-gpu.js passed: create human
2021-08-20 20:42:36 INFO:  test-node-gpu.js human version: 2.1.4
2021-08-20 20:42:36 INFO:  test-node-gpu.js platform: linux x64 agent: NodeJS v16.5.0
2021-08-20 20:42:36 INFO:  test-node-gpu.js tfjs version: 3.8.0
2021-08-20 20:42:36 STATE: test-node-gpu.js passed: set backend: tensorflow
2021-08-20 20:42:36 STATE: test-node-gpu.js passed: load models
2021-08-20 20:42:36 STATE: test-node-gpu.js result: defined models: 14 loaded models: 7
2021-08-20 20:42:36 STATE: test-node-gpu.js passed: warmup: none default
2021-08-20 20:42:38 STATE: test-node-gpu.js passed: warmup: face default
2021-08-20 20:42:38 DATA:  test-node-gpu.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":23.6,"gender":"female"} {"score":0.82,"class":"person"} {"score":0.42,"keypoints":4}
2021-08-20 20:42:38 DATA:  test-node-gpu.js result: performance: load: 289 total: 1504
2021-08-20 20:42:39 STATE: test-node-gpu.js passed: warmup: body default
2021-08-20 20:42:39 DATA:  test-node-gpu.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":29.5,"gender":"female"} {"score":0.72,"class":"person"} {"score":0.92,"keypoints":17}
2021-08-20 20:42:39 DATA:  test-node-gpu.js result: performance: load: 289 total: 1151
2021-08-20 20:42:39 INFO:  test-node-gpu.js test body variants
2021-08-20 20:42:40 STATE: test-node-gpu.js passed: load image: samples/ai-body.jpg [1,1200,1200,3]
2021-08-20 20:42:40 STATE: test-node-gpu.js passed: detect: samples/ai-body.jpg posenet
2021-08-20 20:42:40 DATA:  test-node-gpu.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":29.5,"gender":"female"} {"score":0.72,"class":"person"} {"score":0.96,"keypoints":16}
2021-08-20 20:42:40 DATA:  test-node-gpu.js result: performance: load: 289 total: 735
2021-08-20 20:42:41 STATE: test-node-gpu.js passed: load image: samples/ai-body.jpg [1,1200,1200,3]
2021-08-20 20:42:42 STATE: test-node-gpu.js passed: detect: samples/ai-body.jpg movenet
2021-08-20 20:42:42 DATA:  test-node-gpu.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":29.5,"gender":"female"} {"score":0.72,"class":"person"} {"score":0.92,"keypoints":17}
2021-08-20 20:42:42 DATA:  test-node-gpu.js result: performance: load: 289 total: 222
2021-08-20 20:42:42 STATE: test-node-gpu.js passed: detect: random default
2021-08-20 20:42:42 DATA:  test-node-gpu.js result: face: 0 body: 1 hand: 0 gesture: 0 object: 0 person: 0 {} {} {"score":0,"keypoints":0}
2021-08-20 20:42:42 DATA:  test-node-gpu.js result: performance: load: 289 total: 752
2021-08-20 20:42:42 INFO:  test-node-gpu.js test: first instance
2021-08-20 20:42:43 STATE: test-node-gpu.js passed: load image: samples/ai-upper.jpg [1,720,688,3]
2021-08-20 20:42:44 STATE: test-node-gpu.js passed: detect: samples/ai-upper.jpg default
2021-08-20 20:42:44 DATA:  test-node-gpu.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":29.5,"gender":"female"} {"score":0.71,"class":"person"} {"score":0.69,"keypoints":10}
2021-08-20 20:42:44 DATA:  test-node-gpu.js result: performance: load: 289 total: 945
2021-08-20 20:42:44 INFO:  test-node-gpu.js test: second instance
2021-08-20 20:42:44 STATE: test-node-gpu.js passed: load image: samples/ai-upper.jpg [1,720,688,3]
2021-08-20 20:42:45 STATE: test-node-gpu.js passed: detect: samples/ai-upper.jpg default
2021-08-20 20:42:45 DATA:  test-node-gpu.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":29.5,"gender":"female"} {"score":0.71,"class":"person"} {"score":0.69,"keypoints":10}
2021-08-20 20:42:45 DATA:  test-node-gpu.js result: performance: load: 3 total: 914
2021-08-20 20:42:45 INFO:  test-node-gpu.js test: concurrent
2021-08-20 20:42:45 STATE: test-node-gpu.js passed: load image: samples/ai-face.jpg [1,256,256,3]
2021-08-20 20:42:45 STATE: test-node-gpu.js passed: load image: samples/ai-face.jpg [1,256,256,3]
2021-08-20 20:42:46 STATE: test-node-gpu.js passed: load image: samples/ai-body.jpg [1,1200,1200,3]
2021-08-20 20:42:47 STATE: test-node-gpu.js passed: load image: samples/ai-body.jpg [1,1200,1200,3]
2021-08-20 20:42:51 STATE: test-node-gpu.js passed: detect: samples/ai-face.jpg default
2021-08-20 20:42:51 DATA:  test-node-gpu.js result: face: 1 body: 1 hand: 0 gesture: 4 object: 1 person: 1 {"age":23.6,"gender":"female"} {"score":0.82,"class":"person"} {"score":0.47,"keypoints":17}
2021-08-20 20:42:51 DATA:  test-node-gpu.js result: performance: load: 289 total: 4370
2021-08-20 20:42:51 STATE: test-node-gpu.js passed: detect: samples/ai-face.jpg default
2021-08-20 20:42:51 DATA:  test-node-gpu.js result: face: 1 body: 1 hand: 0 gesture: 5 object: 1 person: 1 {"age":23.6,"gender":"female"} {"score":0.82,"class":"person"} {"score":0.47,"keypoints":17}
2021-08-20 20:42:51 DATA:  test-node-gpu.js result: performance: load: 3 total: 4370
2021-08-20 20:42:51 STATE: test-node-gpu.js passed: detect: samples/ai-body.jpg default
2021-08-20 20:42:51 DATA:  test-node-gpu.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":28.5,"gender":"female"} {"score":0.72,"class":"person"} {"score":0.92,"keypoints":17}
2021-08-20 20:42:51 DATA:  test-node-gpu.js result: performance: load: 289 total: 4370
2021-08-20 20:42:51 STATE: test-node-gpu.js passed: detect: samples/ai-body.jpg default
2021-08-20 20:42:51 DATA:  test-node-gpu.js result: face: 1 body: 1 hand: 0 gesture: 3 object: 1 person: 1 {"age":28.5,"gender":"female"} {"score":0.72,"class":"person"} {"score":0.92,"keypoints":17}
2021-08-20 20:42:51 DATA:  test-node-gpu.js result: performance: load: 3 total: 4370
2021-08-20 20:42:51 INFO:  test-node-gpu.js test complete: 15164 ms
2021-08-20 20:42:51 INFO:  test-node-wasm.js start
2021-08-20 20:42:51 ERROR: test-node-wasm.js failed: model server: request to http://localhost:10030/models/ failed, reason: connect ECONNREFUSED 127.0.0.1:10030
2021-08-20 20:42:51 ERROR: test-node-wasm.js aborting test
2021-08-20 20:42:51 INFO:  status: {"passed":46,"failed":1}

File diff suppressed because one or more lines are too long

View File

@ -74,8 +74,9 @@
<li>score: detection confidence score as value</li>
<li>box: bounding box: x, y, width, height normalized to input image resolution</li>
<li>boxRaw: bounding box: x, y, width, height normalized to 0..1</li>
<li>landmarks: landmarks as array of [x, y, z] points of hand, normalized to image resolution</li>
<li>annotations: annotated landmarks for each hand part</li>
<li>keypoints: keypoints as array of [x, y, z] points of hand, normalized to image resolution</li>
<li>annotations: annotated landmarks for each hand part with keypoints</li>
<li>landmarks: annotated landmarks for eachb hand part with logical curl and direction strings</li>
</ul>
</div>
</section>
@ -99,6 +100,7 @@
<li class="tsd-kind-property tsd-parent-kind-interface"><a href="Hand.html#boxRaw" class="tsd-kind-icon">box<wbr>Raw</a></li>
<li class="tsd-kind-property tsd-parent-kind-interface"><a href="Hand.html#id" class="tsd-kind-icon">id</a></li>
<li class="tsd-kind-property tsd-parent-kind-interface"><a href="Hand.html#keypoints" class="tsd-kind-icon">keypoints</a></li>
<li class="tsd-kind-property tsd-parent-kind-interface"><a href="Hand.html#landmarks" class="tsd-kind-icon">landmarks</a></li>
<li class="tsd-kind-property tsd-parent-kind-interface"><a href="Hand.html#score" class="tsd-kind-icon">score</a></li>
</ul>
</section>
@ -110,7 +112,7 @@
<section class="tsd-panel tsd-member tsd-kind-property tsd-parent-kind-interface">
<a name="annotations" class="tsd-anchor"></a>
<h3>annotations</h3>
<div class="tsd-signature tsd-kind-icon">annotations<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">Record</span><span class="tsd-signature-symbol">&lt;</span><span class="tsd-signature-type">string</span><span class="tsd-signature-symbol">, </span><span class="tsd-signature-symbol">[</span><span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">, </span><span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">, </span><span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">]</span><span class="tsd-signature-symbol">[]</span><span class="tsd-signature-symbol">&gt;</span></div>
<div class="tsd-signature tsd-kind-icon">annotations<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">Record</span><span class="tsd-signature-symbol">&lt;</span><span class="tsd-signature-type">&quot;index&quot;</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">&quot;middle&quot;</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">&quot;pinky&quot;</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">&quot;ring&quot;</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">&quot;thumb&quot;</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">&quot;palm&quot;</span><span class="tsd-signature-symbol">, </span><span class="tsd-signature-symbol">[</span><span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">, </span><span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">, </span><span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">]</span><span class="tsd-signature-symbol">[]</span><span class="tsd-signature-symbol">&gt;</span></div>
<aside class="tsd-sources">
</aside>
</section>
@ -142,6 +144,13 @@
<aside class="tsd-sources">
</aside>
</section>
<section class="tsd-panel tsd-member tsd-kind-property tsd-parent-kind-interface">
<a name="landmarks" class="tsd-anchor"></a>
<h3>landmarks</h3>
<div class="tsd-signature tsd-kind-icon">landmarks<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">Record</span><span class="tsd-signature-symbol">&lt;</span><span class="tsd-signature-type">&quot;index&quot;</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">&quot;middle&quot;</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">&quot;pinky&quot;</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">&quot;ring&quot;</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">&quot;thumb&quot;</span><span class="tsd-signature-symbol">, </span><span class="tsd-signature-symbol">{ </span>curl<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">&quot;none&quot;</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">&quot;full&quot;</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">&quot;half&quot;</span><span class="tsd-signature-symbol">; </span>direction<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">&quot;verticalUp&quot;</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">&quot;verticalDown&quot;</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">&quot;horizontalLeft&quot;</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">&quot;horizontalRight&quot;</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">&quot;diagonalUpRight&quot;</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">&quot;diagonalUpLeft&quot;</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">&quot;diagonalDownRight&quot;</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">&quot;diagonalDownLeft&quot;</span><span class="tsd-signature-symbol"> }</span><span class="tsd-signature-symbol">&gt;</span></div>
<aside class="tsd-sources">
</aside>
</section>
<section class="tsd-panel tsd-member tsd-kind-property tsd-parent-kind-interface">
<a name="score" class="tsd-anchor"></a>
<h3>score</h3>
@ -199,6 +208,9 @@
<li class=" tsd-kind-property tsd-parent-kind-interface">
<a href="Hand.html#keypoints" class="tsd-kind-icon">keypoints</a>
</li>
<li class=" tsd-kind-property tsd-parent-kind-interface">
<a href="Hand.html#landmarks" class="tsd-kind-icon">landmarks</a>
</li>
<li class=" tsd-kind-property tsd-parent-kind-interface">
<a href="Hand.html#score" class="tsd-kind-icon">score</a>
</li>

57
types/src/fingerpose/description.d.ts vendored Normal file
View File

@ -0,0 +1,57 @@
declare const Finger: {
thumb: number;
index: number;
middle: number;
ring: number;
pinky: number;
all: number[];
nameMapping: {
0: string;
1: string;
2: string;
3: string;
4: string;
};
pointsMapping: {
0: number[][];
1: number[][];
2: number[][];
3: number[][];
4: number[][];
};
getName: (value: any) => any;
getPoints: (value: any) => any;
};
declare const FingerCurl: {
none: number;
half: number;
full: number;
nameMapping: {
0: string;
1: string;
2: string;
};
getName: (value: any) => any;
};
declare const FingerDirection: {
verticalUp: number;
verticalDown: number;
horizontalLeft: number;
horizontalRight: number;
diagonalUpRight: number;
diagonalUpLeft: number;
diagonalDownRight: number;
diagonalDownLeft: number;
nameMapping: {
0: string;
1: string;
2: string;
3: string;
4: string;
5: string;
6: string;
7: string;
};
getName: (value: any) => any;
};
export { Finger, FingerCurl, FingerDirection };

4
types/src/fingerpose/estimator.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
export declare function estimate(landmarks: any): {
curls: number[];
directions: number[];
};

5
types/src/fingerpose/fingerpose.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
export declare function analyze(keypoints: any): {};
export declare function match(keypoints: any): {
name: string;
confidence: number;
}[];

12
types/src/fingerpose/gesture.d.ts vendored Normal file
View File

@ -0,0 +1,12 @@
export default class Gesture {
name: any;
curls: any;
directions: any;
weights: any;
weightsRelative: any;
constructor(name: any);
addCurl(finger: any, curl: any, confidence: any): void;
addDirection(finger: any, position: any, confidence: any): void;
setWeight(finger: any, weight: any): void;
matchAgainst(detectedCurls: any, detectedDirections: any): number;
}

3
types/src/fingerpose/gestures.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
import Gesture from './gesture';
declare const _default: Gesture[];
export default _default;

View File

@ -17,7 +17,7 @@ export declare type BodyGesture = `leaning ${'left' | 'right'}` | `raise ${'left
/**
* @typedef BodyGesture
*/
export declare type HandGesture = `${'thumb' | 'index finger' | 'middle finger' | 'ring finger' | 'pinky'} forward` | `${'thumb' | 'index finger' | 'middle finger' | 'ring finger' | 'pinky'} up`;
export declare type HandGesture = `${'thumb' | 'index' | 'middle' | 'ring' | 'pinky'} forward` | `${'thumb' | 'index' | 'middle' | 'ring' | 'pinky'} up` | 'victory' | 'thumbs up';
export declare const body: (res: any) => Gesture[];
export declare const face: (res: any) => Gesture[];
export declare const iris: (res: any) => Gesture[];

11
types/src/result.d.ts vendored
View File

@ -96,8 +96,9 @@ export interface Body {
* - score: detection confidence score as value
* - box: bounding box: x, y, width, height normalized to input image resolution
* - boxRaw: bounding box: x, y, width, height normalized to 0..1
* - landmarks: landmarks as array of [x, y, z] points of hand, normalized to image resolution
* - annotations: annotated landmarks for each hand part
* - keypoints: keypoints as array of [x, y, z] points of hand, normalized to image resolution
* - annotations: annotated landmarks for each hand part with keypoints
* - landmarks: annotated landmarks for eachb hand part with logical curl and direction strings
*/
export interface Hand {
id: number;
@ -105,7 +106,11 @@ export interface Hand {
box: [number, number, number, number];
boxRaw: [number, number, number, number];
keypoints: Array<[number, number, number]>;
annotations: Record<string, Array<[number, number, number]>>;
annotations: Record<'index' | 'middle' | 'pinky' | 'ring' | 'thumb' | 'palm', Array<[number, number, number]>>;
landmarks: Record<'index' | 'middle' | 'pinky' | 'ring' | 'thumb', {
curl: 'none' | 'half' | 'full';
direction: 'verticalUp' | 'verticalDown' | 'horizontalLeft' | 'horizontalRight' | 'diagonalUpRight' | 'diagonalUpLeft' | 'diagonalDownRight' | 'diagonalDownLeft';
}>;
}
/** Object results
*

2
wiki

@ -1 +1 @@
Subproject commit c12e036ac382043f4b3a85cf71f93927af56cfe4
Subproject commit 65558ea91f6d5ec2dbc46bf9c46c592d34dce706