add experimental mb3-centernet object detection

pull/134/head
Vladimir Mandic 2021-05-19 08:27:28 -04:00
parent 271b821ab7
commit fa3ab21215
21 changed files with 227798 additions and 5288 deletions

View File

@ -9,10 +9,11 @@ import webRTC from './helpers/webrtc.js';
let human;
const userConfig = {
warmup: 'none',
warmup: 'full',
/*
backend: 'webgl',
async: true,
async: false,
cacheSensitivity: 0,
filter: {
enabled: false,
flip: false,
@ -26,9 +27,9 @@ const userConfig = {
},
hand: { enabled: false },
gesture: { enabled: false },
body: { enabled: true, modelPath: 'posenet.json' },
body: { enabled: false, modelPath: 'posenet.json' },
// body: { enabled: true, modelPath: 'blazepose.json' },
// object: { enabled: true },
object: { enabled: false },
*/
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

75933
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

75939
dist/human.js vendored

File diff suppressed because one or more lines are too long

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

@ -127,7 +127,7 @@ var config = {
debug: true,
async: true,
warmup: "full",
cacheSensitivity: 4e-3,
cacheSensitivity: 5e-3,
filter: {
enabled: true,
width: 0,
@ -192,7 +192,7 @@ var config = {
hand: {
enabled: true,
rotation: false,
skipFrames: 12,
skipFrames: 32,
minConfidence: 0.1,
iouThreshold: 0.1,
maxDetected: 2,
@ -206,7 +206,7 @@ var config = {
},
object: {
enabled: false,
modelPath: "nanodet.json",
modelPath: "mb3-centernet.json",
minConfidence: 0.2,
iouThreshold: 0.4,
maxDetected: 10,
@ -236,7 +236,7 @@ function info() {
}
// src/human.ts
var tf16 = __toModule(require_tfjs_esm());
var tf17 = __toModule(require_tfjs_esm());
// src/tfjs/backend.ts
var tf = __toModule(require_tfjs_esm());
@ -338,16 +338,16 @@ function getBoxCenter(box4) {
box4.startPoint[1] + (box4.endPoint[1] - box4.startPoint[1]) / 2
];
}
function cutBoxFromImageAndResize(box4, image12, cropSize) {
const h = image12.shape[1];
const w = image12.shape[2];
function cutBoxFromImageAndResize(box4, image13, cropSize) {
const h = image13.shape[1];
const w = image13.shape[2];
const boxes = [[
box4.startPoint[1] / h,
box4.startPoint[0] / w,
box4.endPoint[1] / h,
box4.endPoint[0] / w
]];
return tf2.image.cropAndResize(image12, boxes, [0], cropSize);
return tf2.image.cropAndResize(image13, boxes, [0], cropSize);
}
function enlargeBox(box4, factor = 1.5) {
const center = getBoxCenter(box4);
@ -481,11 +481,11 @@ function decodeBounds(boxOutputs, anchors3, inputSize) {
return tf3.concat2d([startNormalized, endNormalized], concatAxis);
}
var BlazeFaceModel = class {
constructor(model6, config3) {
this.model = model6;
this.anchorsData = generateAnchors(model6.inputs[0].shape[1]);
constructor(model7, config3) {
this.model = model7;
this.anchorsData = generateAnchors(model7.inputs[0].shape[1]);
this.anchors = tf3.tensor2d(this.anchorsData);
this.inputSize = model6.inputs[0].shape[2];
this.inputSize = model7.inputs[0].shape[2];
this.config = config3;
}
async getBoundingBoxes(inputImage) {
@ -534,12 +534,12 @@ var BlazeFaceModel = class {
}
};
async function load(config3) {
const model6 = await tf3.loadGraphModel(join(config3.modelBasePath, config3.face.detector.modelPath), {fromTFHub: config3.face.detector.modelPath.includes("tfhub.dev")});
const blazeFace = new BlazeFaceModel(model6, config3);
if (!model6 || !model6.modelUrl)
const model7 = await tf3.loadGraphModel(join(config3.modelBasePath, config3.face.detector.modelPath), { fromTFHub: config3.face.detector.modelPath.includes("tfhub.dev") });
const blazeFace = new BlazeFaceModel(model7, config3);
if (!model7 || !model7.modelUrl)
log("load model failed:", config3.face.detector.modelPath);
else if (config3.debug)
log("load model:", model6.modelUrl);
log("load model:", model7.modelUrl);
return blazeFace;
}
@ -4174,7 +4174,7 @@ async function load3(config3) {
log("cached model:", model.modelUrl);
return model;
}
async function predict2(image12, config3, idx, count2) {
async function predict2(image13, config3, idx, count2) {
if (!model)
return null;
if (skipped < config3.face.emotion.skipFrames && config3.skipFrame && lastCount === count2 && last[idx] && last[idx].length > 0) {
@ -4183,7 +4183,7 @@ async function predict2(image12, config3, idx, count2) {
}
skipped = 0;
return new Promise(async (resolve) => {
const resize = tf6.image.resizeBilinear(image12, [model.inputs[0].shape[2], model.inputs[0].shape[1]], false);
const resize = tf6.image.resizeBilinear(image13, [model.inputs[0].shape[2], model.inputs[0].shape[1]], false);
const [red, green, blue] = tf6.split(resize, 3, 3);
resize.dispose();
const redNorm = tf6.mul(red, rgb[0]);
@ -4266,7 +4266,7 @@ function match(embedding, db, threshold = 0) {
return best;
}
function enhance(input) {
const image12 = tf7.tidy(() => {
const image13 = tf7.tidy(() => {
const tensor = input.image || input.tensor || input;
if (!(tensor instanceof tf7.Tensor))
return null;
@ -4275,9 +4275,9 @@ function enhance(input) {
const norm = crop.mul(255);
return norm;
});
return image12;
return image13;
}
async function predict3(image12, config3, idx, count2) {
async function predict3(image13, config3, idx, count2) {
var _a, _b;
if (!model2)
return null;
@ -4287,7 +4287,7 @@ async function predict3(image12, config3, idx, count2) {
}
skipped2 = 0;
return new Promise(async (resolve) => {
const enhanced = enhance(image12);
const enhanced = enhance(image13);
let resT;
const obj = {
age: 0,
@ -4844,16 +4844,16 @@ function getBoxCenter2(box4) {
box4.startPoint[1] + (box4.endPoint[1] - box4.startPoint[1]) / 2
];
}
function cutBoxFromImageAndResize2(box4, image12, cropSize) {
const h = image12.shape[1];
const w = image12.shape[2];
function cutBoxFromImageAndResize2(box4, image13, cropSize) {
const h = image13.shape[1];
const w = image13.shape[2];
const boxes = [[
box4.startPoint[1] / h,
box4.startPoint[0] / w,
box4.endPoint[1] / h,
box4.endPoint[0] / w
]];
return tf9.image.cropAndResize(image12, boxes, [0], cropSize);
return tf9.image.cropAndResize(image13, boxes, [0], cropSize);
}
function scaleBoxCoordinates2(box4, factor) {
const startPoint = [box4.startPoint[0] * factor[0], box4.startPoint[1] * factor[1]];
@ -16664,9 +16664,9 @@ var anchors = [
// src/handpose/handdetector.ts
var HandDetector = class {
constructor(model6) {
constructor(model7) {
var _a;
this.model = model6;
this.model = model7;
this.anchors = anchors.map((anchor) => [anchor.x, anchor.y]);
this.anchorsTensor = tf10.tensor2d(this.anchors);
this.inputSize = (_a = this.model) == null ? void 0 : _a.inputs[0].shape[2];
@ -16720,9 +16720,9 @@ var HandDetector = class {
async estimateHandBounds(input, config3) {
const inputHeight = input.shape[1];
const inputWidth = input.shape[2];
const image12 = tf10.tidy(() => input.resizeBilinear([this.inputSize, this.inputSize]).div(127.5).sub(1));
const predictions = await this.getBoxes(image12, config3);
image12.dispose();
const image13 = tf10.tidy(() => input.resizeBilinear([this.inputSize, this.inputSize]).div(127.5).sub(1));
const predictions = await this.getBoxes(image13, config3);
image13.dispose();
const hands = [];
if (!predictions || predictions.length === 0)
return hands;
@ -16867,11 +16867,11 @@ var HandPipeline = class {
coord[2]
]);
}
async estimateHands(image12, config3) {
async estimateHands(image13, config3) {
let useFreshBox = false;
let boxes;
if (this.skipped === 0 || this.skipped > config3.hand.skipFrames || !config3.hand.landmarks || !config3.skipFrame) {
boxes = await this.handDetector.estimateHandBounds(image12, config3);
boxes = await this.handDetector.estimateHandBounds(image13, config3);
this.skipped = 0;
}
if (config3.skipFrame)
@ -16890,8 +16890,8 @@ var HandPipeline = class {
if (config3.hand.landmarks) {
const angle = config3.hand.rotation ? computeRotation2(currentBox.palmLandmarks[palmLandmarksPalmBase], currentBox.palmLandmarks[palmLandmarksMiddleFingerBase]) : 0;
const palmCenter = getBoxCenter2(currentBox);
const palmCenterNormalized = [palmCenter[0] / image12.shape[2], palmCenter[1] / image12.shape[1]];
const rotatedImage = config3.hand.rotation ? tf11.image.rotateWithOffset(image12, angle, 0, palmCenterNormalized) : image12.clone();
const palmCenterNormalized = [palmCenter[0] / image13.shape[2], palmCenter[1] / image13.shape[1]];
const rotatedImage = config3.hand.rotation ? tf11.image.rotateWithOffset(image13, angle, 0, palmCenterNormalized) : image13.clone();
const rotationMatrix = buildRotationMatrix2(-angle, palmCenter);
const newBox = useFreshBox ? this.getBoxForPalmLandmarks(currentBox.palmLandmarks, rotationMatrix) : currentBox;
const croppedInput = cutBoxFromImageAndResize2(newBox, rotatedImage, [this.inputSize, this.inputSize]);
@ -17101,13 +17101,13 @@ async function load7(config3) {
log("cached model:", model4.modelUrl);
return model4;
}
async function predict6(image12, config3) {
async function predict6(image13, config3) {
if (!model4)
return null;
if (!config3.body.enabled)
return null;
const imgSize = {width: image12.shape[2], height: image12.shape[1]};
const resize = tf13.image.resizeBilinear(image12, [model4.width, model4.height], false);
const imgSize = { width: image13.shape[2], height: image13.shape[1] };
const resize = tf13.image.resizeBilinear(image13, [model4.width, model4.height], false);
const normalize = tf13.div(resize, [255]);
resize.dispose();
const resT = await model4.predict(normalize);
@ -17134,7 +17134,7 @@ async function predict6(image12, config3) {
return [{ score, keypoints }];
}
// src/nanodet/nanodet.ts
// src/object/nanodet.ts
var nanodet_exports = {};
__export(nanodet_exports, {
load: () => load8,
@ -17142,7 +17142,7 @@ __export(nanodet_exports, {
});
var tf14 = __toModule(require_tfjs_esm());
// src/nanodet/labels.ts
// src/object/labels.ts
var labels = [
{ class: 1, label: "person" },
{ class: 2, label: "bicycle" },
@ -17226,7 +17226,7 @@ var labels = [
{ class: 80, label: "toothbrush" }
];
// src/nanodet/nanodet.ts
// src/object/nanodet.ts
var model5;
var last3 = [];
var skipped3 = Number.MAX_SAFE_INTEGER;
@ -17299,7 +17299,7 @@ async function process2(res, inputSize, outputShape, config3) {
});
}
res.forEach((t) => tf14.dispose(t));
const nmsBoxes = results.map((a) => a.boxRaw);
const nmsBoxes = results.map((a) => [a.boxRaw[1], a.boxRaw[0], a.boxRaw[3], a.boxRaw[2]]);
const nmsScores = results.map((a) => a.score);
let nmsIdx = [];
if (nmsBoxes && nmsBoxes.length > 0) {
@ -17310,7 +17310,7 @@ async function process2(res, inputSize, outputShape, config3) {
results = results.filter((a, idx) => nmsIdx.includes(idx)).sort((a, b) => b.score - a.score);
return results;
}
async function predict7(image12, config3) {
async function predict7(image13, config3) {
if (!model5)
return null;
if (skipped3 < config3.object.skipFrames && config3.skipFrame && last3.length > 0) {
@ -17319,8 +17319,8 @@ async function predict7(image12, config3) {
}
skipped3 = 0;
return new Promise(async (resolve) => {
const outputSize = [image12.shape[2], image12.shape[1]];
const resize = tf14.image.resizeBilinear(image12, [model5.inputSize, model5.inputSize], false);
const outputSize = [image13.shape[2], image13.shape[1]];
const resize = tf14.image.resizeBilinear(image13, [model5.inputSize, model5.inputSize], false);
const norm = resize.div(255);
const transpose = norm.transpose([0, 3, 1, 2]);
norm.dispose();
@ -17335,6 +17335,90 @@ async function predict7(image12, config3) {
});
}
// src/object/centernet.ts
var centernet_exports = {};
__export(centernet_exports, {
load: () => load9,
predict: () => predict8
});
var tf15 = __toModule(require_tfjs_esm());
var model6;
var last4 = [];
var skipped4 = Number.MAX_SAFE_INTEGER;
async function load9(config3) {
if (!model6) {
model6 = await tf15.loadGraphModel(join(config3.modelBasePath, config3.object.modelPath));
const inputs = Object.values(model6.modelSignature["inputs"]);
model6.inputSize = Array.isArray(inputs) ? parseInt(inputs[0].tensorShape.dim[2].size) : null;
if (!model6.inputSize)
throw new Error(`Human: Cannot determine model inputSize: ${config3.object.modelPath}`);
if (!model6 || !model6.modelUrl)
log("load model failed:", config3.object.modelPath);
else if (config3.debug)
log("load model:", model6.modelUrl);
} else if (config3.debug)
log("cached model:", model6.modelUrl);
return model6;
}
async function process3(res, inputSize, outputShape, config3) {
const results = [];
const detections = res.arraySync();
const squeezeT = tf15.squeeze(res);
res.dispose();
const arr = tf15.split(squeezeT, 6, 1);
squeezeT.dispose();
const stackT = tf15.stack([arr[1], arr[0], arr[3], arr[2]], 1);
const boxesT = stackT.squeeze();
const scoresT = arr[4].squeeze();
const classesT = arr[5].squeeze();
arr.forEach((t) => t.dispose());
const nmsT = await tf15.image.nonMaxSuppressionAsync(boxesT, scoresT, config3.object.maxDetected, config3.object.iouThreshold, config3.object.minConfidence);
boxesT.dispose();
scoresT.dispose();
classesT.dispose();
const nms = nmsT.dataSync();
nmsT.dispose();
for (const id of nms) {
const score = detections[0][id][4];
const classVal = detections[0][id][5];
const label = labels[classVal].label;
const boxRaw = [
detections[0][id][0] / inputSize,
detections[0][id][1] / inputSize,
detections[0][id][2] / inputSize,
detections[0][id][3] / inputSize
];
const box4 = [
Math.trunc(boxRaw[0] * outputShape[0]),
Math.trunc(boxRaw[1] * outputShape[1]),
Math.trunc(boxRaw[2] * outputShape[0]),
Math.trunc(boxRaw[3] * outputShape[1])
];
results.push({ score, class: classVal, label, box: box4, boxRaw });
}
return results;
}
async function predict8(image13, config3) {
if (!model6)
return null;
if (skipped4 < config3.object.skipFrames && config3.skipFrame && last4.length > 0) {
skipped4++;
return last4;
}
skipped4 = 0;
return new Promise(async (resolve) => {
const outputSize = [image13.shape[2], image13.shape[1]];
const resize = tf15.image.resizeBilinear(image13, [model6.inputSize, model6.inputSize], false);
let objectT;
if (config3.object.enabled)
objectT = model6.execute(resize, "tower_0/detections");
resize.dispose();
const obj = await process3(objectT, model6.inputSize, outputSize, config3);
last4 = obj;
resolve(obj);
});
}
// src/gesture/gesture.ts
var body = (res) => {
if (!res)
@ -17444,7 +17528,7 @@ var hand = (res) => {
};
// src/image/image.ts
var tf15 = __toModule(require_tfjs_esm());
var tf16 = __toModule(require_tfjs_esm());
// src/image/imagefx.js
function GLProgram(gl, vertexSource, fragmentSource) {
@ -17596,8 +17680,8 @@ function GLImageFilter(params) {
gl.uniform1f(_currentProgram.uniform.flipY, flipY ? -1 : 1);
gl.drawArrays(gl.TRIANGLES, 0, 6);
};
this.apply = function(image12) {
_resize(image12.width, image12.height);
this.apply = function(image13) {
_resize(image13.width, image13.height);
_drawCount = 0;
if (!_sourceTexture)
_sourceTexture = gl.createTexture();
@ -17606,7 +17690,7 @@ function GLImageFilter(params) {
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image12);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image13);
if (_filterChain.length === 0) {
_draw();
return _canvas;
@ -18157,16 +18241,16 @@ var maxSize = 2048;
var inCanvas;
var outCanvas;
var fx;
function process3(input, config3) {
function process4(input, config3) {
let tensor;
if (!input)
throw new Error("Human: Input is missing");
if (!(input instanceof tf15.Tensor) && !(typeof Image !== "undefined" && input instanceof Image) && !(typeof ImageData !== "undefined" && input instanceof ImageData) && !(typeof ImageBitmap !== "undefined" && input instanceof ImageBitmap) && !(typeof HTMLImageElement !== "undefined" && input instanceof HTMLImageElement) && !(typeof HTMLMediaElement !== "undefined" && input instanceof HTMLMediaElement) && !(typeof HTMLVideoElement !== "undefined" && input instanceof HTMLVideoElement) && !(typeof HTMLCanvasElement !== "undefined" && input instanceof HTMLCanvasElement) && !(typeof OffscreenCanvas !== "undefined" && input instanceof OffscreenCanvas)) {
if (!(input instanceof tf16.Tensor) && !(typeof Image !== "undefined" && input instanceof Image) && !(typeof ImageData !== "undefined" && input instanceof ImageData) && !(typeof ImageBitmap !== "undefined" && input instanceof ImageBitmap) && !(typeof HTMLImageElement !== "undefined" && input instanceof HTMLImageElement) && !(typeof HTMLMediaElement !== "undefined" && input instanceof HTMLMediaElement) && !(typeof HTMLVideoElement !== "undefined" && input instanceof HTMLVideoElement) && !(typeof HTMLCanvasElement !== "undefined" && input instanceof HTMLCanvasElement) && !(typeof OffscreenCanvas !== "undefined" && input instanceof OffscreenCanvas)) {
throw new Error("Human: Input type is not recognized");
}
if (input instanceof tf15.Tensor) {
if (input instanceof tf16.Tensor) {
if (input.shape && input.shape.length === 4 && input.shape[0] === 1 && input.shape[3] === 3)
tensor = tf15.clone(input);
tensor = tf16.clone(input);
else
throw new Error(`Human: Input tensor shape must be [1, height, width, 3] and instead was ${input.shape}`);
} else {
@ -18219,7 +18303,7 @@ function process3(input, config3) {
outCanvas.width = inCanvas == null ? void 0 : inCanvas.width;
if ((outCanvas == null ? void 0 : outCanvas.height) !== (inCanvas == null ? void 0 : inCanvas.height))
outCanvas.height = inCanvas == null ? void 0 : inCanvas.height;
fx = tf15.ENV.flags.IS_BROWSER ? new GLImageFilter({canvas: outCanvas}) : null;
fx = tf16.ENV.flags.IS_BROWSER ? new GLImageFilter({ canvas: outCanvas }) : null;
}
if (!fx)
return { tensor: null, canvas: inCanvas };
@ -18260,16 +18344,16 @@ function process3(input, config3) {
let pixels;
if (outCanvas.data) {
const shape = [outCanvas.height, outCanvas.width, 3];
pixels = tf15.tensor3d(outCanvas.data, shape, "int32");
pixels = tf16.tensor3d(outCanvas.data, shape, "int32");
} else if (outCanvas instanceof ImageData) {
pixels = tf15.browser.fromPixels(outCanvas);
pixels = tf16.browser.fromPixels(outCanvas);
} else if (config3.backend === "webgl" || config3.backend === "humangl") {
const tempCanvas = typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(targetWidth, targetHeight) : document.createElement("canvas");
tempCanvas.width = targetWidth;
tempCanvas.height = targetHeight;
const tempCtx = tempCanvas.getContext("2d");
tempCtx == null ? void 0 : tempCtx.drawImage(outCanvas, 0, 0);
pixels = tf15.browser.fromPixels(tempCanvas);
pixels = tf16.browser.fromPixels(tempCanvas);
} else {
const tempCanvas = typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(targetWidth, targetHeight) : document.createElement("canvas");
tempCanvas.width = targetWidth;
@ -18277,7 +18361,7 @@ function process3(input, config3) {
const tempCtx = tempCanvas.getContext("2d");
tempCtx == null ? void 0 : tempCtx.drawImage(outCanvas, 0, 0);
const data = tempCtx == null ? void 0 : tempCtx.getImageData(0, 0, targetWidth, targetHeight);
pixels = tf15.browser.fromPixels(data);
pixels = tf16.browser.fromPixels(data);
}
const casted = pixels.toFloat();
tensor = casted.expandDims(0);
@ -18311,7 +18395,7 @@ var options = {
roundRect: 28,
drawPoints: false,
drawLabels: true,
drawBoxes: false,
drawBoxes: true,
drawPolygons: true,
fillPolygons: false,
useDepth: true,
@ -19537,7 +19621,7 @@ var Human = class {
return null;
if (!input)
return "input is not defined";
if (this.tf.ENV.flags.IS_NODE && !(input instanceof tf16.Tensor))
if (this.tf.ENV.flags.IS_NODE && !(input instanceof tf17.Tensor))
return "input must be a tensor";
try {
this.tf.getBackend();
@ -19600,7 +19684,7 @@ var Human = class {
});
__privateAdd(this, _skipFrame, async (input) => {
if (this.config.cacheSensitivity === 0)
return true;
return false;
const resizeFact = 50;
const reduced = input.resizeBilinear([Math.trunc(input.shape[1] / resizeFact), Math.trunc(input.shape[2] / resizeFact)]);
const sumT = this.tf.sum(reduced);
@ -19673,8 +19757,8 @@ var Human = class {
if (!img)
return null;
let res;
if (typeof tf16["node"] !== "undefined") {
const data = tf16["node"].decodeJpeg(img);
if (typeof tf17["node"] !== "undefined") {
const data = tf17["node"].decodeJpeg(img);
const expanded = data.expandDims(0);
this.tf.dispose(data);
res = await this.detect(expanded, this.config);
@ -19685,7 +19769,7 @@ var Human = class {
}
return res;
});
this.tf = tf16;
this.tf = tf17;
this.draw = draw_exports;
this.version = version;
this.config = mergeDeep(config, userConfig);
@ -19707,16 +19791,18 @@ var Human = class {
emotion: null,
embedding: null,
nanodet: null,
centernet: null,
faceres: null
};
this.image = (input) => process3(input, this.config);
this.image = (input) => process4(input, this.config);
this.classes = {
facemesh: facemesh_exports,
emotion: emotion_exports,
faceres: faceres_exports,
body: this.config.body.modelPath.includes("posenet") ? posenet_exports : blazepose_exports,
hand: handpose_exports,
nanodet: nanodet_exports
nanodet: nanodet_exports,
centernet: centernet_exports
};
this.faceTriangulation = triangulation;
this.faceUVMap = uvmap;
@ -19762,6 +19848,7 @@ var Human = class {
this.models.posenet,
this.models.blazepose,
this.models.nanodet,
this.models.centernet,
this.models.faceres
] = await Promise.all([
this.models.face || (this.config.face.enabled ? load2(this.config) : null),
@ -19769,7 +19856,8 @@ var Human = class {
this.models.handpose || (this.config.hand.enabled ? load6(this.config) : null),
this.models.posenet || (this.config.body.enabled && this.config.body.modelPath.includes("posenet") ? load5(this.config) : null),
this.models.blazepose || (this.config.body.enabled && this.config.body.modelPath.includes("blazepose") ? load7(this.config) : null),
this.models.nanodet || (this.config.object.enabled ? load8(this.config) : null),
this.models.nanodet || (this.config.object.enabled && this.config.object.modelPath.includes("nanodet") ? load8(this.config) : null),
this.models.centernet || (this.config.object.enabled && this.config.object.modelPath.includes("centernet") ? load9(this.config) : null),
this.models.faceres || (this.config.face.enabled && this.config.face.description.enabled ? load4(this.config) : null)
]);
} else {
@ -19783,8 +19871,10 @@ var Human = class {
this.models.posenet = await load5(this.config);
if (this.config.body.enabled && !this.models.blazepose && this.config.body.modelPath.includes("blazepose"))
this.models.blazepose = await load7(this.config);
if (this.config.object.enabled && !this.models.nanodet)
if (this.config.object.enabled && !this.models.nanodet && this.config.object.modelPath.includes("nanodet"))
this.models.nanodet = await load8(this.config);
if (this.config.object.enabled && !this.models.centernet && this.config.object.modelPath.includes("centernet"))
this.models.centernet = await load9(this.config);
if (this.config.face.enabled && this.config.face.description.enabled && !this.models.faceres)
this.models.faceres = await load4(this.config);
}
@ -19812,8 +19902,8 @@ var Human = class {
await __privateGet(this, _checkBackend).call(this);
await this.load();
timeStamp = now();
const process4 = process3(input, this.config);
if (!process4 || !process4.tensor) {
const process5 = process4(input, this.config);
if (!process5 || !process5.tensor) {
log("could not convert input to tensor");
resolve({ error: "could not convert input to tensor" });
return;
@ -19821,7 +19911,7 @@ var Human = class {
this.perf.image = Math.trunc(now() - timeStamp);
this.analyze("Get Image:");
timeStamp = now();
this.config.skipFrame = await __privateGet(this, _skipFrame).call(this, process4.tensor);
this.config.skipFrame = await __privateGet(this, _skipFrame).call(this, process5.tensor);
if (!this.perf.frames)
this.perf.frames = 0;
if (!this.perf.cached)
@ -19837,13 +19927,13 @@ var Human = class {
let objectRes;
let current;
if (this.config.async) {
faceRes = this.config.face.enabled ? detectFace(this, process4.tensor) : [];
faceRes = this.config.face.enabled ? detectFace(this, process5.tensor) : [];
if (this.perf.face)
delete this.perf.face;
} else {
this.state = "run:face";
timeStamp = now();
faceRes = this.config.face.enabled ? await detectFace(this, process4.tensor) : [];
faceRes = this.config.face.enabled ? await detectFace(this, process5.tensor) : [];
current = Math.trunc(now() - timeStamp);
if (current > 0)
this.perf.face = current;
@ -19851,18 +19941,18 @@ var Human = class {
this.analyze("Start Body:");
if (this.config.async) {
if (this.config.body.modelPath.includes("posenet"))
bodyRes = this.config.body.enabled ? predict4(process4.tensor, this.config) : [];
bodyRes = this.config.body.enabled ? predict4(process5.tensor, this.config) : [];
else if (this.config.body.modelPath.includes("blazepose"))
bodyRes = this.config.body.enabled ? predict6(process4.tensor, this.config) : [];
bodyRes = this.config.body.enabled ? predict6(process5.tensor, this.config) : [];
if (this.perf.body)
delete this.perf.body;
} else {
this.state = "run:body";
timeStamp = now();
if (this.config.body.modelPath.includes("posenet"))
bodyRes = this.config.body.enabled ? await predict4(process4.tensor, this.config) : [];
bodyRes = this.config.body.enabled ? await predict4(process5.tensor, this.config) : [];
else if (this.config.body.modelPath.includes("blazepose"))
bodyRes = this.config.body.enabled ? await predict6(process4.tensor, this.config) : [];
bodyRes = this.config.body.enabled ? await predict6(process5.tensor, this.config) : [];
current = Math.trunc(now() - timeStamp);
if (current > 0)
this.perf.body = current;
@ -19870,13 +19960,13 @@ var Human = class {
this.analyze("End Body:");
this.analyze("Start Hand:");
if (this.config.async) {
handRes = this.config.hand.enabled ? predict5(process4.tensor, this.config) : [];
handRes = this.config.hand.enabled ? predict5(process5.tensor, this.config) : [];
if (this.perf.hand)
delete this.perf.hand;
} else {
this.state = "run:hand";
timeStamp = now();
handRes = this.config.hand.enabled ? await predict5(process4.tensor, this.config) : [];
handRes = this.config.hand.enabled ? await predict5(process5.tensor, this.config) : [];
current = Math.trunc(now() - timeStamp);
if (current > 0)
this.perf.hand = current;
@ -19884,13 +19974,19 @@ var Human = class {
this.analyze("End Hand:");
this.analyze("Start Object:");
if (this.config.async) {
objectRes = this.config.object.enabled ? predict7(process4.tensor, this.config) : [];
if (this.config.object.modelPath.includes("nanodet"))
objectRes = this.config.object.enabled ? predict7(process5.tensor, this.config) : [];
else if (this.config.object.modelPath.includes("centernet"))
objectRes = this.config.object.enabled ? predict8(process5.tensor, this.config) : [];
if (this.perf.object)
delete this.perf.object;
} else {
this.state = "run:object";
timeStamp = now();
objectRes = this.config.object.enabled ? await predict7(process4.tensor, this.config) : [];
if (this.config.object.modelPath.includes("nanodet"))
objectRes = this.config.object.enabled ? await predict7(process5.tensor, this.config) : [];
else if (this.config.object.modelPath.includes("centernet"))
objectRes = this.config.object.enabled ? await predict8(process5.tensor, this.config) : [];
current = Math.trunc(now() - timeStamp);
if (current > 0)
this.perf.object = current;
@ -19899,7 +19995,7 @@ var Human = class {
if (this.config.async) {
[faceRes, bodyRes, handRes, objectRes] = await Promise.all([faceRes, bodyRes, handRes, objectRes]);
}
tf16.dispose(process4.tensor);
tf17.dispose(process5.tensor);
let gestureRes = [];
if (this.config.gesture.enabled) {
timeStamp = now();
@ -19918,7 +20014,7 @@ var Human = class {
gesture: gestureRes,
object: objectRes,
performance: this.perf,
canvas: process4.canvas
canvas: process5.canvas
};
resolve(result);
});

View File

@ -128,7 +128,7 @@ var config = {
debug: true,
async: true,
warmup: "full",
cacheSensitivity: 4e-3,
cacheSensitivity: 5e-3,
filter: {
enabled: true,
width: 0,
@ -193,7 +193,7 @@ var config = {
hand: {
enabled: true,
rotation: false,
skipFrames: 12,
skipFrames: 32,
minConfidence: 0.1,
iouThreshold: 0.1,
maxDetected: 2,
@ -207,7 +207,7 @@ var config = {
},
object: {
enabled: false,
modelPath: "nanodet.json",
modelPath: "mb3-centernet.json",
minConfidence: 0.2,
iouThreshold: 0.4,
maxDetected: 10,
@ -237,7 +237,7 @@ function info() {
}
// src/human.ts
var tf16 = __toModule(require_tfjs_esm());
var tf17 = __toModule(require_tfjs_esm());
// src/tfjs/backend.ts
var tf = __toModule(require_tfjs_esm());
@ -339,16 +339,16 @@ function getBoxCenter(box4) {
box4.startPoint[1] + (box4.endPoint[1] - box4.startPoint[1]) / 2
];
}
function cutBoxFromImageAndResize(box4, image12, cropSize) {
const h = image12.shape[1];
const w = image12.shape[2];
function cutBoxFromImageAndResize(box4, image13, cropSize) {
const h = image13.shape[1];
const w = image13.shape[2];
const boxes = [[
box4.startPoint[1] / h,
box4.startPoint[0] / w,
box4.endPoint[1] / h,
box4.endPoint[0] / w
]];
return tf2.image.cropAndResize(image12, boxes, [0], cropSize);
return tf2.image.cropAndResize(image13, boxes, [0], cropSize);
}
function enlargeBox(box4, factor = 1.5) {
const center = getBoxCenter(box4);
@ -482,11 +482,11 @@ function decodeBounds(boxOutputs, anchors3, inputSize) {
return tf3.concat2d([startNormalized, endNormalized], concatAxis);
}
var BlazeFaceModel = class {
constructor(model6, config3) {
this.model = model6;
this.anchorsData = generateAnchors(model6.inputs[0].shape[1]);
constructor(model7, config3) {
this.model = model7;
this.anchorsData = generateAnchors(model7.inputs[0].shape[1]);
this.anchors = tf3.tensor2d(this.anchorsData);
this.inputSize = model6.inputs[0].shape[2];
this.inputSize = model7.inputs[0].shape[2];
this.config = config3;
}
async getBoundingBoxes(inputImage) {
@ -535,12 +535,12 @@ var BlazeFaceModel = class {
}
};
async function load(config3) {
const model6 = await tf3.loadGraphModel(join(config3.modelBasePath, config3.face.detector.modelPath), {fromTFHub: config3.face.detector.modelPath.includes("tfhub.dev")});
const blazeFace = new BlazeFaceModel(model6, config3);
if (!model6 || !model6.modelUrl)
const model7 = await tf3.loadGraphModel(join(config3.modelBasePath, config3.face.detector.modelPath), { fromTFHub: config3.face.detector.modelPath.includes("tfhub.dev") });
const blazeFace = new BlazeFaceModel(model7, config3);
if (!model7 || !model7.modelUrl)
log("load model failed:", config3.face.detector.modelPath);
else if (config3.debug)
log("load model:", model6.modelUrl);
log("load model:", model7.modelUrl);
return blazeFace;
}
@ -4175,7 +4175,7 @@ async function load3(config3) {
log("cached model:", model.modelUrl);
return model;
}
async function predict2(image12, config3, idx, count2) {
async function predict2(image13, config3, idx, count2) {
if (!model)
return null;
if (skipped < config3.face.emotion.skipFrames && config3.skipFrame && lastCount === count2 && last[idx] && last[idx].length > 0) {
@ -4184,7 +4184,7 @@ async function predict2(image12, config3, idx, count2) {
}
skipped = 0;
return new Promise(async (resolve) => {
const resize = tf6.image.resizeBilinear(image12, [model.inputs[0].shape[2], model.inputs[0].shape[1]], false);
const resize = tf6.image.resizeBilinear(image13, [model.inputs[0].shape[2], model.inputs[0].shape[1]], false);
const [red, green, blue] = tf6.split(resize, 3, 3);
resize.dispose();
const redNorm = tf6.mul(red, rgb[0]);
@ -4267,7 +4267,7 @@ function match(embedding, db, threshold = 0) {
return best;
}
function enhance(input) {
const image12 = tf7.tidy(() => {
const image13 = tf7.tidy(() => {
const tensor = input.image || input.tensor || input;
if (!(tensor instanceof tf7.Tensor))
return null;
@ -4276,9 +4276,9 @@ function enhance(input) {
const norm = crop.mul(255);
return norm;
});
return image12;
return image13;
}
async function predict3(image12, config3, idx, count2) {
async function predict3(image13, config3, idx, count2) {
var _a, _b;
if (!model2)
return null;
@ -4288,7 +4288,7 @@ async function predict3(image12, config3, idx, count2) {
}
skipped2 = 0;
return new Promise(async (resolve) => {
const enhanced = enhance(image12);
const enhanced = enhance(image13);
let resT;
const obj = {
age: 0,
@ -4845,16 +4845,16 @@ function getBoxCenter2(box4) {
box4.startPoint[1] + (box4.endPoint[1] - box4.startPoint[1]) / 2
];
}
function cutBoxFromImageAndResize2(box4, image12, cropSize) {
const h = image12.shape[1];
const w = image12.shape[2];
function cutBoxFromImageAndResize2(box4, image13, cropSize) {
const h = image13.shape[1];
const w = image13.shape[2];
const boxes = [[
box4.startPoint[1] / h,
box4.startPoint[0] / w,
box4.endPoint[1] / h,
box4.endPoint[0] / w
]];
return tf9.image.cropAndResize(image12, boxes, [0], cropSize);
return tf9.image.cropAndResize(image13, boxes, [0], cropSize);
}
function scaleBoxCoordinates2(box4, factor) {
const startPoint = [box4.startPoint[0] * factor[0], box4.startPoint[1] * factor[1]];
@ -16665,9 +16665,9 @@ var anchors = [
// src/handpose/handdetector.ts
var HandDetector = class {
constructor(model6) {
constructor(model7) {
var _a;
this.model = model6;
this.model = model7;
this.anchors = anchors.map((anchor) => [anchor.x, anchor.y]);
this.anchorsTensor = tf10.tensor2d(this.anchors);
this.inputSize = (_a = this.model) == null ? void 0 : _a.inputs[0].shape[2];
@ -16721,9 +16721,9 @@ var HandDetector = class {
async estimateHandBounds(input, config3) {
const inputHeight = input.shape[1];
const inputWidth = input.shape[2];
const image12 = tf10.tidy(() => input.resizeBilinear([this.inputSize, this.inputSize]).div(127.5).sub(1));
const predictions = await this.getBoxes(image12, config3);
image12.dispose();
const image13 = tf10.tidy(() => input.resizeBilinear([this.inputSize, this.inputSize]).div(127.5).sub(1));
const predictions = await this.getBoxes(image13, config3);
image13.dispose();
const hands = [];
if (!predictions || predictions.length === 0)
return hands;
@ -16868,11 +16868,11 @@ var HandPipeline = class {
coord[2]
]);
}
async estimateHands(image12, config3) {
async estimateHands(image13, config3) {
let useFreshBox = false;
let boxes;
if (this.skipped === 0 || this.skipped > config3.hand.skipFrames || !config3.hand.landmarks || !config3.skipFrame) {
boxes = await this.handDetector.estimateHandBounds(image12, config3);
boxes = await this.handDetector.estimateHandBounds(image13, config3);
this.skipped = 0;
}
if (config3.skipFrame)
@ -16891,8 +16891,8 @@ var HandPipeline = class {
if (config3.hand.landmarks) {
const angle = config3.hand.rotation ? computeRotation2(currentBox.palmLandmarks[palmLandmarksPalmBase], currentBox.palmLandmarks[palmLandmarksMiddleFingerBase]) : 0;
const palmCenter = getBoxCenter2(currentBox);
const palmCenterNormalized = [palmCenter[0] / image12.shape[2], palmCenter[1] / image12.shape[1]];
const rotatedImage = config3.hand.rotation ? tf11.image.rotateWithOffset(image12, angle, 0, palmCenterNormalized) : image12.clone();
const palmCenterNormalized = [palmCenter[0] / image13.shape[2], palmCenter[1] / image13.shape[1]];
const rotatedImage = config3.hand.rotation ? tf11.image.rotateWithOffset(image13, angle, 0, palmCenterNormalized) : image13.clone();
const rotationMatrix = buildRotationMatrix2(-angle, palmCenter);
const newBox = useFreshBox ? this.getBoxForPalmLandmarks(currentBox.palmLandmarks, rotationMatrix) : currentBox;
const croppedInput = cutBoxFromImageAndResize2(newBox, rotatedImage, [this.inputSize, this.inputSize]);
@ -17102,13 +17102,13 @@ async function load7(config3) {
log("cached model:", model4.modelUrl);
return model4;
}
async function predict6(image12, config3) {
async function predict6(image13, config3) {
if (!model4)
return null;
if (!config3.body.enabled)
return null;
const imgSize = {width: image12.shape[2], height: image12.shape[1]};
const resize = tf13.image.resizeBilinear(image12, [model4.width, model4.height], false);
const imgSize = { width: image13.shape[2], height: image13.shape[1] };
const resize = tf13.image.resizeBilinear(image13, [model4.width, model4.height], false);
const normalize = tf13.div(resize, [255]);
resize.dispose();
const resT = await model4.predict(normalize);
@ -17135,7 +17135,7 @@ async function predict6(image12, config3) {
return [{ score, keypoints }];
}
// src/nanodet/nanodet.ts
// src/object/nanodet.ts
var nanodet_exports = {};
__export(nanodet_exports, {
load: () => load8,
@ -17143,7 +17143,7 @@ __export(nanodet_exports, {
});
var tf14 = __toModule(require_tfjs_esm());
// src/nanodet/labels.ts
// src/object/labels.ts
var labels = [
{ class: 1, label: "person" },
{ class: 2, label: "bicycle" },
@ -17227,7 +17227,7 @@ var labels = [
{ class: 80, label: "toothbrush" }
];
// src/nanodet/nanodet.ts
// src/object/nanodet.ts
var model5;
var last3 = [];
var skipped3 = Number.MAX_SAFE_INTEGER;
@ -17300,7 +17300,7 @@ async function process2(res, inputSize, outputShape, config3) {
});
}
res.forEach((t) => tf14.dispose(t));
const nmsBoxes = results.map((a) => a.boxRaw);
const nmsBoxes = results.map((a) => [a.boxRaw[1], a.boxRaw[0], a.boxRaw[3], a.boxRaw[2]]);
const nmsScores = results.map((a) => a.score);
let nmsIdx = [];
if (nmsBoxes && nmsBoxes.length > 0) {
@ -17311,7 +17311,7 @@ async function process2(res, inputSize, outputShape, config3) {
results = results.filter((a, idx) => nmsIdx.includes(idx)).sort((a, b) => b.score - a.score);
return results;
}
async function predict7(image12, config3) {
async function predict7(image13, config3) {
if (!model5)
return null;
if (skipped3 < config3.object.skipFrames && config3.skipFrame && last3.length > 0) {
@ -17320,8 +17320,8 @@ async function predict7(image12, config3) {
}
skipped3 = 0;
return new Promise(async (resolve) => {
const outputSize = [image12.shape[2], image12.shape[1]];
const resize = tf14.image.resizeBilinear(image12, [model5.inputSize, model5.inputSize], false);
const outputSize = [image13.shape[2], image13.shape[1]];
const resize = tf14.image.resizeBilinear(image13, [model5.inputSize, model5.inputSize], false);
const norm = resize.div(255);
const transpose = norm.transpose([0, 3, 1, 2]);
norm.dispose();
@ -17336,6 +17336,90 @@ async function predict7(image12, config3) {
});
}
// src/object/centernet.ts
var centernet_exports = {};
__export(centernet_exports, {
load: () => load9,
predict: () => predict8
});
var tf15 = __toModule(require_tfjs_esm());
var model6;
var last4 = [];
var skipped4 = Number.MAX_SAFE_INTEGER;
async function load9(config3) {
if (!model6) {
model6 = await tf15.loadGraphModel(join(config3.modelBasePath, config3.object.modelPath));
const inputs = Object.values(model6.modelSignature["inputs"]);
model6.inputSize = Array.isArray(inputs) ? parseInt(inputs[0].tensorShape.dim[2].size) : null;
if (!model6.inputSize)
throw new Error(`Human: Cannot determine model inputSize: ${config3.object.modelPath}`);
if (!model6 || !model6.modelUrl)
log("load model failed:", config3.object.modelPath);
else if (config3.debug)
log("load model:", model6.modelUrl);
} else if (config3.debug)
log("cached model:", model6.modelUrl);
return model6;
}
async function process3(res, inputSize, outputShape, config3) {
const results = [];
const detections = res.arraySync();
const squeezeT = tf15.squeeze(res);
res.dispose();
const arr = tf15.split(squeezeT, 6, 1);
squeezeT.dispose();
const stackT = tf15.stack([arr[1], arr[0], arr[3], arr[2]], 1);
const boxesT = stackT.squeeze();
const scoresT = arr[4].squeeze();
const classesT = arr[5].squeeze();
arr.forEach((t) => t.dispose());
const nmsT = await tf15.image.nonMaxSuppressionAsync(boxesT, scoresT, config3.object.maxDetected, config3.object.iouThreshold, config3.object.minConfidence);
boxesT.dispose();
scoresT.dispose();
classesT.dispose();
const nms = nmsT.dataSync();
nmsT.dispose();
for (const id of nms) {
const score = detections[0][id][4];
const classVal = detections[0][id][5];
const label = labels[classVal].label;
const boxRaw = [
detections[0][id][0] / inputSize,
detections[0][id][1] / inputSize,
detections[0][id][2] / inputSize,
detections[0][id][3] / inputSize
];
const box4 = [
Math.trunc(boxRaw[0] * outputShape[0]),
Math.trunc(boxRaw[1] * outputShape[1]),
Math.trunc(boxRaw[2] * outputShape[0]),
Math.trunc(boxRaw[3] * outputShape[1])
];
results.push({ score, class: classVal, label, box: box4, boxRaw });
}
return results;
}
async function predict8(image13, config3) {
if (!model6)
return null;
if (skipped4 < config3.object.skipFrames && config3.skipFrame && last4.length > 0) {
skipped4++;
return last4;
}
skipped4 = 0;
return new Promise(async (resolve) => {
const outputSize = [image13.shape[2], image13.shape[1]];
const resize = tf15.image.resizeBilinear(image13, [model6.inputSize, model6.inputSize], false);
let objectT;
if (config3.object.enabled)
objectT = model6.execute(resize, "tower_0/detections");
resize.dispose();
const obj = await process3(objectT, model6.inputSize, outputSize, config3);
last4 = obj;
resolve(obj);
});
}
// src/gesture/gesture.ts
var body = (res) => {
if (!res)
@ -17445,7 +17529,7 @@ var hand = (res) => {
};
// src/image/image.ts
var tf15 = __toModule(require_tfjs_esm());
var tf16 = __toModule(require_tfjs_esm());
// src/image/imagefx.js
function GLProgram(gl, vertexSource, fragmentSource) {
@ -17597,8 +17681,8 @@ function GLImageFilter(params) {
gl.uniform1f(_currentProgram.uniform.flipY, flipY ? -1 : 1);
gl.drawArrays(gl.TRIANGLES, 0, 6);
};
this.apply = function(image12) {
_resize(image12.width, image12.height);
this.apply = function(image13) {
_resize(image13.width, image13.height);
_drawCount = 0;
if (!_sourceTexture)
_sourceTexture = gl.createTexture();
@ -17607,7 +17691,7 @@ function GLImageFilter(params) {
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image12);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image13);
if (_filterChain.length === 0) {
_draw();
return _canvas;
@ -18158,16 +18242,16 @@ var maxSize = 2048;
var inCanvas;
var outCanvas;
var fx;
function process3(input, config3) {
function process4(input, config3) {
let tensor;
if (!input)
throw new Error("Human: Input is missing");
if (!(input instanceof tf15.Tensor) && !(typeof Image !== "undefined" && input instanceof Image) && !(typeof ImageData !== "undefined" && input instanceof ImageData) && !(typeof ImageBitmap !== "undefined" && input instanceof ImageBitmap) && !(typeof HTMLImageElement !== "undefined" && input instanceof HTMLImageElement) && !(typeof HTMLMediaElement !== "undefined" && input instanceof HTMLMediaElement) && !(typeof HTMLVideoElement !== "undefined" && input instanceof HTMLVideoElement) && !(typeof HTMLCanvasElement !== "undefined" && input instanceof HTMLCanvasElement) && !(typeof OffscreenCanvas !== "undefined" && input instanceof OffscreenCanvas)) {
if (!(input instanceof tf16.Tensor) && !(typeof Image !== "undefined" && input instanceof Image) && !(typeof ImageData !== "undefined" && input instanceof ImageData) && !(typeof ImageBitmap !== "undefined" && input instanceof ImageBitmap) && !(typeof HTMLImageElement !== "undefined" && input instanceof HTMLImageElement) && !(typeof HTMLMediaElement !== "undefined" && input instanceof HTMLMediaElement) && !(typeof HTMLVideoElement !== "undefined" && input instanceof HTMLVideoElement) && !(typeof HTMLCanvasElement !== "undefined" && input instanceof HTMLCanvasElement) && !(typeof OffscreenCanvas !== "undefined" && input instanceof OffscreenCanvas)) {
throw new Error("Human: Input type is not recognized");
}
if (input instanceof tf15.Tensor) {
if (input instanceof tf16.Tensor) {
if (input.shape && input.shape.length === 4 && input.shape[0] === 1 && input.shape[3] === 3)
tensor = tf15.clone(input);
tensor = tf16.clone(input);
else
throw new Error(`Human: Input tensor shape must be [1, height, width, 3] and instead was ${input.shape}`);
} else {
@ -18220,7 +18304,7 @@ function process3(input, config3) {
outCanvas.width = inCanvas == null ? void 0 : inCanvas.width;
if ((outCanvas == null ? void 0 : outCanvas.height) !== (inCanvas == null ? void 0 : inCanvas.height))
outCanvas.height = inCanvas == null ? void 0 : inCanvas.height;
fx = tf15.ENV.flags.IS_BROWSER ? new GLImageFilter({canvas: outCanvas}) : null;
fx = tf16.ENV.flags.IS_BROWSER ? new GLImageFilter({ canvas: outCanvas }) : null;
}
if (!fx)
return { tensor: null, canvas: inCanvas };
@ -18261,16 +18345,16 @@ function process3(input, config3) {
let pixels;
if (outCanvas.data) {
const shape = [outCanvas.height, outCanvas.width, 3];
pixels = tf15.tensor3d(outCanvas.data, shape, "int32");
pixels = tf16.tensor3d(outCanvas.data, shape, "int32");
} else if (outCanvas instanceof ImageData) {
pixels = tf15.browser.fromPixels(outCanvas);
pixels = tf16.browser.fromPixels(outCanvas);
} else if (config3.backend === "webgl" || config3.backend === "humangl") {
const tempCanvas = typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(targetWidth, targetHeight) : document.createElement("canvas");
tempCanvas.width = targetWidth;
tempCanvas.height = targetHeight;
const tempCtx = tempCanvas.getContext("2d");
tempCtx == null ? void 0 : tempCtx.drawImage(outCanvas, 0, 0);
pixels = tf15.browser.fromPixels(tempCanvas);
pixels = tf16.browser.fromPixels(tempCanvas);
} else {
const tempCanvas = typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(targetWidth, targetHeight) : document.createElement("canvas");
tempCanvas.width = targetWidth;
@ -18278,7 +18362,7 @@ function process3(input, config3) {
const tempCtx = tempCanvas.getContext("2d");
tempCtx == null ? void 0 : tempCtx.drawImage(outCanvas, 0, 0);
const data = tempCtx == null ? void 0 : tempCtx.getImageData(0, 0, targetWidth, targetHeight);
pixels = tf15.browser.fromPixels(data);
pixels = tf16.browser.fromPixels(data);
}
const casted = pixels.toFloat();
tensor = casted.expandDims(0);
@ -18312,7 +18396,7 @@ var options = {
roundRect: 28,
drawPoints: false,
drawLabels: true,
drawBoxes: false,
drawBoxes: true,
drawPolygons: true,
fillPolygons: false,
useDepth: true,
@ -19538,7 +19622,7 @@ var Human = class {
return null;
if (!input)
return "input is not defined";
if (this.tf.ENV.flags.IS_NODE && !(input instanceof tf16.Tensor))
if (this.tf.ENV.flags.IS_NODE && !(input instanceof tf17.Tensor))
return "input must be a tensor";
try {
this.tf.getBackend();
@ -19601,7 +19685,7 @@ var Human = class {
});
__privateAdd(this, _skipFrame, async (input) => {
if (this.config.cacheSensitivity === 0)
return true;
return false;
const resizeFact = 50;
const reduced = input.resizeBilinear([Math.trunc(input.shape[1] / resizeFact), Math.trunc(input.shape[2] / resizeFact)]);
const sumT = this.tf.sum(reduced);
@ -19674,8 +19758,8 @@ var Human = class {
if (!img)
return null;
let res;
if (typeof tf16["node"] !== "undefined") {
const data = tf16["node"].decodeJpeg(img);
if (typeof tf17["node"] !== "undefined") {
const data = tf17["node"].decodeJpeg(img);
const expanded = data.expandDims(0);
this.tf.dispose(data);
res = await this.detect(expanded, this.config);
@ -19686,7 +19770,7 @@ var Human = class {
}
return res;
});
this.tf = tf16;
this.tf = tf17;
this.draw = draw_exports;
this.version = version;
this.config = mergeDeep(config, userConfig);
@ -19708,16 +19792,18 @@ var Human = class {
emotion: null,
embedding: null,
nanodet: null,
centernet: null,
faceres: null
};
this.image = (input) => process3(input, this.config);
this.image = (input) => process4(input, this.config);
this.classes = {
facemesh: facemesh_exports,
emotion: emotion_exports,
faceres: faceres_exports,
body: this.config.body.modelPath.includes("posenet") ? posenet_exports : blazepose_exports,
hand: handpose_exports,
nanodet: nanodet_exports
nanodet: nanodet_exports,
centernet: centernet_exports
};
this.faceTriangulation = triangulation;
this.faceUVMap = uvmap;
@ -19763,6 +19849,7 @@ var Human = class {
this.models.posenet,
this.models.blazepose,
this.models.nanodet,
this.models.centernet,
this.models.faceres
] = await Promise.all([
this.models.face || (this.config.face.enabled ? load2(this.config) : null),
@ -19770,7 +19857,8 @@ var Human = class {
this.models.handpose || (this.config.hand.enabled ? load6(this.config) : null),
this.models.posenet || (this.config.body.enabled && this.config.body.modelPath.includes("posenet") ? load5(this.config) : null),
this.models.blazepose || (this.config.body.enabled && this.config.body.modelPath.includes("blazepose") ? load7(this.config) : null),
this.models.nanodet || (this.config.object.enabled ? load8(this.config) : null),
this.models.nanodet || (this.config.object.enabled && this.config.object.modelPath.includes("nanodet") ? load8(this.config) : null),
this.models.centernet || (this.config.object.enabled && this.config.object.modelPath.includes("centernet") ? load9(this.config) : null),
this.models.faceres || (this.config.face.enabled && this.config.face.description.enabled ? load4(this.config) : null)
]);
} else {
@ -19784,8 +19872,10 @@ var Human = class {
this.models.posenet = await load5(this.config);
if (this.config.body.enabled && !this.models.blazepose && this.config.body.modelPath.includes("blazepose"))
this.models.blazepose = await load7(this.config);
if (this.config.object.enabled && !this.models.nanodet)
if (this.config.object.enabled && !this.models.nanodet && this.config.object.modelPath.includes("nanodet"))
this.models.nanodet = await load8(this.config);
if (this.config.object.enabled && !this.models.centernet && this.config.object.modelPath.includes("centernet"))
this.models.centernet = await load9(this.config);
if (this.config.face.enabled && this.config.face.description.enabled && !this.models.faceres)
this.models.faceres = await load4(this.config);
}
@ -19813,8 +19903,8 @@ var Human = class {
await __privateGet(this, _checkBackend).call(this);
await this.load();
timeStamp = now();
const process4 = process3(input, this.config);
if (!process4 || !process4.tensor) {
const process5 = process4(input, this.config);
if (!process5 || !process5.tensor) {
log("could not convert input to tensor");
resolve({ error: "could not convert input to tensor" });
return;
@ -19822,7 +19912,7 @@ var Human = class {
this.perf.image = Math.trunc(now() - timeStamp);
this.analyze("Get Image:");
timeStamp = now();
this.config.skipFrame = await __privateGet(this, _skipFrame).call(this, process4.tensor);
this.config.skipFrame = await __privateGet(this, _skipFrame).call(this, process5.tensor);
if (!this.perf.frames)
this.perf.frames = 0;
if (!this.perf.cached)
@ -19838,13 +19928,13 @@ var Human = class {
let objectRes;
let current;
if (this.config.async) {
faceRes = this.config.face.enabled ? detectFace(this, process4.tensor) : [];
faceRes = this.config.face.enabled ? detectFace(this, process5.tensor) : [];
if (this.perf.face)
delete this.perf.face;
} else {
this.state = "run:face";
timeStamp = now();
faceRes = this.config.face.enabled ? await detectFace(this, process4.tensor) : [];
faceRes = this.config.face.enabled ? await detectFace(this, process5.tensor) : [];
current = Math.trunc(now() - timeStamp);
if (current > 0)
this.perf.face = current;
@ -19852,18 +19942,18 @@ var Human = class {
this.analyze("Start Body:");
if (this.config.async) {
if (this.config.body.modelPath.includes("posenet"))
bodyRes = this.config.body.enabled ? predict4(process4.tensor, this.config) : [];
bodyRes = this.config.body.enabled ? predict4(process5.tensor, this.config) : [];
else if (this.config.body.modelPath.includes("blazepose"))
bodyRes = this.config.body.enabled ? predict6(process4.tensor, this.config) : [];
bodyRes = this.config.body.enabled ? predict6(process5.tensor, this.config) : [];
if (this.perf.body)
delete this.perf.body;
} else {
this.state = "run:body";
timeStamp = now();
if (this.config.body.modelPath.includes("posenet"))
bodyRes = this.config.body.enabled ? await predict4(process4.tensor, this.config) : [];
bodyRes = this.config.body.enabled ? await predict4(process5.tensor, this.config) : [];
else if (this.config.body.modelPath.includes("blazepose"))
bodyRes = this.config.body.enabled ? await predict6(process4.tensor, this.config) : [];
bodyRes = this.config.body.enabled ? await predict6(process5.tensor, this.config) : [];
current = Math.trunc(now() - timeStamp);
if (current > 0)
this.perf.body = current;
@ -19871,13 +19961,13 @@ var Human = class {
this.analyze("End Body:");
this.analyze("Start Hand:");
if (this.config.async) {
handRes = this.config.hand.enabled ? predict5(process4.tensor, this.config) : [];
handRes = this.config.hand.enabled ? predict5(process5.tensor, this.config) : [];
if (this.perf.hand)
delete this.perf.hand;
} else {
this.state = "run:hand";
timeStamp = now();
handRes = this.config.hand.enabled ? await predict5(process4.tensor, this.config) : [];
handRes = this.config.hand.enabled ? await predict5(process5.tensor, this.config) : [];
current = Math.trunc(now() - timeStamp);
if (current > 0)
this.perf.hand = current;
@ -19885,13 +19975,19 @@ var Human = class {
this.analyze("End Hand:");
this.analyze("Start Object:");
if (this.config.async) {
objectRes = this.config.object.enabled ? predict7(process4.tensor, this.config) : [];
if (this.config.object.modelPath.includes("nanodet"))
objectRes = this.config.object.enabled ? predict7(process5.tensor, this.config) : [];
else if (this.config.object.modelPath.includes("centernet"))
objectRes = this.config.object.enabled ? predict8(process5.tensor, this.config) : [];
if (this.perf.object)
delete this.perf.object;
} else {
this.state = "run:object";
timeStamp = now();
objectRes = this.config.object.enabled ? await predict7(process4.tensor, this.config) : [];
if (this.config.object.modelPath.includes("nanodet"))
objectRes = this.config.object.enabled ? await predict7(process5.tensor, this.config) : [];
else if (this.config.object.modelPath.includes("centernet"))
objectRes = this.config.object.enabled ? await predict8(process5.tensor, this.config) : [];
current = Math.trunc(now() - timeStamp);
if (current > 0)
this.perf.object = current;
@ -19900,7 +19996,7 @@ var Human = class {
if (this.config.async) {
[faceRes, bodyRes, handRes, objectRes] = await Promise.all([faceRes, bodyRes, handRes, objectRes]);
}
tf16.dispose(process4.tensor);
tf17.dispose(process5.tensor);
let gestureRes = [];
if (this.config.gesture.enabled) {
timeStamp = now();
@ -19919,7 +20015,7 @@ var Human = class {
gesture: gestureRes,
object: objectRes,
performance: this.perf,
canvas: process4.canvas
canvas: process5.canvas
};
resolve(result);
});

262
dist/human.node.js vendored
View File

@ -127,7 +127,7 @@ var config = {
debug: true,
async: true,
warmup: "full",
cacheSensitivity: 4e-3,
cacheSensitivity: 5e-3,
filter: {
enabled: true,
width: 0,
@ -192,7 +192,7 @@ var config = {
hand: {
enabled: true,
rotation: false,
skipFrames: 12,
skipFrames: 32,
minConfidence: 0.1,
iouThreshold: 0.1,
maxDetected: 2,
@ -206,7 +206,7 @@ var config = {
},
object: {
enabled: false,
modelPath: "nanodet.json",
modelPath: "mb3-centernet.json",
minConfidence: 0.2,
iouThreshold: 0.4,
maxDetected: 10,
@ -236,7 +236,7 @@ function info() {
}
// src/human.ts
var tf16 = __toModule(require_tfjs_esm());
var tf17 = __toModule(require_tfjs_esm());
// src/tfjs/backend.ts
var tf = __toModule(require_tfjs_esm());
@ -338,16 +338,16 @@ function getBoxCenter(box4) {
box4.startPoint[1] + (box4.endPoint[1] - box4.startPoint[1]) / 2
];
}
function cutBoxFromImageAndResize(box4, image12, cropSize) {
const h = image12.shape[1];
const w = image12.shape[2];
function cutBoxFromImageAndResize(box4, image13, cropSize) {
const h = image13.shape[1];
const w = image13.shape[2];
const boxes = [[
box4.startPoint[1] / h,
box4.startPoint[0] / w,
box4.endPoint[1] / h,
box4.endPoint[0] / w
]];
return tf2.image.cropAndResize(image12, boxes, [0], cropSize);
return tf2.image.cropAndResize(image13, boxes, [0], cropSize);
}
function enlargeBox(box4, factor = 1.5) {
const center = getBoxCenter(box4);
@ -481,11 +481,11 @@ function decodeBounds(boxOutputs, anchors3, inputSize) {
return tf3.concat2d([startNormalized, endNormalized], concatAxis);
}
var BlazeFaceModel = class {
constructor(model6, config3) {
this.model = model6;
this.anchorsData = generateAnchors(model6.inputs[0].shape[1]);
constructor(model7, config3) {
this.model = model7;
this.anchorsData = generateAnchors(model7.inputs[0].shape[1]);
this.anchors = tf3.tensor2d(this.anchorsData);
this.inputSize = model6.inputs[0].shape[2];
this.inputSize = model7.inputs[0].shape[2];
this.config = config3;
}
async getBoundingBoxes(inputImage) {
@ -534,12 +534,12 @@ var BlazeFaceModel = class {
}
};
async function load(config3) {
const model6 = await tf3.loadGraphModel(join(config3.modelBasePath, config3.face.detector.modelPath), {fromTFHub: config3.face.detector.modelPath.includes("tfhub.dev")});
const blazeFace = new BlazeFaceModel(model6, config3);
if (!model6 || !model6.modelUrl)
const model7 = await tf3.loadGraphModel(join(config3.modelBasePath, config3.face.detector.modelPath), { fromTFHub: config3.face.detector.modelPath.includes("tfhub.dev") });
const blazeFace = new BlazeFaceModel(model7, config3);
if (!model7 || !model7.modelUrl)
log("load model failed:", config3.face.detector.modelPath);
else if (config3.debug)
log("load model:", model6.modelUrl);
log("load model:", model7.modelUrl);
return blazeFace;
}
@ -4174,7 +4174,7 @@ async function load3(config3) {
log("cached model:", model.modelUrl);
return model;
}
async function predict2(image12, config3, idx, count2) {
async function predict2(image13, config3, idx, count2) {
if (!model)
return null;
if (skipped < config3.face.emotion.skipFrames && config3.skipFrame && lastCount === count2 && last[idx] && last[idx].length > 0) {
@ -4183,7 +4183,7 @@ async function predict2(image12, config3, idx, count2) {
}
skipped = 0;
return new Promise(async (resolve) => {
const resize = tf6.image.resizeBilinear(image12, [model.inputs[0].shape[2], model.inputs[0].shape[1]], false);
const resize = tf6.image.resizeBilinear(image13, [model.inputs[0].shape[2], model.inputs[0].shape[1]], false);
const [red, green, blue] = tf6.split(resize, 3, 3);
resize.dispose();
const redNorm = tf6.mul(red, rgb[0]);
@ -4266,7 +4266,7 @@ function match(embedding, db, threshold = 0) {
return best;
}
function enhance(input) {
const image12 = tf7.tidy(() => {
const image13 = tf7.tidy(() => {
const tensor = input.image || input.tensor || input;
if (!(tensor instanceof tf7.Tensor))
return null;
@ -4275,9 +4275,9 @@ function enhance(input) {
const norm = crop.mul(255);
return norm;
});
return image12;
return image13;
}
async function predict3(image12, config3, idx, count2) {
async function predict3(image13, config3, idx, count2) {
var _a, _b;
if (!model2)
return null;
@ -4287,7 +4287,7 @@ async function predict3(image12, config3, idx, count2) {
}
skipped2 = 0;
return new Promise(async (resolve) => {
const enhanced = enhance(image12);
const enhanced = enhance(image13);
let resT;
const obj = {
age: 0,
@ -4844,16 +4844,16 @@ function getBoxCenter2(box4) {
box4.startPoint[1] + (box4.endPoint[1] - box4.startPoint[1]) / 2
];
}
function cutBoxFromImageAndResize2(box4, image12, cropSize) {
const h = image12.shape[1];
const w = image12.shape[2];
function cutBoxFromImageAndResize2(box4, image13, cropSize) {
const h = image13.shape[1];
const w = image13.shape[2];
const boxes = [[
box4.startPoint[1] / h,
box4.startPoint[0] / w,
box4.endPoint[1] / h,
box4.endPoint[0] / w
]];
return tf9.image.cropAndResize(image12, boxes, [0], cropSize);
return tf9.image.cropAndResize(image13, boxes, [0], cropSize);
}
function scaleBoxCoordinates2(box4, factor) {
const startPoint = [box4.startPoint[0] * factor[0], box4.startPoint[1] * factor[1]];
@ -16664,9 +16664,9 @@ var anchors = [
// src/handpose/handdetector.ts
var HandDetector = class {
constructor(model6) {
constructor(model7) {
var _a;
this.model = model6;
this.model = model7;
this.anchors = anchors.map((anchor) => [anchor.x, anchor.y]);
this.anchorsTensor = tf10.tensor2d(this.anchors);
this.inputSize = (_a = this.model) == null ? void 0 : _a.inputs[0].shape[2];
@ -16720,9 +16720,9 @@ var HandDetector = class {
async estimateHandBounds(input, config3) {
const inputHeight = input.shape[1];
const inputWidth = input.shape[2];
const image12 = tf10.tidy(() => input.resizeBilinear([this.inputSize, this.inputSize]).div(127.5).sub(1));
const predictions = await this.getBoxes(image12, config3);
image12.dispose();
const image13 = tf10.tidy(() => input.resizeBilinear([this.inputSize, this.inputSize]).div(127.5).sub(1));
const predictions = await this.getBoxes(image13, config3);
image13.dispose();
const hands = [];
if (!predictions || predictions.length === 0)
return hands;
@ -16867,11 +16867,11 @@ var HandPipeline = class {
coord[2]
]);
}
async estimateHands(image12, config3) {
async estimateHands(image13, config3) {
let useFreshBox = false;
let boxes;
if (this.skipped === 0 || this.skipped > config3.hand.skipFrames || !config3.hand.landmarks || !config3.skipFrame) {
boxes = await this.handDetector.estimateHandBounds(image12, config3);
boxes = await this.handDetector.estimateHandBounds(image13, config3);
this.skipped = 0;
}
if (config3.skipFrame)
@ -16890,8 +16890,8 @@ var HandPipeline = class {
if (config3.hand.landmarks) {
const angle = config3.hand.rotation ? computeRotation2(currentBox.palmLandmarks[palmLandmarksPalmBase], currentBox.palmLandmarks[palmLandmarksMiddleFingerBase]) : 0;
const palmCenter = getBoxCenter2(currentBox);
const palmCenterNormalized = [palmCenter[0] / image12.shape[2], palmCenter[1] / image12.shape[1]];
const rotatedImage = config3.hand.rotation ? tf11.image.rotateWithOffset(image12, angle, 0, palmCenterNormalized) : image12.clone();
const palmCenterNormalized = [palmCenter[0] / image13.shape[2], palmCenter[1] / image13.shape[1]];
const rotatedImage = config3.hand.rotation ? tf11.image.rotateWithOffset(image13, angle, 0, palmCenterNormalized) : image13.clone();
const rotationMatrix = buildRotationMatrix2(-angle, palmCenter);
const newBox = useFreshBox ? this.getBoxForPalmLandmarks(currentBox.palmLandmarks, rotationMatrix) : currentBox;
const croppedInput = cutBoxFromImageAndResize2(newBox, rotatedImage, [this.inputSize, this.inputSize]);
@ -17101,13 +17101,13 @@ async function load7(config3) {
log("cached model:", model4.modelUrl);
return model4;
}
async function predict6(image12, config3) {
async function predict6(image13, config3) {
if (!model4)
return null;
if (!config3.body.enabled)
return null;
const imgSize = {width: image12.shape[2], height: image12.shape[1]};
const resize = tf13.image.resizeBilinear(image12, [model4.width, model4.height], false);
const imgSize = { width: image13.shape[2], height: image13.shape[1] };
const resize = tf13.image.resizeBilinear(image13, [model4.width, model4.height], false);
const normalize = tf13.div(resize, [255]);
resize.dispose();
const resT = await model4.predict(normalize);
@ -17134,7 +17134,7 @@ async function predict6(image12, config3) {
return [{ score, keypoints }];
}
// src/nanodet/nanodet.ts
// src/object/nanodet.ts
var nanodet_exports = {};
__export(nanodet_exports, {
load: () => load8,
@ -17142,7 +17142,7 @@ __export(nanodet_exports, {
});
var tf14 = __toModule(require_tfjs_esm());
// src/nanodet/labels.ts
// src/object/labels.ts
var labels = [
{ class: 1, label: "person" },
{ class: 2, label: "bicycle" },
@ -17226,7 +17226,7 @@ var labels = [
{ class: 80, label: "toothbrush" }
];
// src/nanodet/nanodet.ts
// src/object/nanodet.ts
var model5;
var last3 = [];
var skipped3 = Number.MAX_SAFE_INTEGER;
@ -17299,7 +17299,7 @@ async function process2(res, inputSize, outputShape, config3) {
});
}
res.forEach((t) => tf14.dispose(t));
const nmsBoxes = results.map((a) => a.boxRaw);
const nmsBoxes = results.map((a) => [a.boxRaw[1], a.boxRaw[0], a.boxRaw[3], a.boxRaw[2]]);
const nmsScores = results.map((a) => a.score);
let nmsIdx = [];
if (nmsBoxes && nmsBoxes.length > 0) {
@ -17310,7 +17310,7 @@ async function process2(res, inputSize, outputShape, config3) {
results = results.filter((a, idx) => nmsIdx.includes(idx)).sort((a, b) => b.score - a.score);
return results;
}
async function predict7(image12, config3) {
async function predict7(image13, config3) {
if (!model5)
return null;
if (skipped3 < config3.object.skipFrames && config3.skipFrame && last3.length > 0) {
@ -17319,8 +17319,8 @@ async function predict7(image12, config3) {
}
skipped3 = 0;
return new Promise(async (resolve) => {
const outputSize = [image12.shape[2], image12.shape[1]];
const resize = tf14.image.resizeBilinear(image12, [model5.inputSize, model5.inputSize], false);
const outputSize = [image13.shape[2], image13.shape[1]];
const resize = tf14.image.resizeBilinear(image13, [model5.inputSize, model5.inputSize], false);
const norm = resize.div(255);
const transpose = norm.transpose([0, 3, 1, 2]);
norm.dispose();
@ -17335,6 +17335,90 @@ async function predict7(image12, config3) {
});
}
// src/object/centernet.ts
var centernet_exports = {};
__export(centernet_exports, {
load: () => load9,
predict: () => predict8
});
var tf15 = __toModule(require_tfjs_esm());
var model6;
var last4 = [];
var skipped4 = Number.MAX_SAFE_INTEGER;
async function load9(config3) {
if (!model6) {
model6 = await tf15.loadGraphModel(join(config3.modelBasePath, config3.object.modelPath));
const inputs = Object.values(model6.modelSignature["inputs"]);
model6.inputSize = Array.isArray(inputs) ? parseInt(inputs[0].tensorShape.dim[2].size) : null;
if (!model6.inputSize)
throw new Error(`Human: Cannot determine model inputSize: ${config3.object.modelPath}`);
if (!model6 || !model6.modelUrl)
log("load model failed:", config3.object.modelPath);
else if (config3.debug)
log("load model:", model6.modelUrl);
} else if (config3.debug)
log("cached model:", model6.modelUrl);
return model6;
}
async function process3(res, inputSize, outputShape, config3) {
const results = [];
const detections = res.arraySync();
const squeezeT = tf15.squeeze(res);
res.dispose();
const arr = tf15.split(squeezeT, 6, 1);
squeezeT.dispose();
const stackT = tf15.stack([arr[1], arr[0], arr[3], arr[2]], 1);
const boxesT = stackT.squeeze();
const scoresT = arr[4].squeeze();
const classesT = arr[5].squeeze();
arr.forEach((t) => t.dispose());
const nmsT = await tf15.image.nonMaxSuppressionAsync(boxesT, scoresT, config3.object.maxDetected, config3.object.iouThreshold, config3.object.minConfidence);
boxesT.dispose();
scoresT.dispose();
classesT.dispose();
const nms = nmsT.dataSync();
nmsT.dispose();
for (const id of nms) {
const score = detections[0][id][4];
const classVal = detections[0][id][5];
const label = labels[classVal].label;
const boxRaw = [
detections[0][id][0] / inputSize,
detections[0][id][1] / inputSize,
detections[0][id][2] / inputSize,
detections[0][id][3] / inputSize
];
const box4 = [
Math.trunc(boxRaw[0] * outputShape[0]),
Math.trunc(boxRaw[1] * outputShape[1]),
Math.trunc(boxRaw[2] * outputShape[0]),
Math.trunc(boxRaw[3] * outputShape[1])
];
results.push({ score, class: classVal, label, box: box4, boxRaw });
}
return results;
}
async function predict8(image13, config3) {
if (!model6)
return null;
if (skipped4 < config3.object.skipFrames && config3.skipFrame && last4.length > 0) {
skipped4++;
return last4;
}
skipped4 = 0;
return new Promise(async (resolve) => {
const outputSize = [image13.shape[2], image13.shape[1]];
const resize = tf15.image.resizeBilinear(image13, [model6.inputSize, model6.inputSize], false);
let objectT;
if (config3.object.enabled)
objectT = model6.execute(resize, "tower_0/detections");
resize.dispose();
const obj = await process3(objectT, model6.inputSize, outputSize, config3);
last4 = obj;
resolve(obj);
});
}
// src/gesture/gesture.ts
var body = (res) => {
if (!res)
@ -17444,7 +17528,7 @@ var hand = (res) => {
};
// src/image/image.ts
var tf15 = __toModule(require_tfjs_esm());
var tf16 = __toModule(require_tfjs_esm());
// src/image/imagefx.js
function GLProgram(gl, vertexSource, fragmentSource) {
@ -17596,8 +17680,8 @@ function GLImageFilter(params) {
gl.uniform1f(_currentProgram.uniform.flipY, flipY ? -1 : 1);
gl.drawArrays(gl.TRIANGLES, 0, 6);
};
this.apply = function(image12) {
_resize(image12.width, image12.height);
this.apply = function(image13) {
_resize(image13.width, image13.height);
_drawCount = 0;
if (!_sourceTexture)
_sourceTexture = gl.createTexture();
@ -17606,7 +17690,7 @@ function GLImageFilter(params) {
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image12);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image13);
if (_filterChain.length === 0) {
_draw();
return _canvas;
@ -18157,16 +18241,16 @@ var maxSize = 2048;
var inCanvas;
var outCanvas;
var fx;
function process3(input, config3) {
function process4(input, config3) {
let tensor;
if (!input)
throw new Error("Human: Input is missing");
if (!(input instanceof tf15.Tensor) && !(typeof Image !== "undefined" && input instanceof Image) && !(typeof ImageData !== "undefined" && input instanceof ImageData) && !(typeof ImageBitmap !== "undefined" && input instanceof ImageBitmap) && !(typeof HTMLImageElement !== "undefined" && input instanceof HTMLImageElement) && !(typeof HTMLMediaElement !== "undefined" && input instanceof HTMLMediaElement) && !(typeof HTMLVideoElement !== "undefined" && input instanceof HTMLVideoElement) && !(typeof HTMLCanvasElement !== "undefined" && input instanceof HTMLCanvasElement) && !(typeof OffscreenCanvas !== "undefined" && input instanceof OffscreenCanvas)) {
if (!(input instanceof tf16.Tensor) && !(typeof Image !== "undefined" && input instanceof Image) && !(typeof ImageData !== "undefined" && input instanceof ImageData) && !(typeof ImageBitmap !== "undefined" && input instanceof ImageBitmap) && !(typeof HTMLImageElement !== "undefined" && input instanceof HTMLImageElement) && !(typeof HTMLMediaElement !== "undefined" && input instanceof HTMLMediaElement) && !(typeof HTMLVideoElement !== "undefined" && input instanceof HTMLVideoElement) && !(typeof HTMLCanvasElement !== "undefined" && input instanceof HTMLCanvasElement) && !(typeof OffscreenCanvas !== "undefined" && input instanceof OffscreenCanvas)) {
throw new Error("Human: Input type is not recognized");
}
if (input instanceof tf15.Tensor) {
if (input instanceof tf16.Tensor) {
if (input.shape && input.shape.length === 4 && input.shape[0] === 1 && input.shape[3] === 3)
tensor = tf15.clone(input);
tensor = tf16.clone(input);
else
throw new Error(`Human: Input tensor shape must be [1, height, width, 3] and instead was ${input.shape}`);
} else {
@ -18219,7 +18303,7 @@ function process3(input, config3) {
outCanvas.width = inCanvas == null ? void 0 : inCanvas.width;
if ((outCanvas == null ? void 0 : outCanvas.height) !== (inCanvas == null ? void 0 : inCanvas.height))
outCanvas.height = inCanvas == null ? void 0 : inCanvas.height;
fx = tf15.ENV.flags.IS_BROWSER ? new GLImageFilter({canvas: outCanvas}) : null;
fx = tf16.ENV.flags.IS_BROWSER ? new GLImageFilter({ canvas: outCanvas }) : null;
}
if (!fx)
return { tensor: null, canvas: inCanvas };
@ -18260,16 +18344,16 @@ function process3(input, config3) {
let pixels;
if (outCanvas.data) {
const shape = [outCanvas.height, outCanvas.width, 3];
pixels = tf15.tensor3d(outCanvas.data, shape, "int32");
pixels = tf16.tensor3d(outCanvas.data, shape, "int32");
} else if (outCanvas instanceof ImageData) {
pixels = tf15.browser.fromPixels(outCanvas);
pixels = tf16.browser.fromPixels(outCanvas);
} else if (config3.backend === "webgl" || config3.backend === "humangl") {
const tempCanvas = typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(targetWidth, targetHeight) : document.createElement("canvas");
tempCanvas.width = targetWidth;
tempCanvas.height = targetHeight;
const tempCtx = tempCanvas.getContext("2d");
tempCtx == null ? void 0 : tempCtx.drawImage(outCanvas, 0, 0);
pixels = tf15.browser.fromPixels(tempCanvas);
pixels = tf16.browser.fromPixels(tempCanvas);
} else {
const tempCanvas = typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(targetWidth, targetHeight) : document.createElement("canvas");
tempCanvas.width = targetWidth;
@ -18277,7 +18361,7 @@ function process3(input, config3) {
const tempCtx = tempCanvas.getContext("2d");
tempCtx == null ? void 0 : tempCtx.drawImage(outCanvas, 0, 0);
const data = tempCtx == null ? void 0 : tempCtx.getImageData(0, 0, targetWidth, targetHeight);
pixels = tf15.browser.fromPixels(data);
pixels = tf16.browser.fromPixels(data);
}
const casted = pixels.toFloat();
tensor = casted.expandDims(0);
@ -18311,7 +18395,7 @@ var options = {
roundRect: 28,
drawPoints: false,
drawLabels: true,
drawBoxes: false,
drawBoxes: true,
drawPolygons: true,
fillPolygons: false,
useDepth: true,
@ -19537,7 +19621,7 @@ var Human = class {
return null;
if (!input)
return "input is not defined";
if (this.tf.ENV.flags.IS_NODE && !(input instanceof tf16.Tensor))
if (this.tf.ENV.flags.IS_NODE && !(input instanceof tf17.Tensor))
return "input must be a tensor";
try {
this.tf.getBackend();
@ -19600,7 +19684,7 @@ var Human = class {
});
__privateAdd(this, _skipFrame, async (input) => {
if (this.config.cacheSensitivity === 0)
return true;
return false;
const resizeFact = 50;
const reduced = input.resizeBilinear([Math.trunc(input.shape[1] / resizeFact), Math.trunc(input.shape[2] / resizeFact)]);
const sumT = this.tf.sum(reduced);
@ -19673,8 +19757,8 @@ var Human = class {
if (!img)
return null;
let res;
if (typeof tf16["node"] !== "undefined") {
const data = tf16["node"].decodeJpeg(img);
if (typeof tf17["node"] !== "undefined") {
const data = tf17["node"].decodeJpeg(img);
const expanded = data.expandDims(0);
this.tf.dispose(data);
res = await this.detect(expanded, this.config);
@ -19685,7 +19769,7 @@ var Human = class {
}
return res;
});
this.tf = tf16;
this.tf = tf17;
this.draw = draw_exports;
this.version = version;
this.config = mergeDeep(config, userConfig);
@ -19707,16 +19791,18 @@ var Human = class {
emotion: null,
embedding: null,
nanodet: null,
centernet: null,
faceres: null
};
this.image = (input) => process3(input, this.config);
this.image = (input) => process4(input, this.config);
this.classes = {
facemesh: facemesh_exports,
emotion: emotion_exports,
faceres: faceres_exports,
body: this.config.body.modelPath.includes("posenet") ? posenet_exports : blazepose_exports,
hand: handpose_exports,
nanodet: nanodet_exports
nanodet: nanodet_exports,
centernet: centernet_exports
};
this.faceTriangulation = triangulation;
this.faceUVMap = uvmap;
@ -19762,6 +19848,7 @@ var Human = class {
this.models.posenet,
this.models.blazepose,
this.models.nanodet,
this.models.centernet,
this.models.faceres
] = await Promise.all([
this.models.face || (this.config.face.enabled ? load2(this.config) : null),
@ -19769,7 +19856,8 @@ var Human = class {
this.models.handpose || (this.config.hand.enabled ? load6(this.config) : null),
this.models.posenet || (this.config.body.enabled && this.config.body.modelPath.includes("posenet") ? load5(this.config) : null),
this.models.blazepose || (this.config.body.enabled && this.config.body.modelPath.includes("blazepose") ? load7(this.config) : null),
this.models.nanodet || (this.config.object.enabled ? load8(this.config) : null),
this.models.nanodet || (this.config.object.enabled && this.config.object.modelPath.includes("nanodet") ? load8(this.config) : null),
this.models.centernet || (this.config.object.enabled && this.config.object.modelPath.includes("centernet") ? load9(this.config) : null),
this.models.faceres || (this.config.face.enabled && this.config.face.description.enabled ? load4(this.config) : null)
]);
} else {
@ -19783,8 +19871,10 @@ var Human = class {
this.models.posenet = await load5(this.config);
if (this.config.body.enabled && !this.models.blazepose && this.config.body.modelPath.includes("blazepose"))
this.models.blazepose = await load7(this.config);
if (this.config.object.enabled && !this.models.nanodet)
if (this.config.object.enabled && !this.models.nanodet && this.config.object.modelPath.includes("nanodet"))
this.models.nanodet = await load8(this.config);
if (this.config.object.enabled && !this.models.centernet && this.config.object.modelPath.includes("centernet"))
this.models.centernet = await load9(this.config);
if (this.config.face.enabled && this.config.face.description.enabled && !this.models.faceres)
this.models.faceres = await load4(this.config);
}
@ -19812,8 +19902,8 @@ var Human = class {
await __privateGet(this, _checkBackend).call(this);
await this.load();
timeStamp = now();
const process4 = process3(input, this.config);
if (!process4 || !process4.tensor) {
const process5 = process4(input, this.config);
if (!process5 || !process5.tensor) {
log("could not convert input to tensor");
resolve({ error: "could not convert input to tensor" });
return;
@ -19821,7 +19911,7 @@ var Human = class {
this.perf.image = Math.trunc(now() - timeStamp);
this.analyze("Get Image:");
timeStamp = now();
this.config.skipFrame = await __privateGet(this, _skipFrame).call(this, process4.tensor);
this.config.skipFrame = await __privateGet(this, _skipFrame).call(this, process5.tensor);
if (!this.perf.frames)
this.perf.frames = 0;
if (!this.perf.cached)
@ -19837,13 +19927,13 @@ var Human = class {
let objectRes;
let current;
if (this.config.async) {
faceRes = this.config.face.enabled ? detectFace(this, process4.tensor) : [];
faceRes = this.config.face.enabled ? detectFace(this, process5.tensor) : [];
if (this.perf.face)
delete this.perf.face;
} else {
this.state = "run:face";
timeStamp = now();
faceRes = this.config.face.enabled ? await detectFace(this, process4.tensor) : [];
faceRes = this.config.face.enabled ? await detectFace(this, process5.tensor) : [];
current = Math.trunc(now() - timeStamp);
if (current > 0)
this.perf.face = current;
@ -19851,18 +19941,18 @@ var Human = class {
this.analyze("Start Body:");
if (this.config.async) {
if (this.config.body.modelPath.includes("posenet"))
bodyRes = this.config.body.enabled ? predict4(process4.tensor, this.config) : [];
bodyRes = this.config.body.enabled ? predict4(process5.tensor, this.config) : [];
else if (this.config.body.modelPath.includes("blazepose"))
bodyRes = this.config.body.enabled ? predict6(process4.tensor, this.config) : [];
bodyRes = this.config.body.enabled ? predict6(process5.tensor, this.config) : [];
if (this.perf.body)
delete this.perf.body;
} else {
this.state = "run:body";
timeStamp = now();
if (this.config.body.modelPath.includes("posenet"))
bodyRes = this.config.body.enabled ? await predict4(process4.tensor, this.config) : [];
bodyRes = this.config.body.enabled ? await predict4(process5.tensor, this.config) : [];
else if (this.config.body.modelPath.includes("blazepose"))
bodyRes = this.config.body.enabled ? await predict6(process4.tensor, this.config) : [];
bodyRes = this.config.body.enabled ? await predict6(process5.tensor, this.config) : [];
current = Math.trunc(now() - timeStamp);
if (current > 0)
this.perf.body = current;
@ -19870,13 +19960,13 @@ var Human = class {
this.analyze("End Body:");
this.analyze("Start Hand:");
if (this.config.async) {
handRes = this.config.hand.enabled ? predict5(process4.tensor, this.config) : [];
handRes = this.config.hand.enabled ? predict5(process5.tensor, this.config) : [];
if (this.perf.hand)
delete this.perf.hand;
} else {
this.state = "run:hand";
timeStamp = now();
handRes = this.config.hand.enabled ? await predict5(process4.tensor, this.config) : [];
handRes = this.config.hand.enabled ? await predict5(process5.tensor, this.config) : [];
current = Math.trunc(now() - timeStamp);
if (current > 0)
this.perf.hand = current;
@ -19884,13 +19974,19 @@ var Human = class {
this.analyze("End Hand:");
this.analyze("Start Object:");
if (this.config.async) {
objectRes = this.config.object.enabled ? predict7(process4.tensor, this.config) : [];
if (this.config.object.modelPath.includes("nanodet"))
objectRes = this.config.object.enabled ? predict7(process5.tensor, this.config) : [];
else if (this.config.object.modelPath.includes("centernet"))
objectRes = this.config.object.enabled ? predict8(process5.tensor, this.config) : [];
if (this.perf.object)
delete this.perf.object;
} else {
this.state = "run:object";
timeStamp = now();
objectRes = this.config.object.enabled ? await predict7(process4.tensor, this.config) : [];
if (this.config.object.modelPath.includes("nanodet"))
objectRes = this.config.object.enabled ? await predict7(process5.tensor, this.config) : [];
else if (this.config.object.modelPath.includes("centernet"))
objectRes = this.config.object.enabled ? await predict8(process5.tensor, this.config) : [];
current = Math.trunc(now() - timeStamp);
if (current > 0)
this.perf.object = current;
@ -19899,7 +19995,7 @@ var Human = class {
if (this.config.async) {
[faceRes, bodyRes, handRes, objectRes] = await Promise.all([faceRes, bodyRes, handRes, objectRes]);
}
tf16.dispose(process4.tensor);
tf17.dispose(process5.tensor);
let gestureRes = [];
if (this.config.gesture.enabled) {
timeStamp = now();
@ -19918,7 +20014,7 @@ var Human = class {
gesture: gestureRes,
object: objectRes,
performance: this.perf,
canvas: process4.canvas
canvas: process5.canvas
};
resolve(result);
});

59239
dist/tfjs.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

BIN
models/mb3-centernet.bin Normal file

Binary file not shown.

577
models/mb3-centernet.json Normal file

File diff suppressed because one or more lines are too long

View File

@ -68,7 +68,7 @@
"canvas": "^2.8.0",
"chokidar": "^3.5.1",
"dayjs": "^1.10.4",
"esbuild": "^0.12.0",
"esbuild": "^0.12.1",
"eslint": "^7.26.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-plugin-import": "^2.23.2",

View File

@ -319,7 +319,8 @@ const config: Config = {
object: {
enabled: false,
modelPath: 'nanodet.json', // experimental: object detection model, can be absolute path or relative to modelBasePath
modelPath: 'mb3-centernet.json', // experimental: object detection model, can be absolute path or relative to modelBasePath
// can be 'mb3-centernet' or 'nanodet'
minConfidence: 0.2, // threshold for discarding a prediction
iouThreshold: 0.4, // ammount of overlap between two detected objects before one object is removed
maxDetected: 10, // maximum number of objects detected in the input

View File

@ -54,7 +54,7 @@ export const options: DrawOptions = {
roundRect: <number>28,
drawPoints: <Boolean>false,
drawLabels: <Boolean>true,
drawBoxes: <Boolean>false,
drawBoxes: <Boolean>true,
drawPolygons: <Boolean>true,
fillPolygons: <Boolean>false,
useDepth: <Boolean>true,

View File

@ -11,7 +11,8 @@ import * as emotion from './emotion/emotion';
import * as posenet from './posenet/posenet';
import * as handpose from './handpose/handpose';
import * as blazepose from './blazepose/blazepose';
import * as nanodet from './nanodet/nanodet';
import * as nanodet from './object/nanodet';
import * as centernet from './object/centernet';
import * as gesture from './gesture/gesture';
import * as image from './image/image';
import * as draw from './draw/draw';
@ -93,6 +94,7 @@ export class Human {
emotion: Model | null,
embedding: Model | null,
nanodet: Model | null,
centernet: Model | null,
faceres: Model | null,
};
/** Internal: Currently loaded classes */
@ -102,6 +104,7 @@ export class Human {
body: typeof posenet | typeof blazepose;
hand: typeof handpose;
nanodet: typeof nanodet;
centernet: typeof centernet;
faceres: typeof faceres;
};
/** Face triangualtion array of 468 points, used for triangle references between points */
@ -148,6 +151,7 @@ export class Human {
emotion: null,
embedding: null,
nanodet: null,
centernet: null,
faceres: null,
};
// export access to image processing
@ -161,6 +165,7 @@ export class Human {
body: this.config.body.modelPath.includes('posenet') ? posenet : blazepose,
hand: handpose,
nanodet,
centernet,
};
this.faceTriangulation = facemesh.triangulation;
this.faceUVMap = facemesh.uvmap;
@ -231,7 +236,7 @@ export class Human {
const timeStamp = now();
if (userConfig) this.config = mergeDeep(this.config, userConfig);
if (this.#firstRun) {
if (this.#firstRun) { // print version info on first run and check for correct backend setup
if (this.config.debug) log(`version: ${this.version}`);
if (this.config.debug) log(`tfjs version: ${this.tf.version_core}`);
if (this.config.debug) log('platform:', this.sysinfo.platform);
@ -243,7 +248,7 @@ export class Human {
if (this.config.debug) log('tf flags:', this.tf.ENV.flags);
}
}
if (this.config.async) {
if (this.config.async) { // load models concurrently
[
this.models.face,
this.models.emotion,
@ -251,6 +256,7 @@ export class Human {
this.models.posenet,
this.models.blazepose,
this.models.nanodet,
this.models.centernet,
this.models.faceres,
] = await Promise.all([
this.models.face || (this.config.face.enabled ? facemesh.load(this.config) : null),
@ -258,20 +264,22 @@ export class Human {
this.models.handpose || (this.config.hand.enabled ? handpose.load(this.config) : null),
this.models.posenet || (this.config.body.enabled && this.config.body.modelPath.includes('posenet') ? posenet.load(this.config) : null),
this.models.blazepose || (this.config.body.enabled && this.config.body.modelPath.includes('blazepose') ? blazepose.load(this.config) : null),
this.models.nanodet || (this.config.object.enabled ? nanodet.load(this.config) : null),
this.models.nanodet || (this.config.object.enabled && this.config.object.modelPath.includes('nanodet') ? nanodet.load(this.config) : null),
this.models.centernet || (this.config.object.enabled && this.config.object.modelPath.includes('centernet') ? centernet.load(this.config) : null),
this.models.faceres || ((this.config.face.enabled && this.config.face.description.enabled) ? faceres.load(this.config) : null),
]);
} else {
} else { // load models sequentially
if (this.config.face.enabled && !this.models.face) this.models.face = await facemesh.load(this.config);
if (this.config.face.enabled && this.config.face.emotion.enabled && !this.models.emotion) this.models.emotion = await emotion.load(this.config);
if (this.config.hand.enabled && !this.models.handpose) this.models.handpose = await handpose.load(this.config);
if (this.config.body.enabled && !this.models.posenet && this.config.body.modelPath.includes('posenet')) this.models.posenet = await posenet.load(this.config);
if (this.config.body.enabled && !this.models.blazepose && this.config.body.modelPath.includes('blazepose')) this.models.blazepose = await blazepose.load(this.config);
if (this.config.object.enabled && !this.models.nanodet) this.models.nanodet = await nanodet.load(this.config);
if (this.config.object.enabled && !this.models.nanodet && this.config.object.modelPath.includes('nanodet')) this.models.nanodet = await nanodet.load(this.config);
if (this.config.object.enabled && !this.models.centernet && this.config.object.modelPath.includes('centernet')) this.models.centernet = await centernet.load(this.config);
if (this.config.face.enabled && this.config.face.description.enabled && !this.models.faceres) this.models.faceres = await faceres.load(this.config);
}
if (this.#firstRun) {
if (this.#firstRun) { // print memory stats on first run
if (this.config.debug) log('tf engine state:', this.tf.engine().state.numBytes, 'bytes', this.tf.engine().state.numTensors, 'tensors');
this.#firstRun = false;
}
@ -343,7 +351,7 @@ export class Human {
// check if input changed sufficiently to trigger new detections
/** @hidden */
#skipFrame = async (input) => {
if (this.config.cacheSensitivity === 0) return true;
if (this.config.cacheSensitivity === 0) return false;
const resizeFact = 50;
const reduced = input.resizeBilinear([Math.trunc(input.shape[1] / resizeFact), Math.trunc(input.shape[2] / resizeFact)]);
const sumT = this.tf.sum(reduced);
@ -476,12 +484,14 @@ export class Human {
// run nanodet
this.analyze('Start Object:');
if (this.config.async) {
objectRes = this.config.object.enabled ? nanodet.predict(process.tensor, this.config) : [];
if (this.config.object.modelPath.includes('nanodet')) objectRes = this.config.object.enabled ? nanodet.predict(process.tensor, this.config) : [];
else if (this.config.object.modelPath.includes('centernet')) objectRes = this.config.object.enabled ? centernet.predict(process.tensor, this.config) : [];
if (this.perf.object) delete this.perf.object;
} else {
this.state = 'run:object';
timeStamp = now();
objectRes = this.config.object.enabled ? await nanodet.predict(process.tensor, this.config) : [];
if (this.config.object.modelPath.includes('nanodet')) objectRes = this.config.object.enabled ? await nanodet.predict(process.tensor, this.config) : [];
else if (this.config.object.modelPath.includes('centernet')) objectRes = this.config.object.enabled ? await centernet.predict(process.tensor, this.config) : [];
current = Math.trunc(now() - timeStamp);
if (current > 0) this.perf.object = current;
}

80
src/object/centernet.ts Normal file
View File

@ -0,0 +1,80 @@
import { log, join } from '../helpers';
import * as tf from '../../dist/tfjs.esm.js';
import { labels } from './labels';
let model;
let last: Array<{}> = [];
let skipped = Number.MAX_SAFE_INTEGER;
export async function load(config) {
if (!model) {
model = await tf.loadGraphModel(join(config.modelBasePath, config.object.modelPath));
const inputs = Object.values(model.modelSignature['inputs']);
model.inputSize = Array.isArray(inputs) ? parseInt(inputs[0].tensorShape.dim[2].size) : null;
if (!model.inputSize) throw new Error(`Human: Cannot determine model inputSize: ${config.object.modelPath}`);
if (!model || !model.modelUrl) log('load model failed:', config.object.modelPath);
else if (config.debug) log('load model:', model.modelUrl);
} else if (config.debug) log('cached model:', model.modelUrl);
return model;
}
async function process(res, inputSize, outputShape, config) {
const results: Array<{ score: number, class: number, label: string, box: number[], boxRaw: number[] }> = [];
const detections = res.arraySync();
const squeezeT = tf.squeeze(res);
res.dispose();
const arr = tf.split(squeezeT, 6, 1); // x1, y1, x2, y2, score, class
squeezeT.dispose();
const stackT = tf.stack([arr[1], arr[0], arr[3], arr[2]], 1); // tf.nms expects y, x
const boxesT = stackT.squeeze();
const scoresT = arr[4].squeeze();
const classesT = arr[5].squeeze();
arr.forEach((t) => t.dispose());
// @ts-ignore boxesT type is not correctly inferred
const nmsT = await tf.image.nonMaxSuppressionAsync(boxesT, scoresT, config.object.maxDetected, config.object.iouThreshold, config.object.minConfidence);
boxesT.dispose();
scoresT.dispose();
classesT.dispose();
const nms = nmsT.dataSync();
nmsT.dispose();
for (const id of nms) {
const score = detections[0][id][4];
const classVal = detections[0][id][5];
const label = labels[classVal].label;
const boxRaw = [
detections[0][id][0] / inputSize,
detections[0][id][1] / inputSize,
detections[0][id][2] / inputSize,
detections[0][id][3] / inputSize,
];
const box = [
Math.trunc(boxRaw[0] * outputShape[0]),
Math.trunc(boxRaw[1] * outputShape[1]),
Math.trunc(boxRaw[2] * outputShape[0]),
Math.trunc(boxRaw[3] * outputShape[1]),
];
results.push({ score, class: classVal, label, box, boxRaw });
}
return results;
}
export async function predict(image, config) {
if (!model) return null;
if ((skipped < config.object.skipFrames) && config.skipFrame && (last.length > 0)) {
skipped++;
return last;
}
skipped = 0;
return new Promise(async (resolve) => {
const outputSize = [image.shape[2], image.shape[1]];
const resize = tf.image.resizeBilinear(image, [model.inputSize, model.inputSize], false);
let objectT;
if (config.object.enabled) objectT = model.execute(resize, 'tower_0/detections');
resize.dispose();
const obj = await process(objectT, model.inputSize, outputSize, config);
last = obj;
resolve(obj);
});
}

View File

@ -78,7 +78,7 @@ async function process(res, inputSize, outputShape, config) {
// normally nms is run on raw results, but since boxes need to be calculated this way we skip calulcation of
// unnecessary boxes and run nms only on good candidates (basically it just does IOU analysis as scores are already filtered)
const nmsBoxes = results.map((a) => a.boxRaw);
const nmsBoxes = results.map((a) => [a.boxRaw[1], a.boxRaw[0], a.boxRaw[3], a.boxRaw[2]]); // switches coordinates from x,y to y,x as expected by tf.nms
const nmsScores = results.map((a) => a.score);
let nmsIdx: any[] = [];
if (nmsBoxes && nmsBoxes.length > 0) {

2
wiki

@ -1 +1 @@
Subproject commit 534d4d77d99b0fc71913e8ef6242e4c6461614f5
Subproject commit fa896c5330432f26839d362b81ea9128db60d86b