Skip to content

Commit f84d379

Browse files
authored
Update WatershedOperation (#607)
This is a breaking change. Any saved pipelines with a watershed operation will fail to load.
1 parent 7996fc8 commit f84d379

File tree

1 file changed

+85
-17
lines changed

1 file changed

+85
-17
lines changed

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

Lines changed: 85 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010

1111
import com.google.common.collect.ImmutableList;
1212

13+
import org.bytedeco.javacpp.opencv_core;
14+
15+
import java.util.ArrayList;
1316
import java.util.List;
1417

1518
import static org.bytedeco.javacpp.opencv_core.CV_32SC1;
@@ -19,15 +22,20 @@
1922
import static org.bytedeco.javacpp.opencv_core.Mat;
2023
import static org.bytedeco.javacpp.opencv_core.MatVector;
2124
import static org.bytedeco.javacpp.opencv_core.Point;
25+
import static org.bytedeco.javacpp.opencv_core.Point2f;
2226
import static org.bytedeco.javacpp.opencv_core.Scalar;
23-
import static org.bytedeco.javacpp.opencv_core.bitwise_not;
27+
import static org.bytedeco.javacpp.opencv_imgproc.CV_CHAIN_APPROX_TC89_KCOS;
2428
import static org.bytedeco.javacpp.opencv_imgproc.CV_FILLED;
29+
import static org.bytedeco.javacpp.opencv_imgproc.CV_RETR_EXTERNAL;
2530
import static org.bytedeco.javacpp.opencv_imgproc.circle;
2631
import static org.bytedeco.javacpp.opencv_imgproc.drawContours;
32+
import static org.bytedeco.javacpp.opencv_imgproc.findContours;
33+
import static org.bytedeco.javacpp.opencv_imgproc.pointPolygonTest;
2734
import static org.bytedeco.javacpp.opencv_imgproc.watershed;
2835

2936
/**
30-
* GRIP {@link Operation} for {@link org.bytedeco.javacpp.opencv_imgproc#watershed}.
37+
* GRIP {@link Operation} for
38+
* {@link org.bytedeco.javacpp.opencv_imgproc#watershed}.
3139
*/
3240
public class WatershedOperation implements Operation {
3341

@@ -40,21 +48,25 @@ public class WatershedOperation implements Operation {
4048
.build();
4149

4250
private final SocketHint<Mat> srcHint = SocketHints.Inputs.createMatSocketHint("Input", false);
43-
private final SocketHint<ContoursReport> contoursHint = new SocketHint.Builder<>(ContoursReport
44-
.class)
45-
.identifier("Contours")
46-
.initialValueSupplier(ContoursReport::new)
47-
.build();
51+
private final SocketHint<ContoursReport> contoursHint =
52+
new SocketHint.Builder<>(ContoursReport.class)
53+
.identifier("Contours")
54+
.initialValueSupplier(ContoursReport::new)
55+
.build();
4856

49-
private final SocketHint<Mat> outputHint = SocketHints.Inputs.createMatSocketHint("Output", true);
57+
private final SocketHint<ContoursReport> outputHint =
58+
new SocketHint.Builder<>(ContoursReport.class)
59+
.identifier("Features")
60+
.initialValueSupplier(ContoursReport::new)
61+
.build();
5062

5163
private final InputSocket<Mat> srcSocket;
5264
private final InputSocket<ContoursReport> contoursSocket;
53-
private final OutputSocket<Mat> outputSocket;
65+
private final OutputSocket<ContoursReport> outputSocket;
5466

5567
@SuppressWarnings("JavadocMethod")
56-
public WatershedOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory
57-
outputSocketFactory) {
68+
public WatershedOperation(InputSocket.Factory inputSocketFactory,
69+
OutputSocket.Factory outputSocketFactory) {
5870
srcSocket = inputSocketFactory.create(srcHint);
5971
contoursSocket = inputSocketFactory.create(contoursHint);
6072
outputSocket = outputSocketFactory.create(outputHint);
@@ -85,29 +97,85 @@ public void perform() {
8597
final ContoursReport contourReport = contoursSocket.getValue().get();
8698
final MatVector contours = contourReport.getContours();
8799

100+
final int maxMarkers = 253;
101+
if (contours.size() > maxMarkers) {
102+
throw new IllegalArgumentException(
103+
"A maximum of " + maxMarkers + " contours can be used as markers."
104+
+ " Filter contours before connecting them to this operation if this keeps happening."
105+
+ " The contours must also all be external; nested contours will not work");
106+
}
107+
88108
final Mat markers = new Mat(input.size(), CV_32SC1, new Scalar(0.0));
89109
final Mat output = new Mat(markers.size(), CV_8UC1, new Scalar(0.0));
90110

91111
try {
92112
// draw foreground markers (these have to be different colors)
93113
for (int i = 0; i < contours.size(); i++) {
94-
drawContours(markers, contours, i, Scalar.all((i + 1) * (255 / contours.size())),
95-
CV_FILLED, LINE_8, null, 2, null);
114+
drawContours(markers, contours, i, Scalar.all(i + 1), CV_FILLED, LINE_8, null, 2, null);
96115
}
97116

98117
// draw background marker a different color from the foreground markers
99-
// TODO maybe make this configurable? There may be something in the corner
100-
circle(markers, new Point(5, 5), 3, Scalar.WHITE, -1, LINE_8, 0);
118+
Point backgroundLabel = fromPoint2f(findBackgroundMarker(markers, contours));
119+
circle(markers, backgroundLabel, 1, Scalar.WHITE, -1, LINE_8, 0);
101120

121+
// Perform watershed
102122
watershed(input, markers);
103123
markers.convertTo(output, CV_8UC1);
104-
bitwise_not(output, output); // watershed inverts colors; invert them back
105124

106-
outputSocket.setValue(output);
125+
List<Mat> contourList = new ArrayList<>();
126+
for (int i = 1; i < contours.size(); i++) {
127+
Mat dst = new Mat();
128+
output.copyTo(dst, opencv_core.equals(markers, i).asMat());
129+
MatVector contour = new MatVector(); // vector with a single element
130+
findContours(dst, contour, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_TC89_KCOS);
131+
assert contour.size() == 1;
132+
contourList.add(contour.get(0).clone());
133+
contour.get(0).deallocate();
134+
contour.deallocate();
135+
}
136+
MatVector foundContours = new MatVector(contourList.toArray(new Mat[contourList.size()]));
137+
outputSocket.setValue(new ContoursReport(foundContours, output.rows(), output.cols()));
107138
} finally {
108139
// make sure that the working mat is freed to avoid a memory leak
109140
markers.release();
110141
}
111142
}
112143

144+
/**
145+
* Finds the first available point to place a background marker for the watershed operation.
146+
*/
147+
private static Point2f findBackgroundMarker(Mat markers, MatVector contours) {
148+
final int cols = markers.cols();
149+
final int rows = markers.rows();
150+
final int minDist = 5;
151+
Point2f backgroundLabel = new Point2f();
152+
boolean found = false;
153+
// Don't place use a marker anywhere within 5 pixels of the edge of the image,
154+
// or within 5 pixels of a contour
155+
for (int x = minDist; x < cols - minDist && !found; x++) {
156+
for (int y = minDist; y < rows - minDist && !found; y++) {
157+
backgroundLabel.x(x);
158+
backgroundLabel.y(y);
159+
boolean isOpen = true;
160+
for (int c = 0; c < contours.size(); c++) {
161+
isOpen = pointPolygonTest(contours.get(c), backgroundLabel, true) <= -minDist;
162+
if (!isOpen) {
163+
// We know (x,y) is in a contour, don't need to check if it's in any others
164+
break;
165+
}
166+
}
167+
found = isOpen;
168+
}
169+
}
170+
if (!found) {
171+
// Should only happen if the image is clogged with contours
172+
throw new IllegalStateException("Could not find a point for the background label");
173+
}
174+
return backgroundLabel;
175+
}
176+
177+
private static Point fromPoint2f(Point2f p) {
178+
return new Point((int) p.x(), (int) p.y());
179+
}
180+
113181
}

0 commit comments

Comments
 (0)