How to hide overlapping pixels using Pillow? - python

I have two images of similar dimensions as such:
Since the outer circle should have close to overlapping pixels, I would like to have a resultant image that has the inner circle from image A and the square from image B. I thought inverting image A and then calling PIL.Image.composite(imageA, imageB, mask) would do something but it just gave me a combination of imageA and imageB.
Is there a way to do what I want using Pillow or perhaps using numpy somehow to make white the pixels that are similar between both images?

I think you are looking for an XOR between the two images.
I'll work up to it slowly in case you don't do many logical expression evaluations, so starting with OR, you will get white pixels out as a result where the either image A OR image B has white pixels. Then with an AND, you will get white pixels out where both image A AND image B are white. Finally, with an XOR, you will get white pixels out where either image A or image B but exclusively one or the other but not both have white pixels.
In code, that looks like this:
#!/usr/local/bin/python3
from PIL import Image, ImageChops
# Load up the two images, discarding any alpha channel
im1 = Image.open('im1.png').convert('1')
im2 = Image.open('im2.png').convert('1')
# XOR the images together
result = ImageChops.logical_xor(im1,im2)
result = ImageChops.invert(result)
# Save the result
result.save('result.png')

Related

How can I overlay one image over another so that dark background is transparent?

I have 2 images, test1.jpg and test2.jpg that are RGB images. They have been converted from a 2D numpy array so they are monochrome images. They have the same shape. When I use the paste function, I only see one of the images instead of both.
Here are the test1 and test2 jpgs:
.
This is what I get after doing test1.paste(test2) and test1.save('final.jpg'):
Why is it only showing test2?
Here is my code:
im1 = Image.open('test1.jpg')
im2 = Image.open('test2.jpg')
im1.paste(im2)
im1.save('final.jpg')
You simply need to choose the lighter of your two images at each point with PIL Channel Operations:
from PIL import Image, ImageChops
im1 = Image.open('test1.jpeg')
im2 = Image.open('test2.jpeg')
# Choose lighter of the two images at each pixel location
combined = ImageChops.lighter(im1,im2)
Note that you could use paste() as you originally intended, but that it will paste all the black as well as the white pixels from image2 over image1. In order to avoid that, you would need to make a mask and only paste where image2 is non-zero. That might look like this:
im1 = Image.open('test1.jpeg')
im2 = Image.open('test2.jpeg')
# Make greyscale mask from image2
mask = im2.convert('L')
mask = mask.point(lambda i: 255 if i>0 else 0)
# Paste image2 into image1 only where image2 has non-black content
im1.paste(im2, mask=mask)
I just think the ImageChops.lighter() method is simpler.
Note that these two methods will give subtly different results. For example, if a pixel is 192 in image1 and 67 in image2, the ImageChops.lighter() method will result in 192, whereas the paste() method will see there is something in image2, and therefore give you the 67. Your choice!
paste is the wrong tool for this job, because it completely replaces the original image with the image you're pasting. It appears you wanted something closer to the blend function which produces a mix of both images. Unfortunately this has the side effect of darkening the image, because white blended with black becomes a middle gray; you'll probably want to double the values to compensate.
final = Image.blend(test1, test2, 0.5)
final = Image.eval(final, lambda x: 2*x))
One solution would be to use openCV instead of PIL. OpenCV's imagery is identical to a 2D numpy array, which means you can instead just use basic matrix addition to produce a combined image given "black" pixels are just 0.
import cv2 as cv
im1 = cv.imread('test1.jpg')
im2 = cv.imread('test2.jpg')
im3 = im1 + im2
cv.imwrite('test3.jpg', im3)
The black pixels remain black, as you're adding 0 to 0, while the filled pixels have the filled values of the two images combined (in this case, 0 + a maximum of 255 given no overlap between the two images)
The resulting image:
In the case of overlapping non-zero pixels, you may want to apply a 255 ceiling to the arrays, or instead normalize the combined image by the new maximum if that is preferred for your use case.

Change background color for Thresholderd image

I have been trying to write a code to extract cracks from an image using thresholding. However, I wanted to keep the background black. What would be a good solution to keep the outer boundary visible and the background black. Attached below is the original image along with the threshold image and the code used to extract this image.
import cv2
#Read Image
img = cv2.imread('Original.png')
# Convert into gray scale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Image processing ( smoothing )
# Averaging
blur = cv2.blur(gray,(3,3))
ret,th1 = cv2.threshold(blur,145,255,cv2.THRESH_BINARY)
inverted = np.invert(th1)
plt.figure(figsize = (20,20))
plt.subplot(121),plt.imshow(img)
plt.title('Original'),plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(inverted,cmap='gray')
plt.title('Threshold'),plt.xticks([]), plt.yticks([])
Method 1
Assuming the circle in your images stays in one spot throughout your image set you can manually create a black 'mask' image with a white hole in the middle, then overlay it on the final inverted image.
You can easily make the mask image using your favorite image editor's magic wand tool.
I made this1 by also expanding the circle inwards by one pixel to take into account some of the pixels the magic wand tool couldn't catch.
You would then use the mask image like this:
mask = cv2.imread('/path/to/mask.png')
masked = cv2.bitwise_and(inverted, inverted, mask=mask)
Method 2
If the circle does NOT stay is the same spot throughout your entire image set you can try to make the mask from all the fully black pixels in your original image. This assumes that the 'sample' itself (the thing with the cracks) does not contain fully black pixels. Although this will result in the text on the bottom left to be left white.
# make all the non black pixels white
_,mask = cv2.threshold(gray,1,255,cv2.THRESH_BINARY)
1 The original is not the same size as your inverted image and thus the mask I made won't actually fit, you're gonna have to make it yourself.

Edit image pixel by pixel - Python

I have two images, one overlay and one background.
I want to create a new image, by editing overlay image and manipulating it to show only the pixels which have blue colour in the background image.
I dont want to add the background, it is only for selecting the pixels.
Rest part should be transparent.
Any hints or ideas please? PS: I edited result image with paint so its not perfect.
Image 1 is background image.
Image 2 is overlay image.
Image 3 is the check I want to perform. (to find out which pixels have blue in background and making remaining pixels transparent)
Image 4 is the result image after editing.
I renamed your images according to my way of thinking, so I took this as image.png:
and this as mask.png:
Then I did what I think you want as follows. I wrote it quite verbosely so you can see all the steps along the way:
#!/usr/local/bin/python3
from PIL import Image
import numpy as np
# Open input images
image = Image.open("image.png")
mask = Image.open("mask.png")
# Get dimensions
h,w=image.size
# Resize mask to match image, taking care not to introduce new colours (Image.NEAREST)
mask = mask.resize((h,w), Image.NEAREST)
mask.save('mask_resized.png')
# Convert both images to numpy equivalents
npimage = np.array(image)
npmask = np.array(mask)
# Make image transparent where mask is not blue
# Blue pixels in mask seem to show up as RGB(163 204 255)
npimage[:,:,3] = np.where((npmask[:,:,0]<170) & (npmask[:,:,1]<210) & (npmask[:,:,2]>250),255,0).astype(np.uint8)
# Identify grey pixels in image, i.e. R=G=B, and make transparent also
RequalsG=np.where(npimage[:,:,0]==npimage[:,:,1],1,0)
RequalsB=np.where(npimage[:,:,0]==npimage[:,:,2],1,0)
grey=(RequalsG*RequalsB).astype(np.uint8)
npimage[:,:,3] *= 1-grey
# Convert numpy image to PIL image and save
PILrgba=Image.fromarray(npimage)
PILrgba.save("result.png")
And this is the result:
Notes:
a) Your image already has an (unused) alpha channel present.
b) Any lines starting:
npimage[:,:,3] = ...
are just modifying the 4th channel, i.e. the alpha/transparency channel of the image

How can I change the background of only white pixels?

I have two grayscale images of the same size, one of them is this one:
I'm trying to add a background to this image, which is to just change the white pixels to the respective pixels in the other picture. The best result I've managed to do is just a bitwise and of all the pixels of both pictures but the resultant picture is distorted inside James Bond. I also tried a weighted add between the two pictures but when I increase the weight of the James Bond image, it's white pixels are visible in the resultant image.
To combine with a second image, ensure that both images have the same dimensions (which yours do). They can then be combined
import cv2
img_jb = cv2.imread('james_bond.png')
img_007 = cv2.imread('007_logo.png')
height, width, channels = img_jb.shape
img_007_resized = cv2.resize(img_007, (width, height), interpolation=cv2.INTER_CUBIC)
threshold = img_jb > 240
img_jb[threshold] = img_007_resized[threshold]
cv2.imwrite('james_bond_logo.png', img_jb)
Giving you:
numpy allows you to work on the indexes of an array that match a given criteria. This has the effect of copying pixels from the background image into the foreground image where the foreground image has a value above 240.

Pillow handles PNG files incorrectly

I can successfully convert a rectangular image into a png with transparent rounded corners like this:
However, when I take this transparent cornered image and I want to use it in another image generated with Pillow, I end up with this:
The transparent corners become black. I've been playing around with this for a while but I can't find any way in which the transparent parts of an image don't turn black once I place them on another image with Pillow.
Here is the code I use:
mask = Image.open('Test mask.png').convert('L')
im = Image.open('boat.jpg')
im.resize(mask.size)
output = ImageOps.fit(im, mask.size, centering=(0.5, 0.5))
output.putalpha(mask)
output.save('output.png')
im = Image.open('output.png')
image_bg = Image.new('RGBA', (1292,440), (255,255,255,100))
image_fg = im.resize((710, 400), Image.ANTIALIAS)
image_bg.paste(image_fg, (20, 20))
image_bg.save('output2.jpg')
Is there a solution for this? Thanks.
Per some suggestions I exported the 2nd image as a PNG, but then I ended up with an image with holes in it:
Obviously I want the second image to have a consistent white background without holes.
Here is what I actually want to end up with. The orange is only placed there to highlight the image itself. It's a rectangular image with white background, with a picture placed into it with rounded corners.
If you paste an image with transparent pixels onto another image, the transparent pixels are just copied as well. It looks like you only want to paste the non-transparent pixels. In that case, you need a mask for the paste function.
image_bg.paste(image_fg, (20, 20), mask=image_fg)
Note the third argument here. From the documentation:
If a mask is given, this method updates only the regions indicated by
the mask. You can use either "1", "L" or "RGBA" images (in the latter
case, the alpha band is used as mask). Where the mask is 255, the
given image is copied as is. Where the mask is 0, the current value
is preserved. Intermediate values will mix the two images together,
including their alpha channels if they have them.
What we did here is provide an RGBA image as mask, and use the alpha channel as mask.

Categories

Resources