web-dev-qa-db-ja.com

PILサムネイルが画像を回転させていますか?

(デジタルカメラから)大きな(巨大な)画像を撮り、それらをWebに表示できるものに変換しようとしています。これは簡単なようですが、おそらくそうでしょう。ただし、PILを使用してサムネイルバージョンを作成しようとすると、ソースイメージが高さよりも高い場合、結果のイメージが90度回転し、ソースイメージの上部が結果のイメージの左側に表示されます。ソース画像が高さよりも幅が広い場合、結果の画像は正しい(元の)方向になります。サイズとして送って頂いた2タプルと関係があるのでしょうか?アスペクト比を維持するためのものであると思われるため、サムネイルを使用しています。それとも私は完全に盲目で、何かばかげているのですか?タプルのサイズは1000,1000です。ARを維持したまま、最長の辺を1000ピクセルに縮小したいからです。

コードはシンプルに見える

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

助けてくれてありがとう。

48
Hoopes

以下により良い答えがあることに注意してください。


画像が横よりも高い場合、カメラが回転したことを意味します。一部のカメラはこれを検出し、その情報を画像の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は、PILのExifTagsモジュールよりもEXIFをうまく処理できる可能性があります。

imagemagick は、バッチサムネイルを作成する別の方法です。

13
unutbu

「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()
61
storm_to

xilvarの答えはとてもいいですが、拒否された編集で修正したい2つのマイナーな欠点があったので、答えとして投稿します。

1つには、ファイルがJPEGでない場合、またはexifデータが存在しない場合、xilvarのソリューションは失敗します。もう1つは、適切な量ではなく、常に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()
32
Maik Hoepfel

他の回答で提案されているバージョンと機能的に同じですが、私の意見ではよりクリーンなバージョンを共有せざるを得ません:

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

これは、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
20

Hoopesの答えは素晴らしいですが、回転ではなく転置メソッドを使用する方がはるかに効率的です。 Rotateは、各ピクセルに対して実際のフィルター処理された計算を実行し、事実上、画像全体の複雑なサイズ変更を行います。また、現在のPILライブラリには、回転した画像の端に黒い線が追加されるバグがあるようです。 TransposeはLOTが高速で、そのバグがありません。代わりに転置を使用するためにフープの答えを微調整しました。

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

私はプログラミングの初心者です。PythonとPILなので、前の回答のコード例は私には複雑に思われます。タグを反復する代わりに、タグのキーに直行しました。 python Shellで、向きのキーが274であることがわかります。

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

image._getexif()関数を使用して、画像内のExifTagを取得します。方向タグが存在しない場合、エラーがスローされるため、try/exceptを使用します。

Pillowのドキュメントには、回転と転置のパフォーマンスや結果に違いはないと記載されています。両方の機能のタイミングを確認して確認しました。それはより簡潔なので、私は回転を使用します。

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

こんにちは私は画像の回転を達成しようとしていました、そしてこの投稿の以前の答えのおかげで私はそれをしました。しかし、私はソリューションをアップグレードし、それを共有したいと思います。私は誰かがこれが役に立つと思うことを望みます。

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

368だけでなく、すべての向きを処理するソリューションが必要でした。

私はRoman Odaiskyの solution を試しました-包括的できれいに見えました。ただし、さまざまな方向の値を持つ実際の画像でテストすると、誤った結果が生じる場合があります(例: this one with orientation set to 0)。

別の 実行可能な解決策 はDobes Vandermeerの可能性があります。しかし、ロジックをより簡単に記述できると思うので(私が好む)、私はそれを試していません。

さて、これ以上面倒なことはしませんが、これはよりシンプルで、保守しやすい(私の意見では)バージョンです。

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

テストしたところ、前述のすべてのexif方向の画像で機能することがわかりました。ただし、独自のテストも行ってください。

1
Hassan Baig

他の答えに加えて、関数を実行する前にim.copy()を使用するため、問題が発生していました。これにより、必要なexifデータが削除されます。 im.copy()を実行する前に、exifデータを保存してください。

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

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

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

ここにいくつかの良い答えがあります、私はクリーンアップされたバージョンを投稿したかっただけです...関数はすでに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
0
orion11

これにはすべて、より簡単な方法があります。

    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)

それが役に立てば幸いです:)

0
Ignacio Bares