Миниатюра PIL вращает мое изображение? - PullRequest
40 голосов
/ 19 ноября 2010

Я пытаюсь взять большие (огромные) изображения (с цифровой камеры) и преобразовать их во что-то, что я могу показать в Интернете.Это кажется простым и, вероятно, должно быть.Однако, когда я пытаюсь использовать PIL для создания миниатюрных версий, если мое исходное изображение выше, чем его ширина, результирующее изображение поворачивается на 90 градусов, так что верхняя часть исходного изображения находится слева от полученного изображения.Если исходное изображение шире, чем оно высокое, полученное изображение имеет правильную (исходную) ориентацию.Может ли это быть связано с 2-мя кортежами, которые я посылаю в качестве размера?Я использую миниатюру, потому что, похоже, она должна была сохранить соотношение сторон.Или я просто полностью ослеп и делаю что-то глупое?Размер кортежа равен 1000, 1000, потому что я хочу, чтобы самая длинная сторона была сокращена до 1000 пикселей, сохраняя при этом AR.

Код кажется простым

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

Заранее благодарен за любую помощь.

Ответы [ 11 ]

62 голосов
/ 02 июня 2011

Я согласен почти со всем, что ответили "Унутбу" и Игнасио Васкес-Абрамс, однако ...

Флаг ориентации EXIF ​​может иметь значение от 1 до 8 в зависимости от того, как камера удерживается.

Портретная фотография может быть сделана верхней частью камеры слева или правым краем, пейзажная фотография может быть сделана вверх ногами.

Вот код, который учитывает это (протестировано с 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()
32 голосов
/ 18 июля 2012

Ответ xilvar очень хороший, но у него было два небольших недостатка, которые я хотел исправить в отклоненном редактировании, поэтому я опубликую его как ответ.

С одной стороны, решение xilvar не будет выполнено, если файл неЭто JPEG или если нет данных exif.А для другого он всегда поворачивается на 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()
27 голосов
/ 26 мая 2015

Чувствую себя вынужденным поделиться моей версией, которая функционально идентична тем, что были предложены в других ответах, но, на мой взгляд, чище:

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)
20 голосов
/ 08 января 2014

Вот версия, которая работает для всех 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
13 голосов
/ 19 ноября 2010

Обратите внимание, что ниже приведены лучшие ответы.


Если изображение выше, чем его ширина, это означает, что камера была повернута. Некоторые камеры могут обнаружить это и записать эту информацию в метаданные 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. Я полагаю, что для обработки метаданных EXIF ​​используется pyexiv2 . pyexiv2 может обрабатывать EXIF ​​лучше, чем модуль ExifTags в PIL.

imagemagick - еще одна возможность для создания миниатюр партий.

6 голосов
/ 09 марта 2012

Ответ Hoopes отличный, но гораздо эффективнее использовать метод транспонирования, чем вращать. Rotate выполняет фактический отфильтрованный расчет для каждого пикселя, эффективно изменяя размер всего изображения. Кроме того, в текущей библиотеке PIL, похоже, есть ошибка, из-за которой к краям повернутых изображений добавляется черная линия. Транспонирование намного быстрее и не содержит этой ошибки. Я просто подправил ответ обручей, чтобы использовать вместо него транспонирование.

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()
5 голосов
/ 31 декабря 2014

Я новичок в программировании, Python и PIL, поэтому примеры кода в предыдущих ответах кажутся мне сложными. Вместо того, чтобы перебирать теги, я просто пошел к нему ключом тега. В оболочке python вы можете видеть, что ключ ориентации - 274.

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

Я использую функцию image._getexif(), чтобы получить изображения ExifTags на изображении. Если тег ориентации отсутствует, он выдает ошибку, поэтому я использую try / исключением.

Документация подушки говорит, что нет никакой разницы в производительности или результатах между поворотом и транспонированием. Я подтвердил это, выбрав обе функции. Я использую вращение, потому что оно более лаконично.

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
1 голос
/ 08 февраля 2018

Мне нужно решение, которое учитывает все ориентации, а не только 3, 6 и 8.

Я попробовал решение Романа Одайского - оно выглядело полным и чистым. Однако тестирование его с реальными изображениями с различными значениями ориентации иногда приводило к ошибочным результатам (например, это с ориентацией, установленной на 0).

Другим жизнеспособным решением может быть Добес Вандермер. Но я не пробовал, потому что чувствую, что можно написать логику проще (что я предпочитаю).

Итак, без лишних слов, вот более простая, более понятная (на мой взгляд) версия:

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. Однако, пожалуйста, сделайте также свои собственные тесты.

1 голос
/ 05 февраля 2014

Здравствуйте, я пытался добиться поворота изображения, и благодаря предыдущим ответам в этом посте я сделал это. Но я обновил решение и хотел бы поделиться им. Я надеюсь, что кто-то найдет это полезным.

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')
...
0 голосов
/ 16 августа 2018

В дополнение к другим ответам у меня возникли проблемы, потому что я бы использовал 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)
...