Skip to content

Commit eaf2e4d

Browse files
SamCarlbergazure-pipelines
authored andcommitted
Add contour angle to contours report
Angle is from minAreaRect, and is in the range [-90, 0) Rework contours operations to be a bit more functional Make filter contours a bit more optimized
1 parent d4d5d5e commit eaf2e4d

File tree

6 files changed

+219
-55
lines changed

6 files changed

+219
-55
lines changed

core/src/main/java/edu/wpi/grip/core/operations/composite/ContoursReport.java

Lines changed: 47 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,21 @@
55
import edu.wpi.grip.core.operations.network.Publishable;
66
import edu.wpi.grip.core.sockets.NoSocketTypeLabel;
77
import edu.wpi.grip.core.sockets.Socket;
8+
import edu.wpi.grip.core.util.LazyInit;
9+
import edu.wpi.grip.core.util.PointerStream;
810

911
import com.google.auto.value.AutoValue;
1012

13+
import org.bytedeco.javacpp.opencv_core.RotatedRect;
14+
import org.bytedeco.javacpp.opencv_imgproc;
15+
1116
import java.util.ArrayList;
1217
import java.util.List;
13-
import java.util.Optional;
18+
import java.util.stream.Stream;
1419

1520
import static org.bytedeco.javacpp.opencv_core.Mat;
1621
import static org.bytedeco.javacpp.opencv_core.MatVector;
1722
import static org.bytedeco.javacpp.opencv_core.Rect;
18-
import static org.bytedeco.javacpp.opencv_imgproc.boundingRect;
1923
import static org.bytedeco.javacpp.opencv_imgproc.contourArea;
2024
import static org.bytedeco.javacpp.opencv_imgproc.convexHull;
2125

@@ -31,7 +35,9 @@ public final class ContoursReport implements Publishable {
3135
private final int rows;
3236
private final int cols;
3337
private final MatVector contours;
34-
private Optional<Rect[]> boundingBoxes = Optional.empty();
38+
private final LazyInit<Rect[]> boundingBoxes = new LazyInit<>(this::computeBoundingBoxes);
39+
private final LazyInit<RotatedRect[]> rotatedBoundingBoxes =
40+
new LazyInit<>(this::computeMinAreaBoundingBoxes);
3541

3642
/**
3743
* Construct an empty report. This is used as a default value for {@link Socket}s containing
@@ -70,9 +76,10 @@ public List<Contour> getProcessedContours() {
7076
double[] width = getWidth();
7177
double[] height = getHeights();
7278
double[] solidity = getSolidity();
79+
double[] angles = getAngles();
7380
for (int i = 0; i < contours.size(); i++) {
7481
processedContours.add(Contour.create(area[i], centerX[i], centerY[i], width[i], height[i],
75-
solidity[i]));
82+
solidity[i], angles[i]));
7683
}
7784
return processedContours;
7885
}
@@ -82,66 +89,51 @@ public List<Contour> getProcessedContours() {
8289
* boxes are used to compute several different properties, so it's probably not a good idea to
8390
* compute them over and over again.
8491
*/
85-
private synchronized Rect[] computeBoundingBoxes() {
86-
if (!boundingBoxes.isPresent()) {
87-
Rect[] bb = new Rect[(int) contours.size()];
88-
for (int i = 0; i < contours.size(); i++) {
89-
bb[i] = boundingRect(contours.get(i));
90-
}
91-
92-
boundingBoxes = Optional.of(bb);
93-
}
92+
private Rect[] computeBoundingBoxes() {
93+
return PointerStream.ofMatVector(contours)
94+
.map(opencv_imgproc::boundingRect)
95+
.toArray(Rect[]::new);
96+
}
9497

95-
return boundingBoxes.get();
98+
private RotatedRect[] computeMinAreaBoundingBoxes() {
99+
return PointerStream.ofMatVector(contours)
100+
.map(opencv_imgproc::minAreaRect)
101+
.toArray(RotatedRect[]::new);
96102
}
97103

98104
@PublishValue(key = "area", weight = 0)
99105
public double[] getArea() {
100-
final double[] areas = new double[(int) contours.size()];
101-
for (int i = 0; i < contours.size(); i++) {
102-
areas[i] = contourArea(contours.get(i));
103-
}
104-
return areas;
106+
return PointerStream.ofMatVector(contours)
107+
.mapToDouble(opencv_imgproc::contourArea)
108+
.toArray();
105109
}
106110

107111
@PublishValue(key = "centerX", weight = 1)
108112
public double[] getCenterX() {
109-
final double[] centers = new double[(int) contours.size()];
110-
final Rect[] boundingBoxes = computeBoundingBoxes();
111-
for (int i = 0; i < contours.size(); i++) {
112-
centers[i] = boundingBoxes[i].x() + boundingBoxes[i].width() / 2;
113-
}
114-
return centers;
113+
return Stream.of(boundingBoxes.get())
114+
.mapToDouble(r -> r.x() + r.width() / 2)
115+
.toArray();
115116
}
116117

117118
@PublishValue(key = "centerY", weight = 2)
118119
public double[] getCenterY() {
119-
final double[] centers = new double[(int) contours.size()];
120-
final Rect[] boundingBoxes = computeBoundingBoxes();
121-
for (int i = 0; i < contours.size(); i++) {
122-
centers[i] = boundingBoxes[i].y() + boundingBoxes[i].height() / 2;
123-
}
124-
return centers;
120+
return Stream.of(boundingBoxes.get())
121+
.mapToDouble(r -> r.y() + r.height() / 2)
122+
.toArray();
125123
}
126124

127125
@PublishValue(key = "width", weight = 3)
128126
public synchronized double[] getWidth() {
129-
final double[] widths = new double[(int) contours.size()];
130-
final Rect[] boundingBoxes = computeBoundingBoxes();
131-
for (int i = 0; i < contours.size(); i++) {
132-
widths[i] = boundingBoxes[i].width();
133-
}
134-
return widths;
127+
return Stream.of(boundingBoxes.get())
128+
.mapToDouble(Rect::width)
129+
.toArray();
135130
}
136131

137132
@PublishValue(key = "height", weight = 4)
138133
public synchronized double[] getHeights() {
139-
final double[] heights = new double[(int) contours.size()];
140-
final Rect[] boundingBoxes = computeBoundingBoxes();
141-
for (int i = 0; i < contours.size(); i++) {
142-
heights[i] = boundingBoxes[i].height();
143-
}
144-
return heights;
134+
return Stream.of(boundingBoxes.get())
135+
.mapToDouble(Rect::height)
136+
.toArray();
145137
}
146138

147139
@PublishValue(key = "solidity", weight = 5)
@@ -156,11 +148,19 @@ public synchronized double[] getSolidity() {
156148
return solidities;
157149
}
158150

151+
@PublishValue(key = "angle", weight = 6)
152+
public synchronized double[] getAngles() {
153+
return Stream.of(rotatedBoundingBoxes.get())
154+
.mapToDouble(RotatedRect::angle)
155+
.toArray();
156+
}
157+
159158
@AutoValue
160159
public abstract static class Contour {
161160
public static Contour create(double area, double centerX, double centerY, double width, double
162-
height, double solidity) {
163-
return new AutoValue_ContoursReport_Contour(area, centerX, centerY, width, height, solidity);
161+
height, double solidity, double angle) {
162+
return new AutoValue_ContoursReport_Contour(area, centerX, centerY, width, height, solidity,
163+
angle);
164164
}
165165

166166
public abstract double area();
@@ -174,5 +174,7 @@ public static Contour create(double area, double centerX, double centerY, double
174174
public abstract double height();
175175

176176
public abstract double solidity();
177+
178+
public abstract double angle();
177179
}
178180
}

core/src/main/java/edu/wpi/grip/core/operations/composite/FilterContoursOperation.java

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@
1515

1616
import static org.bytedeco.javacpp.opencv_core.Mat;
1717
import static org.bytedeco.javacpp.opencv_core.MatVector;
18-
import static org.bytedeco.javacpp.opencv_core.Rect;
1918
import static org.bytedeco.javacpp.opencv_imgproc.arcLength;
20-
import static org.bytedeco.javacpp.opencv_imgproc.boundingRect;
2119
import static org.bytedeco.javacpp.opencv_imgproc.contourArea;
2220
import static org.bytedeco.javacpp.opencv_imgproc.convexHull;
2321

@@ -74,6 +72,8 @@ public class FilterContoursOperation implements Operation {
7472
private final SocketHint<Number> maxRatioHint =
7573
SocketHints.Inputs.createNumberSpinnerSocketHint("Max Ratio", 1000, 0, Integer.MAX_VALUE);
7674

75+
private final SocketHint<List<Number>> angleHint =
76+
SocketHints.Inputs.createNumberListRangeSocketHint("Angle", -90, 0);
7777

7878
private final InputSocket<ContoursReport> contoursSocket;
7979
private final InputSocket<Number> minAreaSocket;
@@ -87,6 +87,7 @@ public class FilterContoursOperation implements Operation {
8787
private final InputSocket<Number> maxVertexSocket;
8888
private final InputSocket<Number> minRatioSocket;
8989
private final InputSocket<Number> maxRatioSocket;
90+
private final InputSocket<List<Number>> angleSocket;
9091

9192
private final OutputSocket<ContoursReport> outputSocket;
9293

@@ -106,6 +107,7 @@ public FilterContoursOperation(InputSocket.Factory inputSocketFactory, OutputSoc
106107
this.maxVertexSocket = inputSocketFactory.create(maxVertexHint);
107108
this.minRatioSocket = inputSocketFactory.create(minRatioHint);
108109
this.maxRatioSocket = inputSocketFactory.create(maxRatioHint);
110+
this.angleSocket = inputSocketFactory.create(angleHint);
109111

110112
this.outputSocket = outputSocketFactory.create(contoursHint);
111113
}
@@ -124,7 +126,8 @@ public List<InputSocket> getInputSockets() {
124126
maxVertexSocket,
125127
minVertexSocket,
126128
minRatioSocket,
127-
maxRatioSocket
129+
maxRatioSocket,
130+
angleSocket
128131
);
129132
}
130133

@@ -139,6 +142,7 @@ public List<OutputSocket> getOutputSockets() {
139142
@SuppressWarnings("unchecked")
140143
public void perform() {
141144
final InputSocket<ContoursReport> inputSocket = contoursSocket;
145+
final ContoursReport report = inputSocket.getValue().get();
142146
final double minArea = minAreaSocket.getValue().get().doubleValue();
143147
final double minPerimeter = minPerimeterSocket.getValue().get().doubleValue();
144148
final double minWidth = minWidthSocket.getValue().get().doubleValue();
@@ -151,9 +155,10 @@ public void perform() {
151155
final double maxVertexCount = maxVertexSocket.getValue().get().doubleValue();
152156
final double minRatio = minRatioSocket.getValue().get().doubleValue();
153157
final double maxRatio = maxRatioSocket.getValue().get().doubleValue();
158+
final double minAngle = angleSocket.getValue().get().get(0).doubleValue();
159+
final double maxAngle = angleSocket.getValue().get().get(1).doubleValue();
154160

155-
156-
final MatVector inputContours = inputSocket.getValue().get().getContours();
161+
final MatVector inputContours = report.getContours();
157162
final MatVector outputContours = new MatVector(inputContours.size());
158163
final Mat hull = new Mat();
159164

@@ -164,15 +169,14 @@ public void perform() {
164169
for (int i = 0; i < inputContours.size(); i++) {
165170
final Mat contour = inputContours.get(i);
166171

167-
final Rect bb = boundingRect(contour);
168-
if (bb.width() < minWidth || bb.width() > maxWidth) {
172+
if (report.getWidth()[i] < minWidth || report.getWidth()[i] > maxWidth) {
169173
continue;
170174
}
171-
if (bb.height() < minHeight || bb.height() > maxHeight) {
175+
if (report.getHeights()[i] < minHeight || report.getHeights()[i] > maxHeight) {
172176
continue;
173177
}
174178

175-
final double area = contourArea(contour);
179+
final double area = report.getArea()[i];
176180
if (area < minArea) {
177181
continue;
178182
}
@@ -191,11 +195,16 @@ public void perform() {
191195
continue;
192196
}
193197

194-
final double ratio = (double) bb.width() / (double) bb.height();
198+
final double ratio = report.getWidth()[i] / report.getHeights()[i];
195199
if (ratio < minRatio || ratio > maxRatio) {
196200
continue;
197201
}
198202

203+
final double angle = report.getAngles()[i];
204+
if (angle < minAngle || angle > maxAngle) {
205+
continue;
206+
}
207+
199208
outputContours.put(filteredContourCount++, contour);
200209
}
201210

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package edu.wpi.grip.core.util;
2+
3+
import java.util.Objects;
4+
import java.util.function.Supplier;
5+
6+
/**
7+
* A holder for data that gets lazily initialized.
8+
*
9+
* @param <T> the type of held data
10+
*/
11+
public class LazyInit<T> {
12+
13+
private T value = null;
14+
private final Supplier<? extends T> factory;
15+
16+
/**
17+
* Creates a new lazily initialized data holder.
18+
*
19+
* @param factory the factory to use to create the held value
20+
*/
21+
public LazyInit(Supplier<? extends T> factory) {
22+
this.factory = Objects.requireNonNull(factory, "factory");
23+
}
24+
25+
/**
26+
* Gets the value, initializing it if it has not yet been created.
27+
*
28+
* @return the held value
29+
*/
30+
public T get() {
31+
if (value == null) {
32+
value = factory.get();
33+
}
34+
return value;
35+
}
36+
37+
/**
38+
* Clears the held value. The next call to {@link #get()} will re-instantiate the held value.
39+
*/
40+
public void clear() {
41+
value = null;
42+
}
43+
44+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package edu.wpi.grip.core.util;
2+
3+
import java.util.stream.LongStream;
4+
import java.util.stream.Stream;
5+
6+
import static org.bytedeco.javacpp.opencv_core.Mat;
7+
import static org.bytedeco.javacpp.opencv_core.MatVector;
8+
9+
/**
10+
* Utility class for streaming native vector wrappers like {@code MatVector}
11+
* ({@code std::vector<T>}) with the Java {@link Stream} API.
12+
*/
13+
public final class PointerStream {
14+
15+
/**
16+
* Creates a stream of {@code Mat} objects in a {@code MatVector}.
17+
*
18+
* @param vector the vector of {@code Mats} to stream
19+
*
20+
* @return a new stream object for the contents of the vector
21+
*/
22+
public static Stream<Mat> ofMatVector(MatVector vector) {
23+
return LongStream.range(0, vector.size())
24+
.mapToObj(vector::get);
25+
}
26+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package edu.wpi.grip.core.util;
2+
3+
import org.junit.Test;
4+
5+
import java.util.function.Supplier;
6+
7+
import static org.junit.Assert.assertEquals;
8+
9+
public class LazyInitTest {
10+
11+
@Test
12+
public void testFactoryIsOnlyCalledOnce() {
13+
final String output = "foo";
14+
final int[] count = {0};
15+
final Supplier<String> factory = () -> {
16+
count[0]++;
17+
return output;
18+
};
19+
20+
LazyInit<String> lazyInit = new LazyInit<>(factory);
21+
lazyInit.get();
22+
assertEquals(1, count[0]);
23+
24+
lazyInit.get();
25+
assertEquals("Calling get() more than once should only call the factory once", 1, count[0]);
26+
}
27+
28+
@Test
29+
public void testClear() {
30+
final String output = "foo";
31+
final int[] count = {0};
32+
final Supplier<String> factory = () -> {
33+
count[0]++;
34+
return output;
35+
};
36+
LazyInit<String> lazyInit = new LazyInit<>(factory);
37+
lazyInit.get();
38+
assertEquals(1, count[0]);
39+
40+
lazyInit.clear();
41+
lazyInit.get();
42+
assertEquals(2, count[0]);
43+
}
44+
45+
}

0 commit comments

Comments
 (0)