I have a binary image, dimensions 64x63 where every pixel is a 1 or 0.
import struct
from PIL import Image
import numpy
...
i1 = Image.frombytes('1', (64, 63), r[3], 'raw')
How can I invert this image?
Edit
I attempted the suggested solution:
from PIL import Image
import PIL.ImageOps
i1 = PIL.ImageOps.invert(i1)
However, this resulted in the error:
raise IOError("not supported for this image mode")
IOError: not supported for this image mode
And this, I believe, is due to the fact that the image is neither RGB nor L(greyscale). Instead, it is a binary image file where each pixel is only either 0 or 1.
Another option is to convert the image to a supported inversion format, before inverting it, ie:
import PIL.ImageOps
inverted_image = PIL.ImageOps.invert( image.convert('RGB') )
If you're willing to convert i1 to a numpy array you can just do
i1 = 1 - numpy.asarray(i1)
I came across the exact same problem. There is a solution without going to the trouble of using numpy.
you can use ImageMath:
from PIL import ImageMath
i2 = ImageMath.eval('255-(a)',a=i1)
This way you can invert a binary (mode 1 in PIL) image.
For more an ImageMath refer to here.
The easiest solution is to do what ImageOps.invert itself does:
i1 = i1.point(lambda x: 255-x)
ImageOps.invert checks the mode and refuses to work if it's anything other than 'L' or 'RGB', but this method actually works with many other modes, with a few caveats:
If there's an alpha channel, it will invert it too.
If it's a palette image, it will invert the palette indexes, not the palette entries.
Here's a more complete implementation of invert that tries to do the right thing with all common modes:
def invert(im):
if im.mode in {'P', 'PA'}:
pmode, pal = im.palette.getdata()
pal = Image.frombytes(pmode, (len(pal) // len(pmode), 1), pal)
im = im.copy()
im.palette.palette = invert(pal).tobytes()
return im
elif im.mode in {'LA', 'La', 'RGBA', 'RGBa', 'RGBX'}:
return im.point([*range(255, -1, -1)] * (len(im.mode) - 1) + [*range(256)])
else:
# This may fail
return im.point(lambda x: 255-x)
Related
How can I compare two images? I found Python's PIL library, but I do not really understand how it works.
To check does jpg files are exactly the same use pillow library:
from PIL import Image
from PIL import ImageChops
image_one = Image.open(path_one)
image_two = Image.open(path_two)
diff = ImageChops.difference(image_one, image_two)
if diff.getbbox():
print("images are different")
else:
print("images are the same")
based on Victor Ilyenko's answer, I needed to convert the images to RGB as PIL fails silently when the .png you're trying to compare contains a alpha channel.
image_one = Image.open(path_one).convert('RGB')
image_two = Image.open(path_two).convert('RGB')
It appears that Viktor's implementation can fail when the images are different sizes.
This version also compares alpha values. Visually identical pixels are (I think) treated as identical, such as (0, 0, 0, 0) and (0, 255, 0, 0).
from PIL import ImageChops
def are_images_equal(img1, img2):
equal_size = img1.height == img2.height and img1.width == img2.width
if img1.mode == img2.mode == "RGBA":
img1_alphas = [pixel[3] for pixel in img1.getdata()]
img2_alphas = [pixel[3] for pixel in img2.getdata()]
equal_alphas = img1_alphas == img2_alphas
else:
equal_alphas = True
equal_content = not ImageChops.difference(
img1.convert("RGB"), img2.convert("RGB")
).getbbox()
return equal_size and equal_alphas and equal_content
Another implementation of Viktor's answer using Pillow:
from PIL import Image
im1 = Image.open('image1.jpg')
im2 = Image.open('image2.jpg')
if list(im1.getdata()) == list(im2.getdata()):
print("Identical")
else:
print ("Different")
Note:
This might not work correctly if the images have an alpha channel, or are using different modes.
That did work for me, you just have to set the quantity of different pixels you accept, in my case 100, as the image diference was a complete black image but still had 25 diferent pixels. I tested other completelly diffferent images and they had diferrent pixels by thousands:
from PIL import ImageChops
if len(set(ImageChops.difference(img1, img2).getdata())) > 100:
print("Both images are diffent!")
You can use ImageChops.difference in combination with getbbox:
from PIL import Image
from PIL import ImageChops
def are_equal(image_a: Image, image_b: Image) -> bool:
diff = ImageChops.difference(image_a, image_b)
channels = diff.split()
for channel in channels:
if channel.getbbox() is not None:
return False
return True
The reason why we need to split the channels is because of the (possible presence of an) alpha channel. If you have two images that have exactly the same alpha channel (e.g. by both being fully opaque), then ImageChops.difference will produce an image whose alpha channel is zero all over. This means that for the pixel values of the image, we ignore the other channels because we know they final pixel is just black. Thus, the bbox of the difference of two fully opaque images is None, which is not what we want.
Pillow's basic Image.resize function doesn't appear to have any options for SRGB-aware filtering. Is there a way to do SRGB-aware resizing in Pillow?
I could do it manually by converting the image to float and applying the SRGB transforms myself...but I'm hoping there's a built-in way.
I ended up implementing sRGB-aware resize myself using the following routine. It takes an 8-bit RGB image and a target size and resampling filter.
from PIL import Image
import numpy as np
def SRGBResize(im, size, filter):
# Convert to numpy array of float
arr = np.array(im, dtype=np.float32) / 255.0
# Convert sRGB -> linear
arr = np.where(arr <= 0.04045, arr/12.92, ((arr+0.055)/1.055)**2.4)
# Resize using PIL
arrOut = np.zeros((size[1], size[0], arr.shape[2]))
for i in range(arr.shape[2]):
chan = Image.fromarray(arr[:,:,i])
chan = chan.resize(size, filter)
arrOut[:,:,i] = np.array(chan).clip(0.0, 1.0)
# Convert linear -> sRGB
arrOut = np.where(arrOut <= 0.0031308, 12.92*arrOut, 1.055*arrOut**(1.0/2.4) - 0.055)
# Convert to 8-bit
arrOut = np.uint8(np.rint(arrOut * 255.0))
# Convert back to PIL
return Image.fromarray(arrOut)
After a lot of reading and trial and error I have stumbled upon a good solution. It assumes an sRGB image, converts it to linear colour space to do the resizing, then converts back to sRGB.
There is a slight downside in that a colour depth of 8 bits per pixel is used even when the image is in it's linear form. This results in a loss of variance in darker regions. Reading from this issue post it seems there is no way to convert to a higher depth using Pillow unfortunately.
from PIL import Image
from PIL.ImageCms import profileToProfile
SRGB_PROFILE = 'sRGB.icc'
LINEARIZED_PROFILE = 'linearized-sRGB.icc'
im = Image.open(IN_PATH)
im = profileToProfile(im, SRGB_PROFILE, LINEARIZED_PROFILE)
im = im.resize((WIDTH, HEIGHT), Image.ANTIALIAS)
im = profileToProfile(im, LINEARIZED_PROFILE, SRGB_PROFILE)
im.save(OUT_PATH)
You'll need a linearised ICC colour profile as Pillow/lcms can't do it without. You can get one from this issue post and the author mentions in the file "no copyright, use freely". You'll also need an sRGB profile which should be easily obtainable from your OS or online.
Much of the processing time is taken up computing the transformations from sRGB and back again. If you are going to be doing a lot of these operations you can store these transformations to re-use them like so:
from PIL.ImageCms import buildTransform, applyTransform
SRGB_TO_LINEARIZED = buildTransform(SRGB_PROFILE, LINEARIZED_PROFILE, 'RGB', 'RGB')
LINEARIZED_TO_SRGB = buildTransform(LINEARIZED_PROFILE, SRGB_PROFILE, 'RGB', 'RGB')
im = applyTransform(im, SRGB_TO_LINEARIZED)
im = im.resize((WIDTH, HEIGHT), Image.ANTIALIAS)
im = applyTransform(im, LINEARIZED_TO_SRGB)
I hope this helps and I'd be interested to hear if anyone has any ideas on resolving the 8 bit colour space issue.
99% of image resize implementations will not get sRGB right (which, unfortunately, is 99.9% of image material), and those who do usually will do it right by default and give you the option to opt out of gamma de/encoding.
[opinionated mode on, read with care]
IOW, if there is no option you likely have to add the code yourself - or just use pamscale. If a library doesn't get sRGB right it will have other flaws anyway.
[opinionated mode off]
You could de/encode yourself as discussed in
http://www.imagemagick.org/discourse-server/viewtopic.php?t=15955
but a from quick glance it seems pillow is not capable of doing that trick.
Upon doing my homework, I stumbled across a problem concerning Python and image manipulation. I must say, using the Image lib is not an option. So here it is
from scipy.misc import imread,imsave
from numpy import zeros
imga = zeros([100,100,3])
h = len(imga)
w = len(imga[0])
for y in range(h):
for x in range(w):
imga[y,x] = [255,255,255]
imsave("Result.jpg",imga)
I would assume it makes my picture white, but it turns it black, and I have no idea why
It's not about the code (and I know it looks very ugly). Its just about the fact, that it is a black image.
Every color in an image is represented by one byte. So to create an image array, you should set it's dtype to uint8.
And, you don't need for-loop to set every elements to 255, you can use fill() method or slice index:
import numpy as np
img = np.zeros([100,100,3],dtype=np.uint8)
img.fill(255) # or img[:] = 255
Easy!
Check the below Code:
whiteFrame = 255 * np.ones((1000,1000,3), np.uint8)
255 is the color for filling the bytes.
1000, 1000 is the size of the image.
3 is the color channel for the image.
And unit8 is the type
Goodluck
Here's a simple way to create a white image with a python one liner.
$ python3 -c "from PIL import Image;Image.new('RGB', (1900, 1080), color = (255,255,255)).save('Img.jpg')"
This will create a white image with a width of 1900 and hight of 1080.
When creating imga, you need to set the unit type. Specifically, change the following line of code:
imga = zeros([100,100,3], dtype=np.uint8)
And, add the following to your imports:
import numpy as np
That gives a white image on my machine.
The headline is too broad and shows up at Google first. I needed a white image and used PIL and numpy. PILlow actually works well with numpy
import numpy as np
from PIL import Image
img = np.zeros([100,100,3],dtype=np.uint8)
img.fill(255) # numpy array!
im = Image.fromarray(img) #convert numpy array to image
im.save('whh.jpg')
Just regarding the headline of this question, I did need a white image as well as a pillow input. And the solutions presented here did not work for me.
Therefore here a different way to generate white images for other purposes:
from PIL import Image
img = Image.new('RGB', (200, 50), color = (255,255,255))
Size and color may be changed in the 2nd and 3rd parameter of the Image.new()-function.
And if you want to write something on this image or save it, this would be example code for this.
from PIL import ImageFont, ImageDraw
fnt = ImageFont.truetype("Pillow/Tests/fonts/FreeMono.ttf", 30)
ImageDraw.Draw(img).text((0,0), "hello world", font=fnt, fill=(0,0,0))
img.save('test.jpg')
# Create an array with a required colours
# The colours are given in BGR [B, G, R]
# The array is created with values of ones, the size is (H, W, Channels)
# The format of the array is uint8
# This array needs to be converted to an image of type uint8
selectedColor = [75, 19, 77] * np.ones((640, 480, 3), np.uint8)
imgSelectedColor = np.uint8(np.absolute(selectedColor))
Using the Python Imaging Library, I can call
img.convert("P", palette=Image.ADAPTIVE)
or
img.convert("P", palette=Image.WEB)
but is there a way to convert to an arbitrary palette?
p = []
for i in range(0, 256):
p.append(i, 0, 0)
img.convert("P", palette=p)
where it'll map each pixel to the closest colour found in the image? Or is this supported for Image.WEB and nothing else?
While looking through the source code of convert() I saw that it references im.quantize.
quantize can take a palette argument. If you provide an Image that has a palette, this function will take that palette and apply it to the image.
Example:
src = Image.open("sourcefilewithpalette.bmp")
new = Image.open("unconvertednew24bit.bmp")
converted = new.quantize(palette=src)
converted.save("converted.bmp")
The other provided answer didn't work for me (it did some really bad double palette conversion or something,) but this solution did.
The ImagePalette module docs's first example shows how to attach a palette to an image, but that image must already be of mode "P" or "L". One can, however, adapt the example to convert a full RGB image to a palette of your choice:
from __future__ import division
import Image
palette = []
levels = 8
stepsize = 256 // levels
for i in range(256):
v = i // stepsize * stepsize
palette.extend((v, v, v))
assert len(palette) == 768
original_path = 'original.jpg'
original = Image.open(original_path)
converted = Image.new('P', original.size)
converted.putpalette(palette)
converted.paste(original, (0, 0))
converted.show()
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()
`