UNPKG

speedy-vision

Version:

GPU-accelerated Computer Vision for JavaScript

336 lines (257 loc) 11.9 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. * * feature-detection.js * Unit testing */ describe('Feature detection', function() { let media, square; beforeEach(function() { jasmine.addMatchers(speedyMatchers); }); beforeEach(async function() { const image = [ await loadImage('masp.jpg'), await loadImage('square.png') ]; media = await Speedy.load(image[0]); square = await Speedy.load(image[1]); }); afterEach(async function() { await media.release(); await square.release(); }); function createKeypointDetectionPipeline(detector, media, maxKeypoints = 99999) { const pipeline = Speedy.Pipeline(); const source = Speedy.Image.Source(); const greyscale = Speedy.Filter.Greyscale(); const pyramid = Speedy.Image.Pyramid(); const clipper = Speedy.Keypoint.Clipper('clipper'); const sink = Speedy.Keypoint.Sink(); source.media = media; clipper.size = maxKeypoints; source.output().connectTo(greyscale.input()); greyscale.output().connectTo(pyramid.input()); pyramid.output().connectTo(detector.input()); detector.output().connectTo(clipper.input()); clipper.output().connectTo(sink.input()); pipeline.init(source, greyscale, pyramid, detector, clipper, sink); return pipeline; } describe('FAST', function() { const createPipeline = (media) => { const fast = Speedy.Keypoint.Detector.FAST('fast'); return createKeypointDetectionPipeline(fast, media); }; it('finds the corners of a square', async function() { const pipeline = createPipeline(square); const features = (await pipeline.run()).keypoints; const numFeatures = features.length; print(`Found ${numFeatures} features`); displayFeatures(square, features); expect(numFeatures).toBeGreaterThanOrEqual(4); pipeline.release(); }); it('finds features in multiple scales', async function() { const pipeline = createPipeline(media); const fast = pipeline.node('fast'); const depths = [1, 3, 5, 7]; for(const depth of depths) { fast.threshold = 50; fast.levels = depth; fast.scaleFactor = Math.sqrt(2); fast.capacity = 8192; const set = new Set(); const features = (await pipeline.run()).keypoints; for(let i = 0; i < features.length; i++) set.add(features[i].lod); print(`With depth = ${depth}, we get ${features.length} features in ${set.size} different scales.`); displayFeatures(media, features); expect(set.size).toBeGreaterThanOrEqual(depth); } pipeline.release(); }); it('gets you less features if you increase the threshold', async function() { const pipeline = createPipeline(media); const fast = pipeline.node('fast'); let lastNumFeatures = 0; const t = linspace(64, 4, 5); for(const threshold of t) { fast.threshold = threshold; fast.capacity = 8192; const features = (await pipeline.run()).keypoints; const numFeatures = features.length; print(`With threshold = ${threshold}, we get ${numFeatures} features.`); displayFeatures(media, features); expect(numFeatures).toBeGreaterThan(lastNumFeatures); lastNumFeatures = numFeatures; } expect(lastNumFeatures).toBeGreaterThan(0); pipeline.release(); }); }); describe('Harris', function() { const createPipeline = (media) => { const harris = Speedy.Keypoint.Detector.Harris('harris'); return createKeypointDetectionPipeline(harris, media); }; it('finds the corners of a square', async function() { const pipeline = createPipeline(square); const features = (await pipeline.run()).keypoints; const numFeatures = features.length; print(`Found ${numFeatures} features`); displayFeatures(square, features); expect(numFeatures).toBeGreaterThanOrEqual(4); pipeline.release(); }); it('finds features in multiple scales', async function() { const pipeline = createPipeline(media); const harris = pipeline.node('harris'); const depths = [1, 3, 5, 7]; for(const depth of depths) { harris.quality = 0.10; harris.levels = depth; harris.scaleFactor = Math.sqrt(2); harris.capacity = 8192; const set = new Set(); const features = (await pipeline.run()).keypoints; for(let i = 0; i < features.length; i++) set.add(features[i].lod); print(`With depth = ${depth}, we get ${features.length} features in ${set.size} different scales.`); displayFeatures(media, features); expect(set.size).toBeGreaterThanOrEqual(depth); } pipeline.release(); }); it('gets you less features if you increase the quality', async function() { const pipeline = createPipeline(media); const harris = pipeline.node('harris'); let lastNumFeatures = 0; const q = linspace(0.5, 0.1, 5); for(const quality of q) { harris.quality = quality; harris.capacity = 8192; const features = (await pipeline.run()).keypoints; const numFeatures = features.length; print(`With quality = ${quality}, we get ${numFeatures} features.`); displayFeatures(media, features); expect(numFeatures).toBeGreaterThan(lastNumFeatures); lastNumFeatures = numFeatures; } expect(lastNumFeatures).toBeGreaterThan(0); pipeline.release(); }); }); describe('ORB', function() { const createPipeline = (media) => { const pipeline = Speedy.Pipeline(); const source = Speedy.Image.Source(); const greyscale = Speedy.Filter.Greyscale(); const pyramid = Speedy.Image.Pyramid(); const fast = Speedy.Keypoint.Detector.FAST('fast'); const gaussian = Speedy.Filter.GaussianBlur(); const blurredPyramid = Speedy.Image.Pyramid(); const clipper = Speedy.Keypoint.Clipper('clipper'); const orb = Speedy.Keypoint.Descriptor.ORB(); const sink = Speedy.Keypoint.Sink(); source.media = media; gaussian.kernelSize = Speedy.Size(9, 9); gaussian.sigma = Speedy.Vector2(2, 2); fast.capacity = 8192; fast.threshold = 80; fast.levels = 12; // pyramid levels fast.scaleFactor = 1.19; // approx. 2^0.25 //clipper.size = 800; // up to how many features? source.output().connectTo(greyscale.input()); greyscale.output().connectTo(pyramid.input()); pyramid.output().connectTo(fast.input()); fast.output().connectTo(clipper.input()); clipper.output().connectTo(orb.input('keypoints')); greyscale.output().connectTo(gaussian.input()); gaussian.output().connectTo(blurredPyramid.input()); blurredPyramid.output().connectTo(orb.input('image')); orb.output().connectTo(sink.input()); pipeline.init(source, greyscale, pyramid, gaussian, blurredPyramid, fast, clipper, orb, sink); return pipeline; } it('computes a feature descriptor of 32 bytes', async function() { const pipeline = createPipeline(media); const features = (await pipeline.run()).keypoints; print(`Found ${features.length} feature descriptors of 32 bytes`); displayFeatures(media, features); for(let i = 0; i < features.length; i++) expect(features[i].descriptor.size).toEqual(32); expect(features.length).toBeGreaterThan(0); pipeline.release(); }); it('computes different feature descriptors', async function() { const set = new Set(); const pipeline = createPipeline(media); const features = (await pipeline.run()).keypoints; for(let i = 0; i < features.length; i++) set.add(features[i].descriptor.toString()); print(`Found ${set.size} different descriptors in ${features.length} features`); displayFeatures(media, features); expect(features.length).toBeGreaterThan(0); expect(set.size / features.length).toBeGreaterThan(0.5); pipeline.release(); }); }); describe('Clipper', function() { const createPipeline = (media) => { const fast = Speedy.Keypoint.Detector.FAST('fast'); return createKeypointDetectionPipeline(fast, media); }; const tests = [0, 100, 300]; for(const max of tests) { it(`finds up to ${max} features`, async function() { const pipeline = createPipeline(media); const clipper = pipeline.node('clipper'); const fast = pipeline.node('fast'); const t = [20, 30, 50]; for(const threshold of t) { fast.threshold = threshold; clipper.size = max; const features = (await pipeline.run()).keypoints; const numFeatures = features.length; print(`With FAST threshold = ${threshold.toFixed(2)} and max = ${max}, we find ${numFeatures} features`); displayFeatures(media, features); expect(numFeatures).toBeLessThanOrEqual(max); } pipeline.release(); }); } }); describe('Context loss', function() { const createPipeline = (media) => { const fast = Speedy.Keypoint.Detector.FAST('fast'); return createKeypointDetectionPipeline(fast, media); }; it('recovers from WebGL context loss', async function() { const pipeline = createPipeline(media); const f1 = (await pipeline.run()).keypoints; await pipeline._gpu.loseAndRestoreWebGLContext(); const f2 = (await pipeline.run()).keypoints; print('Lose WebGL context, repeat the algorithm'); displayFeatures(media, f1, 'Before losing context'); displayFeatures(media, f2, 'After losing context'); expect(f1).toEqual(f2); pipeline.release(); }); }); });