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

Hoo*_*pes 44 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")
Run Code Online (Sandbox Code Playgroud)

在此先感谢您的帮助.

sto*_*_to 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()
Run Code Online (Sandbox Code Playgroud)

  • 为什么要遍历所有标签,它不是总是相同的键吗? (3认同)
  • “fororientation...break”逻辑块的目的是什么? (2认同)

Mai*_*fel 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()
Run Code Online (Sandbox Code Playgroud)


Rom*_*sky 31

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

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)
Run Code Online (Sandbox Code Playgroud)

  • 感谢你.这是最完整的答案,使用最合适的工具.其他答案要么使用`rotate`而不是`transpose`,要么缺少所有八种可能的状态.JPEG退出方向用于移调,而不是用于旋转.http://jpegclub.org/exif_orientation.html (3认同)
  • 只有`import functools`丢失了,仍然应该是Python 2.7中开箱即用的可接受答案。 (2认同)

Dob*_*eer 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
Run Code Online (Sandbox Code Playgroud)


Jac*_*ing 15

Pillow 有一个 API可以自动处理 EXIF 方向标签:

from PIL import Image, ImageOps

original_image = Image.open(filename)

fixed_image = ImageOps.exif_transpose(original_image)
Run Code Online (Sandbox Code Playgroud)


unu*_*tbu 13

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


当图片高于宽度时,表示相机已旋转.有些相机可以检测到这一点并在图片的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")
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

  • 我赞成处理所有 8 个方向的版本。此外,这里有一组很棒的测试图像 https://github.com/recurser/exif-orientation-examples 来自 Dave Perrett。 (2认同)

Has*_*aig 10

我需要一个解决所有方向的解决方案,而不仅仅是3,68

我尝试了 Roman Odaisky 的解决方案- 它看起来全面而干净。然而,与有时会导致错误的结果(如各个方位值实际图像测试它这样一个有方向设置为0)。

另一个可行的解决方案可能是 Dobes Vandermeer's。但是我没有尝试过,因为我觉得可以更简单地编写逻辑(我更喜欢)。

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

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
Run Code Online (Sandbox Code Playgroud)

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


小智 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()
Run Code Online (Sandbox Code Playgroud)

  • 如果[3,6,8]中的exif [orientation],你可以粉碎那个条件为<code>:image = image.transpose(Image.ROTATE_180)</ code> (2认同)

FeF*_*oFu 5

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

>>>from PIL import ExifTags
>>>ExifTags.TAGS
Run Code Online (Sandbox Code Playgroud)

我使用该image._getexif()函数来获取图像中的ExifTags.如果不存在方向标记,则会抛出错误,因此我使用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
Run Code Online (Sandbox Code Playgroud)