Why does this function add a border to all my images? - python

I'm using PIL.
im = im.rotate(angle=-90, expand = True)
When I do this, it adds a greyish border to my image.
Why?
Here's m full code. Note that if I don't rotate, it adds no borders
def fixRotation(f, quality=96, image_type="JPEG"):
#http://sylvana.net/jpegcrop/exif_orientation.html
d =getEXIF(f)
if d:
orientation = int(d['Orientation'])
im = Image.open(StringIO(f))
if orientation == 6:
im = im.rotate(angle=-90, expand = True)
elif orientation == 3:
im = im.rotate(angle=-180, expand=True)
elif orientation == 8:
im = im.rotate(angle=-270, expand=True)
else:
#It doesn't add a border here.
im = im.rotate(0, expand=True)
res = StringIO()
im.save(res, image_type, quality=quality)
res.seek(0)
return res
else:
return StringIO(f)

I made some experiment and indeed image size is altered but I didn't understad the exact behavior. To me looks like a bug in PIL... you should report it.
If you only need k*90 degrees then to do the rotation you can also use numpy...
img = Image.fromarray(numpy.rot90(numpy.array(img), n))
n is the number of times to rotate by 90 degrees.

Note the following (CPython 2.6, PIL 1.1.7):
import Image
i= Image.open("someimage.jpg")
ir0= i.rotate(-90)
ir1= i.rotate(-90, expand=1)
for img in i, ir0, ir1:
print(img.size)
# output follows
(720, 400)
(400, 720)
(401, 721)
When the angle is a multiple of 90, expand is not needed.
expand in the case of right-angle multiples seems to activate a bug
So, if all you care about is 90°-180°-270° rotations, just drop the expand=1 argument; even better, use the transpose method (see Geometrical Transforms in the PIL tutorial )

When rotating, the algorithm (essentially) averages each pixel with the "next" pixel value. For all pixels "inside" the image, the next pixel is defined. For all pixels at the edge of the image, that pixel is undefined.
So the grey is the average between the known perimeter pixel and an undefined exterior pixel.

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)

Crop image borders dynamically

How can I crop images that looks like this and save as 3 different images?
The issue is that images are different in size and non-proportional, so I want to make a code that dynamically cuts black borders but not the black part which is inside the picture.
Here is the desired outcome:
Below is the sample code I've made which works only for one specific image.
from PIL import Image
im = Image.open(r"image.jpg")
# Setting the points for cropped image1
# im1 = im.crop((left, top, right, bottom))
im1 = im.crop((...))
im2 = im.crop((...))
im3 = im.crop((...))
im1 = im1.save(r"image1.jpg")
im2 = im2.save(r"image2.jpg")
im3 = im3.save(r"image3.jpg")
Finally I've found the solution. Here is what I did:
from PIL import Image, ImageChops
def RemoveBlackBorders(img):
bg = Image.new(img.mode, img.size, img.getpixel((0,0)))
diff = ImageChops.difference(img, bg)
diff = ImageChops.add(diff, diff, 2.0, -100)
bbox = diff.getbbox()
if bbox:
return img.crop(bbox)
# Opens a image in RGB mode
im = Image.open(r"C:\Path\Image.jpg")
# removing borders
im = RemoveBlackBorders(im)
# getting midpoint from size
width, height = im.size
mwidth = width/2
# assign shape of figure from the midpoint
#crop((x,y of top left, x, y of bottom right))
im1 = im.crop((0, 0, mwidth-135, height))
im2 = im.crop((mwidth-78, 0, mwidth+84, height))
im3 = im.crop((mwidth+135, 0, width, height))
The function to remove borders I've found from here.
Although the solution is not completely dynamic, it still solves my problem with ~90% accuracy. But I believe there should be a more universal approach for this problem.
If the areas have always the same size and the same top and bottom coordinates the following should work:
The coordinates for the crops can be retrieved by calculating the sums per rows and per columns, then analyzing them.
import cv2
import numpy as np
im = cv2.imread(image_path)
sum_of_rows = np.sum(im, axis=(1,2))
sum_of_cols = np.sum(im, axis=(0,2))
The top and bottom can be calculated by calculating the sum for each row (each sum value being calculated R+G+B, the value should be zero for black). Then looking for the first value being different form zero and the last value being different than zero. Both indicating the top and bottom.
top = np.argmax(sum_of_rows > 0)
bottom = top + np.argmax(sum_of_rows[top:]==0)
The same can be done for the sum for each column, but here checking for multiple left and right values.

OpenCV inverse warpPolar output out of frame

When I try to make an inverse polar transformation to my image, the output is outside of the output image. There are also some weird white patterns on the top. I tried to make the output image larger but the circle is on the left side so it didn't help.
I am trying to make a line circle using warpPolar function, for that first I'm flipping the line and giving it a black area as shown on the image, then using the cv2.warpPolar function with WARP_INVERSE_MAP flag.
How can I fully draw the circle, and get its bounding box is my question.
line = np.ones(shape=(20,475),dtype=np.uint8)*255
flipped = cv2.rotate(line,cv2.ROTATE_90_CLOCKWISE)
cv2.imshow('flipped',flipped)
h,w = flipped.shape
radius = int(h / (2*np.pi))
new_image = np.zeros(shape=(h,radius+w),dtype=np.uint8)
h2,w2 = new_image.shape
new_image[: ,w2-w:w2] = flipped
cv2.imshow('polar',new_image)
h,w = new_image.shape
center = (w/2,h)
output= cv2.warpPolar(new_image,center=center,maxRadius=radius,dsize=(1500,1500),flags=cv2.WARP_INVERSE_MAP + cv2.WARP_POLAR_LINEAR)
cv2.imshow('output',output)
cv2.waitKey(0)
Note: I am not getting the same result as you showed above when I tried the same code. You may miss some code lines to add ?
If I didn't misunderstand your problem,you are trying to get this result: (If I am wrong, I will update the answer accordingly)
The only point you are missing is that defining the center and radius. You are making inverse transform here, the input is created by you not warpPolar. Since you are defining size as (1500,1500), you need to update center and radius accordingly. Here is my code giving this result:
import cv2
import numpy as np
line = np.ones(shape=(20,475),dtype=np.uint8)*255
flipped = cv2.rotate(line,cv2.ROTATE_90_CLOCKWISE)
cv2.imshow('flipped',flipped)
h,w = flipped.shape
radius = int(h / (2*np.pi))
new_image = np.zeros(shape=(h,radius+w),dtype=np.uint8)
h2,w2 = new_image.shape
new_image[: ,w2-w:w2] = flipped
cv2.imshow('polar',new_image)
h,w = new_image.shape
center = (750,750)
maxRadius = 750
output= cv2.warpPolar(new_image,center=center,maxRadius=radius,dsize=(1500,1500),flags=cv2.WARP_INVERSE_MAP + cv2.WARP_POLAR_LINEAR)
cv2.imshow('output',output)
cv2.waitKey(0)

Shrink image using python?

import image
img = image.Image("test_image.png")
ratio = 0.5
#Make new image screen.
new_width = img.getWidth()*(ratio)
new_height = img.getHeight()*(ratio)
new_img = image.EmptyImage(new_width,new_height)
win = image.ImageWin(new_width,new_height)
for col in range(img.getHeight()):
for row in range(img.getWidth()):
p = img.getPixel(row*ratio, col*ratio) #Color value stays the same.
new_img.setPixel(row*ratio,col*ratio,p)
new_img.draw(win)
win.exitonclick()
This is my attempt to shrink an image using Python. I know my algorithm is clearly not correct but I can't seem to wrap my head around what I should do instead.
It's giving me an image of the two upper corners of the original image put side by side in my new smaller image screen. What am I missing?

Mirror Image but wrong size

I am trying to input an image (image1) and flip it horizontally and then save to a file (image2). This works but not the way I want it to
currently this code gives me a flipped image but it just shows the bottom right quarter of the image, so it is the wrong size. Am I overwriting something somewhere? I just want the code to flip the image horizontally and show the whole picture flipped. Where did I go wrong?
and I cannot just use a mirror function or reverse function, I need to write an algorithm
I get the correct window size but the incorrect image size
def Flip(image1, image2):
img = graphics.Image(graphics.Point(0, 0), image1)
X, Y = img.getWidth(), img.getHeight()
for y in range(Y):
for x in range(X):
r, g, b = img.getPixel(x,y)
color = graphics.color_rgb(r, g, b)
img.setPixel(X-x, y, color)
win = graphics.GraphWin(img, img.getWidth(), img.getHeight())
img.draw(win)
img.save(image2)
I think your problem is in this line:
win = graphics.GraphWin(img, img.getWidth(), img.getHeight())
The first argument to the GraphWin constructor is supposed to be the title, but you are instead giving it an Image object. It makes me believe that maybe the width and height you are supplying are then being ignored. The default width and height for GraphWin is 200 x 200, so depending on the size of your image, that may be why only part of it is being drawn.
Try something like this:
win = graphics.GraphWin("Flipping an Image", img.getWidth(), img.getHeight())
Another problem is that your anchor point for the image is wrong. According to the docs, the anchor point is where the center of the image will be rendered (thus at 0,0 you are only seeing the bottom right quadrant of the picture). Here is a possible solution if you don't know what the size of the image is at the time of creation:
img = graphics.Image(graphics.Point(0, 0), image1)
img.move(img.getWidth() / 2, img.getHeight() / 2)
You are editing your source image. It would be
better to create an image copy and set those pixels instead:
create a new image for editing:
img_new = img
Assign the pixel values to that:
img_new.setPixel(X-x, y, color)
And draw that instead:
win = graphics.GraphWin(img_new, img_new.getWidth(), img_new.getHeight())
img_new.draw(win)
img_new.save(image2)
This will also check that your ranges are correct. if they are not, you will see both flipped and unflipped portions in the final image, showing which portions are outside of your ranges.
If you're not opposed to using an external library, I'd recommend the Python Imaging Library. In particular, the ImageOps module has a mirror function that should do exactly what you want.

Categories

Resources