Saving RGBA image with PIL.Image.frombuffer() - python

I need to save a transparent image made from a numpy array. I can save the image with:
img = Image.fromarray(data, 'RGB')
But I need it to be transparent so I tried to save it with :
img = Image.fromarray(data, 'RGBA')
Then I get this error :
File "/home/pi/Documents/Projet/GetPos.py", line 51, in click
img = Image.fromarray(data, 'RGBA')
File "/usr/lib/python2.7/dist-packages/PIL/Image.py", line 2217, in
fromarray
return frombuffer(mode, size, obj, "raw", rawmode, 0, 1)
File "/usr/lib/python2.7/dist-packages/PIL/Image.py", line 2162, in
frombuffer
core.map_buffer(data, size, decoder_name, None, 0, args)
ValueError: buffer is not large enough
I made some research but everythings looks very complicated for the simple thing I'm trying to do...
Does anyone can help me on this one ?
Here's my complete code ( I'm pretty new to python :) ):
mouse = pymouse.PyMouse()
posX, posY = mouse.position()
print(mouse.position())
w, h = 1920, 1080
data = np.zeros((h, w, 3), dtype=np.uint8)
for x in range(posX-20, posX+20):
if x > 1679:
data[posY, w-1] = [255, 0, 0]
else:
data[posY, x] = [255, 0, 0]
for y in range(posY-20, posY+20):
if y > 1049:
data[h-1, posX] = [255, 0, 0]
else:
data[y, posX] = [255, 0, 0]
img = Image.fromarray(data, 'RGBA')
##img = Image.frombuffer('RGBA', [1080, 1920], data, "raw", 'RGBA', 0, 1)
img.save('my.png')

In order to save a transparant image, you need to have a fourth value per pixel called the alpha channel, which determines the opacity of your pixel. (RGBA stands for red, green, blue and alpha.) So the only thing that has to be changed in your code is essentialy providing that 4th alpha value using tuples of 4 values instead of 3 for a pixel. Setting the 4th value to 255 means it's completely visible, 0 would make it a 100% transparant. In the following example I simply set every pixel that you were drawing red completely visible, the others will be transparent:
mouse = pymouse.PyMouse()
posX, posY = mouse.position()
w, h = 1920, 1080
data = np.zeros((h, w, 4), dtype=np.uint8)
for x in range(posX-20, posX+20):
if x > 1679:
data[posY, w-1] = [255, 0, 0, 255]
else:
data[posY, x] = [255, 0, 0, 255]
for y in range(posY-20, posY+20):
if y > 1049:
data[h-1, posX] = [255, 0, 0, 255]
else:
data[y, posX] = [255, 0, 0, 255]
img = Image.fromarray(data, 'RGBA')
img.save('my.png')

Related

Setting the alpha channel of an image, based on the pixel values in another image

I have two images. In one image all non-alpha channel pixels are equal to 0, and I'd like the alpha channel values to equal 255 where in the other image which is of equal size, the pixels are anything but 0. In this attempt, I'm attempting to create a 4 channel np array based off of the original image, and then use np.argwhere to find where the pixel valeus are non-zero, and then in the new np array, set the alpha channel value based on that.
For example, for each pixel in my input image with values [255, 255, 255], I'd like the corresponding pixel in my new image to be [0, 0, 0, 255]. For each pixel in my input image with values [0, 0, 0], I'd like the corresponding pixel in my new image to be [0, 0, 0, 0].
mask_file = cv.imread(r'PlateMask_0001.png', cv.IMREAD_UNCHANGED)
scale_factor = 0.125
w = int(mask_file.shape[1] * scale_factor)
h = int(mask_file.shape[0] * scale_factor)
scaled = cv.resize(mask_file, (w, h))
coords = np.argwhere(scaled > 0)
new_object = np.zeros((120, 160, 4))
new_object[coords, :] = 255
cv.imshow('Mask', mask)
cv.imshow('Scaled', new_object)
cv.waitKey(0)
cv.destroyAllWindows()
This is my first question on Stack so please feel free to suggest improvements on question formatting, etc. Thank you.
Consider img1 to be your original image and img2 to be the image where alpha channel needs to be modified.
In the following, the alpha channel of img2 contains value 255 in the coordinate where img1 has (255, 255, 255):
img2[:,:,3][img1 == (255, 255, 255)] = 255
Likewise for value 0:
img2[:,:,3][img1 == (0, 0, 0)] = 0

How to change all the black pixels to white (OpenCV)?

I am new to OpenCV and I do not understand how to traverse and change all the pixels of black with colour code exact RGB(0,0,0) to white colour RGB(255,255,255).
Is there any function or way to check all the pixel and if RGB(0,0,0) the make it to RGB(255,255,255).
Assuming that your image is represented as a numpy array of shape (height, width, channels) (what cv2.imread returns), you can do:
height, width, _ = img.shape
for i in range(height):
for j in range(width):
# img[i, j] is the RGB pixel at position (i, j)
# check if it's [0, 0, 0] and replace with [255, 255, 255] if so
if img[i, j].sum() == 0:
img[i, j] = [255, 255, 255]
A faster, mask-based approach looks like this:
# get (i, j) positions of all RGB pixels that are black (i.e. [0, 0, 0])
black_pixels = np.where(
(img[:, :, 0] == 0) &
(img[:, :, 1] == 0) &
(img[:, :, 2] == 0)
)
# set those pixels to white
img[black_pixels] = [255, 255, 255]
Subtract 255 from each Pixel and get the positive values only
For grayscale and black and white images
sub_array = 255*np.ones(28, dtype = int)
img_Invert = np.abs(np.subtract(img,sub_array))
cv.rectangle(img,(0,0),(img.shape[1],img.shape[0],(255,255,255),thickness=-1)
cv.imshow('img',img)

Map scalars to arrays based on values : Image Processing 2D to 3D - NumPy / Python

Given a Numpy matrix of shape (height, width), I am looking for the fastest way to create another Numpy matrix of shape (height, width, 4) where 4 represents RGBA values. I would like to do this value-based; so, for all values of 0 in the first matrix I would like to have a value of [255, 255, 255, 0] in the second matrix at the same location.
I would like to do this with NumPy without needing to slowly iterate like below:
for i in range(0, height):
for j in range(0, width):
if image[i][j] = 0:
new_image[i][j] = [255, 255, 255, 0]
elif image[i][j] = 1:
new_image[i][j] = [0, 255, 0, 0.5]
As you can see, I am creating a matrix where the value 0 becomes transparent white, and 1 becomes green with an alpha of 0.5; are there faster NumPy solutions?
I am guessing numpy.where should greatly help speed up the process, but I haven't yet figured out the proper implementation for multiple and many value translations.
For a cleaner solutiuon, especially when working with multiple labels, we could make use of np.searchsorted to trace back the values for the mapping, like so -
# Edit to include more labels and values here
label_ar = np.array([0,1]) # sorted label array
val_ar = np.array([[255, 255, 255, 0],[0, 255, 0, 0.5]])
# Get output array
out = val_ar[np.searchsorted(label_ar, image)]
Note that this assumes that all unique labels from image are in label_ar.
So, now let's say we have two more labels 2 and 3 in image, something like this -
for i in range(0, height):
for j in range(0, width):
if image[i,j] == 0:
new_image[i,j] = [255, 255, 255, 0]
elif image[i,j] == 1:
new_image[i,j] = [0, 255, 0, 0.5]
elif image[i,j] == 2:
new_image[i,j] = [0, 255, 255, 0.5]
elif image[i,j] == 3:
new_image[i,j] = [255, 255, 255, 0.5]
We will edit the labels and values accordingly and use the same searchsorted solution -
label_ar = np.array([0,1,2,3]) # sorted label array
val_ar = np.array([
[255, 255, 255, 0],
[0, 255, 0, 0.5],
[0, 255, 255, 0.5],
[255, 255, 255, 0.5]])
You are right np.where is how you solve this problem. Where is a vectorized function so it should be much faster than your solution.
I'm making an assumption here that there is It doesn't have an elif that I'm aware of, but you can get around that by nesting where statements.
new_image = np.where(
image == 0,
[255, 255, 255, 0],
np.where(
image == 1,
[0, 255, 0, 0.5],
np.nan
)
)

CV2 SeamlessClone (-215:Assertion Failed)

I have a neural network that outputs segments of a face - I'm working on a function that combines these segments together and then clones them into a real face.
Example images are here: https://imgur.com/a/HnpqhEE, I do not have the reputation to include them inline.
So far my function takes the makeup face and lips segment and combines them with addition. The left and right eyes are then cloned in with seamlessClone (the right eye is flipped first).
Then the combined makeup segments are cloned into the normal face.
Very occasionally, my combination function fails and returns a (-215:Assertion failed) 0 <= roi.x && 0 <= roi.width && roi.x + roi.width <= m.cols && 0 <= roi.y && 0 <= roi.height && roi.y + roi.height <= m.rows in function 'cv::Mat::Mat' error.
My function is below, I have only seen it error at the last seamlessClone
def combineFace(images, radius = 70):
# Given image segments and eye radii, combine face.
realFace = tensor2im(images['realNormal'])
makeupFace = tensor2im(images['fakeMakeupFace'])
makeupLeft = tensor2im(images['fakeMakeupLeft'])
makeupRight = tensor2im(images['fakeMakeupRight'])
makeupLips = tensor2im(images['fakeMakeupLips'])
makeupRight = cv2.flip(makeupRight, 1)
# I use cv2 and dlib to get face landmarks and interesting points.
normalLandmarks = faceLandmarks(realFace)
facePoints = getFacePoints(normalLandmarks)
# PP means pupil points
outerPoints, leftPP, rightPP, lipPoints, eyeMids = facePoints
# eyeMid is (x, y) of center of eye obtained from landmark points
leftEye = eyeMids[0]
rightEye = eyeMids[1]
faceMask = np.zeros(realFace.shape, realFace.dtype)
cv2.fillPoly(faceMask, [outerPoints], [255, 255, 255])
cv2.fillPoly(faceMask, [lipPoints], [0, 0, 0])
cv2.fillPoly(faceMask, [leftPP], [0, 0, 0])
cv2.fillPoly(faceMask, [rightPP], [0, 0, 0])
# Occasionally, the eye segments overlap eachother so I cut the right eye from the left and vice
# versa
leftMask = np.zeros(realFace.shape, realFace.dtype)
cv2.circle(leftMask, leftEye, radius, [255, 255, 255], -1)
cv2.circle(leftMask, rightEye, radius, [0, 0, 0], -1)
# Errors if i do not use UMat
cv2.circle(cv2.UMat(makeupLeft), rightEye, radius, [0, 0, 0], -1)
rightMask = np.zeros(realFace.shape, realFace.dtype)
cv2.circle(rightMask, rightEye, radius, [255, 255, 255], -1)
cv2.circle(rightMask, leftEye, radius, [0, 0, 0], -1)
cv2.circle(cv2.UMat(makeupRight), leftEye, radius, [0, 0, 0], -1)
# Combine face output and lips
baseCombine = makeupFace + makeupLips
# Left Eye
output = cv2.seamlessClone(makeupLeft, baseCombine, leftMask, leftEye, cv2.MIXED_CLONE)
output = cv2.seamlessClone(makeupRight, output, rightMask, rightEye, cv2.MIXED_CLONE)
# Get center of face
faceRect = cv2.boundingRect(outerPoints)
x, y, w, h = faceRect
output = cv2.bitwise_and(output, faceMask)
center = ( x + w // 2, y + h // 2)
# I have only seen the function error at this point
combinedFace = cv2.seamlessClone(output, realFace, faceMask, center, cv2.MIXED_CLONE)
return combinedFace
Any idea why this is occasionally erroring?
All input images have the form (256, 256, 3)
This version of the function works much better. There was something wrong with my face center calculation which was causing the error
def combineFace(images, radius = 70):
# Given image segments and eye radii, combine face.
realFace = tensor2im(images['realNormal'])
makeupFace = tensor2im(images['fakeMakeupFace'])
makeupLeft = tensor2im(images['fakeMakeupLeft'])
makeupRight = tensor2im(images['fakeMakeupRight'])
makeupLips = tensor2im(images['fakeMakeupLips'])
# Right eye is flipped before input into the network.
makeupRight = cv2.flip(makeupRight, 1)
normalLandmarks = faceLandmarks(realFace)
facePoints = getFacePoints(normalLandmarks)
outerPoints, leftPP, rightPP, lipPoints, eyeMids = facePoints
leftEye = eyeMids[0]
rightEye = eyeMids[1]
leftMask = np.zeros(makeupLeft.shape, makeupLeft.dtype)
cv2.circle(leftMask, leftEye, radius, [255, 255, 255], -1)
cv2.circle(leftMask, rightEye, radius, [0, 0, 0], -1)
# Errors if i do not use cv2.UMat
cv2.circle(cv2.UMat(makeupLeft), rightEye, radius, [0, 0, 0], -1)
rightMask = np.zeros(makeupRight.shape, makeupRight.dtype)
cv2.circle(rightMask, rightEye, radius, [255, 255, 255], -1)
cv2.circle(rightMask, leftEye, radius, [0, 0, 0], -1)
cv2.circle(cv2.UMat(makeupRight), leftEye, radius, [0, 0, 0], -1)
# Base output is combination of face without lips and pupils + lips
baseCombine = makeupFace + makeupLips
# Areas around eyes are changes
output = cv2.seamlessClone(makeupLeft, baseCombine, leftMask, leftEye, cv2.MIXED_CLONE)
output = cv2.seamlessClone(makeupRight, output, rightMask, rightEye, cv2.MIXED_CLONE)
# Find center of face
faceRect = cv2.boundingRect(outerPoints)
x, y, w, h = faceRect
if x < 0:
x = 0
if y < 0:
y = 0
faceCenter = ( x + w // 2, y + h // 2)
croppedOutput = output[y:y+h, x:x+w]
faceMask = np.zeros(realFace.shape, realFace.dtype)
cv2.fillPoly(faceMask, [outerPoints], [255, 255, 255])
cv2.fillPoly(faceMask, [lipPoints], [0, 0, 0])
cv2.fillPoly(faceMask, [leftPP], [0, 0, 0])
cv2.fillPoly(faceMask, [rightPP], [0, 0, 0])
croppedMask = faceMask[y:y+h, x:x+w]
if len(croppedOutput) == 0:
print("OUTPUT 0")
print("FACE RECT: ", faceRect)
sourceW, sourceH, sCH = realFace.shape
width, height, ch = croppedOutput.shape
faceWidth = width/2
faceHeight = height/2
xdiff = 0
ydiff = 0
cx = faceCenter[0]
cy = faceCenter[1]
if cx - faceWidth < 0:
# Face overflows left
xdiff = abs(cx - faceWidth)
if cx + faceWidth > sourceW:
xdiff = (cx + faceWidth - sourceW) * -1
if cy + faceHeight > sourceH:
ydiff = (cy + faceHeight - sourceH) * -1
if cy - faceHeight < 0:
ydiff = abs(cy - faceHeight)
centerx = int(cx + xdiff)
centery = int(cy + ydiff)
center = (centerx, centery)
# We move center, also move mask?
combinedFace = cv2.seamlessClone(croppedOutput, realFace, croppedMask, center, cv2.MIXED_CLONE)
return combinedFace

With the Python Imaging Library (PIL), how does one compose an image with an alpha channel over another image?

I have two images, both with alpha channels. I want to put one image over the other, resulting in a new image with an alpha channel, just as would occur if they were rendered in layers. I would like to do this with the Python Imaging Library, but recommendations in other systems would be fantastic, even the raw math would be a boon; I could use NumPy.
This appears to do the trick:
from PIL import Image
bottom = Image.open("a.png")
top = Image.open("b.png")
r, g, b, a = top.split()
top = Image.merge("RGB", (r, g, b))
mask = Image.merge("L", (a,))
bottom.paste(top, (0, 0), mask)
bottom.save("over.png")
Pillow 2.0 now contains an alpha_composite function that does this.
img3 = Image.alpha_composite(img1, img2)
I couldn't find an alpha composite function in PIL, so here is my attempt at implementing it with numpy:
import numpy as np
from PIL import Image
def alpha_composite(src, dst):
'''
Return the alpha composite of src and dst.
Parameters:
src -- PIL RGBA Image object
dst -- PIL RGBA Image object
The algorithm comes from http://en.wikipedia.org/wiki/Alpha_compositing
'''
# http://stackoverflow.com/a/3375291/190597
# http://stackoverflow.com/a/9166671/190597
src = np.asarray(src)
dst = np.asarray(dst)
out = np.empty(src.shape, dtype = 'float')
alpha = np.index_exp[:, :, 3:]
rgb = np.index_exp[:, :, :3]
src_a = src[alpha]/255.0
dst_a = dst[alpha]/255.0
out[alpha] = src_a+dst_a*(1-src_a)
old_setting = np.seterr(invalid = 'ignore')
out[rgb] = (src[rgb]*src_a + dst[rgb]*dst_a*(1-src_a))/out[alpha]
np.seterr(**old_setting)
out[alpha] *= 255
np.clip(out,0,255)
# astype('uint8') maps np.nan (and np.inf) to 0
out = out.astype('uint8')
out = Image.fromarray(out, 'RGBA')
return out
For example given these two images,
img1 = Image.new('RGBA', size = (100, 100), color = (255, 0, 0, 255))
draw = ImageDraw.Draw(img1)
draw.rectangle((33, 0, 66, 100), fill = (255, 0, 0, 128))
draw.rectangle((67, 0, 100, 100), fill = (255, 0, 0, 0))
img1.save('/tmp/img1.png')
img2 = Image.new('RGBA', size = (100, 100), color = (0, 255, 0, 255))
draw = ImageDraw.Draw(img2)
draw.rectangle((0, 33, 100, 66), fill = (0, 255, 0, 128))
draw.rectangle((0, 67, 100, 100), fill = (0, 255, 0, 0))
img2.save('/tmp/img2.png')
alpha_composite produces:
img3 = alpha_composite(img1, img2)
img3.save('/tmp/img3.png')

Categories

Resources