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