/* * Copyright (c) 2011-2016, Peter Abeles. All Rights Reserved. * * This file is part of BoofCV (http://boofcv.org). * * 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. */ package boofcv.abst.feature.detect.extract; import boofcv.alg.misc.ImageMiscOps; import boofcv.struct.QueueCorner; import boofcv.struct.image.GrayF32; import boofcv.testing.BoofTesting; import georegression.struct.point.Point2D_I16; import org.junit.Test; import java.util.Random; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; /** * @author Peter Abeles */ public abstract class GeneralNonMaxSuppressionChecks { Random rand = new Random(123); int width = 20; int height = 25; NonMaxSuppression alg; GrayF32 image = new GrayF32(width,height); QueueCorner candidatesMin = new QueueCorner(10); QueueCorner candidatesMax = new QueueCorner(10); QueueCorner foundMax = new QueueCorner(10); QueueCorner foundMin = new QueueCorner(10); public abstract NonMaxSuppression createAlg(); public void testAll() { getUsesCandidates(); setIgnoreBorder_basic(); setIgnoreBorder_conflict(); setThreshold(); setSearchRadius(); ignoreMAX_VALUE(); checkSubImage(); } public void init() { alg = createAlg(); alg.setIgnoreBorder(0); alg.setSearchRadius(1); alg.setThresholdMinimum(-5); alg.setThresholdMaximum(5); ImageMiscOps.fill(image,0); if( alg.getUsesCandidates() ) { candidatesMin.reset(); candidatesMax.reset(); } else { candidatesMin = null; candidatesMax = null; } } @Test public void getUsesCandidates() { init(); if( !alg.getUsesCandidates() ) return; // add maximums and minimums, some will be in the candidates list and others not setPixel(2, 4, true, 10); image.set(2,2,10); setPixel(3, 4, false, -10); image.set(3,2,-9); // with the candidate list only (2,4) should be found foundMin.reset();foundMax.reset(); alg.process(image,candidatesMin, candidatesMax,foundMin,foundMax); checkDetectedSize(1,1); if( alg.canDetectMaximums() ) checkFor(2,4,foundMax); if( alg.canDetectMinimums() ) checkFor(3,4,foundMin); // All points from the list, none should be found candidatesMin.reset(); candidatesMax.reset(); foundMin.reset(); foundMax.reset(); alg.process(image,candidatesMin, candidatesMax,foundMin,foundMax); assertEquals(0, foundMin.size); assertEquals(0, foundMax.size); // since it got two different answers that were dependent on the candidate list it passes the test } /** * Makes sure that the border just defines the region in which an exteme can be found. If a pixel is within * the exclusion zone and larger magnitude than a near by pixel inside, the inside pixel can't be an exteme */ @Test public void setIgnoreBorder_conflict() { if( alg.canDetectMaximums() ) setIgnoreBorder_conflict(true); if( alg.canDetectMinimums() ) setIgnoreBorder_conflict(false); } private void setIgnoreBorder_conflict( boolean checkMax ) { init(); alg.setSearchRadius(1); float sign = checkMax ? 1 : -1; setPixel(0, 1, checkMax , sign*90); setPixel(1, 1, checkMax , sign*30); // with no border (0,1) should be a peak foundMin.reset();foundMax.reset(); alg.process(image,candidatesMin, candidatesMax,foundMin,foundMax); if( checkMax ) { assertEquals(0, foundMin.size); assertEquals(1, foundMax.size); } else { assertEquals(1, foundMin.size); assertEquals(0, foundMax.size); } // now with a border there should be no maximum. 30 gets knocked out because 90 is next to it foundMin.reset();foundMax.reset(); alg.setIgnoreBorder(1); alg.process(image,candidatesMin, candidatesMax,foundMin,foundMax); assertEquals(0, foundMin.size); assertEquals(0, foundMax.size); } @Test public void setIgnoreBorder_basic() { setIgnoreBorder_basic( true ); setIgnoreBorder_basic( false ); } public void setIgnoreBorder_basic( boolean checkMax ) { init(); float a = checkMax ? 1 : -1; // place features inside the image at the border of where they can be processed setPixel(0,0, checkMax, 10*a); setPixel(width-1,height-1, checkMax, 10*a); // should find both of them foundMin.reset();foundMax.reset(); alg.process(image,candidatesMin, candidatesMax,foundMin,foundMax); if( checkMax ) { checkDetectedSize(0, 2); } else { checkDetectedSize(2, 0); } // now it shouldn't find them foundMin.reset(); foundMax.reset(); alg.setIgnoreBorder(1); alg.process(image, candidatesMin, candidatesMax, foundMin, foundMax); assertEquals(0, foundMin.size); assertEquals(0, foundMax.size); } @Test public void setThreshold() { init(); setPixel(5,6, true, 10); setPixel(5,4, false, -10); foundMin.reset(); foundMax.reset(); alg.process(image, candidatesMin, candidatesMax, foundMin, foundMax); checkDetectedSize(1, 1); // shouldn't find it after the threshold is set above its values alg.setThresholdMinimum(-20); alg.setThresholdMaximum(20); foundMin.reset(); foundMax.reset(); alg.process(image, candidatesMin, candidatesMax, foundMin, foundMax); checkDetectedSize(0, 0); } @Test public void setSearchRadius() { init(); setPixel(5, 6, true, 9); setPixel(7,6, true, 10); setPixel(6, 6, false, -9); setPixel(8,6, false, -10); // should find them both on the first pass foundMin.reset(); foundMax.reset(); alg.setSearchRadius(1); alg.process(image, candidatesMin, candidatesMax, foundMin, foundMax); checkDetectedSize(2, 2); // only one of the aftear the search radius is expanded alg.setSearchRadius(2); foundMin.reset(); foundMax.reset(); alg.process(image, candidatesMin, candidatesMax, foundMin, foundMax); checkDetectedSize(1, 1); } @Test public void ignoreMAX_VALUE() { init(); setPixel(4,5, true, Float.MAX_VALUE); setPixel(4,6, false, -Float.MAX_VALUE); foundMin.reset(); foundMax.reset(); alg.process(image, candidatesMin, candidatesMax, foundMin, foundMax); checkDetectedSize(0,0); // sanity check resetCandidates(); setPixel(4, 5, true, 10); setPixel(4, 6, false, -10); foundMin.reset(); foundMax.reset(); alg.process(image, candidatesMin, candidatesMax, foundMin, foundMax); checkDetectedSize(1,1); } private void resetCandidates() { if( candidatesMin != null ) candidatesMin.reset(); if( candidatesMax != null ) candidatesMax.reset(); } /** * When processing a sub-image it should produce the same results as when processing a regular image */ @Test public void checkSubImage() { init(); ImageMiscOps.fillGaussian(image,rand,0,2,-100,100); // make every pixel as a candidate since I'm not sure which ones are extremes. for( int i = 0; i < image.height; i++ ) for( int j = 0; j < image.width; j++ ) { if( candidatesMin != null ) candidatesMin.add(j,i); if( candidatesMax != null ) candidatesMax.add(j,i); } // the original input image foundMin.reset(); foundMax.reset(); alg.process(image, candidatesMin, candidatesMax, foundMin, foundMax); int origMin = foundMin.size; int origMax = foundMax.size; // if sub-images are correctly handled this should produce identical results GrayF32 sub = BoofTesting.createSubImageOf(image); foundMin.reset(); foundMax.reset(); alg.process(sub, candidatesMin, candidatesMax, foundMin, foundMax); int subMin = foundMin.size; int subMax = foundMax.size; assertEquals(origMin,subMin); assertEquals(origMax,subMax); } public void checkDetectedSize( int min , int max ) { if( alg.canDetectMaximums() ) { assertEquals(max,foundMax.size); } else { assertEquals(0,foundMax.size); } if( alg.canDetectMinimums() ) { assertEquals(min,foundMin.size); } else { assertEquals(0,foundMin.size); } } public void checkFor( int x , int y , QueueCorner list) { int numFound = 0; for( int i = 0; i < list.size; i++ ) { Point2D_I16 p = list.get(i); if( p.x == x && p.y == y ) numFound++; } if( numFound > 1 ) fail("Found multiple points of "+x+" "+y); else if( numFound == 0 ) fail("Point "+x+" "+y+" was not found"); } public void setPixel(int x, int y, boolean isMax, float intensity) { image.set(x,y,intensity); if( alg.getUsesCandidates() ) { if( isMax ) candidatesMax.add(x,y); else candidatesMin.add(x,y); } } }