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

时间:2010-11-19 19:14:28

标签: python python-imaging-library

我正在尝试拍摄大(巨大)图像(来自数码相机),并将它们转换为我可以在网络上显示的内容。这似乎很简单,也许应该是。但是,当我尝试使用PIL创建缩略图版本时,如果我的源图像高于它的宽度,则生成的图像旋转90度,使得源图像的顶部位于结果图像的左侧。如果源图像宽度高于高图像,则生成的图像是正确的(原始)方向。它可能与我发送的2元组大小有关吗?我正在使用缩略图,因为它似乎是为了保持纵横比。或者我只是完全失明,做一些愚蠢的事情?大小元组是1000,1000,因为我希望最长边缩小到1000像素,同时保持AR。

代码似乎很简单

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

提前感谢您的帮助。

13 个答案:

答案 0 :(得分:62)

我同意“unutbu”和Ignacio Vazquez-Abrams回答的几乎所有内容,但是......

EXIF方向标志的值可以在1到8之间,具体取决于相机的握持方式。

可以在左侧或右边的相机顶部拍摄人像照片,可以将照片上下拍摄。

这是考虑到这一点的代码(使用DSLR尼康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()

答案 1 :(得分:32)

xilvar的回答非常好,但有两个小错误,我想在被拒绝的编辑中修复,所以我会将其作为答案发布。

首先,如果文件不是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()

答案 2 :(得分:26)

感觉被迫分享我的版本,其功能与其他答案中建议的相同,但在我看来,更清洁:

import Image
import functools

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.
    """

    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)

答案 3 :(得分:20)

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

答案 4 :(得分:12)

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


当图片比较宽时,表示相机已旋转。有些相机可以检测到这一点并在图片的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")

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

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

phatch是一个用Python编写的批处理照片编辑器,可以处理/保存EXIF元数据。您可以使用此程序制作缩略图,也可以查看其源代码以了解如何在Python中执行此操作。我相信它使用pyexiv2来处理EXIF元数据。 pyexiv2可以比PIL的ExifTags模块更好地处理EXIF。

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

答案 5 :(得分:6)

Hoope的答案很好,但使用转置方法而不是旋转效率要高得多。旋转对每个像素执行实际的滤波计算,实际上是整个图像的复杂大小调整。此外,当前的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()

答案 6 :(得分:5)

我是编程,Python和PIL的菜鸟,所以前面的答案中的代码示例对我来说似乎很复杂。我没有遍历标签,而是直接使用标签键。在python shell中,您可以看到该方向的键是274.

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

我使用image._getexif()函数来获取图像中的ExifTags。如果没有orientation标签,则会抛出错误,因此我使用try / except。

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

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

答案 7 :(得分:2)

枕头具有an API可自动处理EXIF方向标签:

from PIL import Image, ImageOps

images = Image.open(filename)  # original rotated image

image = ImageOps.exif_transpose(image)

答案 8 :(得分:1)

您好我试图实现图像的旋转,并且由于此帖子中的先前答案,我做到了。但我升级了解决方案并想分享它。我希望有人会觉得这很有用。

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

答案 9 :(得分:1)

我需要一个能够处理所有方向的解决方案,而不仅仅是368

我尝试了罗曼·奥迪斯基的solution - 它看起来很全面,干净。但是,使用具有各种方向值的实际图像对其进行测试有时会导致错误的结果(例如this one,方向设置为0)。

另一个viable solution可能是Dobes Vandermeer。但我还没有尝试过,因为我觉得可以更简单地编写逻辑(我更喜欢)。

所以,不用多说,这里有一个更简单,更易于维护的版本(在我看来):

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

经过测试,发现可以处理所有提到的exif方向的图像。但是,也请自己做一些测试。

答案 10 :(得分:1)

有一个更简单的方法可以解决所有问题:

    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)

我希望它会有所帮助:)

答案 11 :(得分:0)

这里有一些很好的答案,我只想发布一个清理过的版本...... 该函数假设您已经在某处完成了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

答案 12 :(得分:0)

除了其他答案外,我遇到了一些问题,因为在运行函数之前我会使用im.copy() -这会剥离必要的exif数据。确保在运行im.copy()之前保存exif数据:

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

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

if exif:
    im = transpose_im(im, exif)