UNPKG

speedy-vision

Version:

GPU-accelerated Computer Vision for JavaScript

164 lines (145 loc) 5.76 kB
/* * speedy-vision.js * GPU-accelerated Computer Vision for JavaScript * Copyright 2020-2022 Alexandre Martins <alemartf(at)gmail.com> * * 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 * * http://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. * * subpixel.js * Subpixel refinement of keypoint location */ import { SpeedyPipelineNode } from '../../pipeline-node'; import { SpeedyPipelineNodeKeypointDetector } from './detectors/detector'; import { SpeedyPipelineMessageType, SpeedyPipelineMessageWithKeypoints, SpeedyPipelineMessageWithImage } from '../../pipeline-message'; import { InputPort, OutputPort } from '../../pipeline-portbuilder'; import { SpeedyGPU } from '../../../../gpu/speedy-gpu'; import { SpeedyTexture } from '../../../../gpu/speedy-texture'; import { ImageFormat } from '../../../../utils/types'; import { Utils } from '../../../../utils/utils'; import { IllegalArgumentError } from '../../../../utils/errors'; import { SpeedyPromise } from '../../../speedy-promise'; /** @typedef {"quadratic1d"|"taylor2d"|"bicubic-upsample"|"bilinear-upsample"} SubpixelRefinementMethod */ /** @const {Object<SubpixelRefinementMethod,string>} method name to program name */ const METHOD2PROGRAM = Object.freeze({ 'quadratic1d': 'subpixelQuadratic1d', 'taylor2d': 'subpixelTaylor2d', 'bicubic-upsample': 'subpixelBicubic', 'bilinear-upsample': 'subpixelBilinear', }); /** * Subpixel refinement of keypoint location */ export class SpeedyPipelineNodeKeypointSubpixelRefiner extends SpeedyPipelineNode { /** * Constructor * @param {string} [name] name of the node */ constructor(name = undefined) { super(name, 2, [ InputPort('image').expects(SpeedyPipelineMessageType.Image).satisfying( ( /** @type {SpeedyPipelineMessageWithImage} */ msg ) => msg.format === ImageFormat.GREY ), InputPort('keypoints').expects(SpeedyPipelineMessageType.Keypoints), OutputPort().expects(SpeedyPipelineMessageType.Keypoints), OutputPort('displacements').expects(SpeedyPipelineMessageType.Vector2), ]); /** @type {SubpixelRefinementMethod} subpixel refinement method */ this._method = 'quadratic1d'; /** @type {number} max iterations for the upsampling methods */ this._maxIterations = 6; /** @type {number} convergence threshold for the upsampling methods */ this._epsilon = 0.1; } /** * Subpixel refinement method * @returns {SubpixelRefinementMethod} */ get method() { return this._method; } /** * Subpixel refinement method * @param {SubpixelRefinementMethod} name */ set method(name) { if(!Object.prototype.hasOwnProperty.call(METHOD2PROGRAM, name)) throw new IllegalArgumentError(`Invalid method: "${name}"`); this._method = name; } /** * Max. iterations for the upsampling methods * @returns {number} */ get maxIterations() { return this._maxIterations; } /** * Max. iterations for the upsampling methods * @param {number} value */ set maxIterations(value) { this._maxIterations = Math.max(0, +value); } /** * Convergence threshold for the upsampling methods * @returns {number} */ get epsilon() { return this._epsilon; } /** * Convergence threshold for the upsampling methods * @param {number} value */ set epsilon(value) { this._epsilon = Math.max(0, +value); } /** * Run the specific task of this node * @param {SpeedyGPU} gpu * @returns {void|SpeedyPromise<void>} */ _run(gpu) { const { encodedKeypoints, descriptorSize, extraSize, encoderLength } = /** @type {SpeedyPipelineMessageWithKeypoints} */ ( this.input('keypoints').read() ); const { image, format } = /** @type {SpeedyPipelineMessageWithImage} */ ( this.input('image').read() ); const tex = this._tex; const program = METHOD2PROGRAM[this._method]; const maxIterations = this._maxIterations; const epsilon = this._epsilon; // note: if you detected the keypoints using a pyramid, // you need to pass that pyramid as input! // we'll compute the offsets for each keypoint const capacity = SpeedyPipelineNodeKeypointDetector.encoderCapacity(descriptorSize, extraSize, encoderLength); const offsetEncoderLength = Math.max(1, Math.ceil(Math.sqrt(capacity))); // 1 pixel per refinement offset const offsets = (gpu.programs.keypoints[program] .outputs(offsetEncoderLength, offsetEncoderLength, tex[0]) )(image, encodedKeypoints, descriptorSize, extraSize, encoderLength, maxIterations, epsilon); // apply the offsets to the keypoints const refinedKeypoints = (gpu.programs.keypoints.transferFlow .outputs(encoderLength, encoderLength, tex[1]) )(offsets, encodedKeypoints, descriptorSize, extraSize, encoderLength); // done! this.output().swrite(refinedKeypoints, descriptorSize, extraSize, encoderLength); this.output('displacements').swrite(offsets); } }