2010-11-19 10 views
37

Estoy intentando tomar imágenes grandes (grandes) (desde una cámara digital) y convertirlas en algo que pueda mostrar en la web. Esto parece sencillo, y probablemente debería serlo. Sin embargo, cuando intento usar PIL para crear versiones de miniaturas, si la imagen de origen es más alta que ancha, la imagen resultante se gira 90 grados, de modo que la parte superior de la imagen de origen está a la izquierda de la imagen resultante. Si la imagen de origen es más ancha que alta, la imagen resultante es la orientación correcta (original). ¿Podría tener que ver con la 2-tupla que envío como el tamaño? Estoy usando miniatura, porque parece que fue para preservar la relación de aspecto. ¿O estoy siendo completamente ciego y haciendo algo tonto? La tupla de tamaño es 1000,1000 porque quiero que el lado más largo se reduzca a 1000 píxeles, mientras se conserva el AR.miniatura PIL está girando mi imagen?

Código parece simple

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

Gracias de antemano por cualquier ayuda.

Respuesta

7

Tenga en cuenta que hay mejores respuestas a continuación.


Cuando una imagen es más alto que ancho, significa que la cámara se gira. Algunas cámaras pueden detectar esto y escribir esa información en los metadatos EXIF ​​de la imagen. Algunos espectadores toman nota de estos metadatos y muestran la imagen de manera apropiada.

PIL puede leer los metadatos de la imagen, pero no escribe/copia los metadatos cuando guarda una Imagen. En consecuencia, su visor de imágenes inteligente no girará la imagen como lo hacía antes.

Siguiendo con el comentario de @Ignacio Vazquez-Abrams, puede leer los metadatos utilizando PIL esta manera, y girar si es necesario:

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

Pero tenga en cuenta que el código anterior puede no funcionar para todas las cámaras.

La solución más fácil quizás sea utilizar algún otro programa para hacer miniaturas.

phatch es un editor de fotos por lotes escrito en Python que puede manejar/preservar metadatos EXIF. Puede usar este programa para hacer sus miniaturas o ver su código fuente para ver cómo hacerlo en Python. Creo que usa el pyexiv2 para manejar los metadatos EXIF. pyexiv2 puede manejar EXIF ​​mejor que el módulo ExifTag de PIL.

imagemagick es otra posibilidad para hacer miniaturas por lotes.

+0

O para leer los datos EXIF ​​de antemano y aplicar la transformación de forma manual. –

+0

Gracias a ambos por sus respuestas. Estoy intentando eliminar todos los datos EXIF, pero luego vuelvo a agregar los datos si es necesario rotarlos. Esto se está convirtiendo en mucho más de un PITA de lo que originalmente pensé que sería. Solo cuestión de elaborar el guión para hacerlo ahora. ¡Gracias de nuevo! – Hoopes

+0

Como está cambiando de tamaño, probablemente no le importe, pero no olvide que incluso una simple rotación a veces es una operación con pérdida en jpegs. –

51

Estoy de acuerdo con casi todo como contestada por "unutbu" e Ignacio Vazquez-Abrams, sin embargo ...

EXIF ​​bandera de orientación puede tener un valor entre 1 y 8 dependiendo de cómo se llevó a cabo la cámara.

La fotografía de retrato puede tomarse con la parte superior de la cámara en el borde izquierdo o derecho, la fotografía horizontal puede tomarse boca abajo.

Aquí es código que tiene esto en cuenta (Probado con réflex digital 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() 
+0

Este es el correcto, mejor que la respuesta aceptada. –

+0

Tenga en cuenta que esto da como resultado una estratagema si se utiliza para archivos que no sean JPEG o si los datos EXIF ​​no están presentes. –

+1

¿Hay algún problema con la sangría de este fragmento de código? –

6

Hoopes respuesta es grande, pero es mucho más eficiente de utilizar el método de transposición en vez de rotar.Girar hace un cálculo real filtrado para cada píxel, de hecho un cambio de tamaño complejo de la imagen completa. Además, la biblioteca PIL actual parece tener un error en el que se agrega una línea negra a los bordes de las imágenes giradas. Transpose es MUCHO más rápido y carece de ese error. Acabo de ajustar la respuesta de hoopes para usar transposición en su lugar. respuesta

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

puede destruir ese condicional hasta if exif[orientation] in [3,6,8]: image = image.transpose(Image.ROTATE_180)

28

de xilvar es muy agradable, pero tenía dos defectos menores que quería fijar en una edición rechazado, así que voy a publicar como una respuesta.

Por un lado, la solución de xilvar falla si el archivo no es JPEG o si no hay datos exif presentes. Y para el otro, siempre giraba 180 grados en lugar de la cantidad apropiada.

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

Yo usaría 'orientation = exif.get (orientación, Ninguno)' y luego 'si la orientación es None: return' y agregaría algunos registros que posiblemente exilien la imagen exif. No digo que pueda causar errores a todos, pero me pasó a mí y puede ser muy raro. –

+0

Usaría 'orientation = next (k para k, v en ExifTags.TAGS.items() si v == 'Orientation')' ya que este script depende de esta etiqueta y el 'ExifTags.py' de PIL parece tenerlo. – kirpit

19

Aquí hay una versión que funcione para todas las orientaciones 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 
+0

Gran solución. Trabajar como un encanto. –

+0

Hermoso. Gracias, funcionó a la perfección. –

+0

Esto funciona, pero me pregunto si hay algo más de estilo OOP que se pueda hacer aquí, como agregar a la clase Image de PIL. Además, no creo que sea una buena idea tener tanto código en un bloque de prueba. Usted tiene varias cosas que pueden fallar y sería bueno saber en mi humilde opinión. – Tatsh

1

Hola yo estaba tratando de lograr la rotación de la imagen y gracias a las respuestas anteriores en este post lo hice. Pero actualicé la solución y me gustaría compartirla. Espero que alguien encuentre esto útil.

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 

Uso:

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

yo soy un novato a la programación, Python y PIL por lo que los ejemplos de código de las respuestas anteriores parecen complicadas para mí. En lugar de iterar a través de las etiquetas, simplemente fui directamente a la clave de la etiqueta. En el intérprete de Python, se puede ver la clave de que la orientación es 274.

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

utilizo la función image._getexif() a agarrar lo exiftags están en la imagen. Si la etiqueta de orientación no está presente, arroja un error, entonces uso try/except.

La documentación de Pillow dice que no hay diferencia en el rendimiento o los resultados entre la rotación y la transposición. Lo he confirmado sincronizando ambas funciones. Uso rotar porque es más conciso.

rotate(90) gira en sentido antihorario. La función parece aceptar grados negativos.

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 
19

Sintiéndose obligado a compartir mi versión, que es funcionalmente idéntico a los sugeridos en otras respuestas, sin embargo, es, en mi opinión, más limpio:

import functools 

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 (AttributeError, TypeError, KeyError, IndexError): 
     return im 
    else: 
     return functools.reduce(lambda im, op: im.transpose(op), seq, im) 

También en mi caso en particular, que es sirviendo miniaturas de imágenes usando Django y Nginx, encuentro que el encabezado HTTP X-Accel-Redirect es muy conveniente. Permite que el código de Python guarde la miniatura en una ubicación particular y luego la pasa a Nginx, por lo que se trata de enviarla al cliente, liberando el código de Python que requiere más recursos para tareas más complejas que esa.

+0

gracias por compartir :) –

+1

solo falta 'import functools', aún debería haber sido la respuesta aceptada que funciona de la caja en Python 2.7 –

+2

Felicitaciones a usted. Esta es la respuesta más completa, utilizando las herramientas más adecuadas. Otras respuestas utilizan 'rotate' en lugar de' transpose' o carecen de los ocho estados posibles. La orientación de salida JPEG es para transposición, no para rotación. http://jpegclub.org/exif_orientation.html – OdraEncoded

0

Aquí hay algunas buenas respuestas, solo quería publicar una versión limpia ... La función asume que ya ha hecho Image.open() en algún lado, y hará image.save() en otro lugar y solo quiere una función que puede agregar para arreglar la rotación.

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 
1

necesitaba una solución que se encarga de todas las orientaciones, no sólo 3, 6 y 8.

Probé el solution de Roman Odaisky - se veía completo y limpio. Sin embargo, probarlo con imágenes reales con varios valores de orientación a veces dio lugar a resultados erróneos (por ejemplo, this one con orientación establecida en 0).

Otro viable solution podría ser Dobes Vandermeer's. Pero no lo he intentado, porque creo que uno puede escribir la lógica más simplemente (que yo prefiero).

Así que sin más preámbulos, aquí está una más fácil de mantener (en mi opinión) versión más simple,:

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 

probado y encontrado para trabajar con imágenes que tienen todas las orientaciones mencionadas EXIF. Sin embargo, también haga sus propias pruebas también.

Cuestiones relacionadas