PIL缩略图正在旋转我的图像?

我试图从数码相机拍摄大量(巨大的)图像,并将其转换为可以在networking上显示的图像。 这似乎很简单,可能应该是。 但是,当我尝试使用PIL创build缩略图版本时,如果我的源图像高于宽度,则生成的图像将旋转90度,以使源图像的顶部位于生成的图像的左侧。 如果源图像比较高,则生成的图像是正确的(原始)方向。 难道它与我发送的二元组有关吗? 我正在使用缩略图,因为它看起来是为了保持宽高比。 或者我只是完全失明,做一些愚蠢的事情? 大小元组为1000,1000,因为我想将最长的一面缩小到1000像素,同时保留AR。

代码似乎很简单

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

在此先感谢您的帮助。

请注意下面有更好的答案。


当图片比宽度更高时,表示相机已旋转。 一些相机可以检测到这一点,并在图片的EXIF元数据中写入信息。 一些观众注意到这个元数据并适当地显示图像。

PIL可以读取图片的元数据,但是在保存图像时不会写入/复制元数据。 因此,您的智能图像查看器将不会像以前那样旋转图像。

跟随@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") 

但请注意,上述代码可能不适用于所有相机。

最简单的解决scheme可能是使用其他程序来制作缩略图。

phatch是一个用Python编写的批量照片编辑器,可以处理/保存EXIF元数据。 你可以使用这个程序来制作缩略图,或者查看它的源代码,看看如何在Python中做到这一点。 我相信它使用pyexiv2来处理EXIF元数据。 pyexiv2可能比PIL的ExifTags模块更好地处理EXIF。

imagemagick是制作批量缩略图的另一种可能性。

我同意“unutbu”和Ignacio Vazquez-Abrams的回答,但是…

取决于相机的保持方式,EXIF方向标志的值可能在1到8之间。

可以在相机的左上方或右方拍摄人像照片,拍摄的照片可以颠倒。

这是考虑到这一点的代码(testing与数码单镜反光相机尼康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() 

xilvar的答案是非常好的,但有两个小的缺点,我想修复一个被拒绝的编辑,所以我会张贴它作为一个答案。

首先,如果文件不是JPEG格式,或者没有exif数据,xilvar的解决scheme将失败。 而另一方面,它总是旋转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() 

这是一个适用于所有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 

感觉不得不分享我的版本,这在function上与其他答案中提出的版本完全相同,但在我看来,更清晰:

 def image_transpose_exif(im): exif_orientation_tag = 0x0112 # contains an integer, 1 through 8 exif_transpose_sequences = [ # corresponding to the following [], [Image.FLIP_LEFT_RIGHT], [Image.ROTATE_180], [Image.FLIP_TOP_BOTTOM], [Image.FLIP_LEFT_RIGHT, Image.ROTATE_90], [Image.ROTATE_270], [Image.FLIP_TOP_BOTTOM, Image.ROTATE_90], [Image.ROTATE_90], ] try: seq = exif_transpose_sequences[im._getexif()[exif_orientation_tag] - 1] except Exception: return im else: return functools.reduce(lambda im, op: im.transpose(op), seq, im) 

另外在我的特殊用例中,使用Django和Nginx来提供图片缩略图,我发现X-Accel-Redirect HTTP头非常方便。 它允许Python代码将缩略图保存到特定的位置,然后将其传递给Nginx,以便将其发送到客户端,从而释放更多的资源密集型Python代码,以执行比这更复杂的任务。

Hoopes的答案很好,但使用转置方法而不是旋转效率更高。 旋转为每个像素执行实际的过滤计算,实际上是对整个图像进行复杂的调整。 此外,目前的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() 

我是编程,Python和PIL的一个小白,所以前面的答案中的代码示例看起来很复杂。 代替迭代标签,我直接去了标签的关键。 在python shell中,你可以看到方向键是274。

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

我使用image._getexif()函数来获取图像中的ExifTags。 如果方向标记不存在,则会引发错误,所以我使用try / except。

枕头的文档说旋转和转置之间的性能或结果没有区别。 我已经通过计时这两个函数来确认它。 我使用旋转,因为它更简洁。

rotate(90)逆时针旋转。 该function似乎接受负面的程度。

 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 

您好,我试图实现图像的旋转,并感谢以前的答复在这篇文章中,我做到了。 但我升级了解决scheme,并想分享它。 我希望有人会觉得这有用。

 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 

使用:

 >>> 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') ... 

这里有一些很好的答案,我只是想发布一个清理的版本…该函数假设你已经做了Image.open()的地方,并会做image.save()在其他地方,只是想要一个function,你可以放下来修复旋转。

 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