Skip to content

Commit d2aa8a7

Browse files
NonMaxSuppression
- Code Formatting - Small but breaking API change - Local maximums can now be stored in different data structures
1 parent 0659e27 commit d2aa8a7

27 files changed

+590
-693
lines changed

change.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ Version Meaning: <compatible>.<feature>.<bug fix>
88

99
---------------------
1010
Date : 2025/XX/YY
11-
Version : 1.2.5
11+
Version : 1.3.0
1212

13+
- NonMaxSuppression
14+
* Small but breaking API change
15+
* Local maximums can now be stored in different data structures
1316
- ImageProcessingFragment
1417
* Support for warmup frames
1518
- Mean Blur

examples/src/main/java/boofcv/examples/features/ExampleNonMaximumSupression.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022, Peter Abeles. All Rights Reserved.
2+
* Copyright (c) 2025, Peter Abeles. All Rights Reserved.
33
*
44
* This file is part of BoofCV (http://boofcv.org).
55
*
@@ -52,7 +52,7 @@ public class ExampleNonMaximumSupression {
5252

5353
public static BufferedImage renderNonMax( GrayF32 intensity, int radius, float threshold ) {
5454
// Create and configure the feature detector
55-
NonMaxSuppression nonmax = FactoryFeatureExtractor.nonmax(new ConfigExtract(radius, threshold));
55+
NonMaxSuppression<QueueCorner> nonmax = FactoryFeatureExtractor.nonmax(new ConfigExtract(radius, threshold));
5656

5757
// We will only search for the maximums. Other variants will look for minimums or will exclude previous
5858
// candidate detections from being detected twice

main/boofcv-feature/src/benchmark/java/boofcv/alg/feature/detect/extract/BenchmarkExtractors.java

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, Peter Abeles. All Rights Reserved.
2+
* Copyright (c) 2025, Peter Abeles. All Rights Reserved.
33
*
44
* This file is part of BoofCV (http://boofcv.org).
55
*
@@ -49,26 +49,25 @@ public class BenchmarkExtractors {
4949
// @Param({"500","5000"})
5050
public int width = 1000;
5151

52-
// @Param({"2", "5", "20"})
52+
// @Param({"2", "5", "20"})
5353
@Param({"2", "20"})
5454
public int radius;
5555

5656
GrayF32 intensity = new GrayF32(1, 1);
5757
QueueCorner corners;
5858

59-
NonMaxSuppression blockStrictMax;
59+
NonMaxSuppression<QueueCorner> blockStrictMax;
6060

61-
@Setup
62-
public void setup() {
61+
@Setup public void setup() {
6362
BoofConcurrency.USE_CONCURRENT = concurrent;
6463

6564
intensity.reshape(width, width);
6665
corners = new QueueCorner();
6766

68-
Random rand = new Random(234);
67+
var rand = new Random(234);
6968
ImageMiscOps.fillUniform(intensity, rand, 0, 200);
7069

71-
ConfigExtract config = new ConfigExtract();
70+
var config = new ConfigExtract();
7271
config.radius = radius;
7372
config.detectMaximums = true;
7473
config.detectMinimums = false;
@@ -77,50 +76,46 @@ public void setup() {
7776
blockStrictMax = FactoryFeatureExtractor.nonmax(config);
7877
}
7978

80-
@Benchmark
81-
public void blockStrictMax() {
79+
@Benchmark public void blockStrictMax() {
8280
blockStrictMax.process(intensity, null, null, corners, corners);
8381
}
8482

85-
@Benchmark
86-
public void blockStrictMinMax() {
87-
ConfigExtract config = new ConfigExtract();
83+
@Benchmark public void blockStrictMinMax() {
84+
var config = new ConfigExtract();
8885
config.radius = radius;
8986
config.detectMaximums = true;
9087
config.detectMinimums = true;
9188
config.threshold = threshold;
9289
config.useStrictRule = true;
93-
NonMaxSuppression alg = FactoryFeatureExtractor.nonmax(config);
90+
NonMaxSuppression<QueueCorner> alg = FactoryFeatureExtractor.nonmax(config);
9491
alg.process(intensity, null, null, corners, corners);
9592
}
9693

97-
@Benchmark
98-
public void blockRelaxedMax() {
99-
ConfigExtract config = new ConfigExtract();
94+
@Benchmark public void blockRelaxedMax() {
95+
var config = new ConfigExtract();
10096
config.radius = radius;
10197
config.detectMaximums = true;
10298
config.detectMinimums = false;
10399
config.threshold = threshold;
104100
config.useStrictRule = false;
105-
NonMaxSuppression alg = FactoryFeatureExtractor.nonmax(config);
101+
NonMaxSuppression<QueueCorner> alg = FactoryFeatureExtractor.nonmax(config);
106102
alg.process(intensity, null, null, corners, corners);
107103
}
108104

109-
@Benchmark
110-
public void blockRelaxedMinMax() {
111-
ConfigExtract config = new ConfigExtract();
105+
@Benchmark public void blockRelaxedMinMax() {
106+
var config = new ConfigExtract();
112107
config.radius = radius;
113108
config.detectMaximums = true;
114109
config.detectMinimums = true;
115110
config.threshold = threshold;
116111
config.useStrictRule = false;
117-
NonMaxSuppression alg = FactoryFeatureExtractor.nonmax(config);
112+
NonMaxSuppression<QueueCorner> alg = FactoryFeatureExtractor.nonmax(config);
118113
alg.process(intensity, null, null, corners, corners);
119114
}
120115

121-
@Benchmark
122-
public void naiveStrictMax() {
123-
NonMaxExtractorNaive alg = new NonMaxExtractorNaive(true);
116+
@Benchmark public void naiveStrictMax() {
117+
var alg = new NonMaxExtractorNaive<QueueCorner>(true);
118+
alg.storageAccess(QueueCorner::append, QueueCorner::reset);
124119
alg.radius = radius;
125120
alg.thresh = threshold;
126121
alg.process(intensity, corners);

main/boofcv-feature/src/main/java/boofcv/abst/feature/detect/extract/ConfigExtract.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, Peter Abeles. All Rights Reserved.
2+
* Copyright (c) 2025, Peter Abeles. All Rights Reserved.
33
*
44
* This file is part of BoofCV (http://boofcv.org).
55
*

main/boofcv-feature/src/main/java/boofcv/abst/feature/detect/extract/NonMaxLimiter.java

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, Peter Abeles. All Rights Reserved.
2+
* Copyright (c) 2025, Peter Abeles. All Rights Reserved.
33
*
44
* This file is part of BoofCV (http://boofcv.org).
55
*
@@ -20,8 +20,8 @@
2020

2121
import boofcv.alg.feature.detect.selector.FeatureSelectLimitIntensity;
2222
import boofcv.alg.feature.detect.selector.SampleIntensity;
23-
import boofcv.struct.QueueCorner;
2423
import boofcv.struct.image.GrayF32;
24+
import georegression.struct.packed.PackedArrayPoint2D_I16;
2525
import georegression.struct.point.Point2D_I16;
2626
import lombok.Getter;
2727
import lombok.Setter;
@@ -34,14 +34,14 @@
3434
/// points will be sorted by feature intensity. If maximums and minimums are found then the total
3535
/// number refers to the total combined number of features. The intensity that it sorts by is the absolute value.
3636
public class NonMaxLimiter {
37-
@Getter NonMaxSuppression nonmax;
37+
@Getter NonMaxSuppression<PackedArrayPoint2D_I16> nonmax;
3838

3939
/// Maximum number of features it can return. If %le; 0 then there will be no limit
4040
@Getter @Setter int maxTotalFeatures;
4141

4242
// Detected minimums and maximums
43-
QueueCorner originalMin = new QueueCorner();
44-
QueueCorner originalMax = new QueueCorner();
43+
PackedArrayPoint2D_I16 originalMin = new PackedArrayPoint2D_I16();
44+
PackedArrayPoint2D_I16 originalMax = new PackedArrayPoint2D_I16();
4545

4646
// Selects features when too many are detected
4747
FeatureSelectLimitIntensity<LocalExtreme> selector;
@@ -52,14 +52,15 @@ public class NonMaxLimiter {
5252

5353
/// Configures the limiter
5454
///
55-
/// @param nonmax Non-maximum suppression algorithm
55+
/// @param nonmax Non-maximum suppression algorithm. Generic type does not matter.
5656
/// @param maxTotalFeatures The total number of allowed features it can return. Set to a value &le; 0 to disable.
5757
public NonMaxLimiter( NonMaxSuppression nonmax,
5858
FeatureSelectLimitIntensity<LocalExtreme> selector,
5959
int maxTotalFeatures ) {
6060
this.nonmax = nonmax;
6161
this.selector = selector;
6262
this.maxTotalFeatures = maxTotalFeatures;
63+
this.nonmax.storageAccess(PackedArrayPoint2D_I16::append, PackedArrayPoint2D_I16::reset);
6364

6465
selector.setSampler(new SampleIntensity<>() {
6566
@Override
@@ -81,16 +82,16 @@ public void process( GrayF32 intensity ) {
8182
nonmax.process(intensity, null, null, originalMin, originalMax);
8283

8384
foundAll.reset();
84-
for (int i = 0; i < originalMin.size; i++) {
85-
Point2D_I16 p = originalMin.get(i);
85+
for (int i = 0; i < originalMin.size(); i++) {
86+
Point2D_I16 p = originalMin.getTemp(i);
8687
float val = intensity.unsafe_get(p.x, p.y);
87-
foundAll.grow().set(-val, false, p);
88+
foundAll.grow().set(-val, false, p.x, p.y);
8889
}
8990

90-
for (int i = 0; i < originalMax.size; i++) {
91-
Point2D_I16 p = originalMax.get(i);
91+
for (int i = 0; i < originalMax.size(); i++) {
92+
Point2D_I16 p = originalMax.getTemp(i);
9293
float val = intensity.unsafe_get(p.x, p.y);
93-
foundAll.grow().set(val, true, p);
94+
foundAll.grow().set(val, true, p.x, p.y);
9495
}
9596

9697
if (maxTotalFeatures > 0) {
@@ -112,12 +113,12 @@ public static class LocalExtreme implements Comparable<LocalExtreme> {
112113
public float intensity;
113114
/// true if it was a maximum (positive) or minimum (negative intensity)
114115
public boolean max;
115-
public Point2D_I16 location;
116+
public final Point2D_I16 location = new Point2D_I16();
116117

117-
public LocalExtreme set( float intensity, boolean max, Point2D_I16 location ) {
118+
public LocalExtreme set( float intensity, boolean max, int x, int y ) {
118119
this.intensity = intensity;
119120
this.max = max;
120-
this.location = location;
121+
this.location.setTo(x, y);
121122
return this;
122123
}
123124

main/boofcv-feature/src/main/java/boofcv/abst/feature/detect/extract/NonMaxSuppression.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, Peter Abeles. All Rights Reserved.
2+
* Copyright (c) 2025, Peter Abeles. All Rights Reserved.
33
*
44
* This file is part of BoofCV (http://boofcv.org).
55
*
@@ -19,7 +19,6 @@
1919
package boofcv.abst.feature.detect.extract;
2020

2121
import boofcv.struct.ListIntPoint2D;
22-
import boofcv.struct.QueueCorner;
2322
import boofcv.struct.image.GrayF32;
2423
import org.jetbrains.annotations.Nullable;
2524

@@ -45,7 +44,10 @@
4544
/// outside this border can influence if a pixel is a maximum inside, if the local search radius extends that far.
4645
/// This is specified by the border parameter and the valid region is defined as follows:
4746
/// border &le; x &lt; width-border AND border &le; y &lt; height-border
48-
public interface NonMaxSuppression {
47+
public interface NonMaxSuppression<Storage> {
48+
49+
/// Specifies how to manipulate the passed in storage objects
50+
void storageAccess( Add<Storage> opAdd, Reset<Storage> opReset );
4951

5052
/// Process a feature intensity image to extract the point features. If a pixel has an intensity
5153
/// value == -Float.MAX_VALUE or Float.MAX_VALUE it will not be considered for a local min or max, respectively.
@@ -59,7 +61,7 @@ public interface NonMaxSuppression {
5961
/// @param foundMax (Output) Storage for found maximums. Can be null if not used.
6062
void process( GrayF32 intensity,
6163
@Nullable ListIntPoint2D candidateMin, @Nullable ListIntPoint2D candidateMax,
62-
@Nullable QueueCorner foundMin, @Nullable QueueCorner foundMax );
64+
@Nullable Storage foundMin, @Nullable Storage foundMax );
6365

6466
/// Returns true if the algorithm requires a candidate list of corners.
6567
///
@@ -113,4 +115,14 @@ void process( GrayF32 intensity,
113115

114116
/// True if it can detect local minimums.
115117
boolean canDetectMinimums();
118+
119+
/// Adds a point to the storage
120+
@FunctionalInterface interface Add<Storage> {
121+
void add( Storage found, int x, int y );
122+
}
123+
124+
/// Clears and resets the storage
125+
@FunctionalInterface interface Reset<Storage> {
126+
void reset( Storage found );
127+
}
116128
}

main/boofcv-feature/src/main/java/boofcv/abst/feature/detect/extract/WrapperNonMaxCandidate.java

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, Peter Abeles. All Rights Reserved.
2+
* Copyright (c) 2025, Peter Abeles. All Rights Reserved.
33
*
44
* This file is part of BoofCV (http://boofcv.org).
55
*
@@ -20,27 +20,30 @@
2020

2121
import boofcv.alg.feature.detect.extract.NonMaxCandidate;
2222
import boofcv.struct.ListIntPoint2D;
23-
import boofcv.struct.QueueCorner;
2423
import boofcv.struct.image.GrayF32;
2524
import org.jetbrains.annotations.Nullable;
2625

2726
/// Wrapper around the [boofcv.alg.feature.detect.extract.NonMaxCandidate] class.
28-
public class WrapperNonMaxCandidate implements NonMaxSuppression {
29-
NonMaxCandidate extractor;
27+
public class WrapperNonMaxCandidate<Storage> implements NonMaxSuppression<Storage> {
28+
NonMaxCandidate<Storage> extractor;
3029
boolean minimums, maximums;
3130

3231
public WrapperNonMaxCandidate( NonMaxCandidate.Search search, boolean minimums, boolean maximums ) {
33-
this.extractor = new NonMaxCandidate(search);
32+
this.extractor = new NonMaxCandidate<>(search);
3433
this.minimums = minimums;
3534
this.maximums = maximums;
3635
}
3736

38-
public WrapperNonMaxCandidate( NonMaxCandidate extractor, boolean minimums, boolean maximums ) {
37+
public WrapperNonMaxCandidate( NonMaxCandidate<Storage> extractor, boolean minimums, boolean maximums ) {
3938
this.extractor = extractor;
4039
this.minimums = minimums;
4140
this.maximums = maximums;
4241
}
4342

43+
@Override public void storageAccess( Add<Storage> opAdd, Reset<Storage> opReset ) {
44+
this.extractor.storageAccess(opAdd, opReset);
45+
}
46+
4447
@Override public float getThresholdMinimum() {
4548
return extractor.getThresholdMin();
4649
}
@@ -66,8 +69,8 @@ public WrapperNonMaxCandidate( NonMaxCandidate extractor, boolean minimums, bool
6669
}
6770

6871
@Override public void process( GrayF32 intensity,
69-
@Nullable ListIntPoint2D candidateMin, @Nullable ListIntPoint2D candidateMax,
70-
@Nullable QueueCorner foundMin, @Nullable QueueCorner foundMax ) {
72+
@Nullable ListIntPoint2D candidateMin, @Nullable ListIntPoint2D candidateMax,
73+
@Nullable Storage foundMin, @Nullable Storage foundMax ) {
7174
extractor.process(intensity, candidateMin, candidateMax, foundMin, foundMax);
7275
}
7376

main/boofcv-feature/src/main/java/boofcv/abst/feature/detect/extract/WrapperNonMaximumBlock.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, Peter Abeles. All Rights Reserved.
2+
* Copyright (c) 2025, Peter Abeles. All Rights Reserved.
33
*
44
* This file is part of BoofCV (http://boofcv.org).
55
*
@@ -27,18 +27,22 @@
2727
/// Wrapper around the [boofcv.alg.feature.detect.extract.NonMaxBlock] class.
2828
///
2929
/// @author Peter Abeles
30-
public class WrapperNonMaximumBlock implements NonMaxSuppression {
30+
public class WrapperNonMaximumBlock<Storage> implements NonMaxSuppression<Storage> {
3131

3232
// specific implementation
33-
NonMaxBlock alg;
33+
NonMaxBlock<Storage> alg;
3434

35-
public WrapperNonMaximumBlock( NonMaxBlock alg ) {
35+
public WrapperNonMaximumBlock( NonMaxBlock<Storage> alg ) {
3636
this.alg = alg;
3737
}
3838

39+
@Override public void storageAccess( Add<Storage> opAdd, Reset<Storage> opReset ) {
40+
this.alg.storageAccess(opAdd, opReset);
41+
}
42+
3943
@Override public void process( GrayF32 intensity,
4044
@Nullable ListIntPoint2D candidateMin, @Nullable ListIntPoint2D candidateMax,
41-
@Nullable QueueCorner foundMin, @Nullable QueueCorner foundMax ) {
45+
@Nullable Storage foundMin, @Nullable Storage foundMax ) {
4246
alg.process(intensity, foundMin, foundMax);
4347
}
4448

0 commit comments

Comments
 (0)