Reputation: 13
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
Upvotes: 1
Views: 3716
Reputation: 207465
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
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.
Upvotes: 2
Reputation: 11198
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)
Upvotes: 1
Reputation: 168967
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:
Upvotes: 4