BGR values of masked image (OpenCV, Python) - python

Using the follow image..
... I am applying this code to create a circle mask:
import cv2
import numpy as np
img = cv2.imread("car.png")
height, width, depth = img.shape
circle_img = np.zeros((height, width), np.uint8)
mask = cv2.circle(circle_img, (int(width / 2), int(height / 2)), 90, 1, thickness=-1)
masked_img = cv2.bitwise_and(img, img, mask=circle_img)
cv2.imshow("masked", masked_img)
cv2.waitKey(0)
This is the output..
How can I find BGR values of the circle using OpenCV ?

You can do it using numpy arrays.
circle_locations = mask == 1
bgr = img[circle_locations]
EDIT: I'm not sure if your mask has values in {0, 1} though I assume it does. If its background value is 0 and all positive values are forground, just change the == 1 to a > 1.

Related

When I put foreground image on the background, why this distortion happens?

Here is the background image;
Here is the foreground image;
Here is the Mask image;
The code below;
background = cv2.imread('back.jpg')
image = cv2.imread('img.jpg')
mask = cv2.imread('mask.jpg')
crop_background = cv2.resize(background, (image.shape[1], image.shape[0]), interpolation = cv2.INTER_NEAREST)
mask = np.where(mask == 0, mask, image)
crop_background1 = np.where(mask == 0, crop_background, mask)
cv2.imshow(f'{i}', crop_background1)
cv2.waitKey(0)
output image After running the code;
Carefully look at the data types you are handling. All three images are BGR, they have three intensity values per pixel. In the where function you are only checking and setting one intensity value, so you only change the blue channel, instead of all three channels.
Now, you also have to be careful while composing these images. Be aware of pixel values possible “wrapping around” (unsigned integer overflow) and masking the correct pixels. One possible solution is to compose the image using a linear relationship between pixels values. Nothing fancy, just assigning the pixel values according to the mask matrix:
newPixel = (originalPixel * maskPixel + backgroundPixel * (1-maskPixel))
Note what happens when maskPixel is 0 or 1 (You'll need to use float types). You can see that the "ramp" correctly sets the newPixel value. The operation is also vectorizable. Let's check out the new code:
import cv2
import numpy as np
# Image path
path = "D://opencvImages//"
# Reading the images in default mode:
background = cv2.imread(path + "jkU1C.jpg")
image = cv2.imread(path + "QNuXL.jpg")
mask = cv2.imread(path + "6yyNG.png")
# Resize background:
crop_background = cv2.resize(background, (image.shape[1], image.shape[0]), interpolation = cv2.INTER_NEAREST)
# BGR to Gray mask:
mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
# Threshold mask:
_, mask = cv2.threshold(mask, 0, 255, cv2.THRESH_BINARY)
# Back to BGR:
mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
# Convert mask to float in range 0 to 1
mask = mask.astype(np.float64) / 255
# Get new pixel according to the mask matrix:
result = (image * mask + crop_background * (1 - mask))
# Clip values and convert back to uint8:
result = result.clip(0, 255).astype(np.uint8)
# Show result:
cv2.imshow("result", result)
cv2.waitKey(0)
The result:

How to stack a transparent image on another image in opencv python

Wanted to have two images where i have a mask stacked on top of another image. But in doing so, i wish to not have two images blend together, rather have the final image stacked onto each other like layers
Here's my original images
masked image
code
import cv2
import numpy as np
image = cv2.imread('test72.jpg')
image2 = cv2.imread('test63.jpg')
blank = np.full((image.shape[0], image.shape[1], 3), (255,255,255), np.uint8)
circle = cv2.circle(blank, (300,300), 10, (0, 0, 0), thickness= 100)
blur = cv2.blur(circle, (50, 50), 0)
subtract = cv2.subtract(image, blur)
blended = cv2.addWeighted(image2, 1, subtract, 1, 0)
cv2.imwrite('mask.jpg', subtract)
cv2.imwrite('blend.jpg', blended)
cv2.waitKey(0)
cv2.destroyAllWindows()
Here is what the result looks like when the function cv2.addweighted is added, which results in the bannanas blending in the shoes, is there another function in OpenCv i could do to make this stack rather than blend?
Something like this might work in your case:
im1_alpha = blur/255
im2_alpha = (255-blur)/255
out_img = ((image2 * im1_alpha) + (image * im2_alpha)).astype(np.uint8)
plt.imshow(out_img)
Here is another answer!
I followed this tutorial right here! Alpha blending tutorial right here
import cv2
import numpy as np
image = cv2.imread('test72.jpg')
image2 = cv2.imread('test63.jpg')
blank = np.full((image.shape[0], image.shape[1], 3), (255,255,255), np.uint8)
circle = cv2.circle(blank, (300,350), 10, (0, 0, 0), thickness= 100)
blur = cv2.blur(circle, (50, 50), 0)
image = image.astype(float)
image2 = image2.astype(float)
alpha = blur.astype(float)/255
multiply = cv2.multiply(alpha, image2)
multiply2 = cv2.multiply(1.0 - alpha, image)
add = cv2.add(multiply, multiply2).astype(np.uint8)
cv2.imshow('alpha', add)
cv2.waitKey(0)
cv2.destroyAllWindows()

Compositing images by blurred mask in Numpy

I have two images and a mask, all of same dimensions, as Numpy arrays:
Desired output
I would like to merge them in such a way that the output will be like this:
Current code
def merge(lena, rocket, mask):
'''Mask init and cropping'''
mask = np.zeros(lena.shape[:2], dtype='uint8')
cv2.fillConvexPoly(mask, circle, 255) # might be polygon
'''Bitwise operations'''
lena = cv2.bitwise_or(lena, lena, mask=mask)
mask_inv = cv2.bitwise_not(mask) # mask inverting
rocket = cv2.bitwise_or(rocket, rocket, mask=mask_inv)
output = cv2.bitwise_or(rocket, lena)
return output
Current result
This code gives me this result:
Applying cv2.GaussianBlur(mask, (51,51), 0) distorts colors of overlayed image in different ways.
Other SO questions relate to similar problems but not solving exactly this type of blurred compositing.
Update: this gives same result as a current one
mask = np.zeros(lena.shape[:2], dtype='uint8')
mask = cv2.GaussianBlur(mask, (51,51), 0)
mask = mask[..., np.newaxis]
cv2.fillConvexPoly(mask, circle, 1)
output = mask * lena + (1 - mask) * rocket
Temporal solution
Possibly this is not optimal due to many conversions, please advise
mask = np.zeros(generated.shape[:2])
polygon = np.array(polygon, np.int32) # 2d array of x,y coords
cv2.fillConvexPoly(mask, polygon, 1)
mask = cv2.GaussianBlur(mask, (51, 51), 0)
mask = mask.astype('float32')
mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
foreground = cv2.multiply(lena, mask, dtype=cv2.CV_8U)
background = cv2.multiply(rocket, (1 - mask), dtype=cv2.CV_8U)
output = cv2.add(foreground, background)
Please advise how can I blur a mask, properly merge it with foreground and then overlay on background image?
You need to renormalize the mask before blending:
def blend_merge(lena, rocket, mask):
mask = cv2.GaussianBlur(mask, (51, 51), 0)
mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
mask = mask.astype('float32') / 255
foreground = cv2.multiply(lena, mask, dtype=cv2.CV_8U)
background = cv2.multiply(rocket, (1 - mask), dtype=cv2.CV_8U)
output = cv2.add(foreground, background)
return output
A full working example is here.
Here is how to do that in Python/OpenCV. Your second method is close.
Read the 3 input images
Apply linear (or Gaussian) blur to the circle
Stretch the circle to full dynamic range (0 to 255) as a mask
Convert the mask to float in the range 0 to 1 as 3 channels
Apply mask to image1 and inverted mask to image2 via multiplication and add the products together
Convert the result to 8-bit range (0 to 255) clipping to be sure no overflow or wrap around
Save the results
Input images:
import cv2
import numpy as np
# Read images
image1 = cv2.imread('lena_wide.jpg')
image2 = cv2.imread('rocket.jpg')
circle = cv2.imread('white_circle.jpg', cv2.IMREAD_GRAYSCALE)
# linear blur mask
mask = cv2.blur(circle, (30,30))
# alternate using Gaussian blur
#mask = cv2.GaussianBlur(circle, (0,0), sigmaX=10, sigmaY=10)
# stretch mask to full dynamic range
mask = cv2.normalize(mask, None, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)
# convert mask to float in range 0 to 1
maskf = (mask/255).astype(np.float64)
maskf = cv2.merge([maskf,maskf,maskf])
# apply mask to image1 and inverted mask to image2
result = maskf*image1 + (1-maskf)*image2
result = result.clip(0,255).astype(np.uint8)
# save results
cv2.imwrite('white_circle_ramped.jpg', mask)
cv2.imwrite('lena_wide_rocked_composited.png', result)
# show results
cv2.imshow("mask", mask)
cv2.imshow("result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Ramped mask image:
Result:
ADDITION:
Here is an alternate approach using mostly Numpy.
import cv2
import numpy as np
# Read images
image1 = cv2.imread('lena_wide.jpg')
image2 = cv2.imread('rocket.jpg')
circle = cv2.imread('white_circle2.jpg', cv2.IMREAD_GRAYSCALE)
# linear blur mask
mask = cv2.blur(circle, (30,30))
# alternate using Gaussian blur
#mask = cv2.GaussianBlur(circle, (0,0), sigmaX=10, sigmaY=10)
# stretch mask to full dynamic range
mask = cv2.normalize(mask, None, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)
# convert mask to 3 channels
mask = cv2.merge([mask,mask,mask])
# apply mask to image1 and inverted mask to image2
image1_masked = np.multiply(image1, mask/255).clip(0,255).astype(np.uint8)
image2_masked = np.multiply(image2, 1-mask/255).clip(0,255).astype(np.uint8)
# add the two masked images together
result = np.add(image1_masked, image2_masked)
# save results
cv2.imwrite('white_circle_ramped2.jpg', mask)
cv2.imwrite('lena_wide_rocked_composited2.png', result)
# show results
cv2.imshow("mask", mask)
cv2.imshow("image1_masked", image1_masked)
cv2.imshow("image2_masked", image2_masked)
cv2.imshow("result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()

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).

how to use mask to remove the background in python

I want to remove the background by using the mask image. Now, I have already get the mask image.I try to let the value of the original image's background become 0 where the value of mask is 0. But the result is very bad. How can I solve this problem. Thank you
from skimage import io
import numpy as np
img = io.imread("GT06.jpg")
mask = io.imread("GT03.png")
mask2 = np.where((mask==0),0,1).astype('uint8')
img = img*mask2[:,:,np.newaxis]
io.imshow(img)
io.show()
GT06.jpg
GT03.png
This results in:
I want to get the foreground like this:
The problem is that your mask isn't pure black and white, i.e. all 0 or 255 changing you mask two generation to:
mask2 = np.where((mask<200),0,1).astype('uint8')
results in:
You could either play with the mask or the threshold number - I used 200.
In Python you could use OpenCV. Here are instructions to install OpenCV in Python if you don't have it in your system. I think you could do the same with other libraries, the procedure will be the same, the trick is to invert the mask and apply it to some background, you will have your masked image and a masked background, then you combine both.
The image1 is your image masked with the original mask, image2 is the background image masked with the inverted mask, and image3 is the combined image. Important. image1, image2 and image3 must be of the same size and type. The mask must be grayscale.
import cv2
import numpy as np
# opencv loads the image in BGR, convert it to RGB
img = cv2.cvtColor(cv2.imread('E:\\FOTOS\\opencv\\iT5q1.png'),
cv2.COLOR_BGR2RGB)
# load mask and make sure is black&white
_, mask = cv2.threshold(cv2.imread('E:\\FOTOS\\opencv\\SH9jL.png', 0),
0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# load background (could be an image too)
bk = np.full(img.shape, 255, dtype=np.uint8) # white bk, same size and type of image
bk = cv2.rectangle(bk, (0, 0), (int(img.shape[1] / 2), int(img.shape[0] / 2)), 0, -1) # rectangles
bk = cv2.rectangle(bk, (int(img.shape[1] / 2), int(img.shape[0] / 2)), (img.shape[1], img.shape[0]), 0, -1)
# get masked foreground
fg_masked = cv2.bitwise_and(img, img, mask=mask)
# get masked background, mask must be inverted
mask = cv2.bitwise_not(mask)
bk_masked = cv2.bitwise_and(bk, bk, mask=mask)
# combine masked foreground and masked background
final = cv2.bitwise_or(fg_masked, bk_masked)
mask = cv2.bitwise_not(mask) # revert mask to original

Categories