PIL thumbnail obraca moje zdjęcie?

Próbuję zrobić duże (ogromne) zdjęcia (z aparatu cyfrowego) i przekształcić je w coś, co mogę wyświetlić w Internecie. Wydaje się to proste i prawdopodobnie powinno być. Jednak, gdy próbuję użyć PIL do tworzenia miniaturek, jeśli mój obraz źródłowy jest wyższy niż szeroki, wynikowy obraz jest obrócony o 90 stopni, tak że góra obrazu źródłowego znajduje się po lewej stronie wynikowego obrazu. Jeśli obraz źródłowy jest szerszy niż wysoki, wynikający obraz jest prawidłową (oryginalną) orientacją. Czy to może mieć związek z 2 krotką, którą wysłałem jako rozmiar? Używam miniaturki, ponieważ wygląda na to, że miała ona zachować proporcje. Czy po prostu jestem ślepa i robię coś głupiego? Rozmiar krotki to 1000,1000, ponieważ chcę, aby najdłuższa strona została zmniejszona do 1000 pikseli, zachowując AR.

Kod wydaje się prosty

img = Image.open(filename)
img.thumbnail((1000,1000), Image.ANTIALIAS)
img.save(output_fname, "JPEG")
Z góry dzięki za pomoc.
Author: Hoopes, 2010-11-19

13 answers

Proszę zauważyć, że poniżej znajdują się lepsze odpowiedzi.


Gdy obraz jest wyższy niż szeroki, oznacza to, że kamera została obrócona. Niektóre Kamery Mogą to wykryć i zapisać te informacje w metadanych EXIF obrazu. Niektórzy widzowie odnotowują te metadane i odpowiednio wyświetlają obraz.

PIL może odczytywać metadane obrazu, ale nie zapisuje/nie kopiuje metadanych podczas zapisywania obrazu. W związku z tym inteligentna przeglądarka obrazów nie będzie obracać obrazu w taki sposób, jak to miało miejsce wcześniej.

Kontynuując komentarz @ Ignacio Vazquez-Abrams, możesz odczytać metadane używając PIL w ten sposób i obracać w razie potrzeby:

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

Należy jednak pamiętać, że powyższy kod może nie działać dla wszystkich kamer.

Najprostszym rozwiązaniem może być użycie innego programu do tworzenia miniaturek.

Phatch to wsadowy edytor zdjęć napisany w Pythonie, który może obsługiwać / przechowywać metadane EXIF. Możesz albo użyć tego programu do tworzenia miniaturek, albo spojrzeć na jego kod źródłowy, aby zobaczyć, jak to zrobić w Pythonie. Wydaje mi się, że używa pyexiv2 do obsługi metadanych EXIF. pyexiv2 może być w stanie obsłużyć Exif lepiej niż Moduł ExifTags PIL.

Imagemagick to kolejna możliwość tworzenia miniatur wsadowych.

 19
Author: unutbu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-29 19:37:11

Zgadzam się z prawie wszystkim, na co odpowiedzieli jednak" unutbu " i Ignacio Vazquez-Abrams...

Znacznik orientacji EXIF może mieć wartość od 1 do 8 w zależności od sposobu trzymania kamery.

Zdjęcie portretowe można zrobić górną częścią aparatu po lewej lub prawej krawędzi, zdjęcie krajobrazowe można zrobić do góry nogami.

Oto kod, który bierze to pod uwagę (testowany z lustrzanką 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()
 73
Author: storm_to,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-02 18:05:44

Czuję się zmuszony do podzielenia się moją wersją, która jest funkcjonalnie identyczna z tą sugerowaną w innych odpowiedziach, ale jest moim zdaniem czystsza:

import functools

from PIL import Image

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.

    Parameters
    ----------
    im: PIL.Image
       The image to be rotated.
    """

    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)
 44
Author: Roman Odaisky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-05-29 13:01:10

ODPOWIEDŹ Xilvara jest bardzo ładna, ale miała dwa drobne niedociągnięcia, które chciałem naprawić w odrzuconej edycji, więc wrzucę ją jako odpowiedź.

Po pierwsze, rozwiązanie xilvara nie powiedzie się, jeśli plik nie jest JPEG lub jeśli nie ma danych exif. A dla drugiego zawsze obracał się o 180 stopni zamiast odpowiedniej ilości.

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()
 38
Author: Maik Hoepfel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-21 17:50:08

Oto wersja, która działa dla wszystkich 8 orientacji:

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
 21
Author: Dobes Vandermeer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-07 23:10:54

Jestem noobem do programowania, Pythona i PIL więc przykłady kodu w poprzednich odpowiedziach wydają mi się skomplikowane. Zamiast przeglądać tagi, po prostu poszedłem prosto do klucza tagu. W powłoce Pythona widać, że kluczem orientation jest 274.

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

Używam funkcji image._getexif(), Aby pobrać to, co Exiftagi są na obrazku. Jeśli znacznik orientacji nie jest obecny, wyświetla błąd, więc używam try / except.

Dokumentacja poduszki mówi, że nie ma różnicy w wydajność lub Wyniki między rotacją a transpozycją. Potwierdziłem to, synchronizując obie funkcje. Używam rotate, ponieważ jest bardziej zwięzły.

rotate(90) obraca się w kierunku przeciwnym do ruchu wskazówek zegara. Funkcja zdaje się przyjmować ujemne stopnie.

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
Author: FeFiFoFu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-31 06:52:44

Potrzebowałem rozwiązania, które zajmie się wszystkimi orientacjami, nie tylko 3, 6 i 8.

Próbowałem rozwiązania Romana Odaisky ' ego - wyglądało to kompleksowo i czysto. Jednak testowanie go z rzeczywistymi obrazami o różnych wartościach orientacji czasami prowadziło do błędnych wyników (np. Ten z orientacją ustawioną na 0).

Inne realne rozwiązanie może być Dobes Vandermeer. ale nie próbowałem, bo czuję, że można napisać logikę więcej po prostu (co wolę).

Więc bez zbędnych ceregieli, oto prostsza, łatwiejsza do utrzymania (moim zdaniem) wersja:

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

Przetestowano i okazało się, że działa na obrazach ze wszystkimi wymienionymi orientacjami exif. Jednak prosimy również o wykonanie własnych testów.

 7
Author: Hassan Baig,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-08 17:34:22

Pillow posiada API do obsługi znaczników orientacji EXIF automatycznie:

from PIL import Image, ImageOps

original_image = Image.open(filename)

fixed_image = ImageOps.exif_transpose(original_image)
 7
Author: Jace Browning,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-12-29 03:31:06

Odpowiedź Hoopes jest świetna, ale o wiele bardziej efektywne jest stosowanie metody transpozycji niż obracanie. Rotate wykonuje rzeczywiste przefiltrowane obliczenia dla każdego piksela, skutecznie złożoną zmianę rozmiaru całego obrazu. Ponadto, obecna biblioteka PIL wydaje się mieć błąd, w którym czarna linia jest dodawana do krawędzi obróconych obrazów. Transpozycja jest o wiele szybsza i brakuje tego błędu. Właśnie podkręciłem hoopes answer, aby zamiast tego użyć transpozycji.

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
Author: xilvar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-09 02:14:21

Dodając do innych odpowiedzi, miałem problemy, ponieważ używałem im.copy() przed uruchomieniem funkcji-to pozbawiłoby niezbędne dane exif. Upewnij się, że przed uruchomieniem im.copy() zapisujesz dane exif:

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

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

if exif:
    im = transpose_im(im, exif)
 3
Author: raphaelrk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-16 00:48:57

Witam starałem się osiągnąć rotację obrazu i dzięki poprzednim odpowiedziom w tym poście zrobiłem to. Ale ulepszyłem rozwiązanie i chciałbym się nim podzielić. Mam nadzieję, że komuś się to przyda.

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

Użycie:

>>> 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')
...
 1
Author: Bartosz Dabrowski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-05 16:00:02

Jest łatwiejsze podejście do tego wszystkiego:

    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)

Mam nadzieję, że to pomoże:)

 1
Author: Ignacio Bares,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-02-18 10:29:46

Jest tu kilka dobrych odpowiedzi, chciałem tylko zamieścić wersję oczyszczoną... Funkcja zakłada, że już zrobiłeś Obraz.otwórz gdzieś () i wykona obraz.Zapisz () gdzie indziej i po prostu chcesz funkcję, którą możesz wpaść, aby naprawić rotację.

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
 0
Author: orion11,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-13 20:25:13