Create transparent image in opencv python - python

I am trying to make a transparent image and draw on it, and after I will addWeighted over the base image.
How can I initialize fully transparent image with width and hight in openCV python?
EDIT: I want to make a effect like in Photoshop, having stack of the layers, all stacked layers are initially transparent and drawing is performed on fully transparent layer. On the end I will merge all layers to get final image

For creating a transparent image you need a 4 channel matrix, 3 of which would represent RGB colors and the 4th channel would represent Alpha channel, To create a transparent image, you can ignore the RGB values and directly set the alpha channel to be 0. In Python OpenCV uses numpy to manipulate matrices, so a transparent image can be created as
import numpy as np
import cv2
img_height, img_width = 300, 300
n_channels = 4
transparent_img = np.zeros((img_height, img_width, n_channels), dtype=np.uint8)
# Save the image for visualization
cv2.imwrite("./transparent_img.png", transparent_img)

If you want to draw on several "layers" and then stack the drawings together, then how about this:
import cv2
import numpy as np
#create 3 separate BGRA images as our "layers"
layer1 = np.zeros((500, 500, 4))
layer2 = np.zeros((500, 500, 4))
layer3 = np.zeros((500, 500, 4))
#draw a red circle on the first "layer",
#a green rectangle on the second "layer",
#a blue line on the third "layer"
red_color = (0, 0, 255, 255)
green_color = (0, 255, 0, 255)
blue_color = (255, 0, 0, 255)
cv2.circle(layer1, (255, 255), 100, red_color, 5)
cv2.rectangle(layer2, (175, 175), (335, 335), green_color, 5)
cv2.line(layer3, (170, 170), (340, 340), blue_color, 5)
res = layer1[:] #copy the first layer into the resulting image
#copy only the pixels we were drawing on from the 2nd and 3rd layers
#(if you don't do this, the black background will also be copied)
cnd = layer2[:, :, 3] > 0
res[cnd] = layer2[cnd]
cnd = layer3[:, :, 3] > 0
res[cnd] = layer3[cnd]
cv2.imwrite("out.png", res)

To convert an image's white parts to transparent:
import cv2
import numpy as np
img = cv2.imread("image.png", cv2.IMREAD_UNCHANGED)
img[np.where(np.all(img[..., :3] == 255, -1))] = 0
cv2.imwrite("transparent.png", img)

Related

How to change the set of pixel colors in contatc with black color

Considering this image:
I would like to change the set of white pixels in contact with black pixels by red, this way:
I tried to use this code in python:
import numpy as np
from PIL import Image
im = Image.open('image.png')
data = np.array(im)
r1, g1, b1 = 255, 255, 255 # Original value
r2, g2, b2 = 0, 0, 255 # Value that we want to replace it with
red, green, blue = data[:,:,0], data[:,:,1], data[:,:,2]
mask = (red == r1) & (green == g1) & (blue == b1)
data[:,:,:3][mask] = [r2, g2, b2]
im = Image.fromarray(data)
But I changed all white pixels by red. But could be an UNIX approach suggestion too.
Please, post lossless versions of your input images. Lossy images modify the value of the pixels, creating artifacts that affect processing. I recreated your image and saved it as a lossless PNF file.
I'm using OpenCV to get the result you want. I created a mask with the non-zero elements of your original input. Then, I used Flood-fill to fill the outer shapes with the color you want. The final image can be obtained if you AND both images.
Let's see the code:
# import opencv:
import cv2
# image path
path = "D://opencvImages//"
fileName = "rectsLossless.png"
# Reading an image in default mode:
inputImage = cv2.imread(path + fileName)
# Grayscale image:
grayscaleImage = cv2.cvtColor(inputImage, cv2.COLOR_BGR2GRAY)
# Get non-zero mask:
binaryThresh = 1
_, binaryMask = cv2.threshold(grayscaleImage, binaryThresh, 255, cv2.THRESH_BINARY)
This bit creates the non-zero pixels mask:
This will help to zero all the elements that are non-white. That image is the first part of the mask. Now, let's fill the outer shapes with red color. This is achieved in three steps:
# Get image dimensions:
(imageHeight, imageWidth) = inputImage.shape[:2]
# Get image center:
xCenter = int(0.5 * imageWidth)
yCenter = int(0.5 * imageHeight)
# Get flood-fill target color
floodColor = inputImage[yCenter, xCenter]
print("Flood Color: %s" % floodColor)
# numpy array to tuple
floodColor = (int(floodColor[0]), int(floodColor[1]), int(floodColor[2]))
The first step gets the actual filling color. I suppose that the red is located more or less at the center of the image. Then, the second step involves filling all the "foreground" pixels with white. Let's seed at the top left corner:
# Flood fill at top left corner:
leftCorner = (1, 1)
whiteColor = (255, 255, 255)
cv2.floodFill(inputImage, None, leftCorner, whiteColor)
This is the result:
Note how the shapes that are partially outside of the red rectangle are all now connected by the white color. Let's fill again, but this time using the red color I extracted previously:
# Second Flood-fill
cv2.floodFill(inputImage, None, leftCorner, floodColor)
This yields the following image:
Let's create the final image by ANDing this result with the original non-zero mask:
# Create final image:
outImage = cv2.bitwise_and(inputImage, inputImage, mask=binaryMask)
This is the final result:
The question is very close to this question.
My solution is close too...
Assuming the colors are black white and red (the general case may be trickier), we may use the following stages:
Fill the black background with white color (using cv2.floodFill).
The white object on the red boundary are merged with the background.
Fill the white background with black color (using cv2.floodFill).
The white object on the red boundary are going to be black.
Copy the red color channel from the original image to "filled" image.
The red channel of a white pixel is 255, so black and white becomes red.
Code sample:
import cv2
import numpy as np
img = cv2.imread('red_white_black.jpg')
# Copy the original image to img2
img2 = img.copy()
# Fill the black background with white color
cv2.floodFill(img2, None, seedPoint=(0, 0), newVal=(255, 255, 255), loDiff=(50, 50, 50), upDiff=(50, 50, 50))
cv2.imshow('black background', img2) # Show img2 for testing
# Fill the white background with black color
cv2.floodFill(img2, None, seedPoint=(0, 0), newVal=(0, 0, 0), loDiff=(50, 50, 50), upDiff=(50, 50, 50))
cv2.imshow('white background', img2) # Show img2 for testing
# Copy the red color channel from the original image to img2
img2[:, :, 2] = img[:, :, 2]
cv2.imshow('img2', img2) # Show img2 for testing
cv2.waitKey()
cv2.destroyAllWindows()
Results:
Black background:
White background:
img2:
The black margins around the red, are because the original image is JPEG and not PNG (colors are not pure), and the red is not pure red.
We may fix it using the following code (the code in not very elegant)...
red = img[:, :, 2]
r = np.median(img[:, :, 2][red > 50])
g = np.median(img[:, :, 1][red > 50])
b = np.median(img[:, :, 0][red > 50])
mask = np.logical_and(img[:, :, 0] > 100, img2[:, :, 0] <= 100)
img3 = img2.copy()
img3[:, :, 2][mask] = r
img3[:, :, 1][mask] = g
img3[:, :, 0][mask] = b
img3[:, :, 2] = cv2.morphologyEx(img3[:, :, 2], cv2.MORPH_CLOSE, np.ones((3, 3), np.uint8))
img3[:, :, 1] = cv2.morphologyEx(img3[:, :, 1], cv2.MORPH_OPEN, np.ones((3, 3), np.uint8))
img3[:, :, 0] = cv2.morphologyEx(img3[:, :, 0], cv2.MORPH_OPEN, np.ones((3, 3), np.uint8))
cv2.imshow('img3', img3)
cv2.waitKey()
cv2.destroyAllWindows()
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()

How to draw a red shape on a black background in cv2?

I have an image with a black background. How am I supposed to draw a red shape on it? The red color should be (0,0,255) right? But it shows as black so it's not visible with a black background. The best I could do was to create a white rectangle in the following example. My question is how should I make it red? I think I'm missing something simple here. Please help.
import cv2
import numpy as np
img = np.zeros((500,500,1), np.uint16)
cv2.rectangle(img, (200, 200), (300, 300), (0, 0, 255), 5)
cv2.imshow('image',img)
cv2.waitKey(100000)
You will see the rectangle doesn't even show up:
And the best I could do was a white rectangle like this:
img = np.zeros((500,500,1), np.uint16)
cv2.rectangle(img, (200, 200), (300, 300), (2**16, 0, 0), 5)
cv2.imshow('image',img)
cv2.waitKey(100000)
You are trying to draw color (3 channels) on a black (one channel) image. You need to convert the black image to 3 channels. Here is how I do it in Python/OpenCV
import cv2
import numpy as np
# create one channel black image (grayscale)
img = np.zeros((500,500))
# convert to 3 channel black (color)
img = cv2.merge([img,img,img])
# draw on it in color
cv2.rectangle(img, (200, 200), (300, 300), (0, 0, 255), 5)
cv2.imshow('image',img)
cv2.waitKey(0)
The result is a red square outline on black background

I want to mask multiple Image horizontally

I have few Journal pages images where there are two columns I want to mask one column white without a changing the dimension.which means the output image should have same dimensions as input image even though there is one column.
I was able to mask image but the mask part is coming black which I want as white.
import cv2
import numpy as np
# Load the original image
image = cv2.imread(filename = "D:\output_final_word5\image1.jpg")
# Create the basic black image
mask = np.zeros(shape = image.shape, dtype = "uint8")
# Draw a white, filled rectangle on the mask image
cv2.rectangle(img = mask, pt1 = (0, 0), pt2 = (795, 3000), color = (255, 255,
255), thickness = -1)
# Apply the mask and display the result
maskedImg = cv2.bitwise_and(src1 = image, src2 = mask)
#cv2.namedWindow(winname = "masked image", flags = cv2.WINDOW_NORMAL)
cv2.imshow("masked image",maskedImg)
cv2.waitKey(delay = 0)
cv2.imwrite("D:\Test_Mask.jpg",maskedImg)
My final objective is to read a folder where are several Journal pages In which need to be saved by masking first one column and then another column without affecting the dimension of Input image and mask part should be white.
Below are Input Image Attached...
And Output Should be like this....
You don't need mask to draw rectangle. You can draw it directly on image.
You can also use image.copy() to create second image with other column
BTW: if 795 is in the middle of width then you can use image.shape to get its (height,width) and use width//2 instead of 795 so it will work with images which have different widths. But if 795 is not ideally in the middle then use half_width = 795
import cv2
image_1 = cv2.imread('image.jpg')
image_2 = image_1.copy()
height, width, depth = image_1.shape # it gives `height,width`, not `width,height`
half_width = width//2
#half_width = 795
cv2.rectangle(img=image_1, pt1=(0, 0), pt2=(half_width, height), color=(255, 255, 255), thickness=-1)
cv2.rectangle(img=image_2, pt1=(half_width, 0), pt2=(width, height), color=(255, 255, 255), thickness=-1)
cv2.imwrite("image_1.jpg", image_1)
cv2.imwrite("image_2.jpg", image_2)
cv2.imshow("image 1", image_1)
cv2.imshow("image 2", image_2)
cv2.waitKey(0)
cv2.destroyAllWindows()

Merging background with transparent image in PIL

I have a png image as background and I want to add a transparent mesh to this background but this doesn't work as expected. The background image is converted to transparent on places where I apply transparent mesh.
I am doing:
from PIL import Image, ImageDraw
map_background = Image.open(MAP_BACKGROUND_FILE).convert('RGBA')
map_mesh = Image.new('RGBA', (width, height), (0, 0, 0, 0))
draw = ImageDraw.Draw(map_mesh)
# Create mesh using: draw.line([...], fill=(255, 255, 255, 50), width=1)
...
map_background.paste(map_mesh, (0, 0), map_mesh)
But the result is:
You can see a chessboard pattern if you look carefully (used in graphics programs as no background). Transparent lines makes the background layer transparent too in places where both layers met. But I only want the transparent line to be added on top of the background.
I can solve it with:
map_background.paste((255,255,255), (0, 0), map_mesh)
but as I use different colors for different lines, I would have to make for every color this process. If I had 100 colors, then I need 100 layers what is not very good solution.
What you are trying to do is to composite the grid onto the background, and for that you need to use Image.blend or Image.composite. Here's an example using the latter to composite red lines with random alpha values onto a white background:
import Image, ImageDraw, random
background = Image.new('RGB', (100, 100), (255, 255, 255))
foreground = Image.new('RGB', (100, 100), (255, 0, 0))
mask = Image.new('L', (100, 100), 0)
draw = ImageDraw.Draw(mask)
for i in range(5, 100, 10):
draw.line((i, 0, i, 100), fill=random.randrange(256))
draw.line((0, i, 100, i), fill=random.randrange(256))
result = Image.composite(background, foreground, mask)
From left to right:
[background] [mask]
[foreground]
[result]
(If you are happy to write the result back to the background image, then you can use one of the masked versions of Image.paste, as pointed out by Paulo Scardine in a deleted answer.)
I had trouble getting the above examples to work well. Instead, this worked for me:
import numpy as np
import Image
import ImageDraw
def add_craters(image, craterization=20.0, width=256, height=256):
foreground = Image.new('RGBA', (width, height), (0, 0, 0, 0))
draw = ImageDraw.Draw(foreground)
for c in range(0, craterization):
x = np.random.randint(10, width-10)
y = np.random.randint(10, height-10)
radius = np.random.randint(2, 10)
dark_color = (0, 0, 0, 128)
draw.ellipse((x-radius, y-radius, x+radius, y+radius), fill=dark_color)
image_new = Image.composite(foreground, image, foreground)
return image_new

Categories