web-dev-qa-db-fra.com

PIL thumbnail fait pivoter mon image?

J'essaie de prendre de grandes images (énormes) (d'un appareil photo numérique) et de les convertir en quelque chose que je peux afficher sur le Web. Cela semble simple et devrait probablement l'être. Cependant, lorsque j'essaie d'utiliser PIL pour créer des versions miniatures, si l'image source est plus haute que large, l'image résultante est pivotée de 90 degrés, de sorte que le haut de l'image source se trouve à gauche de l'image résultante. Si l'image source est plus large que haute, l'image résultante présente l'orientation correcte (d'origine). Cela pourrait-il avoir à voir avec le 2-Tuple que j'envoie en tant que taille? J'utilise la vignette, car il semble que l'objectif était de préserver les proportions. Ou suis-je juste complètement aveugle et fais quelque chose de bête? La taille Tuple est 1000,1000 parce que je veux que le côté le plus long soit réduit à 1000 pixels, tout en conservant les AR.

Le code semble simple

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

Merci d'avance pour votre aide.

39
Hoopes

Veuillez noter qu'il existe de meilleures réponses ci-dessous.


Quand une image est plus haute que large, cela signifie que la caméra a été tournée. Certaines caméras peuvent détecter cela et écrire ces informations dans les métadonnées EXIF ​​de l'image. Certains spectateurs prennent note de ces métadonnées et affichent l'image de manière appropriée.

PIL peut lire les métadonnées de l'image, mais il ne les écrit/copie pas lorsque vous enregistrez une image. Par conséquent, votre visionneuse d'images intelligente ne fera pas pivoter l'image comme auparavant.

Pour faire suite au commentaire de @Ignacio Vazquez-Abrams, vous pouvez lire les métadonnées à l’aide de PIL de cette façon et effectuer une rotation si nécessaire:

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

Mais sachez que le code ci-dessus peut ne pas fonctionner pour toutes les caméras.

La solution la plus simple consiste peut-être à utiliser un autre programme pour créer des vignettes.

phatch est un éditeur de photos par lot écrit en Python qui peut gérer/préserver les métadonnées EXIF. Vous pouvez soit utiliser ce programme pour créer vos vignettes, soit consulter son code source pour savoir comment procéder ainsi en Python. Je crois qu’il utilise le pyexiv2 pour traiter les métadonnées EXIF. pyexiv2 est peut-être capable de gérer EXIF ​​mieux que le module ExifTags de PIL.

imagemagick est une autre possibilité de créer des vignettes de lot.

12
unutbu

Je suis d'accord avec presque tout ce que "unutbu" et Ignacio Vazquez-Abrams ont répondu, mais ...

L'indicateur d'orientation EXIF ​​peut avoir une valeur comprise entre 1 et 8 en fonction de la manière dont la caméra a été tenue.

Une photo de portrait peut être prise avec le haut de l'appareil photo à gauche ou à droite, une photo de paysage peut être prise à l'envers.

Voici le code qui en tient compte (Testé avec le reflex numérique 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()
61
storm_to

la réponse de xilvar est très gentille, mais il y avait deux lacunes mineures que je voulais corriger dans une édition refusée. Je vais donc la poster comme réponse.

D'une part, la solution de xilvar échoue si le fichier n'est pas un fichier JPEG ou en l'absence de données exif. Et pour les autres, il faisait toujours pivoter de 180 degrés au lieu de la quantité appropriée.

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()
32
Maik Hoepfel

Se sentir obligé de partager ma version, qui est fonctionnellement identique à celles suggérées dans d’autres réponses, mais qui, à mon avis, est plus propre:

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)
25
Roman Odaisky

Voici une version qui fonctionne pour les 8 orientations:

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
20
Dobes Vandermeer

Hoopes répond que c’est génial, mais il est beaucoup plus efficace d’utiliser la méthode de transposition que de faire pivoter. Rotation effectue un calcul filtré réel pour chaque pixel, ce qui correspond effectivement à un redimensionnement complexe de l’ensemble de l’image. En outre, la bibliothèque PIL actuelle semble contenir un bogue dans lequel une ligne noire est ajoutée aux bords des images pivotées. Transposer est BEAUCOUP plus rapide et manque de ce bogue. Je viens de peaufiner les cintres répondre pour utiliser transposer à la place.

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
xilvar

Je suis un habitué de la programmation, Python et PIL, les exemples de code des réponses précédentes me paraissent donc compliqués. Au lieu de parcourir les balises, je me suis contenté de lui donner la clé. Dans le shell Python, vous pouvez voir que la clé de cette orientation est 274. 

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

J'utilise la fonction image._getexif() pour récupérer ce que sont les ExifTags dans l'image. Si la balise d'orientation n'est pas présente, une erreur est générée. J'utilise donc try/except.

La documentation de Pillow indique qu'il n'y a pas de différence de performance ou de résultat entre rotation et transposition. Je l'ai confirmé en chronométrant les deux fonctions. J'utilise rotation parce que c'est plus concis.

rotate(90) tourne dans le sens anti-horaire. La fonction semble accepter les degrés négatifs.

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
5
FeFiFoFu

Bonjour, j'essayais d'obtenir une rotation de l'image et, grâce aux réponses précédentes, je l'ai fait. Mais j'ai mis à jour la solution et j'aimerais la partager. J'espère que quelqu'un trouvera cela utile. 

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

Utilisation:

>>> 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
Bartosz Dabrowski

J'avais besoin d'une solution prenant en compte toutes les orientations, pas seulement 3, 6 et 8

J'ai essayé la solution de Roman Odaisky - elle avait l'air complète et propre. Cependant, le tester avec des images réelles avec différentes valeurs d'orientation entraînait parfois des résultats erronés (par exemple, celui-ci avec l'orientation définie sur 0).

Une autre { solution viable } _ pourrait être Dobes Vandermeer. Mais je ne l'ai pas essayé, car je sens que l'on peut écrire la logique plus simplement (ce que je préfère). 

Donc, sans plus tarder, voici une version plus simple et plus maintenable (à mon avis):

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

Testé et trouvé pour travailler sur des images avec toutes les orientations exif mentionnées. Cependant, veuillez également faire vos propres tests.

1
Hassan Baig

En ajoutant aux autres réponses, j'avais des problèmes parce que j'utiliserais im.copy() avant d'exécuter les fonctions - cela effacerait les données exif nécessaires. Avant d'exécuter im.copy(), assurez-vous de sauvegarder les données exif:

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

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

if exif:
    im = transpose_im(im, exif)
0
raphaelrk

Il y a quelques bonnes réponses ici, je voulais simplement poster une version nettoyée ... La fonction suppose que vous avez déjà fait Image.open () quelque part fonction que vous pouvez déposer pour corriger la rotation. 

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
orion11