replace mask with original image opencv Python - python

I am trying to replace objects which I found using a mask with the original images pixels. I have a mask that shows black where the object is not detected and white if detected. I am then using the image in a where statement
image[np.where((image2 == [255,255,255].any(axis = 2))
I am stuck here and I have no idea how to change found white values to what the original image is (to use alongside other masks). I have tried image.shape and this did not work.
Thanks.

Make a copy of the mask and then draw the original image over the white pixels of the mask from the white pixel coordinates. You can also check mask == 255 to compare element-wise. You don't need np.where because you can index arrays via the boolean mask created by mask == 255.
out = mask.copy()
out[mask == 255] = original_image[mask == 255]

You can use bitwise operations. Try this:
replaced_image = cv2.bitwise_and(original_image,original_image,mask = your_mask)
Visit https://docs.opencv.org/3.3.0/d0/d86/tutorial_py_image_arithmetics.html
to learn more about bitwise operations

import os
import cv2
from netpbmfile import imread
img_dir = '.'
mask_dir = '.'
new_bg = 'image.png'
def get_foreground(fg_image_name, mask_name, bg_image_name):
fg_image = cv2.imread(fg_image_name)
mask = imread(mask_name)
mask_inverse = (1-mask)
bg_image = cv2.imread(bg_image_name)
bg_image = cv2.resize(bg_image, (fg_image.shape[1], fg_image.shape[0]))
foregound = cv2.bitwise_and(fg_image, fg_image, mask=mask)
background = cv2.bitwise_and(bg_image, bg_image, mask=mask_inverse)
composite = foregound + background
return composite
image_fg = get_foreground(os.path.join(img_dir, "NP1_0.jpg"), os.path.join(mask_dir, "NP1_0_mask.pbm"), new_bg)
cv2.imwrite("foreground.jpg", image_fg)

Related

What is the Fastest way to change pixel values of an RGB image using Numpy / Opencv / scikit-image

Given a binary image, what is the fastest and Pythonic way to convert the image to RGB and then modify it's pixels?
I have these two ways but they don't feel good to me
def get_mask(rgb_image_path):
mask = np.array(Image.open(rgb_image_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 mask
def approach1(mask):
mask = np.logical_not(mask)
mask = mask.astype(np.uint8)
mask = mask*255
red = mask.copy()
blue = mask.copy()
green = mask.copy()
red[red == 0] = 26
blue[blue == 0] = 237
green[green == 0] = 160
mask = np.stack([red, blue, green], axis = -1)
plt.imshow(mask)
def approach2(mask):
mask = np.logical_not(mask)
mask = mask.astype(np.uint8)
mask = np.stack([mask,mask,mask], axis = -1)
mask = mask*255
width,height, channels = mask.shape
for i in range(width):
for j in range(height):
if mask[i][j][0] == 0:
mask[i][j] = (26,237,160)
plt.imshow(mask)
Below is the Image
I suppose the most simple way is this:
def mask_coloring(mask):
expected_color = (26, 237, 160)
color_mask = np.zeros((mask.shape[0], mask.shape[1], 3), dtype=np.uint8)
color_mask[mask == 255.0, :] = expected_color
plt.imshow(color_mask)
By the way, similar approach can be found here.
The easiest way to do this is to take advantage of the fact that you only have and only want 2 colours and to use a fast, space-efficient palette image that doesn't require you to iterate over all the pixels, or store 3 bytes per pixel:
from PIL import Image
# Open your image
im = Image.open('oqX2g.gif')
# Create a new palette where the 1st entry is black (0,0,0) and the 2nd is your colour (26,237,160) then pad out to 256 RGB entries
palette = [0,0,0] + [26,237,160] + [0,0,0] * 254
# Put palette into image and save
im.putpalette(palette)
im.save('result.png')
Read more about palette images here.
What if you want the black background to become magenta, and the foreground green? Oh, and you want it as an RGB Numpy array?
Just change the palette and convert:
from PIL import Image
import numpy as np
# Open your image, push in new palette
im = Image.open('oqX2g.gif')
palette = [255,0,255] + [0,255,0] + [0,0,0] * 254
im.putpalette(palette)
# Make RGB Numpy array
na = np.array(im.convert('RGB'))

How to properly insert mask into original image?

My goal is to cover a face with circular noise (salt and pepper/black and white dots), however what I have managed to achieve is just rectangular noise.
Using this image:
I found the face coordinates (x,y,w,h) = [389, 127, 209, 209]
And using this function added noise Like:
img = cv2.imread('like_this.jpg')
x,y,w,h = [389, 127, 209, 209]
noised = add_noise(img[y:y+h,x:x+w])
new = img.copy()
new[y:y+h,x:x+w] = noised
cv2.imshow('new', new)
From x,y,w,h I found that I want my circle to be at (493, 231) with radius 105
I researched I found something about masking and bitwise operations, so I tried:
mask = np.zeros(new.shape[:2], dtype='uint8')
cv2.circle(mask, (493, 231), 105, 255, -1)
new_gray = cv2.cvtColor(new, cv2.COLOR_BGR2GRAY)
masked = cv2.bitwise_and(new_gray, new_gray , mask=mask)
cv2.imshow('masked', masked)
Here, the problem arises:
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
OR = cv2.bitwise_or(masked, img_gray) # removes the black dots, idk why
cv2.imshow('bitwise - OR', OR)
The black dots get removed from the noise and besides that, I can't seem to convert OR back to BGR.
Maybe there is a better way to do that.
Please, help/guidance needed!
So the issue is how to use masking. There are two options, numpy and OpenCV.
numpy
Since you copied the noisy area into the result and now want to restore everything outside of the circle, I'll use mask == 0 to get a boolean array that is true everywhere outside of the circle.
With numpy, boolean arrays can be used as indices. The result is a "view", it behaves like a slice. Operations through it affect the original data.
noised = add_noise(img[y:y+h,x:x+w])
new = img.copy()
new[y:y+h,x:x+w] = noised # whole rectangle affected
new[mask == 0] = img[mask == 0] # restore everything outside of the circle
All three arrays (mask, new, img) need to have the same shape.
OpenCV, masks
Not much point to it with Python and numpy available, but many of its C++ APIs take an optional mask argument that modifies that function's operation. Mat::copyTo() is one such method.
OpenCV, bitwise operations
With bitwise operations, the mask would no longer just label each pixel as true or false, but it would have to be 3-channels and all eight bits of every value count, so it must contain only 0 and 255 (0xFF).
I'll erase everything outside of the circle first, then add back the part of the source that is outside of the circle. Both operations use bitwise_and. Two operations are required because bitwise operations can't just "overwrite". They react to both operands. I'll also use numpy's ~ operator to bitwise-negate the mask.
bitwise_mask = cv.cvtColor(mask, cv.COLOR_GRAY2BGR) # blow it up
new = cv.bitwise_and(new, bitwise_mask)
new += cv.bitwise_and(img, ~bitwise_mask)
Bitwise opperations function on binary conditions turning "off" a pixel if it has a value of zero, and turning it "on" if the pixel has a value greater than zero.
In your case the bitwise_or "removes" both the black background of the mask and the black points of the noise.
I propose to do it like this:
# Read image
img = cv2.imread('like_this.jpg')
x,y,w,h = [389, 127, 209, 209]
# Add squared noise
noised = add_noise(img[y:y+h,x:x+w])
new = img.copy()
new[y:y+h,x:x+w] = noised
# Transform the image to graylevel
new_gray = cv2.cvtColor(new, cv2.COLOR_BGR2GRAY)
# Create a mask in which black noise is equal to 0 and white to 2
mask = np.zeros(new.shape[:2], dtype='uint8')
mask[new_gray==0] = 1
mask[new_gray==255] = 2
# Mask the previous mask with a circle
circle = np.zeros(new.shape[:2], dtype='uint8')
circle = cv2.circle(circle, (493, 231), 105, 1, -1)
mask = mask * circle
plt.imshow(mask)
plt.show()
# Join the mask adding the noise to the image
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray[mask==1] = 0
gray[mask==2] = 255
cv2.imshow('Result', gray)
cv2.waitKey(0)
Hope it works. If you need more help let me know.

How do I crop an image based on custom mask in python?

Below I have attached two images. I want the first image to be cropped in a heart shape according to the mask image (2nd image).
I searched for solutions but I was not able to get the simple and easier way to do this. Kindly help me with the solution.
2 images:
Image to be cropped:
Mask image:
Let's start by loading the temple image from sklearn:
from sklearn.datasets import load_sample_images
dataset = load_sample_images()
temple = dataset.images[0]
plt.imshow(temple)
Since, we need to use the second image as mask, we must do a binary thresholding operation. This will create a black and white masked image, which we can then use to mask the former image.
from matplotlib.pyplot import imread
heart = imread(r'path_to_im\heart.jpg', cv2.IMREAD_GRAYSCALE)
_, mask = cv2.threshold(heart, thresh=180, maxval=255, type=cv2.THRESH_BINARY)
We can now trim the image so its dimensions are compatible with the temple image:
temple_x, temple_y, _ = temple.shape
heart_x, heart_y = mask.shape
x_heart = min(temple_x, heart_x)
x_half_heart = mask.shape[0]//2
heart_mask = mask[x_half_heart-x_heart//2 : x_half_heart+x_heart//2+1, :temple_y]
plt.imshow(heart_mask, cmap='Greys_r')
Now we have to slice the image that we want to mask, to fit the dimensions of the actual mask. Another shape would have been to resize the mask, which is doable, but we'd then end up with a distorted heart image. To apply the mask, we have cv2.bitwise_and:
temple_width_half = temple.shape[1]//2
temple_to_mask = temple[:,temple_width_half-x_half_heart:temple_width_half+x_half_heart]
masked = cv2.bitwise_and(temple_to_mask,temple_to_mask,mask = heart_mask)
plt.imshow(masked)
If you want to instead make the masked (black) region transparent:
tmp = cv2.cvtColor(masked, cv2.COLOR_BGR2GRAY)
_,alpha = cv2.threshold(tmp,0,255,cv2.THRESH_BINARY)
b, g, r = cv2.split(masked)
rgba = [b,g,r, alpha]
masked_tr = cv2.merge(rgba,4)
plt.axis('off')
plt.imshow(dst)
Since I am on a remote server, cv2.imshow doesnt work for me. I imported plt.
This code does what you are looking for:
import cv2
import matplotlib.pyplot as plt
img_org = cv2.imread('~/temple.jpg')
img_mask = cv2.imread('~/heart.jpg')
##Resizing images
img_org = cv2.resize(img_org, (400,400), interpolation = cv2.INTER_AREA)
img_mask = cv2.resize(img_mask, (400,400), interpolation = cv2.INTER_AREA)
for h in range(len(img_mask)):
for w in range(len(img_mask)):
if img_mask[h][w][0] == 0:
for i in range(3):
img_org[h][w][i] = 0
else:
continue
plt.imshow(img_org)

inverting image in Python with OpenCV

I want to load a color image, convert it to grayscale, and then invert the data in the file.
What I need: to iterate over the array in OpenCV and change every single value with this formula (it might be wrong but it seems reasonable for me):
img[x,y] = abs(img[x,y] - 255)
but I don't understand why doesn't it works:
def inverte(imagem, name):
imagem = abs(imagem - 255)
cv2.imwrite(name, imagem)
def inverte2(imagem, name):
for x in np.nditer(imagem, op_flags=['readwrite']):
x = abs(x - 255)
cv2.imwrite(name, imagem)
if __name__ == '__main__':
nome = str(sys.argv[1])
image = cv2.imread(nome)
gs_imagem = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
inverte(gs_imagem, "invertida.png")
inverte2(gs_imagem, "invertida2.png")
I don't want to do an explicit loop (I am trying to be more pythonic). I can see that in one image that got a white background it turned black, but only this it doesn't looks like the other colors are having much (if any) change.
You almost did it. You were tricked by the fact that abs(imagem-255) will give a wrong result since your dtype is an unsigned integer. You have to do (255-imagem) in order to keep the integers unsigned:
def inverte(imagem, name):
imagem = (255-imagem)
cv2.imwrite(name, imagem)
You can also invert the image using the bitwise_not function of OpenCV:
imagem = cv2.bitwise_not(imagem)
Alternatively, you could invert the image using the bitwise_not function of OpenCV:
imagem = cv2.bitwise_not(imagem)
I liked this example.
You can use "tilde" operator to do it:
import cv2
image = cv2.imread("img.png")
image = ~image
cv2.imwrite("img_inv.png",image)
This is because the "tilde" operator (also known as unary operator) works doing a complement dependent on the type of object
for example for integers, its formula is:
x + (~x) = -1
but in this case, opencv use an "uint8 numpy array object" for its images so its range is from 0 to 255
so if we apply this operator to an "uint8 numpy array object" like this:
import numpy as np
x1 = np.array([25,255,10], np.uint8) #for example
x2 = ~x1
print (x2)
we will have as a result:
[230 0 245]
because its formula is:
x2 = 255 - x1
and that is exactly what we want to do to solve the problem.
You can also do it with numpy.
import cv2
import numpy as np
image = cv2.imread('your_image', 0)
inverted = np.invert(image)
cv2.imwrite('inverted.jpg', inverted)
In Python/OpenCV, I think you want:
img = cv2.absDiff(img, 255)
Why not use the first line in the question with numpy?
inverted = np.abs(image - 255)
Just as simple as that. No iteration or any other function needed. numpy does that automatically for us :)
def invert(self, image=None):
if image is None:
image = self.image
image = image.astype("uint8") # --> pay attention to the image type
inverted = np.invert(image)
(thresh, im_bw) = cv2.threshold(inverted, 0, 1, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
return im_bw

How to invert colors of image with PIL (Python-Imaging)?

I need to convert series of images drawn as white on black background letters to images where white and black are inverted (as negative). How can I achieve this using PIL?
Try the following from the docs: https://pillow.readthedocs.io/en/stable/reference/ImageOps.html
from PIL import Image
import PIL.ImageOps
image = Image.open('your_image.png')
inverted_image = PIL.ImageOps.invert(image)
inverted_image.save('new_name.png')
Note: "The ImageOps module contains a number of 'ready-made' image processing operations. This module is somewhat experimental, and most operators only work on L and RGB images."
If the image is RGBA transparent this will fail... This should work though:
from PIL import Image
import PIL.ImageOps
image = Image.open('your_image.png')
if image.mode == 'RGBA':
r,g,b,a = image.split()
rgb_image = Image.merge('RGB', (r,g,b))
inverted_image = PIL.ImageOps.invert(rgb_image)
r2,g2,b2 = inverted_image.split()
final_transparent_image = Image.merge('RGBA', (r2,g2,b2,a))
final_transparent_image.save('new_file.png')
else:
inverted_image = PIL.ImageOps.invert(image)
inverted_image.save('new_name.png')
For anyone working with an image in "1" mode (i.e., 1-bit pixels, black and white, stored with one pixel per byte -- see docs), you need to convert it into "L" mode before calling PIL.ImageOps.invert.
Thus:
im = im.convert('L')
im = ImageOps.invert(im)
im = im.convert('1')
now ImageOps must be:
PIL.ImageChops.invert(PIL.Image.open(imagepath))
note that this works for me in python 3.8.5
In case someone is inverting a CMYK image, the current implementations of PIL and Pillow don't seem to support this and throw an error. You can, however, easily circumvent this problem by inverting your image's individual bands using this handy function (essentially an extension of Greg Sadetsky's post above):
def CMYKInvert(img) :
return Image.merge(img.mode, [ImageOps.invert(b.convert('L')) for b in img.split()])
Of course ImageOps does its job well, but unfortunately it can't work with some modes like 'RGBA'. This code will solve this problem.
def invert(image: Image.Image) -> Image.Image:
drawer = ImageDraw.Draw(image)
pixels = image.load()
for x in range(image.size[0]):
for y in range(image.size[1]):
data = pixels[x, y]
if data != (0, 0, 0, 0) and isinstance(data, tuple):
drawer.point((x, y), (255 - data[0], 255 - data[1], 255 - data[2], data[3]))
return image
from PIL import Image
img = Image.open("archive.extension")
pixels = img.load()
for i in range(img.size[0]):
for j in range(img.size[1]):
x,y,z = pixels[i,j][0],pixels[i,j][1],pixels[i,j][2]
x,y,z = abs(x-255), abs(y-255), abs(z-255)
pixels[i,j] = (x,y,z)
img.show()
`

Categories