10
10
11
11
import com .google .common .collect .ImmutableList ;
12
12
13
+ import org .bytedeco .javacpp .opencv_core ;
14
+
15
+ import java .util .ArrayList ;
13
16
import java .util .List ;
14
17
15
18
import static org .bytedeco .javacpp .opencv_core .CV_32SC1 ;
19
22
import static org .bytedeco .javacpp .opencv_core .Mat ;
20
23
import static org .bytedeco .javacpp .opencv_core .MatVector ;
21
24
import static org .bytedeco .javacpp .opencv_core .Point ;
25
+ import static org .bytedeco .javacpp .opencv_core .Point2f ;
22
26
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 ;
24
28
import static org .bytedeco .javacpp .opencv_imgproc .CV_FILLED ;
29
+ import static org .bytedeco .javacpp .opencv_imgproc .CV_RETR_EXTERNAL ;
25
30
import static org .bytedeco .javacpp .opencv_imgproc .circle ;
26
31
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 ;
27
34
import static org .bytedeco .javacpp .opencv_imgproc .watershed ;
28
35
29
36
/**
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}.
31
39
*/
32
40
public class WatershedOperation implements Operation {
33
41
@@ -40,21 +48,25 @@ public class WatershedOperation implements Operation {
40
48
.build ();
41
49
42
50
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 ();
48
56
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 ();
50
62
51
63
private final InputSocket <Mat > srcSocket ;
52
64
private final InputSocket <ContoursReport > contoursSocket ;
53
- private final OutputSocket <Mat > outputSocket ;
65
+ private final OutputSocket <ContoursReport > outputSocket ;
54
66
55
67
@ SuppressWarnings ("JavadocMethod" )
56
- public WatershedOperation (InputSocket .Factory inputSocketFactory , OutputSocket . Factory
57
- outputSocketFactory ) {
68
+ public WatershedOperation (InputSocket .Factory inputSocketFactory ,
69
+ OutputSocket . Factory outputSocketFactory ) {
58
70
srcSocket = inputSocketFactory .create (srcHint );
59
71
contoursSocket = inputSocketFactory .create (contoursHint );
60
72
outputSocket = outputSocketFactory .create (outputHint );
@@ -85,29 +97,85 @@ public void perform() {
85
97
final ContoursReport contourReport = contoursSocket .getValue ().get ();
86
98
final MatVector contours = contourReport .getContours ();
87
99
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
+
88
108
final Mat markers = new Mat (input .size (), CV_32SC1 , new Scalar (0.0 ));
89
109
final Mat output = new Mat (markers .size (), CV_8UC1 , new Scalar (0.0 ));
90
110
91
111
try {
92
112
// draw foreground markers (these have to be different colors)
93
113
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 );
96
115
}
97
116
98
117
// 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 );
101
120
121
+ // Perform watershed
102
122
watershed (input , markers );
103
123
markers .convertTo (output , CV_8UC1 );
104
- bitwise_not (output , output ); // watershed inverts colors; invert them back
105
124
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 ()));
107
138
} finally {
108
139
// make sure that the working mat is freed to avoid a memory leak
109
140
markers .release ();
110
141
}
111
142
}
112
143
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
+
113
181
}
0 commit comments