1

Input picture

The picture above is the sample input I am taking and I want to find the coordinates of all the red pixels in this image and store it in a list and then, later on, iterate this list and draw circles around each of the coordinates which we found above in the image using OpenCV's cv2.circle function. I am doing the following:

coord = []

for i in range(img.shape[0]):
    for j in range(img.shape[1]):
        if img[i,j,0]!=0 and img[i,j,1]!=0 and img[i,j,2]!=255:
            img[i,j,0]=0
            img[i,j,1]=0
            img[i,j,2]=0
        else:
            img[i,j,0]=0
            img[i,j,1]=0
            img[i,j,2]=255  
            coord.append([i,j])

for l in range(len(coord)):
    px=coord[l][0]
    py=coord[l][1]
    cv2.circle(img,(px,py),5,(0,255,255),1)

But doing the above isn't making a circle on all the coordinates. I guess there is something wrong with the storing of coordinates and accessing them. Can anyone point out the error and help me please.

I am getting the following output which isn't correct

3 Answers3

3

The main problem here is that when writing those circles back out, your px and py are transposed. You'll have to do (py, px).

But, in addition, to make finding the red pixels much faster (135 times faster on my machine!), use a combination of

  • cv2.inRange (which generates a binary mask image, where the matching pixels are 1 and non-matching are 0)
  • np.argwhere (which returns the indexes of a matrix where the value is non-zero)
import cv2
import numpy as np

img = cv2.imread("RvegM.png")
red_pixels = np.argwhere(cv2.inRange(img, (0, 0, 250), (0, 0, 255)))
for px, py in red_pixels:
    cv2.circle(img, (py, px), 5, (0, 255, 255), 1)
cv2.imwrite("out.png", img)

out.png ends up looking like this:

enter image description here

AKX
  • 152,115
  • 15
  • 115
  • 172
  • @MarkSetchell i'm really in need of help with this question, can you help me, please? https://stackoverflow.com/questions/61216402/how-to-improve-image-segmentation-using-the-watershed – Carlos Diego Apr 28 '20 at 16:58
2

You'll be there all day with for loops! You'd be miles faster to do a morphological convolution with a ring kernel. I can show you how to do it quickly in ImageMagick but you can do it just the same with OpenCV.

Here is the basic command:

magick stars.png -morphology convolve ring:3.5,4.5 result.png

enter image description here

I'll run it again and this time, ask ImageMagick to show me the kernel - hopefully you can see the 1s forming a ring with inner radius 3.5 pixels and outer radius 4.5 pixels:

convert stars.png -define morphology:showkernel=1 -morphology convolve ring:3.5,4.5 result.png

Output

Kernel "Ring" of size 9x9+4+4 with values from 1 to 1
Forming a output range from 0 to 32 (Sum 32)
 0: nan     nan       1       1       1       1       1     nan     nan
 1: nan       1       1     nan     nan     nan       1       1     nan
 2:   1       1     nan     nan     nan     nan     nan       1       1
 3:   1     nan     nan     nan     nan     nan     nan     nan       1
 4:   1     nan     nan     nan     nan     nan     nan     nan       1
 5:   1     nan     nan     nan     nan     nan     nan     nan       1
 6:   1       1     nan     nan     nan     nan     nan       1       1
 7: nan       1       1     nan     nan     nan       1       1     nan
 8: nan     nan       1       1       1       1       1     nan     nan

There is an excellent description of the fascinating subject of morphology and how it works by Anthony Thyssen here.


Here is an OpenCV Python version of the same technique:

#!/usr/bin/env python3

import cv2
from skimage.draw import circle_perimeter
import numpy as np

# Load image
im = cv2.imread('stars.png')

# Ring shape structuring element 9x9 with a central circle radius 4 of 1s
selem = np.zeros((9, 9), dtype=np.uint8)
rr, cc = circle_perimeter(4, 4, 4)
selem[rr, cc] = 1

# Do the morphology just on red channel
dilated = cv2.dilate(im[...,2], selem, iterations=1)

# Put modified red channel back into original image and save
im[:,:,2] = dilated
cv2.imwrite('result.png', im)

The results are the same as above.

Mark Setchell
  • 191,897
  • 31
  • 273
  • 432
1

Your conditions are poorly written. First of all, it's better to search in a range, if the pixel value < 10 we can consider it black.

For red, we can check if B and G channels have value less than < 10 (you can change it), and R channel has value > 220.

coord = []

for i in range(img.shape[0]):
    for j in range(img.shape[1]):
        if img[i,j,0]<10 and img[i,j,1]<10 and img[i,j,2]>220:
            img[i,j,0]=0
            img[i,j,1]=0
            img[i,j,2]=255
            coord.append([i,j])
        else:
            img[i,j,0]=0
            img[i,j,1]=0
            img[i,j,2]=0

for l in range(len(coord)):
    px=coord[l][0]
    py=coord[l][1]
    cv2.circle(img,(py,px),5,(0,255,255),1)
Zabir Al Nazi
  • 10,298
  • 4
  • 33
  • 60
  • OP does have a image in which there are only two colors, though, so this doesn't change the situation. – AKX Apr 28 '20 at 14:07
  • You can still have an image with different pixel range but they may appear as if only two colors exist. The images are most often compressed and it's not smart to assume the pixels will be exactly 0 and 255 value, hence it's better approach I think. In the end, this will also work if only two colors exist, so I don't see any harm. – Zabir Al Nazi Apr 28 '20 at 14:11
  • @ZabirAlNazi i'm really in need of help with this question, can you help me, please? https://stackoverflow.com/questions/61216402/how-to-improve-image-segmentation-using-the-watershed – Carlos Diego Apr 28 '20 at 16:58
  • @ZabirAlNazi Thanks! – Aastha Chowdhary Apr 28 '20 at 17:23