I have a question like Mask a 3d array with a 2d mask in numpy, but the answer from that isn't working for my issue; I am trying to get elements of an RGB image selected based on a 2d mask. I created a 2d mask with values of 1, on the elements that I want to preserve, with the rest being 0. I then want to apply this mask to an RGB image array and want to retain values only for elements that match the mask value of 1. I tried this code and it bombed. How do I select the pixels (and its values) based on the mask location. My bombed code based on the solution that is in the link is below.
import numpy as np
from PIL import Image, ImageDraw
polygon = [(5,5), (10,5), (15,15), (2,15)]
width = 20
height = 20
# create a mask
img = Image.new('L', (width, height), 0)
ImageDraw.Draw(img).polygon(polygon, outline=1, fill=1)
mask = np.array(img)
mask = mask[:,:,np.newaxis]
a_arr = np.arange(1200).reshape(20,20,3) # create a test image type array
o_arr = np.ma.array(a_arr, mask=mask)
print o_arr
Why not just create a boolean array from your mask and index the image based on that array. Like so:
import numpy as np
from PIL import Image, ImageDraw
polygon = [(5,5), (10,5), (15,15), (2,15)]
width = 20
height = 20
# create a mask
img = Image.new('L', (width, height), 0)
ImageDraw.Draw(img).polygon(polygon, outline=1, fill=1)
mask = np.array(img)
b_mask = mask.astype(bool)
a_arr = np.arange(1200).reshape(20,20,3)
o_arr = a_arr[b_mask]
print(o_arr)
Update
Based on your comment have you tried just stacking the mask along the third dimension?
Instead of mask = mask[:, :, np.newaxis] use mask = np.dstack((mask, mask, mask))
Related
I have 2 images from Carvana Image Dataset where image is jpg and mask is gif. I have converted the mask as grayscale as 0 or 1 and now want to overlay it over the image to see these 3 original, mask, superimposed side by side using matplotlib. What is the right way to do this?
from PIL import Image
def get_pair(image_path, mask_path):
image = np.array(Image.open(image_path).convert('RGB'))
mask = np.array(Image.open(mask_path).convert('L'), dtype = np.float32) # Mask should be Grayscale so each value is either 0 or 255
mask[mask == 255.0] = 1.0 # whereever there is 255, convert it to 1: (1 == 255 == White)
return image, mask
One way could be:
image, mask = data[0]
image = image / 255
mask = np.stack((mask,)*3, axis=-1)
blended = image * mask
plt.imshow(blended)
but it shows only the car and everything else as black
Below are the 2 images
and I want to plot these 3 as:
There might be a misconception in what you expect.
... but it shows only the car and everything else as black
This is how a binary mask usually operates.
The following selfcontained code (with the images from above saved accordingly) might explain what happens. Note the comments near blended1 = ...
from PIL import Image
import numpy as np
from matplotlib import pyplot as plt
def get_pair(image_path, mask_path):
image = np.array(Image.open(image_path).convert('RGB'))
mask = np.array(Image.open(mask_path).convert('L'), dtype = np.float32) # Mask should be Grayscale so each value is either 0 or 255
mask[mask == 255.0] = 1.0 # whereever there is 255, convert it to 1: (1 == 255 == White)
return image, mask
img, mask = get_pair("img.jpg", "mask.gif")
print(f"{img.shape=}") # -> img.shape=(1280, 1918, 3)
print(f"{mask.shape=}") # -> img.shape=(1280, 1918)
mask2 = np.stack((mask,)*3, axis=-1)
print(f"{mask2.shape=}") # -> img.shape=(1280, 1918, 3)
# rescale image
img = img /255
# set every pixel to (0, 0, 0) (black) where mask is 0 and
# keep every pixel unchanged where mask is 1
# this is how a mask is usually applied
blended1 = img*mask2
# set every pixel to (1, 1, 1) (white) where mask is 1 and
# keep every pixel unchanged where mask is 0
blended2 = np.clip(img+mask2, 0, 1)
fig, axx = plt.subplots(1, 4, figsize=(8, 18))
for ax, arr, title in zip(axx,
[img, mask, blended1, blended2],
["original", "mask", "blended1", "blended2"]):
ax.imshow(arr)
ax.axis("off")
ax.set_title(title)
plt.show()
The resulting image:
I have a image and I want to do HE or CLAHE on specific area of the image.
I already have a mask for the image.
Is there any possible way to do so?
Here is the code to achieve that :
import cv2 as cv
import numpy as np
# Load your color image
#src = cv.imread("___YourImagePath__.jpg",
#cv.IMREAD_COLOR)
#Create random color image
src = np.random.randint(255, size=(800,800,3),dtype=np.uint8)
cv.imshow('Random Color Image',src)
cv.waitKey(0)
# conver to gray
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
# process gray image
equalized = cv.equalizeHist(gray)
# create a mask (binary image with same size as source image )
height,width,depth = src.shape
mask = np.zeros((height,width))
cv.circle(mask,( int(width/2),int(height/2)),int(width/3),1,thickness=-1)
# display mask
cv.imshow('Mask',mask)
cv.waitKey(0)
# Copy processed region using the mask
ProcessedRegion = np.where(mask!=0,equalized,gray)
#display result
cv.imshow('Processed region result', ProcessedRegion)
cv.waitKey(0)
Output :
To do so you need to perform the operation on the pixel intensities of the image which fall within the mask. For that these intensities must be stored separately.
Procedure:
Get the pixel locations of those in white (255), within the mask.
Pick intensity values (0 - 255) from the gray image present in these locations.
Perform your operation (CLAHE or HE) on these intensities. The result is a different collection of intensities.
Place these new intensity values in the collected locations.
Sample:
Input image:
Mask image:
Code:
import cv2
import numpy as np
# read sample image, convert to grayscale
img = cv2.imread('flower.jpg')
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# read mask image as binary image
mask = cv2.imread('flower_mask.jpg', 0)
# Step 1: store locations with value 255 (white)
loc = np.where(mask == 255)
# Step 2: Pick intensity values in these locations from the grayscale image:
values = gray[loc]
# Step 3: Histogram equalization on these values:
enhanced_values = cv2.equalizeHist(values)
# Step 4: Store these enhanced values in those locations:
gray2 = gray_img.copy()
for i, coord in enumerate(zip(loc[0], loc[1])):
gray2[coord[0], coord[1]] = enhanced_values[i][0]
cv2.imshow('Enhanced image', gray2)
Enhance image:
Grayscale image:
I'm trying to remove the background of an image using a mask where the alpha value of a pixel is proportional to the black intensity. For instance, given the following input image and mask, the result contains "faded" areas:
Result:
Note the faded areas. Basically I'm trying to imitate the layer mask function in Photoshop.
I'm able to turn the mask into alpha using binary threshold, but I wonder how to make the alpha proportional. The code for binary threshold is as follows:
mask = cv2.imread(mask_path, 0)
mask2 = np.where(mask<50, 0, 1).astype('uint8')
img = img * mask2[:, :, np.newaxis]
_, alpha = cv2.threshold(mask2, 0, 255, cv2.THRESH_BINARY)
png = np.dstack((img, alpha))
cv2.imwrite(dest_path, png)
I suppose it may perhaps be irrelevant as thresholds are probably not needed for layer masking.
I'm not sure if this is what you want, but you can get the proportional effect by subtracting the values of the mask from the image. That means you have to invert the mask, so the amount of alpha you want to remove is white. For subtract(), the input arrays need to have the same size, so convert the inverted mask to 3 color channels. If the size of the mask is not equal to the background image, you'll first have to create a subimage.
import cv2
import numpy as np
# load background image
img = cv2.imread('grass.jpg')
# load alpha mask as grayscale
mask = cv2.imread('a_mask.jpg',0)
# invert mask and convert to 3 color channels
mask = cv2.bitwise_not(mask)
fullmask = cv2.cvtColor(mask,cv2.COLOR_GRAY2BGR)
# create a subimage with the size of the mask
xOffset = 30
yOffset = 30
height, width = mask.shape[:2]
subimg = img[yOffset:yOffset+height,xOffset:xOffset+width]
#subtract mask values from subimage
res = cv2.subtract(subimg,fullmask)
# put subimage back in background
img[yOffset:yOffset+height,xOffset:xOffset+width] = res
#display result
cv2.imshow('Result',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
I have a set of points that make a shape (closed polyline). Now I want to copy/crop all pixels from some image inside this shape, leaving the rest black/transparent. How do I do this?
For example, I have this:
and I want to get this:
*edit - updated to work with images that have an alpha channel.
This worked for me:
Make a mask with all black (all masked)
Fill a polygon with white in the shape of your ROI
combine the mask and your image to get the ROI with black everywhere else
You probably just want to keep the image and mask separate for functions that accept masks. However, I believe this does what you specifically asked for:
import cv2
import numpy as np
# original image
# -1 loads as-is so if it will be 3 or 4 channel as the original
image = cv2.imread('image.png', -1)
# mask defaulting to black for 3-channel and transparent for 4-channel
# (of course replace corners with yours)
mask = np.zeros(image.shape, dtype=np.uint8)
roi_corners = np.array([[(10,10), (300,300), (10,300)]], dtype=np.int32)
# fill the ROI so it doesn't get wiped out when the mask is applied
channel_count = image.shape[2] # i.e. 3 or 4 depending on your image
ignore_mask_color = (255,)*channel_count
cv2.fillPoly(mask, roi_corners, ignore_mask_color)
# from Masterfool: use cv2.fillConvexPoly if you know it's convex
# apply the mask
masked_image = cv2.bitwise_and(image, mask)
# save the result
cv2.imwrite('image_masked.png', masked_image)
The following code would be helpful for cropping the images and get them in a white background.
import cv2
import numpy as np
# load the image
image_path = 'input image path'
image = cv2.imread(image_path)
# create a mask with white pixels
mask = np.ones(image.shape, dtype=np.uint8)
mask.fill(255)
# points to be cropped
roi_corners = np.array([[(0, 300), (1880, 300), (1880, 400), (0, 400)]], dtype=np.int32)
# fill the ROI into the mask
cv2.fillPoly(mask, roi_corners, 0)
# The mask image
cv2.imwrite('image_masked.png', mask)
# applying th mask to original image
masked_image = cv2.bitwise_or(image, mask)
# The resultant image
cv2.imwrite('new_masked_image.png', masked_image)
Input Image:
Mask Image:
Resultant output image:
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).