Python PIL缩略图正在旋转我的图像?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4228530/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
PIL thumbnail is rotating my image?
提问by Hoopes
I'm attempting to take large (huge) images (from a digital camera), and convert them into something that I can display on the web. This seems straightforward, and probably should be. However, when I attempt to use PIL to create thumbnail versions, if my source image is taller than it is wide, the resulting image is rotated 90 degrees, such that the top of the source image is on the left of the resulting image. If the source image is wider than it is tall, the resulting image is the correct (original) orientation. Could it have to do with the 2-tuple I send in as the size? I'm using thumbnail, because it appears it was meant to preserve the aspect ratio. Or am I just being completely blind, and doing something dumb? The size tuple is 1000,1000 because I want the longest side to be shrunk to 1000 pixels, while keeping AR preserved.
我正在尝试拍摄大型(巨大)图像(来自数码相机),并将它们转换为可以在网络上显示的内容。这看起来很简单,而且可能应该如此。但是,当我尝试使用 PIL 创建缩略图版本时,如果我的源图像的高度大于宽度,则生成的图像将旋转 90 度,这样源图像的顶部位于生成的图像的左侧。如果源图像的宽度大于高度,则生成的图像是正确的(原始)方向。它可能与我作为大小发送的 2 元组有关吗?我正在使用缩略图,因为它似乎是为了保留纵横比。或者我只是完全失明,做了一些愚蠢的事情?大小元组是 1000,1000,因为我希望最长边缩小到 1000 像素,同时保留 AR。
Code seems simple
代码看起来很简单
img = Image.open(filename)
img.thumbnail((1000,1000), Image.ANTIALIAS)
img.save(output_fname, "JPEG")
Thanks in advance for any help.
在此先感谢您的帮助。
采纳答案by unutbu
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.
当图片的高度大于宽度时,表示相机已旋转。一些相机可以检测到这一点并将该信息写入图片的 EXIF 元数据中。一些观众会注意到这个元数据并适当地显示图像。
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.
PIL 可以读取图片的元数据,但在您保存图像时不会写入/复制元数据。因此,您的智能图像查看器不会像以前那样旋转图像。
Following up on @Ignacio Vazquez-Abrams's comment, you can read the metadata using PIL this way, and rotate if necessary:
跟进@Ignacio Vazquez-Abrams 的评论,您可以通过这种方式使用 PIL 读取元数据,并在必要时进行旋转:
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.
最简单的解决方案可能是使用其他一些程序来制作缩略图。
phatchis 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 pyexiv2to handle the EXIF metadata. pyexiv2 may be able to handle EXIF better than the PIL's ExifTags module.
phatch是一个用 Python 编写的批量照片编辑器,可以处理/保留 EXIF 元数据。您可以使用此程序制作缩略图,也可以查看其源代码以了解如何在 Python 中执行此操作。我相信它使用pyexiv2来处理 EXIF 元数据。pyexiv2 可能能够比 PIL 的 ExifTags 模块更好地处理 EXIF。
imagemagickis another possibility for making batch thumbnails.
imagemagick是制作批量缩略图的另一种可能性。
回答by storm_to
I agree with almost everything as answered by "unutbu" and Ignacio Vazquez-Abrams, however...
我同意“unutbu”和 Ignacio Vazquez-Abrams 回答的几乎所有内容,但是......
EXIF Orientation flag can have a value between 1 and 8 depending on how the camera was held.
EXIF 方向标志的值可以在 1 到 8 之间,具体取决于相机的握持方式。
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)
这是考虑到这一点的代码(使用数码单反相机尼康 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()
回答by xilvar
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.
Hoopes 的回答很好,但使用转置方法而不是旋转方法效率更高。Rotate 对每个像素进行实际的滤波计算,有效地对整个图像进行复杂的大小调整。此外,当前的 PIL 库似乎有一个错误,即在旋转图像的边缘添加了一条黑线。转置要快很多,而且没有那个错误。我只是调整了 hoopes 答案以使用转置代替。
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()
回答by Maik Hoepfel
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.
xilvar 的回答非常好,但有两个小缺点我想在被拒绝的编辑中修复,所以我将其作为答案发布。
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.
一方面,如果文件不是 JPEG 或不存在 exif 数据,则 xilvar 的解决方案将失败。而对于另一个,它总是旋转 180 度而不是适当的量。
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()
回答by Dobes Vandermeer
Here's a version that works for all 8 orientations:
这是一个适用于所有 8 个方向的版本:
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
回答by Bartosz Dabrowski
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')
...
回答by FeFiFoFu
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.
我是编程、Python 和 PIL 的菜鸟,所以前面答案中的代码示例对我来说似乎很复杂。我没有遍历标签,而是直接找到了标签的键。在 python shell 中,您可以看到方向的键是 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.
我使用该image._getexif()函数来获取图像中的 ExifTags。如果方向标签不存在,它会抛出一个错误,所以我使用 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.
Pillow 的文档说旋转和转置之间的性能或结果没有区别。我已经通过对这两个功能进行计时来确认这一点。我使用旋转是因为它更简洁。
rotate(90)rotates counter-clockwise. The function seems to accept negative degrees.
rotate(90)逆时针旋转。该函数似乎接受负度数。
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
回答by Roman Odaisky
Feeling compelled to share my version, which is functionally identical to ones suggested in other answers, yet is, in my opinion, cleaner:
感觉不得不分享我的版本,它在功能上与其他答案中建议的版本相同,但在我看来,它更清晰:
import functools
from PIL import Image
def image_transpose_exif(im):
"""
Apply Image.transpose to ensure 0th row of pixels is at the visual
top of the image, and 0th column is the visual left-hand side.
Return the original image if unable to determine the orientation.
As per CIPA DC-008-2012, the orientation field contains an integer,
1 through 8. Other values are reserved.
Parameters
----------
im: PIL.Image
The image to be rotated.
"""
exif_orientation_tag = 0x0112
exif_transpose_sequences = [ # Val 0th row 0th col
[], # 0 (reserved)
[], # 1 top left
[Image.FLIP_LEFT_RIGHT], # 2 top right
[Image.ROTATE_180], # 3 bottom right
[Image.FLIP_TOP_BOTTOM], # 4 bottom left
[Image.FLIP_LEFT_RIGHT, Image.ROTATE_90], # 5 left top
[Image.ROTATE_270], # 6 right top
[Image.FLIP_TOP_BOTTOM, Image.ROTATE_90], # 7 right bottom
[Image.ROTATE_90], # 8 left bottom
]
try:
seq = exif_transpose_sequences[im._getexif()[exif_orientation_tag]]
except Exception:
return im
else:
return functools.reduce(type(im).transpose, seq, im)
回答by orion11
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.
这里有一些很好的答案,我只是想发布一个清理过的版本......该函数假设您已经在某处完成了 Image.open(),并且将在其他地方执行 image.save() 并且只想要一个您可以使用的函数插入以修复旋转。
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
回答by Hassan Baig
I needed a solution that takes care of all orientations, not just 3, 6and 8.
我需要一个解决所有方向的解决方案,而不仅仅是3,6和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 onewith orientation set to 0).
我尝试了 Roman Odaisky 的解决方案- 它看起来全面而干净。然而,与有时会导致错误的结果(如各个方位值实际图像测试它这样一个有方向设置为0)。
Another viable solutioncould be Dobes Vandermeer's. But I haven't tried it, because I feel one can write the logic more simply (which I prefer).
另一个可行的解决方案可能是 Dobes Vandermeer's。但是我没有尝试过,因为我觉得可以更简单地编写逻辑(我更喜欢)。
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.
经过测试,发现可以处理具有所有提到的 exif 方向的图像。但是,也请进行自己的测试。

