Skip to content

Commit 7aac91f

Browse files
committed
Merge pull request #545 from Sam-Carlberg/master
Added distance transform, normalize, and watershed operations
2 parents ff4d1ce + f4271a1 commit 7aac91f

File tree

4 files changed

+324
-0
lines changed

4 files changed

+324
-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
@@ -43,5 +43,8 @@ public static void addOperations(EventBus eventBus) {
4343
eventBus.post(new OperationAddedEvent(new NTPublishOperation<>(BlobsReport.class)));
4444
eventBus.post(new OperationAddedEvent(new NTPublishOperation<>(LinesReport.class)));
4545
eventBus.post(new OperationAddedEvent(new PublishVideoOperation()));
46+
eventBus.post(new OperationAddedEvent(new DistanceTransformOperation()));
47+
eventBus.post(new OperationAddedEvent(new NormalizeOperation()));
48+
eventBus.post(new OperationAddedEvent(new WatershedOperation()));
4649
}
4750
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
2+
package edu.wpi.grip.core.operations.composite;
3+
4+
import com.google.common.eventbus.EventBus;
5+
6+
import edu.wpi.grip.core.InputSocket;
7+
import edu.wpi.grip.core.Operation;
8+
import edu.wpi.grip.core.OutputSocket;
9+
import edu.wpi.grip.core.SocketHint;
10+
import edu.wpi.grip.core.SocketHints;
11+
12+
import java.io.InputStream;
13+
import java.util.Optional;
14+
15+
import org.bytedeco.javacpp.opencv_core.Mat;
16+
import static org.bytedeco.javacpp.opencv_core.*;
17+
import static org.bytedeco.javacpp.opencv_imgproc.*;
18+
19+
/**
20+
* GRIP {@link Operation} for
21+
* {@link org.bytedeco.javacpp.opencv_imgproc#distanceTransform}.
22+
*/
23+
public class DistanceTransformOperation implements Operation {
24+
25+
private enum Type {
26+
27+
DIST_L1("CV_DIST_L1", CV_DIST_L1),
28+
DIST_L2("CV_DIST_L2", CV_DIST_L2),
29+
DIST_C("CV_DIST_C", CV_DIST_C);
30+
31+
private final String label;
32+
private final int value;
33+
34+
private Type(String label, int value) {
35+
this.label = label;
36+
this.value = value;
37+
}
38+
39+
@Override
40+
public String toString() {
41+
return label;
42+
}
43+
}
44+
45+
/** Masks are either 0x0, 3x3, or 5x5 */
46+
private enum MaskSize {
47+
48+
ZERO("0x0", 0),
49+
THREE("3x3", 3),
50+
FIVE("5x5", 5);
51+
52+
private final String label;
53+
private final int value;
54+
55+
private MaskSize(String label, int value) {
56+
this.label = label;
57+
this.value = value;
58+
}
59+
60+
@Override
61+
public String toString() {
62+
return label;
63+
}
64+
}
65+
66+
private final SocketHint<Mat> srcHint = SocketHints.Inputs.createMatSocketHint("Input", false);
67+
private final SocketHint<Type> typeHint = SocketHints.createEnumSocketHint("Type", Type.DIST_L2);
68+
private final SocketHint<MaskSize> maskSizeHint = SocketHints.createEnumSocketHint("Mask size", MaskSize.ZERO);
69+
70+
private final SocketHint<Mat> outputHint = SocketHints.Inputs.createMatSocketHint("Output", true);
71+
72+
@Override
73+
public String getName() {
74+
return "Distance Transform";
75+
}
76+
77+
@Override
78+
public String getDescription() {
79+
return "Sets the values of pixels in a binary image to their distance to the nearest black pixel.";
80+
}
81+
82+
@Override
83+
public Category getCategory() {
84+
return Category.IMAGE_PROCESSING;
85+
}
86+
87+
@Override
88+
public Optional<InputStream> getIcon() {
89+
return Optional.of(getClass().getResourceAsStream("/edu/wpi/grip/ui/icons/opencv.png"));
90+
}
91+
92+
@Override
93+
public InputSocket<?>[] createInputSockets(EventBus eventBus) {
94+
return new InputSocket<?>[]{
95+
new InputSocket<>(eventBus, srcHint),
96+
new InputSocket<>(eventBus, typeHint),
97+
new InputSocket<>(eventBus, maskSizeHint)
98+
};
99+
}
100+
101+
@Override
102+
public OutputSocket<?>[] createOutputSockets(EventBus eventBus) {
103+
return new OutputSocket<?>[]{
104+
new OutputSocket<>(eventBus, outputHint)
105+
};
106+
}
107+
108+
@Override
109+
public void perform(InputSocket<?>[] inputs, OutputSocket<?>[] outputs) {
110+
final Mat input = (Mat) inputs[0].getValue().get();
111+
112+
if (input.type() != CV_8U) {
113+
throw new IllegalArgumentException("Distance transform only works on 8-bit binary images");
114+
}
115+
116+
final Type type = (Type) inputs[1].getValue().get();
117+
final MaskSize maskSize = (MaskSize) inputs[2].getValue().get();
118+
119+
final OutputSocket<Mat> outputSocket = (OutputSocket<Mat>) outputs[0];
120+
final Mat output = outputSocket.getValue().get();
121+
122+
distanceTransform(input, output, type.value, maskSize.value);
123+
output.convertTo(output, CV_8U);
124+
125+
outputSocket.setValue(output);
126+
}
127+
128+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
2+
package edu.wpi.grip.core.operations.composite;
3+
4+
import com.google.common.eventbus.EventBus;
5+
6+
import edu.wpi.grip.core.InputSocket;
7+
import edu.wpi.grip.core.Operation;
8+
import edu.wpi.grip.core.OutputSocket;
9+
import edu.wpi.grip.core.SocketHint;
10+
import edu.wpi.grip.core.SocketHints;
11+
12+
import java.io.InputStream;
13+
import java.util.Optional;
14+
15+
import static org.bytedeco.javacpp.opencv_core.*;
16+
17+
/**
18+
* GRIP {@link Operation} for
19+
* {@link org.bytedeco.javacpp.opencv_core#normalize}.
20+
*/
21+
public class NormalizeOperation implements Operation {
22+
23+
private enum Type {
24+
25+
INF("NORM_INF", NORM_INF),
26+
L1("NORM_L1", NORM_L1),
27+
L2("NORM_L2", NORM_L2),
28+
MINMAX("NORM_MINMAX", NORM_MINMAX);
29+
30+
private final String label;
31+
private final int value;
32+
33+
private Type(String name, int value) {
34+
this.label = name;
35+
this.value = value;
36+
}
37+
38+
@Override
39+
public String toString() {
40+
return label;
41+
}
42+
43+
}
44+
45+
private final SocketHint<Mat> srcHint = SocketHints.Inputs.createMatSocketHint("Input", false);
46+
private final SocketHint<Type> typeHint = SocketHints.createEnumSocketHint("Type", Type.MINMAX);
47+
private final SocketHint<Number> aHint = SocketHints.Inputs.createNumberSpinnerSocketHint("Alpha", 0.0, 0, Double.MAX_VALUE);
48+
private final SocketHint<Number> bHint = SocketHints.Inputs.createNumberSpinnerSocketHint("Beta", 255, 0, Double.MAX_VALUE);
49+
50+
private final SocketHint<Mat> dstHint = SocketHints.Inputs.createMatSocketHint("Output", true);
51+
52+
@Override
53+
public String getName() {
54+
return "Normalize";
55+
}
56+
57+
@Override
58+
public String getDescription() {
59+
return "Normalizes or remaps the pixel values in an image.";
60+
}
61+
62+
@Override
63+
public Category getCategory() {
64+
return Category.IMAGE_PROCESSING;
65+
}
66+
67+
@Override
68+
public Optional<InputStream> getIcon() {
69+
return Optional.of(getClass().getResourceAsStream("/edu/wpi/grip/ui/icons/opencv.png"));
70+
}
71+
72+
@Override
73+
public InputSocket<?>[] createInputSockets(EventBus eventBus) {
74+
return new InputSocket<?>[]{
75+
new InputSocket<>(eventBus, srcHint),
76+
new InputSocket<>(eventBus, typeHint),
77+
new InputSocket<>(eventBus, aHint),
78+
new InputSocket<>(eventBus, bHint)
79+
};
80+
}
81+
82+
@Override
83+
public OutputSocket<?>[] createOutputSockets(EventBus eventBus) {
84+
return new OutputSocket<?>[]{
85+
new OutputSocket<>(eventBus, dstHint)
86+
};
87+
}
88+
89+
@Override
90+
public void perform(InputSocket<?>[] inputs, OutputSocket<?>[] outputs) {
91+
final Mat input = (Mat) inputs[0].getValue().get();
92+
final Type type = (Type) inputs[1].getValue().get();
93+
final Number a = (Number) inputs[2].getValue().get();
94+
final Number b = (Number) inputs[3].getValue().get();
95+
96+
final OutputSocket<Mat> outputSocket = (OutputSocket<Mat>) outputs[0];
97+
final Mat output = outputSocket.getValue().get();
98+
99+
normalize(input, output, a.doubleValue(), b.doubleValue(), type.value, -1, null);
100+
101+
outputSocket.setValue(output);
102+
}
103+
104+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
2+
package edu.wpi.grip.core.operations.composite;
3+
4+
import com.google.common.eventbus.EventBus;
5+
6+
import edu.wpi.grip.core.InputSocket;
7+
import edu.wpi.grip.core.Operation;
8+
import edu.wpi.grip.core.OutputSocket;
9+
import edu.wpi.grip.core.SocketHint;
10+
import edu.wpi.grip.core.SocketHints;
11+
12+
import static org.bytedeco.javacpp.opencv_core.*;
13+
import static org.bytedeco.javacpp.opencv_imgproc.*;
14+
15+
/**
16+
* GRIP {@link Operation} for
17+
* {@link org.bytedeco.javacpp.opencv_imgproc#watershed}.
18+
*/
19+
public class WatershedOperation implements Operation {
20+
21+
private final SocketHint<Mat> srcHint = SocketHints.Inputs.createMatSocketHint("Input", false);
22+
private final SocketHint<ContoursReport> contoursHint = new SocketHint.Builder<>(ContoursReport.class)
23+
.identifier("Contours")
24+
.initialValueSupplier(ContoursReport::new)
25+
.build();
26+
27+
private final SocketHint<Mat> outputHint = SocketHints.Inputs.createMatSocketHint("Output", true);
28+
29+
@Override
30+
public String getName() {
31+
return "Watershed";
32+
}
33+
34+
@Override
35+
public String getDescription() {
36+
return "Isolates overlapping objects from the background and each other";
37+
}
38+
39+
@Override
40+
public InputSocket<?>[] createInputSockets(EventBus eventBus) {
41+
return new InputSocket<?>[]{
42+
new InputSocket<>(eventBus, srcHint),
43+
new InputSocket<>(eventBus, contoursHint)
44+
};
45+
}
46+
47+
@Override
48+
public OutputSocket<?>[] createOutputSockets(EventBus eventBus) {
49+
return new OutputSocket<?>[]{
50+
new OutputSocket<>(eventBus, outputHint)
51+
};
52+
}
53+
54+
@Override
55+
public void perform(InputSocket<?>[] inputs, OutputSocket<?>[] outputs) {
56+
final Mat input = (Mat) inputs[0].getValue().get();
57+
if (input.type() != CV_8UC3) {
58+
throw new IllegalArgumentException("Watershed only works on 8-bit, 3-channel images");
59+
}
60+
61+
final ContoursReport contourReport = (ContoursReport) inputs[1].getValue().get();
62+
final MatVector contours = contourReport.getContours();
63+
64+
final OutputSocket<Mat> outputSocket = (OutputSocket<Mat>) outputs[0];
65+
final Mat markers = new Mat(input.size(), CV_32SC1, new Scalar(0.0));
66+
final Mat output = new Mat(markers.size(), CV_8UC1, new Scalar(0.0));
67+
68+
try {
69+
// draw foreground markers (these have to be different colors)
70+
for (int i = 0; i < contours.size(); i++) {
71+
drawContours(markers, contours, i, Scalar.all((i + 1) * (255 / contours.size())), CV_FILLED, LINE_8, null, 2, null);
72+
}
73+
74+
// draw background marker a different color from the foreground markers
75+
// TODO maybe make this configurable? There may be something in the corner
76+
circle(markers, new Point(5, 5), 3, Scalar.WHITE, -1, LINE_8, 0);
77+
78+
watershed(input, markers);
79+
markers.convertTo(output, CV_8UC1);
80+
bitwise_not(output, output); // watershed inverts colors; invert them back
81+
82+
outputSocket.setValue(output);
83+
} finally {
84+
// make sure that the working mat is freed to avoid a memory leak
85+
markers.release();
86+
}
87+
}
88+
89+
}

0 commit comments

Comments
 (0)