I have a static image that I would like to animate to appear like this (except starting from a black image, not a white image):
(image is from this post: Create animated gif from static image)
Here is the code:
import random
import imageio
import numpy as np
from PIL import Image
img = Image.open('/Users/tom/Desktop/sink.jpeg')
pixels = img.load()
width, height = img.size
img2 = Image.new('RGB', img.size, color='black')
pixels2 = img2.load()
coord = []
for x in range(width):
for y in range(height):
coord.append((x, y))
images = []
while coord:
x, y = random.choice(coord)
pixels2[x, y] = pixels[x, y]
coord.remove((x, y))
if len(coord) % 500 == 0:
images.append(np.array(img2))
imageio.mimsave('/Users/tom/Desktop/sink.gif', images)
When I run the code, the script never stops/outputs anything. Anyone know why?
Your code works, it is just very slow. If you are okay with a transparent background you can do something like this:
import numpy as np
import imageio.v3 as iio
rng = np.random.default_rng()
px_per_iter = 1000
img = iio.imread("imageio:chelsea.png")
n_pixels = img.shape[0] * img.shape[1]
batches = int(np.ceil(n_pixels / px_per_iter)) # number of frames
pixels = rng.permutation(n_pixels) # order in which pixels are revealed
frames = np.zeros((batches + 1, *img.shape[:2], 4), dtype=np.uint8)
for batch_idx in range(batches):
idx_batch = pixels[px_per_iter*batch_idx:px_per_iter*(batch_idx+1)]
y_idx, x_idx = np.unravel_index(idx_batch, img.shape[:2])
frame = frames[batch_idx+1]
frame[y_idx, x_idx, :3] = img[y_idx, x_idx]
frame[y_idx, x_idx, 3] = 255 # make added pixels non-transparent
iio.imwrite("fancy.gif", frames, loop=True)
(500kb GIF)
If you need the black background, you can use something like this; however, be aware that it will produce larger files:
import numpy as np
import imageio.v3 as iio
rng = np.random.default_rng()
px_per_iter = 1000
img = iio.imread("imageio:chelsea.png")
n_pixels = img.shape[0] * img.shape[1]
batches = int(np.ceil(n_pixels / px_per_iter)) # number of frames
pixels = rng.permutation(n_pixels) # order in which pixels are revealed
frames = np.zeros((batches + 1, *img.shape), dtype=np.uint8)
for batch_idx in range(batches):
idx_batch = pixels[px_per_iter*batch_idx:px_per_iter*(batch_idx+1)]
y_idx, x_idx = np.unravel_index(idx_batch, img.shape[:2])
frame = frames[batch_idx+1]
frame[:] = frames[batch_idx]
frame[y_idx, x_idx] = img[y_idx, x_idx]
iio.imwrite("fancy.gif", frames)
(result exceeds 2MB, which is SO's limit)
I have written a code to flip an image vertically pixel-by-pixel. However, the code makes the image being mirrored along the line x = height/2.
I have tried to correct the code by setting the range of "i" from (0, h) to (0, h//2) but the result is still the same.
Original Photo Resulted Photo
#import libraries
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
#read image (set image as m)
m = Image.open('lena.bmp')
#change image to array (set array as np_array)
np_array = np.array(m)
#define the width(w) and height(h) of the image
h, w = np_array.shape
#make the image upside down
for i in range(0,h):
for j in range(0,w):
np_array[i,j] = np_array[h-1-i,j]
#change array back to image (set processed image as pil_image)
pil_image = Image.fromarray(np_array)
#open the processed image
pil_image.show()
#save the processed image
pil_image.save('upsidedown.bmp')
The above given code is replacing the image pixels inplace, that is why the result is a mirrored image.
If you want to flip the image pixel by pixel, just create a new array with same shape and then replace pixels in this new array. For example:
#import libraries
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
#read image (set image as m)
m = Image.open('A-Input-image_Q320.jpg')
#change image to array (set array as np_array)
np_array = np.array(m)
new_np_array = np.copy(np_array)
#define the width(w) and height(h) of the image
h, w = np_array.shape
#make the image upside down
for i in range(0,h):
for j in range(0,w):
new_np_array[i,j] = np_array[h-1-i,j]
#change array back to image (set processed image as pil_image)
pil_image = Image.fromarray(new_np_array)
#open the processed image
pil_image.show()
#save the processed image
pil_image.save('upsidedown.bmp')
I want to convert my b&w image(.png) to binary array(black is 1 white is 0). I have written some code, but it's not working. Error says: argument 2 to map() must support iteration.
Here is my code:
from PIL import Image
from resizeimage import resizeimage
import sys
def threshold(col):
s = sum(col)
return int(s > 255 * 3 // 2)
img = Image.open("filename.png")
ratio = float((img.size[1]) / (img.size[0]))
img = resizeimage.resize_cover(img, [100, int(ratio * 100)])
pixels = img.getdata()
binary = list(map(threshold, img))
array2d = [binary[i * img.size[0] : (i+1) * img.size[0]] for i in range(img.size[1])]
print('\n'.join(''.join(map(str, line)) for line in array2d))
Here is the image:
You need to convert your image to grayscale first, since PIL opens it as RGB. Then, invert the 0 & 255 values. Then, you can convert the non-zero values to 1. Here's one way:
from PIL import Image
import numpy as np
img = Image.open('bw_circle.png').convert('L')
np_img = np.array(img)
np_img = ~np_img # invert B&W
np_img[np_img > 0] = 1
And an alternative way using PIL for the inversion:
from PIL import Image, ImageOps
import numpy as np
img = Image.open('bw_circle.png').convert('L')
img_inverted = ImageOps.invert(img)
np_img = np.array(img_inverted)
np_img[np_img > 0] = 1
I've the following code about the imageio Python library, which loads 2 images I have from the current folder, replaces all the colors > 200 with 0 (making it darker), and then printing the result to a new .gif image:
import imageio
import numpy as np
im = 'image1.png'
im2 = 'image2.png'
images = []
images.append(imageio.imread(im))
images.append(imageio.imread(im2))
imageio.mimsave('surface1.gif', images, duration = 0.5)
im4 = imageio.imread('surface1.gif')
im4[im4 > 200] = 0
imageio.imwrite('movie.gif', im4, format='gif')
The problem is that the generated image contains only 1 frame, only 1 image, not both of the images which I already "merged" in a surface1.gif. Why is that?
Using the get_reader and get_writer objects you can do it like this:
import imageio
import numpy as np
im = 'image1.png'
im2 = 'image2.png'
images = []
images.append(imageio.imread(im))
images.append(imageio.imread(im2))
imageio.mimsave('surface1.gif', images, duration = 0.5)
im4 = imageio.get_reader('surface1.gif')
writer = imageio.get_writer('movie.gif', duration = 0.5)
for im in im4:
im[im > 200] = 0
writer.append_data(im[:, :, :])
writer.close()
I tested it and works as expected.
Can anyone help me figure out what's happening in my image auto-cropping script? I have a png image with a large transparent area/space. I would like to be able to automatically crop that space out and leave the essentials. Original image has a squared canvas, optimally it would be rectangular, encapsulating just the molecule.
here's the original image:
Doing some googling i came across PIL/python code that was reported to work, however in my hands, running the code below over-crops the image.
import Image
import sys
image=Image.open('L_2d.png')
image.load()
imageSize = image.size
imageBox = image.getbbox()
imageComponents = image.split()
rgbImage = Image.new("RGB", imageSize, (0,0,0))
rgbImage.paste(image, mask=imageComponents[3])
croppedBox = rgbImage.getbbox()
print imageBox
print croppedBox
if imageBox != croppedBox:
cropped=image.crop(croppedBox)
print 'L_2d.png:', "Size:", imageSize, "New Size:",croppedBox
cropped.save('L_2d_cropped.png')
the output is this:
Can anyone more familiar with image-processing/PLI can help me figure out the issue?
Install Pillow
pip install Pillow
and use as
from PIL import Image
image=Image.open('L_2d.png')
imageBox = image.getbbox()
cropped = image.crop(imageBox)
cropped.save('L_2d_cropped.png')
When you search for boundaries by mask=imageComponents[3], you search only by blue channel.
You can use numpy, convert the image to array, find all non-empty columns and rows and then create an image from these:
import Image
import numpy as np
image=Image.open('L_2d.png')
image.load()
image_data = np.asarray(image)
image_data_bw = image_data.max(axis=2)
non_empty_columns = np.where(image_data_bw.max(axis=0)>0)[0]
non_empty_rows = np.where(image_data_bw.max(axis=1)>0)[0]
cropBox = (min(non_empty_rows), max(non_empty_rows), min(non_empty_columns), max(non_empty_columns))
image_data_new = image_data[cropBox[0]:cropBox[1]+1, cropBox[2]:cropBox[3]+1 , :]
new_image = Image.fromarray(image_data_new)
new_image.save('L_2d_cropped.png')
The result looks like
If anything is unclear, just ask.
I tested most of the answers replied in this post, however, I was ended up my own answer. I used anaconda python3.
from PIL import Image, ImageChops
def trim(im):
bg = Image.new(im.mode, im.size, im.getpixel((0,0)))
diff = ImageChops.difference(im, bg)
diff = ImageChops.add(diff, diff, 2.0, -100)
#Bounding box given as a 4-tuple defining the left, upper, right, and lower pixel coordinates.
#If the image is completely empty, this method returns None.
bbox = diff.getbbox()
if bbox:
return im.crop(bbox)
if __name__ == "__main__":
bg = Image.open("test.jpg") # The image to be cropped
new_im = trim(bg)
new_im.show()
Here's another version using pyvips.
import sys
import pyvips
image = pyvips.Image.new_from_file(sys.argv[1])
left, top, width, height = image.find_trim(threshold=2, background=[255, 255, 255])
image = image.crop(left, top, width, height)
image.write_to_file(sys.argv[2])
The pyvips trimmer is useful for photographic images. It does a median filter, subtracts the background, finds pixels over the threshold, and removes up to the first and last row and column outside this set. The median and threshold mean it is not thrown off by things like JPEG compression, where noise or invisible compression artefacts can confuse other trimmers.
If you don't supply the background argument, it uses the pixel at (0, 0). threshold defaults to 10, which is about right for JPEG.
Here it is running on an 8k x 8k pixel NASA earth image:
$ time ./trim.py /data/john/pics/city_lights_asia_night_8k.jpg x.jpg
real 0m1.868s
user 0m13.204s
sys 0m0.280s
peak memory: 100mb
Before:
After:
There's a blog post with some more discussion here.
This is an improvement over snew's reply, which works for transparent background. With mathematical morphology we can make it work on white background (instead of transparent), with the following code:
from PIL import Image
from skimage.io import imread
from skimage.morphology import convex_hull_image
from skimage.color import rgb2gray
im = imread('L_2d.jpg')
plt.imshow(im)
plt.title('input image')
plt.show()
# create a binary image
im1 = 1 - rgb2gray(im)
threshold = 0.5
im1[im1 <= threshold] = 0
im1[im1 > threshold] = 1
chull = convex_hull_image(im1)
plt.imshow(chull)
plt.title('convex hull in the binary image')
plt.show()
imageBox = Image.fromarray((chull*255).astype(np.uint8)).getbbox()
cropped = Image.fromarray(im).crop(imageBox)
cropped.save('L_2d_cropped.jpg')
plt.imshow(cropped)
plt.show()
pilkit already contains processor for automatic cropping TrimBorderColor. SOmething like this should work:
from pilkit.lib import Image
from pilkit.processors import TrimBorderColor
img = Image.open('/path/to/my/image.png')
processor = TrimBorderColor()
new_img = processor.process(img)
https://github.com/matthewwithanm/pilkit/blob/b24990167aacbaab3db6d8ec9a02f9ad42856898/pilkit/processors/crop.py#L33
Came across this post recently and noticed the PIL library has changed. I re-implemented this with openCV:
import cv2
def crop_im(im, padding=0.1):
"""
Takes cv2 image, im, and padding % as a float, padding,
and returns cropped image.
"""
bw = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
rows, cols = bw.shape
non_empty_columns = np.where(bw.min(axis=0)<255)[0]
non_empty_rows = np.where(bw.min(axis=1)<255)[0]
cropBox = (int(min(non_empty_rows) * (1 - padding)),
int(min(max(non_empty_rows) * (1 + padding), rows)),
int(min(non_empty_columns) * (1 - padding)),
int(min(max(non_empty_columns) * (1 + padding), cols)))
cropped = im[cropBox[0]:cropBox[1]+1, cropBox[2]:cropBox[3]+1 , :]
return cropped
im = cv2.imread('testimage.png')
cropped = crop_im(im)
cv2.imshow('', cropped)
cv2.waitKey(0)
I know that this post is old but, for some reason, none of the suggested answers worked for me. So I hacked my own version from existing answers:
import Image
import numpy as np
import glob
import shutil
import os
grey_tolerance = 0.7 # (0,1) = crop (more,less)
f = 'test_image.png'
file,ext = os.path.splitext(f)
def get_cropped_line(non_empty_elms,tolerance,S):
if (sum(non_empty_elms) == 0):
cropBox = ()
else:
non_empty_min = non_empty_elms.argmax()
non_empty_max = S - non_empty_elms[::-1].argmax()+1
cropBox = (non_empty_min,non_empty_max)
return cropBox
def get_cropped_area(image_bw,tol):
max_val = image_bw.max()
tolerance = max_val*tol
non_empty_elms = (image_bw<=tolerance).astype(int)
S = non_empty_elms.shape
# Traverse rows
cropBox = [get_cropped_line(non_empty_elms[k,:],tolerance,S[1]) for k in range(0,S[0])]
cropBox = filter(None, cropBox)
xmin = [k[0] for k in cropBox]
xmax = [k[1] for k in cropBox]
# Traverse cols
cropBox = [get_cropped_line(non_empty_elms[:,k],tolerance,S[0]) for k in range(0,S[1])]
cropBox = filter(None, cropBox)
ymin = [k[0] for k in cropBox]
ymax = [k[1] for k in cropBox]
xmin = min(xmin)
xmax = max(xmax)
ymin = min(ymin)
ymax = max(ymax)
ymax = ymax-1 # Not sure why this is necessary, but it seems to be.
cropBox = (ymin, ymax-ymin, xmin, xmax-xmin)
return cropBox
def auto_crop(f,ext):
image=Image.open(f)
image.load()
image_data = np.asarray(image)
image_data_bw = image_data[:,:,0]+image_data[:,:,1]+image_data[:,:,2]
cropBox = get_cropped_area(image_data_bw,grey_tolerance)
image_data_new = image_data[cropBox[0]:cropBox[1]+1, cropBox[2]:cropBox[3]+1 , :]
new_image = Image.fromarray(image_data_new)
f_new = f.replace(ext,'')+'_cropped'+ext
new_image.save(f_new)