Python error while cropping images: tile cannot extend outside image - python

I have a folder of JPG images having size 2048x1536. In every image date, time, temperature are given in the top and camera model name is given at the end. I want to crop only upper part and lower part of those image.
Image sample: https://drive.google.com/file/d/1TefkFws5l2RBnI2iH22EI5vkje8JbeBk/view?usp=sharing
With the below code, I am getting error -tile cannot extend outside image **for any size I provide, for example (500,500,500,500) **. My target it (1500, 2000, 1500, 2000)
from PIL import Image
import os
#Create an Image Object from an Image
dir=r"C:\\Users\\Desktop\\crop1"
output_dir = r"C:\\Users\\Desktop\\crop2"
file_names = os.listdir(dir)
for file_name in file_names:
file_path = dir +"\{}".format(file_name)
im = Image.open(r"{}".format(file_path))
cropped = im.crop((2000,1500,2000,1500))
output_file= output_dir+"\{}".format(file_name)
cropped.save(r"{}".format(output_file))

"(2000,1500,2000,1500)" is an empty box, which could explain why crop fails even if the "tile cannot extend outside image" error isn't exactly fitting. The 4-tuple argument to crop has the meaning "(left, upper, right, lower)". Example from the Image.crop() documentation:
from PIL import Image
im = Image.open("hopper.jpg")
# The crop method from the Image module takes four coordinates as input.
# The right can also be represented as (left+width)
# and lower can be represented as (upper+height).
(left, upper, right, lower) = (20, 20, 100, 100)
# Here the image "im" is cropped and assigned to new variable im_crop
im_crop = im.crop((left, upper, right, lower))

Related

I get an 'AttributeError: shape' when trying to rotate an image with OpenCV

I am trying to import, resize, and conditionally rotate an image with OpenCV but I'm running into some trouble. To bring in the image and resize it I use:
def draw_plane(self):
# Import image for plane
plane_path = 'planes/' + self.plane + '.jpg'
plane = Image.open(plane_path)
# Get image size
bg_height = plane.size[1]
bg_width = plane.size[0]
# Resize and crop image
if bg_height > bg_width:
# Resize
ratio = bg_width/bg_height
img_width = self.p_width
img_height = int(self.p_height/ratio)
plane_resized = plane.resize((img_width,img_height))
# Crop
top = int((img_height-self.p_height)/2)
bottom = int(((img_height-self.p_height)/2)+self.p_height)
plane_cropped = plane_resized.crop((0,top,self.p_width,bottom))
print('top:',top,'\nbottom:',bottom)
self.plane_img = plane_cropped
if bg_height < bg_width:
# Resize
ratio = bg_height/bg_width
img_width = int(self.p_width/ratio)
img_height = self.p_height
plane_resized = plane.resize((img_width,img_height))
# Crop
left = int((img_width-self.p_width)/2)
right = int(((img_width-self.p_width)/2)+self.p_width)
plane_cropped = plane_resized.crop((left,0,right,self.p_height))
self.plane_img = plane_cropped
else:
pass
If the name of an image being used as a frame for plane is in a list I call the following method and if the first item in a list of attributes for the final composition is "Polaroid" I want it to rotate plane.
def adjust_plane(self):
if a.attr[0] == 'polaroid':
plane = self.plane_img
height, width = plane.shape[:2] <----
center = (width/2, height/2)
rotate_matrix = cv2.getRotationMatrix2D(center=center, angle=-30, scale=1)
rotated_plane = cv2.warpAffine(plane, rotate_matrix, (width, height))
self.plane_img = rotated_plane
But when I run the code I get: "AttributeError: shape" on the line I noted in the code block. This is all taking place in the same class, including the conditional that triggers adjust_plane().
I admit that I am at a point in learning to program that I am just beginning to wrap my head around objects as a concept. Is there maybe some issue that this is no longer an image but is an "image object", if there is such a thing? Any help is appreciated, I've been chewing on this error for far too long.
It appears that the issue is that OpenCV and PIL images don't play well together. Someone else could better explain exactly why.
I brought in OpenCV to rotate the image because I thought PIL could not rotate an image by specific degree rather than just 90° steps, but I was wrong about that. The code below accomplishes what I wanted with the PIL library and I was able to do away with OpenCV.
plane.rotate(30, Image.NEAREST, expand = 1)

Python collage of four image together

I am trying to combine 4 images, image 1 on top left, image 2 on top right, image 3 on bottom left and image 4 on bottom right. However, my images are different sizes and not sure how to resize the images to same size. I am pretty new to Python and this is my first time using PIL.
I have this so far (after opening the images)
img1 = img1.resize(img2.size)
img1 = img1.resize(img3.size)
img1 = img1.resize(img4.size)
This shall suffice your basic requirement.
This shall suffice your basic requirement.
Steps:
Images are read and stored to list of arrays using io.imread(img) in
a list comprehension.
We resize images to custom height and width.You can change IMAGE_WIDTH,IMAGE_HEIGHT as per your need with respect
to the input image size.
You just have to pass the location of n
images (n=4 for your case) to the function.
If you are
passing more than 2 images (for your case 4), it will work create 2
rows of images. In the top row, images in the first half of the list
are stacked and the remaining ones are placed in bottom row using
hconcat().
The two rows are stacked vertically using vconcat().
Finally, we convert the result to RGB image using
image.convert("RGB") and is saved using image.save().
The code:
import cv2
from PIL import Image
from skimage import io
IMAGE_WIDTH = 1920
IMAGE_HEIGHT = 1080
def create_collage(images):
images = [io.imread(img) for img in images]
images = [cv2.resize(image, (IMAGE_WIDTH, IMAGE_HEIGHT)) for image in images]
if len(images) > 2:
half = len(images) // 2
h1 = cv2.hconcat(images[:half])
h2 = cv2.hconcat(images[half:])
concat_images = cv2.vconcat([h1, h2])
else:
concat_images = cv2.hconcat(images)
image = Image.fromarray(concat_images)
# Image path
image_name = "result.jpg"
image = image.convert("RGB")
image.save(f"{image_name}")
return image_name
images=["image1.png","image2.png","image3.png","image4.png"]
#image1 on top left, image2 on top right, image3 on bottom left,image4 on bottom right
create_collage(images)
To create advanced college make you can look into this:
https://codereview.stackexchange.com/questions/275727/python-3-script-to-make-photo-collages

Masking two images and merge into one image

I am working on a project where I am using different masks on two different pictures and than would like to combine them into one picture. So far I have the masking (albeit it has some errors on the edges) and now I am trying to combine the images.
how can I improve the masking so the result on has no errors on the edges (see images )
how do I effectively combine the images into one to result in the third image? I have been trying to use some transparency effects but it hasn't worked. What I am trying to do is merge the two images so they form a complete circle. If any of the original images are needed please let me know
from PIL import Image
# load images
img_day = Image.open('Day.jpeg')
img_night = Image.open('Night_mirror.jpg')
night_mask = Image.open('Masks/12.5.jpg')
day_mask = Image.open('Masks/11.5.jpg')
# convert images
#img_org = img_org.convert('RGB') # or 'RGBA'
night_mask = night_mask.convert('L') # grayscale
day_mask = day_mask.convert('L')
# the same size
img_day = img_day.resize((750,750))
img_night = img_night.resize((750,750))
night_mask = night_mask.resize((750,750))
day_mask = day_mask.resize((750,750))
# add alpha channel
img_day.putalpha(day_mask)
img_night.putalpha(night_mask)
img_night = img_night.rotate(-170)
# save as png which keeps alpha channel
img_day.save('image_day.png')
img_night.save('image_night.png')
img_night.show()
img_day.show()
Any help is appreciated
The main problem are the (JPG) artifacts in your masks (white line at the top, "smoothed" edges). Why not use ImageDraw.arc to generate the masks on-the-fly? The final step you need is to use Image.composite to merge your two images.
Here's some code (I took your first image as desired output, thus the chosen angles):
from PIL import Image, ImageDraw
# Load images
img_day = Image.open('day.jpg')
img_night = Image.open('night.jpg')
# Resize images
target_size = (750, 750)
img_day = img_day.resize(target_size)
img_night = img_night.resize(target_size)
# Generate proper masks
day_mask = Image.new('L', target_size)
draw = ImageDraw.Draw(day_mask)
draw.arc([10, 10, 740, 740], 120, 270, 255, 150)
night_mask = Image.new('L', target_size)
draw = ImageDraw.Draw(night_mask)
draw.arc([10, 10, 740, 740], 270, 120, 255, 150)
# Put alpha channels
img_day.putalpha(day_mask)
img_night.putalpha(night_mask)
# Compose and save image
img = Image.composite(img_day, img_night, day_mask)
img.save('img.png')
That'd be the output:
----------------------------------------
System information
----------------------------------------
Platform: Windows-10-10.0.16299-SP0
Python: 3.8.5
Pillow: 8.0.1
----------------------------------------
To you points:
You problem with masking simply orginiates from the fact that your masks are not perfect. Open them in paint and you will see that on the top side, there is a white line remaining. Just use the fill in tool to fill that white part with black. Afterwards it should work.
I suggest mirroring your image horizontally instead of rotating it. You can use PIL.ImageOps.mirror for that. Then you paste one image onto the other image using img.paste(). As a second argument, you give the coordinates where the image should be pasted onto the other, and very importantly, as a third argument, you specify a transparency mask. Since your image already has an alpha channel, you can just use the same image as a mask. PIL will automatically use it's alpha channel for masking. Note that I had to adjust the position of pasting by 4 pixels to overlap the images correctly.
from PIL import Image, ImageOps
# load images
img_day = Image.open('day.jpg')
img_night = Image.open('night.jpg')
night_mask = Image.open('night_mask.jpg')
day_mask = Image.open('day_mask.jpg')
# convert images
#img_org = img_org.convert('RGB') # or 'RGBA'
night_mask = night_mask.convert('L') # grayscale
day_mask = day_mask.convert('L')
# the same size
img_day = img_day.resize((750,750))
img_night = img_night.resize((750,750))
night_mask = night_mask.resize((750,750))
day_mask = day_mask.resize((750,750))
# add alpha channel
img_day.putalpha(day_mask)
img_night.putalpha(night_mask)
img_night = ImageOps.mirror(img_night)
img_night.paste(img_day, (-4, 0), img_day)
img_night.save('composite.png')
Result:

Resizing non uniform images with precise face location

I work at a studio that does school photos and we are trying to make a script to eliminate the job of cropping each photo to a template. The photos we work with are fairly uniform but they vary in resolution and head position a bit. I took up the mantle of trying to write the script with my fairly limited Python knowledge and through a lot of trial and error and online resources I think I have got most of the way there.
At the moment I am trying to figure out the best way to have the image crop from the NumPy array with the head where I want and I just cant find a good flexible solution. The head needs to be positioned slightly differently for pose 1 and pose 2 so its needs to be easy to change on the fly (Probably going to implement some sort of simple GUI to input stuff like that, but for now I can just change the code).
I also need to be able to change the output resolution of the photo so they are all uniform (2000x2500). Anyone have any ideas?
At the moment this is my current code, it just saves the detected face square:
import cv2
import os.path
import glob
# Cascade path
cascPath = 'haarcascade_frontalface_default.xml'
# Create the haar cascade
faceCascade = cv2.CascadeClassifier(cascPath)
#Check for output folder and create if its not there
if not os.path.exists('output'):
os.makedirs('output')
# Read Images
images = glob.glob('*.jpg')
for c, i in enumerate(images):
image = cv2.imread(i, 1)
# Convert to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Find face(s) using cascade
faces = faceCascade.detectMultiScale(
gray,
scaleFactor=1.1, # size of groups
minNeighbors=5, # How many groups around are detected as face for it to be valid
minSize=(500, 500) # Min size in pixels for face
)
# Outputs number of faces found in image
print('Found {0} faces!'.format(len(faces)))
# Places a rectangle on face
for (x, y, w, h) in faces:
imgCrop = image[y:y+h,x:x+w]
if len(faces) > 0:
#Saves Images to output folder with OG name
cv2.imwrite('output/'+ i, imgCrop)
I can crop using it like this:
# Crop Padding
left = 300
right = 300
top = 400
bottom = 1000
for (x, y, w, h) in faces:
imgCrop = image[y-top:y+h+bottom, x-left:x+w+right]
but that outputs pretty random resolutions and changes based on the image resolution
TL;DR
To set a new resolution with the dimension, you can use cv2.resize. There may be a pixel loss so you can use the interpolation method.
The newly resized image may be in BGR format, so you may need to convert to RGB format.
cv2.resize(src=crop, dsize=(2000, 2500), interpolation=cv2.INTER_LANCZOS4)
crop = cv2.cvtColor(crop, cv2.COLOR_BGR2RGB) # Make sure the cropped image is in RGB format
cv2.imwrite("image-1.png", crop)
Suggestion:
One approach is using python's face-recognition library.
The approach is using two sample images for training.
Predict the next image based on training images.
For instance, The followings are the training images:
We want to predict the faces in the below image:
When we get the facial encodings of the training images and apply to the next image:
import face_recognition
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image, ImageDraw
# Load a sample picture and learn how to recognize it.
first_image = face_recognition.load_image_file("images/ex.jpg")
first_face_encoding = face_recognition.face_encodings(first_image)[0]
# Load a second sample picture and learn how to recognize it.
second_image = face_recognition.load_image_file("images/index.jpg")
sec_face_encoding = face_recognition.face_encodings(second_image)[0]
# Create arrays of known face encodings and their names
known_face_encodings = [
first_face_encoding,
sec_face_encoding
]
print('Learned encoding for', len(known_face_encodings), 'images.')
# Load an image with an unknown face
unknown_image = face_recognition.load_image_file("images/babes.jpg")
# Find all the faces and face encodings in the unknown image
face_locations = face_recognition.face_locations(unknown_image)
face_encodings = face_recognition.face_encodings(unknown_image, face_locations)
# Convert the image to a PIL-format image so that we can draw on top of it with the Pillow library
# See http://pillow.readthedocs.io/ for more about PIL/Pillow
pil_image = Image.fromarray(unknown_image)
# Create a Pillow ImageDraw Draw instance to draw with
draw = ImageDraw.Draw(pil_image)
# Loop through each face found in the unknown image
for (top, right, bottom, left), face_encoding in zip(face_locations, face_encodings):
matches = face_recognition.compare_faces(known_face_encodings, face_encoding)
face_distances = face_recognition.face_distance(known_face_encodings, face_encoding)
best_match_index = np.argmin(face_distances)
draw.rectangle(((left, top), (right, bottom)), outline=(0, 0, 255), width=5)
# Remove the drawing library from memory as per the Pillow docs
del draw
# Display the resulting image
plt.imshow(pil_image)
plt.show()
The output will be:
The above is my suggestion. When you create a new resolution with the current image, there will be a pixel loss. Therefore you need to use an interpolation method.
For instance: after finding the face locations, select the coordinates in the original image.
# Add after draw.rectangle function.
crop = unknown_image[top:bottom, left:right]
Set new resolution with the size 2000 x 2500 and interpolation with CV2.INTERN_LANCZOS4.
Possible Question: Why CV2.INTERN_LANCZOS4?
Of course, you can select whatever you like, but in this post CV2.INTERN_LANCZOS4 was suggested.
cv2.resize(src=crop, dsize=(2000, 2500), interpolation=cv2.INTER_LANCZOS4)
Save the image
crop = cv2.cvtColor(crop, cv2.COLOR_BGR2RGB) # Make sure the cropped image is in RGB format
cv2.imwrite("image-1.png", crop)
Outputs are around 4.3 MB Therefore I can't display in here.
From the final result, we clearly see and identify faces. The library precisely finds the faces in the image.
Here what you can do:
Either you can use the training images of your own-set, or you can use the example above.
Apply the face-recognition function for each image, using the trained face-locations and save the results in the directory.
here is how I got it to crop how I wanted, this is added right below the "output number of faces" function
#Get the face postion and output values into variables, might not be needed but I did it
for (x, y, w, h) in faces:
xdis = x
ydis = y
w = w
h = h
#Get scale value by dividing wanted head hight by detected head hight
ws = 600/w
hs = 600/h
#scale image to get head to right size, uses bilinear interpolation by default
scale = cv2.resize(image,(0,0),fx=hs,fy=ws)
#calculate head postion for given values
sxdis = int(xdis*ws) #applying scale to x distance and turning it into a integer
sydis = int(ydis*hs) #applying scale to y distance and turning it into a integer
sycent = sydis+300 #adding half head hight to get center
ystart = sycent-700 #subtract where you want the head center to be in pixels, this is for the vertical
yend = ystart+2500 #Add whatever you want vertical resolution to be
xcent = sxdis+300 #adding half head hight to get center
xstart = xcent-1000 #subtract where you want the head center to be in pixels, this is for the horizontal
xend = xstart+2000 #add whatever you want the horizontal resolution to be
#Crop the image
cropped = scale[ystart:yend, xstart:xend]
Its a mess but it works exactly how I wanted it to work.
ended up going with openCV instead of switching to python-Recognition because of speed but I might switch over if I can get multithreading to work in python-recognition.

How to change the color of a pixel using PIL?

I was trying to change pixel of an image in python using this question. If mode is 0, it changes first pixel in top right corner of image to grey(#C8C8C8). But it doesn't change. There is not enough documentation about draw.point(). What is the problem with this code?
import random
from PIL import Image, ImageDraw
mode = 0
image = Image.open("dom.jpg")
draw = ImageDraw.Draw(image)
width = image.size[0]
height = image.size[1]
pix = image.load()
string = "kod"
n = 0
if (mode == 0):
draw.point((0, 0), (200, 200, 200))
if(mode == 1):
print(pix[0,0][0])
image.save("dom.jpg", "JPEG")
del draw
Is using PIL a must in your case? If not then consider using OpenCV (cv2) for altering particular pixels of image.
Code which alter (0,0) pixel to (200,200,200) looks following way in opencv:
import cv2
img = cv2.imread('yourimage.jpg')
height = img.shape[0]
width = img.shape[1]
img[0][0] = [200,200,200]
cv2.imwrite('newimage.bmp',img)
Note that this code saves image in .bmp format - cv2 can also write .jpg images, but as jpg is generally lossy format, some small details might be lost. Keep in mind that in cv2 [0][0] is left upper corner and first value is y-coordinate of pixel, while second is x-coordinate, additionally color are three values from 0 to 255 (inclusive) in BGR order rather than RGB.
For OpenCV tutorials, including installation see this.

Categories

Resources