Reputation:
I am reading in an image and I would like to color in all non-zero pixels to red. In addition, the background only consists of black. The code I wrote to try and do this is shown below:
import numpy as np
import cv2
second=cv2.imread('second.png') # read the picture
for i in range(second.shape[0]):
for j in range(second.shape[1]):
if second[i,j,0]!=0 and second[i,j,1]!=0 and second[i,j,2]!=0:# if it is not black pixel
second[i,j,0]=0
second[i,j,1]=0
second[i,j,2]=255 # color it in red
cv2.imwrite('result.png',second) # save the colored picture
Here is the image second.png:
Here is the colored image result.png:
How come some of the pixels aren't colored in as red? Note that when I print the colour pixel values of those locations in second.png
that were not red in result.png
, I see that they aren't black.
Does anyone know why this may be?
Upvotes: 0
Views: 138
Reputation: 104464
The accepted answer by David Zwicker is certainly the way to go. However, I would like to suggest something, and not use for
loops with numpy
arrays. I would suggest using a vectorized solution instead as you will most definitely get a boost in performance. To me, this is the way numpy
was meant to be used.
What I would do is split up the image into separate channels with numpy.split
, then check each channel independently. We allocate a mask of the same size as one of the channels, and for each location in the image, if any of the channels is non-zero, we would mark this location to be True
. The result of this operation would be a mask where True
denotes a non-zero pixel, and False
otherwise. You can use numpy.logical_or
, but the standard syntax only accepts two inputs. If you want to use this over multiple inputs (i.e. greater than 2), you need to use the reduce
idiom.
Once you're done finding this mask, use numpy.nonzero
to determine the locations in the mask that are non-zero or True
, and create an output image which is initially all zeroes, then you set the red channel to 255 corresponding to these non-zero locations.
Or in other words:
import numpy as np
import cv2
img = cv2.imread('second.png') # read in image
# Look at each channel independently and see if there are any non-zero pixels
# and merge them together to create a mask
mask = np.logical_or.reduce(np.split(img, 3, axis=2))
# Find those locations in the mask that are non-zero
(rows, cols, _) = np.nonzero(mask)
# Create a blank image, then set the red channel to 255 for those non-zero locations
out = np.zeros(img.shape).astype('uint8')
out[rows,cols,2] = 255
# Show image, wait for key, then close window after
cv2.imshow('Red', out)
cv2.waitKey(0)
cv2.destroyAllWindows()
We get this:
It's totally up to you on what you want to do, but the above (to me) is more Pythonic. Use whatever you're most comfortable with!
Upvotes: 1
Reputation:
You can use cv::split
and cv::merge
if you wont to leave only read channel.
Here is C++ example:
#include <opencv2/opencv.hpp>
int main(int argc, char *argv[])
{
cv::Mat src = cv::imread("second.png");
cv::Mat b_g_r[3];
cv::split(src, b_g_r);
b_g_r[0] = cv::Mat::zeros(src.rows, src.cols, CV_8UC1); //blue to zeros
b_g_r[1] = cv::Mat::zeros(src.rows, src.cols, CV_8UC1); //green to zeros
cv::merge(b_g_r, 3, src);
cv::imshow("Red", src);
cv::waitKey();
return 0;
}
And result:
Upvotes: 1
Reputation: 24268
You should use or
in your condition, such that all pixels that are not black are substituted:
import numpy as np
import cv2
second=cv2.imread('second.png') # read the picture
for i in range(second.shape[0]):
for j in range(second.shape[1]):
if second[i,j,0]!=0 or second[i,j,1]!=0 or second[i,j,2]!=0:# if it is not black pixel
second[i,j,0]=0
second[i,j,1]=0
second[i,j,2]=255 # color it in red
cv2.imwrite('result.png',second) # save the colored picture
Alternatively, you could write if not(second[i,j,0]==0 and second[i,j,1]==0 and second[i,j,2]==0):
as the condition, which is equivalent.
Upvotes: 1