1010
1111import com .google .common .collect .ImmutableList ;
1212
13+ import org .bytedeco .javacpp .opencv_core ;
14+
15+ import java .util .ArrayList ;
1316import java .util .List ;
1417
1518import static org .bytedeco .javacpp .opencv_core .CV_32SC1 ;
1922import static org .bytedeco .javacpp .opencv_core .Mat ;
2023import static org .bytedeco .javacpp .opencv_core .MatVector ;
2124import static org .bytedeco .javacpp .opencv_core .Point ;
25+ import static org .bytedeco .javacpp .opencv_core .Point2f ;
2226import 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 ;
2428import static org .bytedeco .javacpp .opencv_imgproc .CV_FILLED ;
29+ import static org .bytedeco .javacpp .opencv_imgproc .CV_RETR_EXTERNAL ;
2530import static org .bytedeco .javacpp .opencv_imgproc .circle ;
2631import 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 ;
2734import 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 */
3240public 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