Numpy np.any range or threshold - python

I am using python, OpenCV and Numpy. My goal is to find all white pixel and turn it red and turn everything else off or white. My code:
import numpy as np
import cv2
import matplotlib.pyplot as plt
# Read mask
image = cv2.imread("path to my image")
any_white = np.any(image == [255,255,255], axis = -1)
image[any_white]=[255,0,0]
plt.imshow(image)
plt.show()
cv2.imwrite('result.png',image)
Problem 1: Targetting any [255,255,255] doesn't find all, whiteist, I starting finding any [244,244,244], [243,243,243] and so on. Is there a way to set a range of white, maybe from [255,255,255] to [230,230,230]?
Problem 2: clearly, with plt.imshow(image) and plt.show() within python, the result shows red, but when i used cv2.imwrite('result.png',image) to save, it's blue. See result image.

Problem 1:
You can create a mask and set the red channel to False so that you keep the value at 255 if you want to target only the white pixels
mask_bg = (image == [255, 255, 255])
mask_bg[:, :, 0] = False # set red channel mask to false (leave 255 value)
image[mask_bg] = 0 # set all white pixels to [255, 0, 0]
If you want to find all values in a range you can use cv2.inRange:
mask = cv2.inRange(image, (230, 230, 230), (255, 255,255))
Problem 2:
OpenCV uses BGR as default instead of RGB, you can convert from BGR to RGB with:
new_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
cv2.imshow('BGR Image', new_image )
Keep in mind that if you open an image with OpenCV it will be BGR, so convert it before manipulating the channels.

Problem 1:
The pixels you are planning to target may not have the exact value of (255, 255, 255). Hence it is better to binarize the image by setting a range of pixel values. You can find the exact range by creating Trackbars and tuning them manually. You can find more about implementing Trackbars in OpenCV here.
Problem 2:
This happens because OpenCV uses BGR or (Blue, Green, Red) colorspace by default. You can change the colorspace into RGB or (Red, Green, Blue) by using cv2.cvtColor(image, cv2.COLOR_BGR2RGB) before saving.

Related

Convert an image to black and white mask using opencv python

I have a transparent background image and i want to color all transparent region as black and rest of region as white.
imgage = cv2.imread("imgage.png", cv2.IMREAD_UNCHANGED)
trans_mask = image[:,:,3] == 255
image[trans_mask] = [255, 255, 255, 255]
The output i got it. I got inside area to fill white and outside area to fill black. Any suggestion
Original input
The simplest way to do this is starting out with a fully-black image and filling in just the area with positive alpha:
res = np.zeros(image.shape[:2], np.uint8) # black by default
colored_areas = image[...,3] > 0
res[colored_areas] = 255
You say:
i want to color all transparent region as black and rest of region as white.
So this should satisfy your request:
and you get that from this simple code:
image[:,:,3]
Just apply imwrite or imshow+waitKey (or matplotlib) to see the data.

how to isolate yellow part of the picture and make black other parts?

This is the image:
I want to turn all the colours to black except yellow and tried this code but showing an error can anyone please help?
import cv2 as cv
import numpy as np
img = cv.imread('Screenshot 2022-04-10 at 10.02.19 AM.png',1)
if(img.any() == [255, 255, 0]):
cv.imshow('image',img);
else:
ret , thresh1 = cv.threshold(img,500,255,cv.THRESH_BINARY);
cv.imshow("Timg",thresh1);
cv.waitKey(0)
cv.destroyAllWindows()
The error is showing for the if conditional statement.
The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
Steps: TLDR;
Convert image to LAB color space
Otsu Threshold over b-component
Mask the result over the original image in BGR color space
Code
# read the image in BGR
img = cv2.imread("image_path", 1)
# convert image to LAB color space
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
# store b-component
b_component = lab[:,:,2]
# perform Otsu threshold
ret,th = cv2.threshold(b_component, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
# mask over original image
result = cv2.bitwise_and(img, img, mask = th)
Details
Why did I do this?
The LAB color space contains 3 channels:
L-component: highlights brightness value
a-component: represents color values between green and magenta
b-component: represents color values between blue and yellow
Since your image comprised of blue, magenta and yellow, I opted for LAB color space. And specifically the b-component because it highlights yellow color

How do I count the number of flowers (yellow) from the image in python

I'm a newbie in python . I have tried to extract the flower (maybe not too accurate) but I need to : (1). be able count how many features has been extracted. (2) Other objects in the image that appears yellowish but not the object I wanted. Thanks for helping
Below is my code at the moment
import numpy as np
import cv2
from matplotlib import pyplot as plt
img = cv2.imread("female29.jpg")
image = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
image2 = cv2.cvtColor(image, cv2.COLOR_RGB2HSV)
yellow = (204, 204, 0)
light_yellow = (255, 255, 204)
mask = cv2.inRange(image, yellow, light_yellow)
result = cv2.bitwise_and(image, image, mask=mask)
cv2.imshow("image", result)
cv2.waitKey(0)
Sadly there is no easy way to count the number of flowers present solely using opencv as what the above code does is only highlight or apply a mask to parts of image with the pixel values in the range you supplied.
yellow = (204, 204, 0)
light_yellow = (255, 255, 204)
you can try and change different values for your ranges to more accurately mask the flower
probably use an online HSV color picker to adjust the values (keep in mind you will have to scale the values from 0-100 to 0-255).
You can only try adjusting these values in order to prevent other Other objects in the image that appears yellowish but at the end of the day you're just applying a color filter, so everything that is yellow will show up and there is no way to stop that unless you use higher methods like Machine Learning to recognize flowers

Numpy where() creates spots of different color than the ones defined

I'm writing a script that creates a mask for an image. My input image looks like this:
The original image is only 40x40px, here it is for reference:
I want to create a mask of the purple area in the center of the image. This is what I do:
# read the 40x40 image and convert it to RGB
input_image = cv2.cvtColor(cv2.imread('image.png'), cv2.COLOR_BGR2RGB)
# get the value of the color in the center of the image
center_color = input_image[20, 20]
# create the mask: pixels with same color = 255 (white), other pixels = 0 (black)
mask_bw = np.where(input_image == center_color, 255, 0)
# show the image
plt.imshow(mask_bw)
Most of the time this works perfectly fine, but for some images (like the one I attached to this question) I consistently get some blue areas in my mask like on the image below. This is reproducible and the areas are always the same for the same input images.
This is already weird enough, but if I try to remove the blue areas, this doesn't work either.
mask_bw[mask_bw != (255, 255, 255)] = 0 # this doesn't change anything..
Why is this happening and how do I fix this?
Additional info
tried with numpy version 1.17.3 and 1.17.4
Reproduced in my local environment and in a google colab notebook
The main problem is that you're trying to compare three channels but only setting the value for one channel. This is most likely causing the blue areas on the mask. When you use np.where() to set the other pixels to black, you are only setting this on the 1st channel instead of all three channels. You can visualize this by splitting each channel and printing the before/after arrays which will show you that the resulting array values are RGB(0,0,255). So to fix this problem, we need to compare each individual channel then set the desired area in white while setting any black areas on the mask to black for all three channels. Here is one way to do it:
import numpy as np
import cv2
image = cv2.imread('1.png')
center_color = image[20, 20]
b, g, r = cv2.split(image)
mask = (b == center_color[0]) & (g == center_color[1]) & (r == center_color[2])
image[mask] = 255
image[mask==0] = 0
cv2.imshow('image', image)
cv2.waitKey()
A hotfix to remove the blue areas using your current code would be to convert the image to grayscale (1-channel) then change all non-white pixels to black.
import numpy as np
import cv2
# Load image, find color, create mask
image = cv2.imread('1.png')
center_color = image[20, 20]
mask = np.where(image == center_color, 255, 0)
mask = np.array(mask, dtype=np.uint8)
# Convert image to grayscale, convert all non-white pixels to black
mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
mask[mask != 255] = 0
cv2.imshow('mask', mask)
cv2.waitKey()
Here are two alternative methods to obtain a mask of the purple area
Method #1: Work in grayscale space
import numpy as np
import cv2
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
center_color = gray[20, 20]
mask = np.array(np.where(gray == center_color, 255, 0), dtype=np.uint8)
cv2.imshow('mask', mask)
cv2.waitKey()
Method #2: Color thresholding
The idea is to convert the image to HSV color space then use a lower and upper color range to segment the image to create a binary mask
import numpy as np
import cv2
image = cv2.imread('1.png')
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
lower = np.array([0, 124, 0])
upper = np.array([179, 255, 255])
mask = cv2.inRange(hsv, lower, upper)
cv2.imshow('mask', mask)
cv2.waitKey()
Both methods should yield the same result
If you have a 3-channel image (i.e. RGB or BGR or somesuch) and you want to generate a single channel mask (i.e. you want 0/1 or True/False) for each pixel, then you effectively need to group the 3 values into a single using np.all() like this:
import cv2
import numpy as np
# Load image and get centre colour
image = cv2.imread('40x40.png')
cc = im[20, 20]
print(image.shape)
(40, 40, 3)
# Generate list of unique colours present in image so we know what we are dealing with
print(np.unique(im.reshape(-1,3), axis=0))
array([[140, 109, 142],
[151, 106, 140],
[160, 101, 137],
[165, 134, 157],
[175, 149, 171],
[206, 87, 109],
[206, 185, 193]], dtype=uint8)
# Generate mask of pixels matching centre colour
mask_bw = np.where(np.all(im==cc,axis=2), 255, 0)
# Check shape of mask - no 3rd dimension !!!
print(mask_bw.shape)
(40, 40)
# Check unique colours in mask
print(np.unique(mask_bw.reshape(-1,1), axis=0))
array([[ 0],
[255]])

How to multiply two image in Python? [duplicate]

How can I apply mask to a color image in latest python binding (cv2)? In previous python binding the simplest way was to use cv.Copy e.g.
cv.Copy(dst, src, mask)
But this function is not available in cv2 binding. Is there any workaround without using boilerplate code?
Here, you could use cv2.bitwise_and function if you already have the mask image.
For check the below code:
img = cv2.imread('lena.jpg')
mask = cv2.imread('mask.png',0)
res = cv2.bitwise_and(img,img,mask = mask)
The output will be as follows for a lena image, and for rectangular mask.
Well, here is a solution if you want the background to be other than a solid black color. We only need to invert the mask and apply it in a background image of the same size and then combine both background and foreground. A pro of this solution is that the background could be anything (even other image).
This example is modified from Hough Circle Transform. First image is the OpenCV logo, second the original mask, third the background + foreground combined.
# http://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_houghcircles/py_houghcircles.html
import cv2
import numpy as np
# load the image
img = cv2.imread('E:\\FOTOS\\opencv\\opencv_logo.png')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# detect circles
gray = cv2.medianBlur(cv2.cvtColor(img, cv2.COLOR_RGB2GRAY), 5)
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 20, param1=50, param2=50, minRadius=0, maxRadius=0)
circles = np.uint16(np.around(circles))
# draw mask
mask = np.full((img.shape[0], img.shape[1]), 0, dtype=np.uint8) # mask is only
for i in circles[0, :]:
cv2.circle(mask, (i[0], i[1]), i[2], (255, 255, 255), -1)
# get first masked value (foreground)
fg = cv2.bitwise_or(img, img, mask=mask)
# get second masked value (background) mask must be inverted
mask = cv2.bitwise_not(mask)
background = np.full(img.shape, 255, dtype=np.uint8)
bk = cv2.bitwise_or(background, background, mask=mask)
# combine foreground+background
final = cv2.bitwise_or(fg, bk)
Note: It is better to use the opencv methods because they are optimized.
import cv2 as cv
im_color = cv.imread("lena.png", cv.IMREAD_COLOR)
im_gray = cv.cvtColor(im_color, cv.COLOR_BGR2GRAY)
At this point you have a color and a gray image. We are dealing with 8-bit, uint8 images here. That means the images can have pixel values in the range of [0, 255] and the values have to be integers.
Let's do a binary thresholding operation. It creates a black and white masked image. The black regions have value 0 and the white regions 255
_, mask = cv.threshold(im_gray, thresh=180, maxval=255, type=cv.THRESH_BINARY)
im_thresh_gray = cv.bitwise_and(im_gray, mask)
The mask can be seen below on the left. The image on its right is the result of applying bitwise_and operation between the gray image and the mask. What happened is, the spatial locations where the mask had a pixel value zero (black), became pixel value zero in the result image. The locations where the mask had pixel value 255 (white), the resulting image retained its original gray value.
To apply this mask to our original color image, we need to convert the mask into a 3 channel image as the original color image is a 3 channel image.
mask3 = cv.cvtColor(mask, cv.COLOR_GRAY2BGR) # 3 channel mask
Then, we can apply this 3 channel mask to our color image using the same bitwise_and function.
im_thresh_color = cv.bitwise_and(im_color, mask3)
mask3 from the code is the image below on the left, and im_thresh_color is on its right.
You can plot the results and see for yourself.
cv.imshow("original image", im_color)
cv.imshow("binary mask", mask)
cv.imshow("3 channel mask", mask3)
cv.imshow("im_thresh_gray", im_thresh_gray)
cv.imshow("im_thresh_color", im_thresh_color)
cv.waitKey(0)
The original image is lenacolor.png that I found here.
Answer given by Abid Rahman K is not completely correct. I also tried it and found very helpful but got stuck.
This is how I copy image with a given mask.
x, y = np.where(mask!=0)
pts = zip(x, y)
# Assuming dst and src are of same sizes
for pt in pts:
dst[pt] = src[pt]
This is a bit slow but gives correct results.
EDIT:
Pythonic way.
idx = (mask!=0)
dst[idx] = src[idx]
The other methods described assume a binary mask. If you want to use a real-valued single-channel grayscale image as a mask (e.g. from an alpha channel), you can expand it to three channels and then use it for interpolation:
assert len(mask.shape) == 2 and issubclass(mask.dtype.type, np.floating)
assert len(foreground_rgb.shape) == 3
assert len(background_rgb.shape) == 3
alpha3 = np.stack([mask]*3, axis=2)
blended = alpha3 * foreground_rgb + (1. - alpha3) * background_rgb
Note that mask needs to be in range 0..1 for the operation to succeed. It is also assumed that 1.0 encodes keeping the foreground only, while 0.0 means keeping only the background.
If the mask may have the shape (h, w, 1), this helps:
alpha3 = np.squeeze(np.stack([np.atleast_3d(mask)]*3, axis=2))
Here np.atleast_3d(mask) makes the mask (h, w, 1) if it is (h, w) and np.squeeze(...) reshapes the result from (h, w, 3, 1) to (h, w, 3).

Categories