1

I am trying to segment the blue components from a set of images. In most images where blue components have a large spread, otsu thresholded image works properly well. However, for images where blue components are minimal, the results are not ok and seems to include the non-relevant sections. Example below:

enter image description here

Are there ways to improve the otsu thresholding such that only relevant parts are segmented but not necessarily making the other images suffer?

I already tried global and adaptive thresholding but otsu particularly captured betters which however included unnecessary details.

Here's the code:

l_image = remove_background(image)
l_image = cv2.cvtColor(l_image, cv2.COLOR_BGR2GRAY)

ret1,th1 = cv2.threshold(l_image,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)

mask = (th1 != 255)
sel = np.ones_like(image)
sel[mask] = image[mask]
sel = cv2.cvtColor(sel, cv2.COLOR_HSV2BGR)

#we simply set these channels to 0 to remove excess background
sel[:,:,1] = 0
sel[:,:,2] = 0

Here's the sample image. enter image description here

bellawillrise
  • 55
  • 1
  • 7
  • 1
    It can be better to share the code you tried to get those outputs. Also please share the source image seperately so people can take it to ty – Yunus Temurlenk Apr 09 '21 at 05:06
  • 1
    Are the components always blue? Why not use the `HSV` color space and create a binary mask for the blue hue? You can use the `cv2.inRange` function. – stateMachine Apr 09 '21 at 06:42
  • I already tried that method but the results are not that accurate :) – bellawillrise Apr 09 '21 at 07:59
  • 1
    Why do you convert to grayscale if you want to find blue parts? It is color that is the relevant feature. By converting to grayscale you throw away the color information. – Cris Luengo Apr 09 '21 at 13:28
  • 2
    One simple solution, given that you need to distinguish blue from white and not others colors, is converting to HSV or similar, and threshold on saturation (white and gray should have 0 saturation). The green background can be separated in a 2nd step, presumably that’s a constant? – Cris Luengo Apr 09 '21 at 13:31
  • 1
    You should try [multi-otsu](https://stackoverflow.com/questions/22706742/multi-otsumulti-thresholding-with-opencv/53883887#53883887). That takes care of different gray-values. – SKG Apr 11 '21 at 16:29
  • Hi @SKG, I will check this out as well. Thank you for your suggestion. – bellawillrise Apr 13 '21 at 05:08

2 Answers2

3

The main issue with the logic in your code is that you are looking for something that is distinguished primarily by color, but throw away the color information first by converting the image to grayscale.

Instead, consider looking at color properties of each pixel. One easy way to do so is to look at the HCV color space. This is a similar color space to the more common HSV, with "C" for chroma instead of "S" for saturation, where S = C / V. I'm suggesting this because it's so easy to compute the "C" channel, which is the one that would have most of the contrast in this image. Note that all the complexity is in computing "H", the hue, and that would be ideally used to find a specific color independently of its brightness, but that requires a double threshold on the "H" channel plus a threshold on the "S" channel. For this simple case, a single threshold on the "S" channel is sufficient to find the colored regions: we have only blue, we don't care about what color it is, we just want to find the color.

To compute the "C" (chroma) channel, we find the difference between the largest and the smallest of the RGB values (for each pixel independently):

rgbmax = np.amax(image, axis=2)
rgbmin = np.amin(image, axis=2)
c = rgbmax - rgbmin

chroma

As you can guess, a simple threshold of this image leads to finding the colored regions. The green background can easily be subtracted before processing, or after.

Cris Luengo
  • 55,762
  • 10
  • 62
  • 120
  • Hi! Great explanation and worked as intended. Thank you very much. I'm still working on including the faint details/light pixels as it is not included after thresholding. Great solution, thanks! – bellawillrise Apr 10 '21 at 03:35
1

Edit: after @Cris Luengo comment, the green channel works better than the blue one.

You can apply Otsu's threshold on the green channel (of BGR). Results are not perfect but much better.

img = img[:,:,1] #get the green channel

th, img = cv2.threshold(img, 0, 255, cv2.THRESH_OTSU)

enter image description here

output:

enter image description here

Baraa
  • 1,476
  • 1
  • 16
  • 19
  • As far as I understood, OP's main goal is to detect the blue spots, so my idea should do better. Yours probably will do better for the background removal part. – Baraa Apr 09 '21 at 13:41
  • You are right. It's surprising!. And you know what, in my answer, I accidentally used the green channel, not the blue one which gave a good result. Thanks @CrisLuengo – Baraa Apr 09 '21 at 14:16