UNPKG

retinanetjs

Version:

Wrapper for models built using keras-retinanet.

183 lines 16.8 kB
import * as tf from '@tensorflow/tfjs'; import { Zeros } from '@tensorflow/tfjs-layers/dist/initializers'; // tslint:disable-line import { anchorsForShape, defaultAnchorParameters } from './anchors'; class UpsampleLike extends tf.layers.Layer { constructor() { super({}); } computeOutputShape(inputShape) { return [ inputShape[0][0], inputShape[1][1], inputShape[1][2], inputShape[0][3] ]; } call(inputs, _) { const [source, target] = inputs; const targetShape = target.shape; return tf.image.resizeNearestNeighbor(source, [ targetShape[1], targetShape[2] ]); } static get className() { return 'UpsampleLike'; } } class PriorProbability extends Zeros { } // tslint:disable-line /** @nocollapse */ PriorProbability.className = 'PriorProbability'; tf.serialization.registerClass(UpsampleLike); // Needed for serialization. tf.serialization.registerClass(PriorProbability); /** * Represents a RetinaNet model. Rather than creating directly, * it is intended to be created using `load()`. */ export class RetinaNet { constructor(model, classes, preprocessingMode, anchorParams = defaultAnchorParameters) { const [height, width] = model.inputs[0].shape.slice(1, 3); // tslint:disable-next-line if (height === -1 || width === -1) { throw new Error('RetinaNetJS only supports fixed input sizes.'); } // tslint:disable-next-line if (preprocessingMode !== 'tf' && preprocessingMode !== 'caffe') { throw new Error('preprocessingMode must be either `tf` or `caffe`.'); } this.width = width; this.height = height; this.model = model; this.classes = classes; this.preprocessingMode = preprocessingMode; this.anchorParams = anchorParams; } /** * Computes predictions. We currently do not support class-specific filtering. * When non-max suppression is applied, it will be across all boxes, regardless of class. * * @param img The image object on which to run object detection * @param threshold The prediction threshold * @param nmsThreshold The non-max suppresion IoU threshold */ async detect(img, threshold = 0.5, nmsThreshold = 0.5) { // Build model input from image const [X, padX, padY] = tf.tidy(() => { const imageTensor = !(img instanceof tf.Tensor) ? tf.browser.fromPixels(img) : img; return this.handleImageTensor(imageTensor); }); // Run inference const y = this.model.predict(X); const [coords, classification] = tf.tidy(() => { const [boxDeltas, classScores] = y.map(t => t.squeeze([0])); const anchorBoxes = anchorsForShape(this.model.inputs[0].shape.slice(1, 3), this.anchorParams); const mean = [0, 0, 0, 0]; const std = [0.2, 0.2, 0.2, 0.2]; const width = anchorBoxes .slice([0, 2], [-1, 1]) .sub(anchorBoxes.slice([0, 0], [-1, 1])); const height = anchorBoxes .slice([0, 2], [-1, 1]) .sub(anchorBoxes.slice([0, 0], [-1, 1])); const x1y1x2y2 = tf.concat([0, 1, 2, 3].map(i => { return anchorBoxes.slice([0, i], [-1, 1]).add(boxDeltas .slice([0, i], [-1, 1]) .mul(std[i]) .add(mean[i]) .mul(i % 2 === 0 ? width : height)); }), 1); // TODO: Add support for class-specific filtering and turning nms on and off, just // like in retinanet. return [x1y1x2y2, classScores]; }); const selected = await tf.image.nonMaxSuppressionAsync(coords, classification.max(1, false), 300, nmsThreshold, threshold); const detections = tf.tidy(() => { const classificationNms = classification.gather(selected); const coordsNms = coords .gather(selected) .div([ this.width - padX, this.height - padY, this.width - padX, this.height - padY ]); const x1y1x2y2ls = tf .concat([ coordsNms, classificationNms .argMax(1) .expandDims(1) .cast('float32'), classificationNms.max(1, true) ], 1) .arraySync(); return x1y1x2y2ls.map(r => { const [x1, y1, x2, y2, labelIdx, score] = r; return { label: this.classes[labelIdx], score, x1, x2, y1, y2 }; }); }); X.dispose(); y.map(t => t.dispose()); selected.dispose(); return detections; } /** * Remove the model from memory. */ dispose() { this.model.dispose(); } handleImageTensor(imageTensor) { return tf.tidy(() => { const inputHeight = imageTensor.shape[0]; const inputWidth = imageTensor.shape[1]; const [outputHeight, outputWidth] = [this.height, this.width]; const scale = Math.min(outputHeight / inputHeight, outputWidth / inputWidth); const padY = outputHeight - Math.round(scale * inputHeight); const padX = outputWidth - Math.round(scale * inputWidth); const scaledTensor = scale === 1 ? imageTensor : imageTensor.resizeBilinear([ Math.round(scale * inputHeight), Math.round(scale * inputWidth) ]); const paddedTensor = padX === 0 && padY === 0 ? scaledTensor : scaledTensor.pad([[0, padY], [0, padX], [0, 0]]); const normedTensor = this.preprocessingMode === 'tf' ? paddedTensor.sub(127.5).div(127.5) : paddedTensor.sub(tf.tensor3d([[[103.939, 116.779, 123.68]]])); return [normedTensor.expandDims(0).cast('float32'), padX, padY]; }); } } /** * * @param modelPath The path to the model or a `tf.io.IOHandler` object * @param classes The list of detected classes * @param preprocessingMode One of `tf` or `caffe`. Check the `preprocess_images` * method of your backbone to see which you should use. * @param onProgress A callback to report progress * @param anchorParams The anchor parameters for your model */ export async function load(modelPath, classes, preprocessingMode, onProgress, anchorParams = defaultAnchorParameters) { const tfOnProgress = (progress) => onProgress(0.9 * progress, 'Downloading'); const model = await tf.loadLayersModel(modelPath, { onProgress: tfOnProgress, strict: false }); const detector = new RetinaNet(model, classes, preprocessingMode, anchorParams); if (onProgress) onProgress(0.92, 'Building'); // tslint:disable-line setTimeout(async () => { await detector.detect(tf.ones([100, 100, 3])); if (onProgress) onProgress(1.0, 'Finished'); // tslint:disable-line }, 100); return detector; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmV0aW5hbmV0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2xpYi9yZXRpbmFuZXQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUN2QyxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0sMkNBQTJDLENBQUMsQ0FBQyxzQkFBc0I7QUFFekYsT0FBTyxFQUVMLGVBQWUsRUFDZix1QkFBdUIsRUFDeEIsTUFBTSxXQUFXLENBQUM7QUFFbkIsTUFBTSxZQUFhLFNBQVEsRUFBRSxDQUFDLE1BQU0sQ0FBQyxLQUFLO0lBQ3hDO1FBQ0UsS0FBSyxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQ1osQ0FBQztJQUVNLGtCQUFrQixDQUFDLFVBQXNCO1FBQzlDLE9BQU87WUFDTCxVQUFVLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQ2hCLFVBQVUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDaEIsVUFBVSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUNoQixVQUFVLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1NBQ2pCLENBQUM7SUFDSixDQUFDO0lBRU0sSUFBSSxDQUFDLE1BQXFCLEVBQUUsQ0FBUztRQUMxQyxNQUFNLENBQUMsTUFBTSxFQUFFLE1BQU0sQ0FBQyxHQUFHLE1BQU0sQ0FBQztRQUNoQyxNQUFNLFdBQVcsR0FBRyxNQUFNLENBQUMsS0FBSyxDQUFDO1FBQ2pDLE9BQU8sRUFBRSxDQUFDLEtBQUssQ0FBQyxxQkFBcUIsQ0FBQyxNQUFNLEVBQUU7WUFDNUMsV0FBVyxDQUFDLENBQUMsQ0FBQztZQUNkLFdBQVcsQ0FBQyxDQUFDLENBQUM7U0FDZixDQUFDLENBQUM7SUFDTCxDQUFDO0lBRU0sTUFBTSxLQUFLLFNBQVM7UUFDekIsT0FBTyxjQUFjLENBQUM7SUFDeEIsQ0FBQztDQUNGO0FBRUQsTUFBTSxnQkFBaUIs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