5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

問題は

PythonやMATLABなどプログラミングで画像ファイルを読み込む時に、勝手に回転したり左右逆や上下逆になったりしやがる、という不思議な現象に直面したことがありますか?

例えばこの画像

irli.jpg

irli.jpg

普通の画像に見えるけど、これを保存して読み込んで見たら90度回転された姿で現れますよ。

import matplotlib.pyplot as plt

gazou = plt.imread('irli.jpg')
plt.imshow(gazou)
plt.show()

截屏2024-06-13-21.24.01.jpg

ウェブブラウザやよく使われるソフトウェアで開ける時は普通に表示するはずなのに、どうしてPythonではこのように回転していますの?おかしいでしょう?

これについてちゃんと原因があります。この記事では原因と解決方法を説明します。

私も仕事で初めてこのような画像を見つけたのです。最初は訳わからなくて戸惑っていたのですが、ググってみたいこういうこともあるとわかってきました。

こんなことも知らずにアプリを書いて不具合の原因になってしまいました。ついやらかしてしまいました。まだテストの途中だけだからこの段階でこのバグを見つけて対策ができて良かったです。

だからわかったことを纏めて記事に書くことにしました。

他の読み込み方も

因みに上述はmatplotlibで読み込んだが、その他にもskimageや
imageioやPILで読み込んでも同じ結果です。

from skimage import io

gazou = io.imread('irli.jpg')
print(gazou.shape) # (600, 450, 3)

(配列のshapeは「縦、横、色」で表示されます)

import imageio

gazou = imageio.imread('irli.jpg')
print(gazou.shape) # (600, 450, 3)
from PIL import Image

gazou = Image.open('irli.jpg')
print(gazou.height,gazou.width) # 600 450

原因

簡単に言うと、原因はEXIFというメタデータの作用にあります。

この画像は元々実際にこの姿ですが、EXIFデータの中で「90度時計回転」と指定されているからあるべき姿になっているということです。

そのEXIFデータはPython色んな方法で読み込むことができます。Pythonでは例えばPILで調べられます。

from PIL import Image

gazou = Image.open('irli.jpg')
print(gazou._getexif()) # {274: 6}

ここで表示された274はorientation(方向づけ)を示すEXIFデータの番号で、値は1から8の数字です。ここに書いてある6は「90度時計回転」を示すのです。普段は1、つまりなんの回転もないそのままです。

1 そのまま
2 左右逆転
3 180回転(左右上下逆転)
4 上下逆転
5 左下と右上を逆にする
6 90度時計回転
7 左上と右下を逆にする
8 90度反時計回転

普段よく使われているソフトウェアではEXIFデータも考慮されるので正しく表示できるが、自分でPythonではそのEXIFデータを自動的に読み込まないから本来の姿で現れるのです。

PythonのPILなどのモジュールで保存する時普段はexifデータなしです。普段スマホやデジカメで撮った写真がEXIFデータが付いているが、orientationの項目が入っていない場合も多いので、この手法で回転されている画像はそこまで多くないのです。

普段は画像が実際に回転して保存されるため、このような問題が起きる画像は少なくてあまり見かけていないのですが、一部のアプリで撮った写真や何等かのソフトウェアで編集した画像はこのようにされている可能性があるので注意する必要があります。

ソフトウェア開発するならちゃんとこのような画像でも正しく読み込めるようにしておかないとバグの原因になってしまうでしょう。

因みにEXIFについて以前私がこのような記事を書いたことがあります。色んな情報が入っていて利用できます。

解決方法

opencvを使う

Pythonは色んな画像処理関連のモジュールがあってどれも画像を読み込むことができますが、上述の通り殆どどれもEXIFデータを無視しますが、opencvは他と違って、ちゃんと回転してくれます。

import cv2

gazou = cv2.imread('irli.jpg')
print(gazou.shape) # (450, 600, 3)

ただしopencvは普段のRGBではなくBGRで読み込まれるので、これをそのままopencv意外の画像処理モジュールに使ったら変な表示になってしまうでしょう。

例えばそのままmatplotlibで表示したらこうなります。
irli-bgr.jpg

BGRからRGBへ変換するには例えばこうやって色の順番を逆にするのです。

gazou = gazou[:,:,::-1]

だから最初からopencvを使うつもりないのにわざわざopencvで読み込む場合は気をつける必要がありますね。

それにopencvの欠点は、ファイル名が日本語などである場合読み込めないので別の対策が必要となります。このように色々不便なのでこの方法はあまりおすすめしがたいかもしれませんね。

PILを使う

おすすめの対策方法はPILのImageOps.exif_transposeを使うという方法です。これを使うと自動的にEXIFの中のorientationを読んでそれによって適切に修正されます。

このような関数を書いておいたらいつでも正しく読み込むことができます。

import numpy as np
from PIL import Image,ImageOps

def imread(f):
    gazou = Image.open(f)
    exif = gazou._getexif()
    if(exif and 274 in exif and exif[274]!=1):
        gazou = ImageOps.exif_transpose(gazou)
    return np.array(gazou)

試しのこの関数を使って読み込んでみたらちゃんと正常に表示できるはずです。

gazou = imread('irli.jpg')
plt.imshow(gazou)
plt.show()

又は、修正した後の画像をそのまま上書き保存するのです。そうしたら次はもうEXIFの処理をしなくてもいいようになります。

f = 'irli.jpg'
gazou = Image.open(f)
exif = gazou._getexif()
if(exif and 274 in exif and exif[274]!=1):
    gazou = ImageOps.exif_transpose(gazou)
gazou.save(f)

MATLABの場合

MATLABもPythonと同様に普段のやり方で画像を読み込んだらEXIFが無視されて正しく表示されません。

gazou = imread("irli.jpg");
imshow(gazou)

截屏2024-06-13-23.06.26.jpg

EXIFデータはimfinfo関数で取得できるのでこれを使って適切に対策することができます。

ただ残念ながらPythonのPILみたいに自動的に処理してくれる関数がないので、自分でそれぞれの場合の処理を書くしかないのです。

後で使うためにこのようにここで読み込む関数を定義しておきます。

function gazou = gazou_yomikomu(f)
    gazou = imread(f);
    imfo = imfinfo(f);
    if(isfield(imfo,"Orientation"))
        switch imfo.Orientation
            case 2
                gazou = fliplr(gazou);
            case 3
                gazou = rot90(gazou,2);
            case 4
                gazou = flipud(gazou);
            case 5
                gazou = fliplr(rot90(gazou,-1));
            case 6
                gazou = rot90(gazou,-1);
            case 7
                gazou = fliplr(rot90(gazou,1));
            case 8
                gazou = rot90(gazou,1);	
        end
    end
end

これを使って読み込んでみたら漸く正しく表示されます。

gazou = gazou_yomikomu("irli.jpg");
imshow(gazou)

こんな画像の作り方

普段は写真を撮った時や何等かのアプリを使った時起きることですが、自分でexifデータを弄って既存の画像をそのようにすることもできます。実は上述の画像も自分で弄ったものです。

だからおまけにこれを食ったコードもここに載せておきます。ただしPILの他にpiexifというモジュールが必要です。(pipで簡単にインストールできるからすぐ使える)

import piexif
from PIL import Image

f = 'irli.jpg'
gazou = Image.open(f)
gazou = gazou.transpose(2)
exif_dict = {'0th': {274:6}}
gazou.save(f,exif=piexif.dump(exif_dict))

参考

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?