PIL 缩略图是旋转我的形象?

我正在尝试采取大(巨大)图像(从数码相机) ,并转换成我可以在网上显示的东西。这看起来很简单,也许应该如此。但是,当我尝试使用 PIL 来创建缩略图版本时,如果我的源图像高于它的宽度,则生成的图像旋转90度,使得源图像的顶部位于生成的图像的左侧。如果源图像的宽度大于其高度,则生成的图像是正确的(原始的)方向。是不是和我发送的2元组大小有关?我使用缩略图,因为它似乎是为了保持长宽比。还是我完全瞎了,在做傻事?元组的大小是1000,1000,因为我希望将最长的一边缩小到1000像素,同时保留 AR。

代码看起来很简单

img = Image.open(filename)
img.thumbnail((1000,1000), Image.ANTIALIAS)
img.save(output_fname, "JPEG")

提前谢谢你的帮助。

46709 次浏览

Please note that there are better answers below.


When a picture is taller than it is wide, it means the camera was rotated. Some cameras can detect this and write that info in the picture's EXIF metadata. Some viewers take note of this metadata and display the image appropriately.

PIL can read the picture's metadata, but it does not write/copy metadata when you save an Image. Consequently, your smart image viewer will not rotate the image as it did before.

Following up on @Ignacio Vazquez-Abrams's comment, you can read the metadata using PIL this way, and rotate if necessary:

import ExifTags
import Image


img = Image.open(filename)
print(img._getexif().items())
exif=dict((ExifTags.TAGS[k], v) for k, v in img._getexif().items() if k in ExifTags.TAGS)
if not exif['Orientation']:
img=img.rotate(90, expand=True)
img.thumbnail((1000,1000), Image.ANTIALIAS)
img.save(output_fname, "JPEG")

But be aware that the above code may not work for all cameras.

The easiest solution maybe to use some other program to make thumbnails.

phatch is a batch photo editor written in Python which can handle/preserve EXIF metadata. You could either use this program to make your thumbnails, or look at its source code to see how to do this in Python. I believe it uses the pyexiv2 to handle the EXIF metadata. pyexiv2 may be able to handle EXIF better than the PIL's ExifTags module.

imagemagick is another possibility for making batch thumbnails.

I agree with almost everything as answered by "unutbu" and Ignacio Vazquez-Abrams, however...

EXIF Orientation flag can have a value between 1 and 8 depending on how the camera was held.

Portrait photo can be taken with top of the camera on the left, or right edge, landscape photo could be taken upside down.

Here is code that takes this into account (Tested with DSLR Nikon D80)

    import Image, ExifTags


try :
image=Image.open(os.path.join(path, fileName))
for orientation in ExifTags.TAGS.keys() :
if ExifTags.TAGS[orientation]=='Orientation' : break
exif=dict(image._getexif().items())


if   exif[orientation] == 3 :
image=image.rotate(180, expand=True)
elif exif[orientation] == 6 :
image=image.rotate(270, expand=True)
elif exif[orientation] == 8 :
image=image.rotate(90, expand=True)


image.thumbnail((THUMB_WIDTH , THUMB_HIGHT), Image.ANTIALIAS)
image.save(os.path.join(path,fileName))


except:
traceback.print_exc()

Hoopes answer is great, but it is a lot more efficient to use the transpose method rather than rotate. Rotate does an actual filtered calculation for each pixel, effectively a complex resize of the whole image. Also, the current PIL library seems to have a bug in which a black line is added to the edges of rotated images. Transpose is a LOT faster and lacks that bug. I just tweaked hoopes answer to use transpose instead.

import Image, ExifTags


try :
image=Image.open(os.path.join(path, fileName))
for orientation in ExifTags.TAGS.keys() :
if ExifTags.TAGS[orientation]=='Orientation' : break
exif=dict(image._getexif().items())


if   exif[orientation] == 3 :
image=image.transpose(Image.ROTATE_180)
elif exif[orientation] == 6 :
image=image.rotate(Image.ROTATE_180)
elif exif[orientation] == 8 :
image=image.rotate(Image.ROTATE_180)


image.thumbnail((THUMB_WIDTH , THUMB_HIGHT), Image.ANTIALIAS)
image.save(os.path.join(path,fileName))


except:
traceback.print_exc()

xilvar's answer is very nice, but had two minor shortcomings that I wanted to fix in a rejected edit, so I'll post it as an answer.

For one, xilvar's solution fails if the file isn't a JPEG or if there is no exif data present. And for the other, it always rotated 180 degrees instead of the appropriate amount.

import Image, ExifTags


try:
image=Image.open(os.path.join(path, fileName))
if hasattr(image, '_getexif'): # only present in JPEGs
for orientation in ExifTags.TAGS.keys():
if ExifTags.TAGS[orientation]=='Orientation':
break
e = image._getexif()       # returns None if no EXIF data
if e is not None:
exif=dict(e.items())
orientation = exif[orientation]


if orientation == 3:   image = image.transpose(Image.ROTATE_180)
elif orientation == 6: image = image.transpose(Image.ROTATE_270)
elif orientation == 8: image = image.transpose(Image.ROTATE_90)


image.thumbnail((THUMB_WIDTH , THUMB_HIGHT), Image.ANTIALIAS)
image.save(os.path.join(path,fileName))


except:
traceback.print_exc()

Here's a version that works for all 8 orientations:

def flip_horizontal(im): return im.transpose(Image.FLIP_LEFT_RIGHT)
def flip_vertical(im): return im.transpose(Image.FLIP_TOP_BOTTOM)
def rotate_180(im): return im.transpose(Image.ROTATE_180)
def rotate_90(im): return im.transpose(Image.ROTATE_90)
def rotate_270(im): return im.transpose(Image.ROTATE_270)
def transpose(im): return rotate_90(flip_horizontal(im))
def transverse(im): return rotate_90(flip_vertical(im))
orientation_funcs = [None,
lambda x: x,
flip_horizontal,
rotate_180,
flip_vertical,
transpose,
rotate_270,
transverse,
rotate_90
]
def apply_orientation(im):
"""
Extract the oritentation EXIF tag from the image, which should be a PIL Image instance,
and if there is an orientation tag that would rotate the image, apply that rotation to
the Image instance given to do an in-place rotation.


:param Image im: Image instance to inspect
:return: A possibly transposed image instance
"""


try:
kOrientationEXIFTag = 0x0112
if hasattr(im, '_getexif'): # only present in JPEGs
e = im._getexif()       # returns None if no EXIF data
if e is not None:
#log.info('EXIF data found: %r', e)
orientation = e[kOrientationEXIFTag]
f = orientation_funcs[orientation]
return f(im)
except:
# We'd be here with an invalid orientation value or some random error?
pass # log.exception("Error applying EXIF Orientation tag")
return im

Hello I was trying to achieve rotation of image and thanks to previous answers in this post I did it. But I upgraded solution and would like to share it. I hope someone will find this useful.

def get_rotation_code(img):
"""
Returns rotation code which say how much photo is rotated.
Returns None if photo does not have exif tag information.
Raises Exception if cannot get Orientation number from python
image library.
"""
if not hasattr(img, '_getexif') or img._getexif() is None:
return None


for code, name in ExifTags.TAGS.iteritems():
if name == 'Orientation':
orientation_code = code
break
else:
raise Exception('Cannot get orientation code from library.')


return img._getexif().get(orientation_code, None)




class IncorrectRotationCode(Exception):
pass




def rotate_image(img, rotation_code):
"""
Returns rotated image file.


img: PIL.Image file.
rotation_code: is rotation code retrieved from get_rotation_code.
"""
if rotation_code == 1:
return img
if rotation_code == 3:
img = img.transpose(Image.ROTATE_180)
elif rotation_code == 6:
img = img.transpose(Image.ROTATE_270)
elif rotation_code == 8:
img = img.transpose(Image.ROTATE_90)
else:
raise IncorrectRotationCode('{} is unrecognized '
'rotation code.'
.format(rotation_code))
return img

Use:

>>> img = Image.open('/path/to/image.jpeg')
>>> rotation_code = get_rotation_code(img)
>>> if rotation_code is not None:
...     img = rotate_image(img, rotation_code)
...     img.save('/path/to/image.jpeg')
...

I'm a noob to programming, Python and PIL so the code examples in the previous answers seem complicated to me. Instead of iterating through the tags, I just went straight to it the tag's key. In the python shell, you can see that orientation's key is 274.

>>>from PIL import ExifTags
>>>ExifTags.TAGS

I use the image._getexif() function to grab what ExifTags are in the image. If orientation tag is not present, it throws an error, so I use try/except.

Pillow's documentation says there is no difference in performance or results between rotate and transpose. I have confirmed it by timing both functions. I use rotate because it's more concise.

rotate(90) rotates counter-clockwise. The function seems to accept negative degrees.

from PIL import Image, ExifTags


# Open file with Pillow
image = Image.open('IMG_0002.jpg')


#If no ExifTags, no rotating needed.
try:
# Grab orientation value.
image_exif = image._getexif()
image_orientation = image_exif[274]


# Rotate depending on orientation.
if image_orientation == 3:
rotated = image.rotate(180)
if image_orientation == 6:
rotated = image.rotate(-90)
if image_orientation == 8:
rotated = image.rotate(90)


# Save rotated image.
rotated.save('rotated.jpg')
except:
pass

Just use PIL.ImageOps.exif_transpose from Pillow.

Unlike every single function proposed in answers to this question, including my original one, it takes care to remove the orientation field from EXIF (as the image is no longer oriented in a strange way) and also to ensure the return value is a brand new Image object so changes to it can’t affect the original one.

There are some good answers here, I just wanted to post a cleaned up version... The function assumes you've already done Image.open() somewhere, and will do image.save() elsewhere and just want a function you can drop in to fix rotation.

def _fix_image_rotation(image):
orientation_to_rotation_map = {
3: Image.ROTATE_180,
6: Image.ROTATE_270,
8: Image.ROTATE_90,
}
try:
exif = _get_exif_from_image(image)
orientation = _get_orientation_from_exif(exif)
rotation = orientation_to_rotation_map.get(orientation)
if rotation:
image = image.transpose(rotation)


except Exception as e:
# Would like to catch specific exceptions, but PIL library is poorly documented on Exceptions thrown
# Log error here


finally:
return image




def _get_exif_from_image(image):
exif = {}


if hasattr(image, '_getexif'):  # only jpegs have _getexif
exif_or_none = image._getexif()
if exif_or_none is not None:
exif = exif_or_none


return exif




def _get_orientation_from_exif(exif):
ORIENTATION_TAG = 'Orientation'
orientation_iterator = (
exif.get(tag_key) for tag_key, tag_value in ExifTags.TAGS.items()
if tag_value == ORIENTATION_TAG
)
orientation = next(orientation_iterator, None)
return orientation

I needed a solution that takes care of all orientations, not just 3, 6 and 8.

I tried Roman Odaisky's solution - it looked comprehensive and clean. However, testing it with actual images with various orientation values sometimes led to erroneous results (e.g. this one with orientation set to 0).

Another viable solution could be Dobes Vandermeer's. But I haven't tried it, because I feel one can write the logic more simply (which I prefer).

So without further ado, here's a simpler, more maintainable (in my opinion) version:

from PIL import Image


def reorient_image(im):
try:
image_exif = im._getexif()
image_orientation = image_exif[274]
if image_orientation in (2,'2'):
return im.transpose(Image.FLIP_LEFT_RIGHT)
elif image_orientation in (3,'3'):
return im.transpose(Image.ROTATE_180)
elif image_orientation in (4,'4'):
return im.transpose(Image.FLIP_TOP_BOTTOM)
elif image_orientation in (5,'5'):
return im.transpose(Image.ROTATE_90).transpose(Image.FLIP_TOP_BOTTOM)
elif image_orientation in (6,'6'):
return im.transpose(Image.ROTATE_270)
elif image_orientation in (7,'7'):
return im.transpose(Image.ROTATE_270).transpose(Image.FLIP_TOP_BOTTOM)
elif image_orientation in (8,'8'):
return im.transpose(Image.ROTATE_90)
else:
return im
except (KeyError, AttributeError, TypeError, IndexError):
return im

Tested, and found to work on images with all the mentioned exif orientations. However, please also do your own tests too.

Adding to the other answers, I was having issues because I would use im.copy() before running the functions -- this would strip the necessary exif data. Make sure before you run im.copy() you save the exif data:

try:
exif = im._getexif()
except Exception:
exif = None


# ...
# im = im.copy() somewhere
# ...


if exif:
im = transpose_im(im, exif)

There is an easier approach to all this:

    from PIL import image
im1 = Image.open(path_to_image)
im1.thumbnail(size1, Image.ANTIALIAS)
y, z = im1.size
d = z * 1.5
if y > d:
im1.rotate(90, expand=1)

I hope it helps :)

Pillow has an API to handle EXIF orientation tags automatically:

from PIL import Image, ImageOps


original_image = Image.open(filename)


fixed_image = ImageOps.exif_transpose(original_image)

I fix the same issue with ImageOps:

from PIL import Image, ImageOps


img = Image.open(filename)
img = ImageOps.exif_transpose(img)
img.thumbnail((1000,1000), Image.ANTIALIAS)
img.save(output_fname, "JPEG")

More: https://pillow.readthedocs.io/en/stable/reference/ImageOps.html#PIL.ImageOps.exif_transpose