読み込んだ画像が回転したり反射したりする原因と対策（PythonとMATLABで）

# 問題は

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

例えばこの画像
<table>
<tr><td style="text-align: center; border: 1.5px solid #bddad8;"><code>irli.jpg</code>

![irli.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/bbf354be-0e5d-5fd3-060f-9d245e1fd40f.jpeg)</td></tr>
</table>


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

```python
import matplotlib.pyplot as plt

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

![截屏2024-06-13-21.24.01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/90e534da-5d3a-d330-e84b-58ba68b78132.jpeg)

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

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

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

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

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


# 他の読み込み方も

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

gazou = io.imread('irli.jpg')
print(gazou.shape) # (600, 450, 3)
```
（配列のshapeは「縦、横、色」で表示されます）

```python
import imageio

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

```python
from PIL import Image

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

# 原因

簡単に言うと、原因は[EXIF](https://ja.wikipedia.org/wiki/Exchangeable_image_file_format)というメタデータの作用にあります。

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

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

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

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

<table>
<tr><td style="border: 1.5px solid #8866AA;"><font color="#156f6f">1</font></td><td style="border: 1.5px solid #8866AA;">そのまま</td></tr>
<tr><td style="border: 1.5px solid #8866AA;"><font color="#156f6f">2</font></td><td style="border: 1.5px solid #8866AA;">左右逆転</td></tr>
<tr><td style="border: 1.5px solid #8866AA;"><font color="#156f6f">3</font></td><td style="border: 1.5px solid #8866AA;">180回転（左右上下逆転）</td></tr>
<tr><td style="border: 1.5px solid #8866AA;"><font color="#156f6f">4</font></td><td style="border: 1.5px solid #8866AA;">上下逆転</td></tr>
<tr><td style="border: 1.5px solid #8866AA;"><font color="#156f6f">5</font></td><td style="border: 1.5px solid #8866AA;">左下と右上を逆にする</td></tr>
<tr><td style="border: 1.5px solid #8866AA;"><font color="#156f6f">6</font></td><td style="border: 1.5px solid #8866AA;">90度時計回転</td></tr>
<tr><td style="border: 1.5px solid #8866AA;"><font color="#156f6f">7</font></td><td style="border: 1.5px solid #8866AA;">左上と右下を逆にする</td></tr>
<tr><td style="border: 1.5px solid #8866AA;"><font color="#156f6f">8</font></td><td style="border: 1.5px solid #8866AA;">90度反時計回転</td></tr>
</table>

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

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

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

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

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

https://qiita.com/phyblas/items/2c91315af896da32fad6


# 解決方法

## opencvを使う

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

```python
import cv2

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

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

例えばそのままmatplotlibで表示したらこうなります。
![irli-bgr.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/882d551f-87ea-d6b3-5f12-60ff9d1c3fb4.jpeg)

BGRからRGBへ変換するには例えばこうやって色の順番を逆にするのです。
```python
gazou = gazou[:,:,::-1]
```

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

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


## PILを使う

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

このような関数を書いておいたらいつでも正しく読み込むことができます。
```python
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)
```

試しのこの関数を使って読み込んでみたらちゃんと正常に表示できるはずです。
```python
gazou = imread('irli.jpg')
plt.imshow(gazou)
plt.show()
```

又は、修正した後の画像をそのまま上書き保存するのです。そうしたら次はもうEXIFの処理をしなくてもいいようになります。
```python
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が無視されて正しく表示されません。

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

![截屏2024-06-13-23.06.26.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/143bc265-c136-0201-faba-8ee015a33a8a.jpeg)

EXIFデータは[`imfinfo`](https://jp.mathworks.com/help/matlab/ref/imfinfo.html)関数で取得できるのでこれを使って適切に対策することができます。

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

後で使うためにこのようにここで読み込む関数を定義しておきます。
```matlab
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
```

これを使って読み込んでみたら漸く正しく表示されます。
```matlab
gazou = gazou_yomikomu("irli.jpg");
imshow(gazou)
```


# こんな画像の作り方

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

だからおまけにこれを食ったコードもここに載せておきます。ただしPILの他にpiexifというモジュールが必要です。（pipで簡単にインストールできるからすぐ使える）
```python
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))
```


# 参考

- [つい首をかしげてしまう、Exifのwidthとheightと縦と横の話](https://qiita.com/mo256man/items/f7d2f16f369238bada96)
- [moviefileinTOPで画像をロード後、Exifを読んで適切なOrientationで表示する](https://qiita.com/motoishmz/items/b4290958e80ce11777f3)
- [【Python】EXIF情報に合わせて画像を回転させる](https://max999blog.com/python-rotate-image-by-exif-orientation/)
- [スマホで撮った写真が横を向いてしまう（Python Kivyの取説・使い方 番外編）](https://start-python.hateblo.jp/entry/2019/12/23/090000)
- [Python3でJPEG画像のExifを取得し、回転方向を修正する](https://kapibara-sos.net/archives/658)
- [PILでEXIF Orientationタグを考慮して処理](https://qiita.com/Klein/items/a04cf1a6c94d6f03846e)
- [Exif Orientation のうんちく](https://qiita.com/yoya/items/4e14f696e1afd5a54403)
- [Python OpenCV の cv2.imread 及び cv2.imwrite で日本語を含むファイルパスを取り扱う際の問題への対処について](https://qiita.com/SKYS/items/cbde3775e2143cad7455)
- [MATLABで画像表​示するときに画像が横​向きになってしまうの​を直したい](https://jp.mathworks.com/matlabcentral/answers/1878692-matlab)