I'm trying to crop and resize images in Python, and I want them to be in a fixed format
afterwards (47x62 Pixels). However, if the original image is in landscape, my algorithm doesn't work, there are blank areas.
import Image, sys
MAXSIZEX = 47
MAXSIZEY = 62
im = Image.open(sys.argv[1])
(width, height) = im.size
ratio = 1. * MAXSIZEX / MAXSIZEY
im = im.crop((0, 0, int(width*ratio), int(height*ratio)))
im = im.resize((MAXSIZEX, MAXSIZEY), Image.ANTIALIAS)
im.save(sys.argv[2])
I want the resized image to be fully 47x62 - there should be no empty area visible.
You should first check if MAXSIZEX is greater then the width or the MAXSIZEY is greater than the height. If they are first rescale the image and then do the cropping:
MAXSIZEX = 64
MAXSIZEY = 42
width, height = im.size
xrat = width / float(MAXSIZEX)
yrat = height / float(MAXSIZEY)
if xrat < 1 or yrat < 1:
rat = min(xrat, yrat)
im = im.resize((int(width / rat), int(height / rat)))
res = im.crop((0, 0, MAXSIZEX, MAXSIZEY))
res.show()
Chosing x/y as the scaling is an implicit assumption that your source's y dimension will always be smaller relative to your target resolution than your source's x dimension. First, figure out which dimension to scale on, then crop:
width_count = float(width) / MAXSIZEX
height_count = float(height) / MAXSIZEY
if width_count == height_count:
pass
elif width_count < height_count:
im = im.crop(0, 0, width, int(width_count * height / height_count))
else:
im = im.crop(0, 0, int(height_count * width / width_count), height)
Now you know you have the largest subimage from the original that matches your target aspect ratio, so you can resize without warping the image.
Related
The original image is this:
Using this website tool with these settings:
The final result that I am trying to reproduce (the only difference is that I'm going to add a readjustment to the image that is on top, have 1080 width too) here is this:
It is clear that there is a zoom effect in the blurred image, so when I use resize to keep the aspect ratio and the highest quality possible, I use im = im.resize((1080,1080), resample=Image.Resampling.LANCZOS), but as I don't want that, I removed resample=Image.Resampling.LANCZOS imagining that the image would be generated without proportion generating a zoom:
from PIL import Image, ImageFilter, ImageChops
import numpy
import cv2
def remove_border(file_img):
im = Image.open(file_img)
bg = Image.new("RGB", im.size, im.getpixel((0,0)))
diff = ImageChops.difference(im.convert("RGB"), bg)
diff = ImageChops.add(diff, diff, 2.0, -30)
bbox = diff.getbbox()
if bbox:
return im.crop(bbox)
def resize_blur(img_blur,sizers):
img_blur = img_blur.resize(sizers, resample=Image.Resampling.LANCZOS)
img_blur = img_blur.filter(ImageFilter.GaussianBlur(10))
return img_blur
def resize_width_main(img_border,size_width):
img_width = img_border
basewidth = size_width
wpercent = (basewidth/float(img_width.size[0]))
hsize = int((float(img_width.size[1])*float(wpercent)))
img_width = img_width.resize((basewidth,hsize), Image.Resampling.LANCZOS)
return img_width
def center_overlay(name_file,overlay,background):
img = numpy.asarray(overlay)
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
h, w = img.shape[:2]
back = numpy.asarray(background)
back = cv2.cvtColor(back, cv2.COLOR_RGB2BGR)
hh, ww = back.shape[:2]
yoff = round((hh-h)/2)
xoff = round((ww-w)/2)
result = back.copy()
result[yoff:yoff+h, xoff:xoff+w] = img
cv2.imwrite(name_file, result)
def main():
img_border = remove_border('resized_download.png')
img_blur = resize_blur(img_border, (1080,1080))
img_width = resize_width_main(img_border, 1080)
center_overlay('resized.png', img_width, img_blur)
if __name__ == '__main__':
main()
But the current result is this:
A few observations.
Your remove_border function isn't doing a lot; when I tried it with your test image, all it did was remove 9 pixels from the left side. Maybe it has a bug. At the risk of ignoring the lesson of Chesterton's Fence I'd say you don't need it and could eliminate it entirely.
Blurring before you resize makes more sense than doing it the other way around.
When you resize a blurred image, you don't need to worry much about the quality of the resampling filter. I wouldn't use NEAREST, but BILINEAR should be perfectly adequate and reasonably fast.
When you resize an image, the old and new size should have the same aspect ratio or you will get distortion. The resample method makes no difference in this. You are resizing a rectangle into a square, resulting in bad distortion. You can crop your original to the desired aspect ratio before you resize, or crop it after you resize. It will be more efficient to crop first.
def resize_to_target(im, target_size, resample=Image.BILINEAR):
''' Resize an image to a target size. If the aspect ratio
of the original image is different from the target, the
image will be cropped to the destination aspect ratio
before resizing.
'''
if (im.size[0] / im.size[1]) < (target_size[0] / target_size[1]):
# if (im.size[0] * target_size[1]) != (target_size[0] * im.size[1]):
# Existing image is narrower, crop top and bottom
crop_height = round(im.size[0] * target_size[1] / target_size[0])
if crop_height < im.size[1]:
top = (im.size[1] - crop_height) // 2
bottom = top + crop_height
im = im.crop((0, top, im.size[0], bottom))
else:
# existing image is wider, crop left and right
crop_width = round(im.size[1] * target_size[0] / target_size[1])
if crop_width < im.size[0]:
left = (im.size[0] - crop_width) // 2
right = left + crop_width
im = im.crop((left, 0, right, im.size[1]))
return im.resize(target_size, resample=resample)
Iam using an object detection algorithm to detect objects in an image. The code is as below. Image used is of a car as below.
I would like to crop the original image to keep only the object detected in the image PLUS whatever is necessary to maintain aspect ratio between 4/3 and 16/9.
The box around the car is already deduced from below algorithm [variable is box] and the image dimensions are [variable is height,width] in below code.
If we were to do this manually, it would be cumbersome due to the multiple iterations required, as an example: we have to ensure that resizing does not extend beyond the original image size.
There are 3 images included below, the original image, the modified image with car detected, and the resized image to meet a aspect ratio range.(4/3 to 16/9)
Is there an existing function within python to accomplish this task. Hence, resizing/increasing box dimensions from [91, 90, 226, 158] to the minimum necessary amount to be within the limits of original image size 183x275 while maintaining the aspect ratio
Thanks in advance.
CODE:
import cv2
import matplotlib.pyplot as plt
import cvlib as cv
from cvlib.object_detection import draw_bbox
imagepath='/home/usr/Desktop/car.jpeg'
img = cv2.imread(imagepath)
####STEP 1
img1 = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
box, label, count = cv.detect_common_objects(img)
output = draw_bbox(img, box, label, count)
output = cv2.cvtColor(output,cv2.COLOR_BGR2RGB)
plt.figure(figsize=(10,10))
plt.axis('off')
plt.imshow(output)
plt.show()
print("Number of objects in this image are " +str(len(label)))
height,width,_=img1.shape
print(height,width)
print(box)
#box size is [91, 90, 226, 158] (w1,h1,w2,h2)
#image size is 183x275 (heightxwidth)
#STEP2 (cropping original image to car dimensions as per box size)
crop_img = img[90:158, 91:226]
cv2.imshow("cropped", crop_img)
cv2.waitKey(0)
Image Example:
Detect Object (Step1)
Crop Image (Step2)
Expected Outcome(Step3)
The below code crops the image to required coordinates, then increases its size to match the aspect ratio depending if its >16/9 or <4/3. Advantage of this method is that it will not crop the centre object (car) when it resizes the image and corrects aspect ratio, and will increase left side of image if there is no space to increase in right side (and viceversa,for height and width) to achieve the Aspect ratio
import cv2
import math
import sys
imagepath=('/home/usr/Desktop/filename.jpeg')
img=cv2.imread(imagepath)
h,w,_=img.shape#height and width of original image
#Dimensions of car or object you want to crop (See step 2 in question)
crop_dimensions=[96, 56, 602, 686] #w1,h1,w2,h2
def cropimage(crop_dimensions,imgx):
crop_img = imgx[crop_dimensions[1]:crop_dimensions[3], crop_dimensions[0]:crop_dimensions[2]]
return crop_img
crop_img=cropimage(crop_dimensions,img)
height,width,_=crop_img.shape #height and width of cropped image
if width/height>16/9 or width/height<4/3:
crop_centrepoint = ((crop_dimensions[2] - crop_dimensions[0])/2, (crop_dimensions[3] - crop_dimensions[1])/2)
print(crop_centrepoint) #Centre point of cropped image
if width/height<4/3:
print('<4/3')
newwidth=4/3*height
newheight=height
additionalwidth=newwidth-width
w1maxadditional = crop_dimensions[0] - 0 #distance from cropped image to left edge (0)
w2maxadditional=w-crop_dimensions[2]#distance between cropped image and right end
if w2maxadditional > additionalwidth/2:
correction2=0
w2=(additionalwidth/2)
else:
correction2=abs(w2maxadditional-(additionalwidth/2))
w2=w2maxadditional
if w1maxadditional > additionalwidth/2:
correction1=0
w1=(additionalwidth/2)+correction1
else:
correction1=abs(w2maxadditional-(additionalwidth/2))
w1=w1maxadditional
w1=w1+correction2
w2 = w2+correction1
if w1>w1maxadditional:
w1=w1maxadditional
if w2>w2maxadditional:
w2=w2maxadditional
w1 = crop_dimensions[0] - w1
w2 = w2 + crop_dimensions[2]
h1=crop_dimensions[1]
h2=crop_dimensions[3]
if width / height > 16/9:
print('>16/9')
newheight = width * 9 / 16
newwidth = width
additionalheight = newheight - height
h1maxadditional = crop_dimensions[1] - 0 # distance from cropped image to top edge
h2maxadditional = h - crop_dimensions[3] # distance between cropped image to bottom end
if h2maxadditional > additionalheight / 2:
correction2 = 0
h2 = (additionalheight / 2)
else:
correction2 = abs(h2maxadditional - (additionalheight / 2))
h2 = h2maxadditional
if h1maxadditional > additionalheight / 2:
correction1 = 0
h1 = (additionalheight / 2) + correction1
else:
correction1 = abs(h2maxadditional - (additionalheight / 2))
h1 = h1maxadditional
h1 = h1 + correction2
h2 = h2 + correction1
if h1 > h1maxadditional:
h1 = h1maxadditional
if h2 > h2maxadditional:
h2 = h2maxadditional
h1 = crop_dimensions[1] - h1
h2 = h2 + crop_dimensions[3]
w1 = crop_dimensions[0]
w2 = crop_dimensions[2]
else:
[w1,h1,w2,h2]=crop_dimensions
#Rounding down because cropimage function takes integers
w1=math.trunc(w1)
h1=math.trunc(h1)
w2=math.trunc(w2)
h2=math.trunc(h2)
new_image=cropimage([w1,h1,w2,h2],img)
cv2.imshow('img',new_image)
cv2.waitKey(0)
I'm trying to lower the resolution of images to 250.000 pixels using Python but I don't want them to have a specific height or width so that it depends on the image in question. I've been looking at options like resize_thumbnail or use Pillow but in both I have to set the maximum width or height.
You must know the resolution of the image you begin with, therefore scaling it to 250k pixels is just a matter of proportions.
# With Pillow you can read the size property of the image
width, height = image.size
res_0 = width * height
res_1 = 250000
# You need a scale factor to resize the image to res_1
scale_factor = (res_1/res_0)**0.5
resized = image.resize(width * scale_factor, height * scale_factor)
Out of curiosity I digged a little deeper to resize to the highest resolution that keeps the aspect ratio of the original image, if it exists.
An image with an aspect ratio of W:H can be divided in W×H squares whose side is the GCD of the width and height of the resolution.
After finding W and H, what's left to do is to calculate the highest multiples of W and H that fit the given resolution, in our case 250k pixels.
import math
# With Pillow you can read the size property of the image
width, height = 2538, 1080
res_0 = width * height
res_1 = 1920*1080 # 518400
# You need a scale factor to resize the image to Res1
scale_factor = (res_1/res_0)**0.5
ratio = width/height
# The
max_width = width * scale_factor
max_height = height * scale_factor
# Greates Common Divider and W:H
GCD = math.gcd(width, height)
W = width / GCD
H = height / GCD
ratio_width = math.floor(max_resized[0] / W) * W
ratio_height = math.floor(max_resized[1] / H) * H
scaled_size = (max_width, max_height)
# If GCD > 1 the image can be scaled keeping the original aspect ratio
# otherwise it'll approximated
if (GCD > 1):
scaled_size = (ratio_width, ratio_height)
resized = image.resize(scaled_size)
How can I crop an image in the center? Because I know that the box is a 4-tuple defining the left, upper, right, and lower pixel coordinate but I don't know how to get these coordinates so it crops in the center.
Assuming you know the size you would like to crop to (new_width X new_height):
import Image
im = Image.open(<your image>)
width, height = im.size # Get dimensions
left = (width - new_width)/2
top = (height - new_height)/2
right = (width + new_width)/2
bottom = (height + new_height)/2
# Crop the center of the image
im = im.crop((left, top, right, bottom))
This will break if you attempt to crop a small image larger, but I'm going to assume you won't be trying that (Or that you can catch that case and not crop the image).
One potential problem with the proposed solution is in the case there is an odd difference between the desired size, and old size. You can't have a half pixel on each side. One has to choose a side to put an extra pixel on.
If there is an odd difference for the horizontal the code below will put the extra pixel to the right, and if there is and odd difference on the vertical the extra pixel goes to the bottom.
import numpy as np
def center_crop(img, new_width=None, new_height=None):
width = img.shape[1]
height = img.shape[0]
if new_width is None:
new_width = min(width, height)
if new_height is None:
new_height = min(width, height)
left = int(np.ceil((width - new_width) / 2))
right = width - int(np.floor((width - new_width) / 2))
top = int(np.ceil((height - new_height) / 2))
bottom = height - int(np.floor((height - new_height) / 2))
if len(img.shape) == 2:
center_cropped_img = img[top:bottom, left:right]
else:
center_cropped_img = img[top:bottom, left:right, ...]
return center_cropped_img
I feel like the simplest solution that is most suitable for most applications is still missing. The accepted answer has an issue with uneven pixels and especially for ML algorithms, the pixel count of the cropped image is paramount.
In the following example, I would like to crop an image to 224/100, from the center. I do not care if the pixels are shifted to the left or right by 0.5, as long as the output picture will always be of the defined dimensions. It avoids the reliance on math.*.
from PIL import Image
import matplotlib.pyplot as plt
im = Image.open("test.jpg")
left = int(im.size[0]/2-224/2)
upper = int(im.size[1]/2-100/2)
right = left +224
lower = upper + 100
im_cropped = im.crop((left, upper,right,lower))
print(im_cropped.size)
plt.imshow(np.asarray(im_cropped))
The output is before cropping (not shown in code):
after:
The touples show the dimensions.
This is the function I was looking for:
from PIL import Image
im = Image.open("test.jpg")
crop_rectangle = (50, 50, 200, 200)
cropped_im = im.crop(crop_rectangle)
cropped_im.show()
Taken from another answer
I originally used the accepted answer:
import Image
im = Image.open(<your image>)
width, height = im.size # Get dimensions
left = (width - new_width)/2
top = (height - new_height)/2
right = (width + new_width)/2
bottom = (height + new_height)/2
# Crop the center of the image
im = im.crop((left, top, right, bottom))
But I came into the problem mentioned by Dean Pospisil
One potential problem with the proposed solution is in the case there
is an odd difference between the desired size, and old size. You can't
have a half pixel on each side. One has to choose a side to put an
extra pixel on.
Dean Pospisil's solution works, I also came up with my own calculation to fix this:
import Image
im = Image.open(<your image>)
width, height = im.size # Get dimensions
left = round((width - new_width)/2)
top = round((height - new_height)/2)
x_right = round(width - new_width) - left
x_bottom = round(height - new_height) - top
right = width - x_right
bottom = height - x_bottom
# Crop the center of the image
im = im.crop((left, top, right, bottom))
With the accepted answer, an image of 180px x 180px to be cropped to 180px x 101px will result in a cropped image to 180px x 102px.
With my calculation, it will be correctly cropped to 180px x 101px
Crop center and around:
def im_crop_around(img, xc, yc, w, h):
img_width, img_height = img.size # Get dimensions
left, right = xc - w / 2, xc + w / 2
top, bottom = yc - h / 2, yc + h / 2
left, top = round(max(0, left)), round(max(0, top))
right, bottom = round(min(img_width - 0, right)), round(min(img_height - 0, bottom))
return img.crop((left, top, right, bottom))
def im_crop_center(img, w, h):
img_width, img_height = img.size
left, right = (img_width - w) / 2, (img_width + w) / 2
top, bottom = (img_height - h) / 2, (img_height + h) / 2
left, top = round(max(0, left)), round(max(0, top))
right, bottom = round(min(img_width - 0, right)), round(min(img_height - 0, bottom))
return img.crop((left, top, right, bottom))
May be i am late to this party but at least i am here
I want to center crop the image convert 9:16 image to 16:9 portrait to landscape
This is the algo i used :
divide image in 4 equal parts
discard part 1 and part four
Set left to 0, right to width of image
code :
from PIL import Image
im = Image.open('main.jpg')
width, height = im.size
if height > width:
h2 = height/2
h4 = h2/2
border = (0, h4, width, h4*3)
cropped_img = im.crop(border)
cropped_img.save("test.jpg")
before :
after:
I hope this helps
You could use Torchvision's CenterCrop transformation for this. Here's an example
from PIL import Image
from torchvision.transforms import functional as F
crop_size = 256 # can be either an integer or a tuple of ints for (height, width) separately
img = Image.open(<path_to_your_image>)
cropped_img = F.center_crop(img, crop_size)
F.center_crop works with torch.Tensors or PIL.Images and retains the data type i.e. when input is a PIL.Image then output is also a (cropped) PIL.Image. An added bonus is that the above transformation would automatically apply padding in case the input image size is smaller than the requested crop size.
Currently I am using:
os.chdir(album.path)
images = glob.glob('*.*')
# thumbs size
size = 80,80
for image in images:
#create thumb
file, ext = os.path.splitext(image)
im = Image.open(os.path.join(album.path,image))
im.thumbnail(size, Image.ANTIALIAS)
thumb_path = os.path.join(album.path, 'thumbs', file + ".thumb" + ".jpeg")
im.save(thumb_path)
Although this works, I end up with different sizes images (some are portrait and some are landscape), but I want all of the images to have an exact size. Maybe a sensible cropping?
UPDATE:
I don't mind cropping a small portion of the image. When I said sensible cropping I mean something like this algorythm:
if image is portrait:
make width 80px
crop the height (will be more than 80px)
else if image is landscape:
make height 80px
crop the width to 80px (will be more than 80px)
Here is my take on doing a padded fit for an image:
#!/usr/bin/env python
from PIL import Image, ImageChops
F_IN = "/path/to/image_in.jpg"
F_OUT = "/path/to/image_out.jpg"
size = (80,80)
image = Image.open(F_IN)
image.thumbnail(size, Image.ANTIALIAS)
image_size = image.size
thumb = image.crop( (0, 0, size[0], size[1]) )
offset_x = max( (size[0] - image_size[0]) / 2, 0 )
offset_y = max( (size[1] - image_size[1]) / 2, 0 )
thumb = ImageChops.offset(thumb, offset_x, offset_y)
thumb.save(F_OUT)
It first uses the thumbnail operation to bring the image down to within your original bounds and preserving the aspect. Then it crops it back out to actually fill the size of your bounds (since unless the original image was square, it will be smaller now), and we find the proper offset to center the image. The image is offset to the center, so you end up with black padding but no image cropping.
Unless you can make a really sensible guess at a proper center crop without losing possible important image data on the edges, a padded fit approach will work better.
Update
Here is a version that can do either center crop or pad fit.
#!/usr/bin/env python
from PIL import Image, ImageChops, ImageOps
def makeThumb(f_in, f_out, size=(80,80), pad=False):
image = Image.open(f_in)
image.thumbnail(size, Image.ANTIALIAS)
image_size = image.size
if pad:
thumb = image.crop( (0, 0, size[0], size[1]) )
offset_x = max( (size[0] - image_size[0]) / 2, 0 )
offset_y = max( (size[1] - image_size[1]) / 2, 0 )
thumb = ImageChops.offset(thumb, offset_x, offset_y)
else:
thumb = ImageOps.fit(image, size, Image.ANTIALIAS, (0.5, 0.5))
thumb.save(f_out)
source = "/path/to/source/image.JPG"
makeThumb(source, "/path/to/source/image_padded.JPG", pad=True)
makeThumb(source, "/path/to/source/image_centerCropped.JPG", pad=False)
Obviously, you would need to crop or pad the images. You could do something like below to get a maximal centered crop according to the aspect ratio of the thumbnails (untested):
aspect = lambda size: float(size[0]) / float(size[1])
sa = aspect(size)
if aspect(im.size) > sa:
width = int(sa * im.size[1])
left = (im.size[0] - width) / 2
im = im.crop((left, 0, left + width, im.size[1]))
else:
height = int(im.size[0] / sa)
top = (im.size[1] - height) / 2
im = im.crop((0, top, im.size[0], top + height))
im.thumbnail(size, Image.ANTIALIAS)
If you use easy-thumbnails you'll need to set crop to True and upscale to True to always fill-up the space (have the exact same dimensions).
Ex: makes image_2 fits in image_1 dimensions:
thumbnailer = get_thumbnailer(image_2)
thumbnail = thumbnailer.generate_thumbnail(thumbnail_options={
'crop': True,
'upscale': True,
'size': image_1.size
})
image_2 = thumbnail.image