Skip to content

Commit 22a371d

Browse files
committed
Merge branch 'master' into build/fixReleaseArtifacts
2 parents 8562f59 + 6d13835 commit 22a371d

File tree

11 files changed

+496
-0
lines changed

11 files changed

+496
-0
lines changed

core/src/main/java/edu/wpi/grip/core/operations/Operations.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ public static void addOperations(EventBus eventBus) {
1919
eventBus.post(new OperationAddedEvent(new RGBThresholdOperation()));
2020
eventBus.post(new OperationAddedEvent(new HSVThresholdOperation()));
2121
eventBus.post(new OperationAddedEvent(new HSLThresholdOperation()));
22+
eventBus.post(new OperationAddedEvent(new FindContoursOperation()));
23+
eventBus.post(new OperationAddedEvent(new FilterContoursOperation()));
24+
eventBus.post(new OperationAddedEvent(new ConvexHullsOperation()));
2225
eventBus.post(new OperationAddedEvent(new FindBlobsOperation()));
2326
eventBus.post(new OperationAddedEvent(new FindLinesOperation()));
2427
eventBus.post(new OperationAddedEvent(new FilterLinesOperation()));
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package edu.wpi.grip.core.operations.composite;
2+
3+
import static org.bytedeco.javacpp.opencv_core.*;
4+
5+
/**
6+
* The output of {@link FindContoursOperation}. This stores a list of contours (which is basically a list of points) in
7+
* OpenCV objects, as well as the width and height of the image that the contours are from, to give context to the
8+
* points.
9+
*/
10+
public final class ContoursReport {
11+
private int rows, cols;
12+
private MatVector contours = new MatVector();
13+
14+
public ContoursReport() {
15+
this(new MatVector(), -1, -1);
16+
}
17+
18+
public ContoursReport(MatVector contours, int rows, int cols) {
19+
this.contours = contours;
20+
this.rows = rows;
21+
this.cols = cols;
22+
}
23+
24+
public void setContours(MatVector contours) {
25+
this.contours = contours;
26+
}
27+
28+
public MatVector getContours() {
29+
return this.contours;
30+
}
31+
32+
public void setRows(int rows) {
33+
this.rows = rows;
34+
}
35+
36+
public void setCols(int cols) {
37+
this.cols = cols;
38+
}
39+
40+
public int getRows() {
41+
return this.rows;
42+
}
43+
44+
public int getCols() {
45+
return this.cols;
46+
}
47+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package edu.wpi.grip.core.operations.composite;
2+
3+
import com.google.common.eventbus.EventBus;
4+
import edu.wpi.grip.core.InputSocket;
5+
import edu.wpi.grip.core.Operation;
6+
import edu.wpi.grip.core.OutputSocket;
7+
import edu.wpi.grip.core.SocketHint;
8+
9+
import java.io.InputStream;
10+
import java.util.Optional;
11+
12+
import static org.bytedeco.javacpp.opencv_imgproc.convexHull;
13+
14+
/**
15+
* An {@link Operation} that finds the convex hull of each of a list of contours.
16+
* <p>
17+
* This can help remove holes in detected shapes, making them easier to analyze.
18+
*/
19+
public class ConvexHullsOperation implements Operation {
20+
private final SocketHint<ContoursReport> contoursHint = new SocketHint.Builder<>(ContoursReport.class)
21+
.identifier("Contours").initialValueSupplier(ContoursReport::new).build();
22+
23+
@Override
24+
public String getName() {
25+
return "Convex Hulls";
26+
}
27+
28+
@Override
29+
public String getDescription() {
30+
return "Compute the convex hulls of contours.";
31+
}
32+
33+
@Override
34+
public Optional<InputStream> getIcon() {
35+
return Optional.of(getClass().getResourceAsStream("/edu/wpi/grip/ui/icons/convex-hulls.png"));
36+
}
37+
38+
@Override
39+
public InputSocket<?>[] createInputSockets(EventBus eventBus) {
40+
return new InputSocket<?>[]{new InputSocket<>(eventBus, contoursHint)};
41+
}
42+
43+
@Override
44+
public OutputSocket<?>[] createOutputSockets(EventBus eventBus) {
45+
return new OutputSocket<?>[]{new OutputSocket<>(eventBus, contoursHint)};
46+
}
47+
48+
@Override
49+
@SuppressWarnings("unchecked")
50+
public void perform(InputSocket<?>[] inputs, OutputSocket<?>[] outputs) {
51+
final InputSocket<ContoursReport> inputSocket = (InputSocket<ContoursReport>) inputs[0];
52+
final OutputSocket<ContoursReport> outputSocket = (OutputSocket<ContoursReport>) outputs[0];
53+
54+
final ContoursReport inputContours = inputSocket.getValue().get();
55+
final ContoursReport outputContours = outputSocket.getValue().get();
56+
outputContours.getContours().resize(inputContours.getContours().size());
57+
58+
for (int i = 0; i < inputContours.getContours().size(); i++) {
59+
convexHull(inputContours.getContours().get(i), outputContours.getContours().get(i));
60+
}
61+
62+
outputContours.setRows(inputContours.getRows());
63+
outputContours.setCols(inputContours.getCols());
64+
outputSocket.setValue(outputContours);
65+
}
66+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package edu.wpi.grip.core.operations.composite;
2+
3+
import com.google.common.eventbus.EventBus;
4+
import edu.wpi.grip.core.*;
5+
6+
import java.io.InputStream;
7+
import java.util.Optional;
8+
9+
import static org.bytedeco.javacpp.opencv_core.*;
10+
import static org.bytedeco.javacpp.opencv_imgproc.*;
11+
12+
/**
13+
* An {@link Operation} that takes in a list of contours and outputs a list of any contours in the input that match
14+
* all of several criteria. Right now, the user can specify a minimum area, minimum perimeter, and ranges for width
15+
* and height.
16+
* <p>
17+
* This is useful because running a Find Contours on a real-life image typically leads to many small undesirable
18+
* contours from noise and small objects, as well as contours that do not meet the expected characteristics of the
19+
* feature we're actually looking for. So, this operation can help narrow them down.
20+
*/
21+
public class FilterContoursOperation implements Operation {
22+
23+
private final SocketHint<ContoursReport> contoursHint = new SocketHint.Builder<>(ContoursReport.class)
24+
.identifier("Contours").initialValueSupplier(ContoursReport::new).build();
25+
26+
private final SocketHint<Number> minAreaHint =
27+
SocketHints.Inputs.createNumberSpinnerSocketHint("Min Area", 0, 0, Integer.MAX_VALUE);
28+
29+
private final SocketHint<Number> minPerimeterHint =
30+
SocketHints.Inputs.createNumberSpinnerSocketHint("Min Perimeter", 0, 0, Integer.MAX_VALUE);
31+
32+
private final SocketHint<Number> minWidthHint =
33+
SocketHints.Inputs.createNumberSpinnerSocketHint("Min Width", 0, 0, Integer.MAX_VALUE);
34+
35+
private final SocketHint<Number> maxWidthHint =
36+
SocketHints.Inputs.createNumberSpinnerSocketHint("Max Width", 0, 0, Integer.MAX_VALUE);
37+
38+
private final SocketHint<Number> minHeightHint =
39+
SocketHints.Inputs.createNumberSpinnerSocketHint("Min Height", 0, 0, Integer.MAX_VALUE);
40+
41+
private final SocketHint<Number> maxHeightHint =
42+
SocketHints.Inputs.createNumberSpinnerSocketHint("Max Height", 0, 0, Integer.MAX_VALUE);
43+
44+
@Override
45+
public String getName() {
46+
return "Filter Contours";
47+
}
48+
49+
@Override
50+
public String getDescription() {
51+
return "Find contours matching certain criteria.";
52+
}
53+
54+
@Override
55+
public Optional<InputStream> getIcon() {
56+
return Optional.of(getClass().getResourceAsStream("/edu/wpi/grip/ui/icons/find-contours.png"));
57+
}
58+
59+
@Override
60+
public InputSocket<?>[] createInputSockets(EventBus eventBus) {
61+
return new InputSocket<?>[]{
62+
new InputSocket<>(eventBus, contoursHint),
63+
new InputSocket<>(eventBus, minAreaHint),
64+
new InputSocket<>(eventBus, minPerimeterHint),
65+
new InputSocket<>(eventBus, minWidthHint),
66+
new InputSocket<>(eventBus, maxWidthHint),
67+
new InputSocket<>(eventBus, minHeightHint),
68+
new InputSocket<>(eventBus, maxHeightHint),
69+
};
70+
}
71+
72+
@Override
73+
public OutputSocket<?>[] createOutputSockets(EventBus eventBus) {
74+
return new OutputSocket<?>[]{new OutputSocket<>(eventBus, contoursHint)};
75+
}
76+
77+
@Override
78+
@SuppressWarnings("unchecked")
79+
public void perform(InputSocket<?>[] inputs, OutputSocket<?>[] outputs) {
80+
final InputSocket<ContoursReport> inputSocket = (InputSocket<ContoursReport>) inputs[0];
81+
final double minArea = ((Number) inputs[1].getValue().get()).doubleValue();
82+
final double minPerimeter = ((Number) inputs[2].getValue().get()).doubleValue();
83+
final double minWidth = ((Number) inputs[3].getValue().get()).doubleValue();
84+
final double maxWidth = ((Number) inputs[4].getValue().get()).doubleValue();
85+
final double minHeight = ((Number) inputs[5].getValue().get()).doubleValue();
86+
final double maxHeight = ((Number) inputs[6].getValue().get()).doubleValue();
87+
88+
final MatVector inputContours = inputSocket.getValue().get().getContours();
89+
final MatVector outputContours = new MatVector(inputContours.size());
90+
91+
// Add contours from the input vector to the output vector only if they pass all of the criteria (minimum
92+
// area, minimum perimeter, width, and height)
93+
int filteredContourCount = 0;
94+
for (int i = 0; i < inputContours.size(); i++) {
95+
final Mat contour = inputContours.get(i);
96+
final Rect bb = boundingRect(contour);
97+
98+
if (contourArea(contour) < minArea) continue;
99+
if (arcLength(contour, true) < minPerimeter) continue;
100+
if (bb.width() < minWidth || bb.width() > maxWidth) continue;
101+
if (bb.height() < minHeight || bb.height() > maxHeight) continue;
102+
103+
outputContours.put(filteredContourCount++, contour);
104+
}
105+
106+
outputContours.resize(filteredContourCount);
107+
108+
final OutputSocket<ContoursReport> outputSocket = (OutputSocket<ContoursReport>) outputs[0];
109+
outputSocket.getValue().get().setRows(inputSocket.getValue().get().getRows());
110+
outputSocket.getValue().get().setCols(inputSocket.getValue().get().getCols());
111+
outputSocket.getValue().get().setContours(outputContours);
112+
outputSocket.setValueOptional(outputSocket.getValue());
113+
}
114+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package edu.wpi.grip.core.operations.composite;
2+
3+
import com.google.common.eventbus.EventBus;
4+
import edu.wpi.grip.core.*;
5+
6+
import java.io.InputStream;
7+
import java.util.Optional;
8+
9+
import static org.bytedeco.javacpp.opencv_core.*;
10+
import static org.bytedeco.javacpp.opencv_imgproc.*;
11+
12+
/**
13+
* An {@link Operation} that, given a binary image, produces a list of contours of all of the shapes in the image
14+
*/
15+
public class FindContoursOperation implements Operation {
16+
17+
private final SocketHint<Mat> inputHint =
18+
new SocketHint.Builder<>(Mat.class).identifier("Input").build();
19+
20+
private final SocketHint<Boolean> externalHint =
21+
SocketHints.createBooleanSocketHint("External Only", false);
22+
23+
private final SocketHint<ContoursReport> contoursHint = new SocketHint.Builder<>(ContoursReport.class)
24+
.identifier("Contours").initialValueSupplier(ContoursReport::new).build();
25+
26+
@Override
27+
public String getName() {
28+
return "Find Contours";
29+
}
30+
31+
@Override
32+
public String getDescription() {
33+
return "Detect contours in a binary image.";
34+
}
35+
36+
@Override
37+
public Optional<InputStream> getIcon() {
38+
return Optional.of(getClass().getResourceAsStream("/edu/wpi/grip/ui/icons/find-contours.png"));
39+
}
40+
41+
@Override
42+
public InputSocket<?>[] createInputSockets(EventBus eventBus) {
43+
return new InputSocket<?>[]{
44+
new InputSocket<>(eventBus, inputHint),
45+
new InputSocket<>(eventBus, externalHint),
46+
};
47+
}
48+
49+
@Override
50+
public OutputSocket<?>[] createOutputSockets(EventBus eventBus) {
51+
return new OutputSocket<?>[]{new OutputSocket<>(eventBus, contoursHint)};
52+
}
53+
54+
@Override
55+
public Optional<?> createData() {
56+
return Optional.of(new Mat());
57+
}
58+
59+
@Override
60+
@SuppressWarnings("unchecked")
61+
public void perform(InputSocket<?>[] inputs, OutputSocket<?>[] outputs, Optional<?> data) {
62+
final Mat input = ((InputSocket<Mat>) inputs[0]).getValue().get();
63+
final Mat tmp = ((Optional<Mat>) data).get();
64+
final boolean externalOnly = ((InputSocket<Boolean>) inputs[1]).getValue().get();
65+
final OutputSocket<ContoursReport> contoursSocket = (OutputSocket<ContoursReport>) outputs[0];
66+
final ContoursReport contours = contoursSocket.getValue().get();
67+
68+
if (input.empty()) {
69+
return;
70+
}
71+
72+
// findContours modifies its input, so we pass it a temporary copy of the input image
73+
input.copyTo(tmp);
74+
75+
// OpenCV has a few different things it can return from findContours, but for now we only use EXTERNAL and LIST.
76+
// The other ones involve hierarchies of contours, which might be useful in some situations, but probably only
77+
// when processing the contours manually in code (so, not in a graphical pipeline).
78+
findContours(tmp, contours.getContours(), externalOnly ? CV_RETR_EXTERNAL : CV_RETR_LIST,
79+
CV_CHAIN_APPROX_TC89_KCOS);
80+
81+
contours.setRows(input.rows());
82+
contours.setCols(input.cols());
83+
contoursSocket.setValue(contours);
84+
}
85+
}

0 commit comments

Comments
 (0)