Подтвердить что ты не робот

PIL thumbnail поворачивает мое изображение?

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

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

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

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

4b9b3361

Ответ 1

Обратите внимание, что ответы ниже.


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

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

Ответ 2

Я согласен с почти всем, что ответил "unutbu" и Ignacio Vazquez-Abrams, однако...

Флаг ориентации 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()

Ответ 3

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()

Ответ 4

Здесь версия, которая работает для всех 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

Ответ 5

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

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, я считаю, что HTTP-заголовок X-Accel-Redirect очень удобен. Он позволяет коду Python сохранять эскиз в определенном месте, а затем передавать его Nginx, поэтому он имеет дело с отправкой его клиенту, освобождая более ресурсоемкий код Python для более сложных задач, чем это.

Ответ 6

Ответ на уловки велик, но гораздо эффективнее использовать метод транспонирования, а не вращать. Поворот делает фактический отфильтрованный расчет для каждого пикселя, эффективно сложный размер всего изображения. Кроме того, в текущей библиотеке PIL появляется ошибка, в которой черная линия добавляется к краям повернутых изображений. Transpose намного быстрее и не хватает этой ошибки. Я просто настраивал 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()

Ответ 7

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

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

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

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

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

Ответ 8

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

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

Здесь есть несколько хороших ответов, я просто хотел опубликовать очищенную версию... Функция предполагает, что вы уже сделали 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