8
$\begingroup$

I like to reproduce this accepted answer from stackexchange in MMA. While I would like to achieve a better segmentation than what I already got, at the moment my main problem is that the background is segmented too. In the question it is asked how to define markers for Watershed in OpenCV. There is an image of white balls which hopefully we can segment. I did not understand the Python code, however, I tried to reproduce it from the explanation.

From the answer:

As an example, first binarize the input image by Otsu and perform a morphological opening for removing small objects. The result of this step is shown below in the left image. Now with the binary image consider applying the distance transform to it, result at right.

enter image description here enter image description here

This is what I did:

image = Import["https://i.stack.imgur.com/7EU8I.jpg"];
img = ColorConvert[image, "Grayscale"]

enter image description here

img3 = DeleteSmallComponents[Binarize[img, 0.4], Method -> "Mean"]
img4 = DistanceTransform@img3;
ImageAdjust[img4]

The result of the above code that is the binary image is on the left below and its distance transform on the right:

enter image description here enter image description here

From the answer:

With the distance transform result, we can consider some threshold such that we consider only the regions most distant to the background (left image below). Doing this, we can obtain a marker for each object by labeling the different regions after the earlier threshold. Now, we can also consider the border of a dilated version of the left image above to compose our marker. The complete marker is shown below at right (some markers are too dark to be seen, but each white region in the left image is represented at the right image).

enter image description here enter image description here

This is what I did:

img5 = Binarize[img4, 6]
edge = EdgeDetect[Dilation[img3, DiskMatrix[5]]]
marker = Plus[ImageMultiply[img5, img], edge]

enter image description here enter image description here

From the answer:

This marker we have here makes a lot of sense. Each colored water == one marker will start to fill the region, and the watershed transformation will construct dams to impede that the different "colors" merge. If we do the transform, we get the image at left. Considering only the dams by composing them with the original image, we get the result at right. enter image description here enter image description here

This is what I did:

(* Rather than performing the Watershed on the image `img`, I did it on its gradient*)
g = FillingTransform[GradientFilter[img, 1], 0.1, Padding -> 1];
WatershedComponents[g, marker] // Colorize
(*you also could perform the watershed on the color negate of the distance transform of the binary image.
WatershedComponents[ColorNegate[DistanceTransform[img3]], 
  marker] // Colorize*)

enter image description here

As you can see my result is not as good as what is obtained in the mentioned answer. The main question is How can I prevent the segmentation of the background? For example in Matlab and OpenCV it is possible to mark the background before segmentation.

Also if possible how can I improve the segmentation?

To summarize:

1- I have a gray scale image.

2- I binarized it.

3- I perform distance transform on the binary image.

4- I binarized the distance transformed image to get the what they call seeds.

5- I detected the edge of my original image and added to what I get in step 4. This is my marker for watershed transform.

6- I applied the GradientFilter to my original gray scale image.

7- I performed watershed transform on the gradient image using the marker.

$\endgroup$
7
+50
$\begingroup$

It seems that you are almost there. Manipulate your marker a bit by removing the surrounding circle (that's what's causing the poor segmentation in the Watershed step). One way to remove the circle is to delete the small components and then take the ImageDifference --- which basically creates a "DeleteLargeComponents" function. With this change, your Filling and Watershed do not try to segment the background.

marker2 = ImageDifference[Dilation[marker, 1], 
                          DeleteSmallComponents[Dilation[marker, 1], 200]];
g = FillingTransform[GradientFilter[img, 1], 0.1, Padding -> 1];
(w = WatershedComponents[g, marker2]) // Colorize

enter image description here

If you look at Tally[Flatten[w]], you will see that the largest component (the one with the most pixels, marked with deep blue above) is component number 15. This is the background.

$\endgroup$
  • $\begingroup$ Is there a way to mark the background itself? For example the binarized image "img5" (see the question) marks where the possible segments are. Is it possible to tell MMA where the background is? $\endgroup$ – MOON Apr 9 at 15:37
  • $\begingroup$ See update showing the background explicitly. $\endgroup$ – bill s Apr 9 at 21:16
  • $\begingroup$ Thank you. But is it possible to do mark the background before segmentation? For example in Matlab: nl.mathworks.com/help/images/… and OpenCV docs.opencv.org/3.4.3/d3/db4/tutorial_py_watershed.html it is possible to mark the background before segmentation. $\endgroup$ – MOON Apr 10 at 7:48
1
$\begingroup$

I would start doing some adjusting and filtering:

adj = ImageAdjust[img, {2, -.2}]
msf = MeanShiftFilter[adj, 5, .15]
bin = Binarize[msf, FindThreshold[msf]*1.75]

To get to something like this:

enter image description here

Then Delete the small components, distance transform, detect the maxima, blur slightly and repeat distance transform and maxdetect to get to this:

dsc = DeleteSmallComponents[bin, 50];
dtf = DistanceTransform[dsc] // ImageAdjust;
mxd = MaxDetect[dtf];
blr = Blur[mxd, 3];
dtf2 = DistanceTransform[blr] // ImageAdjust;
mxd2 = MaxDetect[dtf2]

to get:

enter image description here

Then gradient, watershed components and colorize:

grd = GradientFilter[dsc, 2];
wsc = WatershedComponents[grd, mxd2];
col = Colorize[wsc]

enter image description here

I don't think there is a way to exclude the background component, by default, but you can eliminate it based on its size through ComponentMeasurements

mask = ComponentMeasurements[wsc, {"Mask", "BoundingBox"}, 10 < #Count < 1000 &];

And you can mark on the image the bounded areas of the selected components:

imageMarked =  Show[image, Graphics[{EdgeForm[{Thick, Blue}], FaceForm[Transparent], Rectangle @@ # & /@ mask[[All, 2, 2]] }] ]

enter image description here

You can also use the mask to trim the areas from the main image

myTrimList = ImageTrim[image, mask[[All, 2, 2]] ]

(here I post a screenshot of the trim list)

enter image description here

I hope it helps,

Regards

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.