UNPKG

tfjs-model-facemesh

Version:

forked from @tensorflow-models/facemesh, used for local deployed tfjs models.

207 lines (206 loc) 11.4 kB
"use strict"; /** * @license * Copyright 2020 Google LLC. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ============================================================================= */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __spreadArrays = (this && this.__spreadArrays) || function () { for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; for (var r = Array(s), k = 0, i = 0; i < il; i++) for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) r[k] = a[j]; return r; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Pipeline = void 0; var tf = require("@tensorflow/tfjs-core"); var box_1 = require("./box"); var LANDMARKS_COUNT = 468; var UPDATE_REGION_OF_INTEREST_IOU_THRESHOLD = 0.25; // The Pipeline coordinates between the bounding box and skeleton models. var Pipeline = /** @class */ (function () { function Pipeline(boundingBoxDetector, meshDetector, meshWidth, meshHeight, maxContinuousChecks, maxFaces) { // An array of facial bounding boxes. this.regionsOfInterest = []; this.runsWithoutFaceDetector = 0; this.boundingBoxDetector = boundingBoxDetector; this.meshDetector = meshDetector; this.meshWidth = meshWidth; this.meshHeight = meshHeight; this.maxContinuousChecks = maxContinuousChecks; this.maxFaces = maxFaces; } /** * Returns an array of predictions for each face in the input. * * @param input - tensor of shape [1, H, W, 3]. */ Pipeline.prototype.predict = function (input) { return __awaiter(this, void 0, void 0, function () { var returnTensors, annotateFace, _a, boxes, scaleFactor_1, scaledBoxes; var _this = this; return __generator(this, function (_b) { switch (_b.label) { case 0: if (!this.shouldUpdateRegionsOfInterest()) return [3 /*break*/, 2]; returnTensors = true; annotateFace = false; return [4 /*yield*/, this.boundingBoxDetector.getBoundingBoxes(input, returnTensors, annotateFace)]; case 1: _a = _b.sent(), boxes = _a.boxes, scaleFactor_1 = _a.scaleFactor; if (boxes.length === 0) { scaleFactor_1.dispose(); this.clearAllRegionsOfInterest(); return [2 /*return*/, null]; } scaledBoxes = boxes.map(function (prediction) { return box_1.enlargeBox(box_1.scaleBoxCoordinates(prediction, scaleFactor_1)); }); boxes.forEach(box_1.disposeBox); this.updateRegionsOfInterest(scaledBoxes); this.runsWithoutFaceDetector = 0; return [3 /*break*/, 3]; case 2: this.runsWithoutFaceDetector++; _b.label = 3; case 3: return [2 /*return*/, tf.tidy(function () { return _this.regionsOfInterest.map(function (box, i) { var face = box_1.cutBoxFromImageAndResize(box, input, [ _this.meshHeight, _this.meshWidth ]).div(255); // The first returned tensor represents facial contours, which are // included in the coordinates. var _a = _this.meshDetector.predict(face), flag = _a[1], coords = _a[2]; var coordsReshaped = tf.reshape(coords, [-1, 3]); var normalizedBox = tf.div(box_1.getBoxSize(box), [_this.meshWidth, _this.meshHeight]); var scaledCoords = tf.mul(coordsReshaped, normalizedBox.concat(tf.tensor2d([1], [1, 1]), 1)) .add(box.startPoint.concat(tf.tensor2d([0], [1, 1]), 1)); var landmarksBox = _this.calculateLandmarksBoundingBox(scaledCoords); var previousBox = _this.regionsOfInterest[i]; box_1.disposeBox(previousBox); _this.regionsOfInterest[i] = landmarksBox; var prediction = { coords: coordsReshaped, scaledCoords: scaledCoords, box: landmarksBox, flag: flag.squeeze() }; return prediction; }); })]; } }); }); }; // Updates regions of interest if the intersection over union between // the incoming and previous regions falls below a threshold. Pipeline.prototype.updateRegionsOfInterest = function (boxes) { for (var i = 0; i < boxes.length; i++) { var box = boxes[i]; var previousBox = this.regionsOfInterest[i]; var iou = 0; if (previousBox && previousBox.startPoint) { // Computing IOU on the CPU for performance. // Using arraySync() rather than await array() because the tensors are // very small, so it's not worth the overhead to call await array(). var _a = box.startEndTensor.arraySync()[0], boxStartX = _a[0], boxStartY = _a[1], boxEndX = _a[2], boxEndY = _a[3]; var _b = previousBox.startEndTensor.arraySync()[0], previousBoxStartX = _b[0], previousBoxStartY = _b[1], previousBoxEndX = _b[2], previousBoxEndY = _b[3]; var xStartMax = Math.max(boxStartX, previousBoxStartX); var yStartMax = Math.max(boxStartY, previousBoxStartY); var xEndMin = Math.min(boxEndX, previousBoxEndX); var yEndMin = Math.min(boxEndY, previousBoxEndY); var intersection = (xEndMin - xStartMax) * (yEndMin - yStartMax); var boxArea = (boxEndX - boxStartX) * (boxEndY - boxStartY); var previousBoxArea = (previousBoxEndX - previousBoxStartX) * (previousBoxEndY - boxStartY); iou = intersection / (boxArea + previousBoxArea - intersection); } if (iou > UPDATE_REGION_OF_INTEREST_IOU_THRESHOLD) { box_1.disposeBox(box); } else { this.regionsOfInterest[i] = box; box_1.disposeBox(previousBox); } } for (var i = boxes.length; i < this.regionsOfInterest.length; i++) { box_1.disposeBox(this.regionsOfInterest[i]); } this.regionsOfInterest = this.regionsOfInterest.slice(0, boxes.length); }; Pipeline.prototype.clearRegionOfInterest = function (index) { if (this.regionsOfInterest[index] != null) { box_1.disposeBox(this.regionsOfInterest[index]); this.regionsOfInterest = __spreadArrays(this.regionsOfInterest.slice(0, index), this.regionsOfInterest.slice(index + 1)); } }; Pipeline.prototype.clearAllRegionsOfInterest = function () { for (var i = 0; i < this.regionsOfInterest.length; i++) { box_1.disposeBox(this.regionsOfInterest[i]); } this.regionsOfInterest = []; }; Pipeline.prototype.shouldUpdateRegionsOfInterest = function () { var roisCount = this.regionsOfInterest.length; var noROIs = roisCount === 0; if (this.maxFaces === 1 || noROIs) { return noROIs; } return roisCount !== this.maxFaces && this.runsWithoutFaceDetector >= this.maxContinuousChecks; }; Pipeline.prototype.calculateLandmarksBoundingBox = function (landmarks) { var xs = landmarks.slice([0, 0], [LANDMARKS_COUNT, 1]); var ys = landmarks.slice([0, 1], [LANDMARKS_COUNT, 1]); var boxMinMax = tf.stack([xs.min(), ys.min(), xs.max(), ys.max()]); var box = box_1.createBox(boxMinMax.expandDims(0)); return box_1.enlargeBox(box); }; return Pipeline; }()); exports.Pipeline = Pipeline;