つい首をかしげてしまう、Exifのwidthとheightと縦と横の話

# はじめに
本記事はExif三部作の最終章となります。

 - [PillowでExif情報を遊ぶ　～辞書の復習を兼ねて～](https://qiita.com/mo256man/items/b4567b0e0945b99f9cff)
 - [piexifで画像にジオタグを付与する　～恐縮だが、指定する緯度経度で写真を撮ってくれないか～](https://qiita.com/mo256man/items/e3d07fbeab58862213ef)
 - つい首をかしげてしまう、Exifのheightとwidthと縦と横の話　　←今ここ

毎度毎度ノリが違っていて恐縮です。

# カメラを傾けて撮影された写真はどう扱われるか
ここでは2015年に出雲駅で撮影した**「富士通のパソコンFMVは出雲生まれ！」の顔出し看板**の写真を使う。
下の画像はWindows10のエクスプローラーの特大アイコン表示のスクリーンショットであって画像そのものではない。

|fujitsu.jpg|
|:-:|
|![exp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520216/3cbb67ee-7f26-3ac3-7c08-5d3109e39288.png)|

プロパティを確認すると幅1920×高さ2560の縦長画像だ。
![prop.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520216/13daa26c-324b-e9fd-e345-fe33b646842a.png)

ところがExif情報を確認すると高さが1920で幅が2560となっている。デジカメは[パナソニックのDMC-FH5](https://panasonic.jp/dc/p-db/DMC-FH5_spec.html)。スマホではなくデジカメだから基本は横長、それを傾けて撮影している。

```Python:ExifTagsでExifを見る
{前略
 'ExifImageHeight': 1920,
 'ExifImageWidth': 2560,
 'Orientation': 6,
 後略}
```

その代わりと言っては何だが、Exifには`Orientation`というプロパティが用意されている。
`Orientation`の定義は以下のとおり。数学的で誤解の余地のない、だが何ともわかりづらい表現になっている。

![orientation.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520216/0cc6d048-21f3-8d02-8269-c3df6056a2cb.png)

## OpenCV
この画像のサイズをOpenCVで取得する。全体のソースは省略するが

```Python:
print (imgCV.shape[:2])

# (2560, 1920)
```

となる。シェイプは行・列の順すなわち高さ・幅の順で、縦長画像であると正しく解釈されている。
デジカメは普通の姿勢では横長になるが90度回転して縦長になっているという事情は考慮されず、最終的な答えだけが出ている。

## PIL

次にPILでサイズを確認する。

```Python:
print (imgPIL.size)

# (2560, 1920)
```

となる。OpenCVと同じ…いや違う、PILの`size`は幅・高さの順。ということはOpenCVと扱いが逆になってるじゃないですか。
実際に画像を表示させてみると、
![plt.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520216/5b17d206-44c7-1810-9d90-69473c7f6782.png)
ギャー！`Orientation`が考慮されておらずカメラの初期状態の向きになってる！

これを避けるにあたり、`Orientation`の値を取得して何番がどの向きだからと自前で計算して回転させる必要はない。`Orientation`に従って正しく回転してくれるメソッド`exif_transpose`がPILの`ImageOps`モジュールにあるのだ（バージョン6.0.0以降）。

ここはソースを挙げておこう。`exif_transpose`で回転するだけならExif情報を読み取る`ExifTags`を呼び出す必要はない。

```Python:ソース10
from PIL import Image, ImageOps
import matplotlib.pyplot as plt
import numpy as np

filename = "fujitsu.jpg"
imgPIL = Image.open(filename)

print (f"元画像のサイズ:{imgPIL.size}")
arrPIL = np.asarray(imgPIL)
plt.imshow(arrPIL)
plt.show()

img_transpose = ImageOps.exif_transpose(imgPIL)
print (f"回転後のサイズ:{img_transpose.size}")
arr_transpose = np.asarray(img_transpose)
plt.imshow(arr_transpose)
plt.show()
```

```Python:結果10
元画像のサイズ:(2560, 1920)
回転後のサイズ:(1920, 2560)
```

これでPILでも正しい向きで表示することができるようになった。
![plt2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520216/c571bc9f-1981-cc92-96b2-b42a7a9a7e0a.png)

## Excel VBA
ここで突然Excel VBAの話になる。実はこれまでの話は職場で後輩君にVBAのプログラムを書かせてみてはじめて気づいたことだったりする。

```vb:ExcelVBA
Sub AddPic()
    Dim filename As String
    Dim sh As Shape
    filename = "fujitsu.jpg"
    
    Set sh = ActiveSheet.Shapes.AddPicture( _
        filename:=ThisWorkbook.Path & "\" & filename, _
        linktofile:=True, _
        savewithdocument:=False, _
        Left:=0, _
        Top:=0, _
        Width:=-1, _
        Height:=-1)
    Debug.Print ("width:  " & sh.Width)
    Debug.Print ("height: " & sh.Height)
End Sub
```

結果はこう。倍率が100%だったり101%だったりするのはExcelの持病なのでスルーするとして、縦長画像が期待通りに縦長に貼られていることがわかる。
だが高さと幅では幅のほうが大きく、また回転角も設定されている。本来は横長画像だったのが90度回転していると正しく解釈されているわけだ。
![excel.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520216/522b6a41-eebe-80b8-b13d-3f38c1a268ac.jpeg)
だがそのことに気づかずに「高さは`Height`で幅は`Width`だ」だけで進んでいってとんでもないことになり、後輩君と一緒に悩んで今回のネタを思いついたというわけ。

## 画像ビューアによる違い
**ViX**は約20年前に公開されたフリーの画像ビューア。脆弱性が見つかり使わないよう呼びかけられているが、これよりも使いやすいソフトにはいまだ出会っていない。
このソフトの数少ない欠点の一つが`Orientation`による回転に対応していないこと。まあ、20年前のソフトだしねえ。

**XnView**はかなり新しいフリーの画像ビューア。これはExifやGPSの情報を持っているとか回転されているとかいった情報がサムネイル上にアイコン表示されており、非常にわかりやすい。
私がXnViewに完全に乗り換えない理由は…本記事とは関係ないので省略。

||ViX|XnView|
|:-|:-:|:-:|
|サムネイル|![fujitsu_vix.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520216/fb845913-89c0-9be0-3dc2-9e1dc25ae42b.png)|![fujitsu_xnview.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520216/388a82cf-bb07-e0bc-ccd5-941bfcf1d2ef.png)|
|プロパティ|![fujitsu_vix_exif.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520216/1b041a3d-0223-53bc-7d7f-3b1c5716fb8f.png)|![fujitsu_xnview_exif.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520216/19d2256c-d7b6-d986-dedd-5c9aca975e54.png)|

# ギャラリー

ここで画像をお見せする意味はほとんどないのだが、コロナ禍でなかなか遠出できないので気分高揚のために画像を貼る。
また旅行に行きたいな。

### 2004年、デジカメで撮った音止の滝
![fall_xnview.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520216/2bad9a5f-0bdf-74e5-7361-db15e9eef1de.png)
音止の滝は白糸の滝と同エリアにある富士山麓の名所。デジカメはキヤノンの[IXY DIGITAL 200](https://faq.canon.jp/app/answers/detail/a_id/10676/~/%E3%80%90%E3%82%B3%E3%83%B3%E3%83%91%E3%82%AF%E3%83%88%E3%83%87%E3%82%B8%E3%82%BF%E3%83%AB%E3%82%AB%E3%83%A1%E3%83%A9%E3%80%91ixy-digital-200-%E6%A9%9F%E7%A8%AE%E4%BB%95%E6%A7%98)。デジカメなので基本は横長。
`Orientation`は1。そのため、カメラを90度倒して縦長になるように撮ったのだが、ビューアソフト・PIL・OpenCVともに横長のまま表示される。
`Orientation`=1の正しい挙動なのでこのことについて文句を言ってはいけない。先ほどの`Orientation`の定義をよく読むとよい。たとえカメラにセンサーがなく向きを検知できなくても、`Orientation`のデフォルト値は1なのだ。

### 2007年、デジカメで撮った昔の雑誌の1ページ
![login_xnview.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520216/7ebab258-37a4-f65d-1107-61bfc2b930c3.png)
デジカメはパナソニックの[DMC-FX9](https://panasonic.jp/dc/p-db/DMC-FX9_spec.html)。デジカメなので基本は横長。`Orientation`は1。
XnViewのサムネイルでは普通に横長で表示されているが、ビューア画面では縦長で表示された。また、ViXではサムネイル・ビューア画面ともに期待通り縦長で表示された。横長が普通のはずなのに。
PILで表示させると、`exif_transpose`を使わずとも縦長で表示された。OpenCVでも同様。ありがたいことだが、なぜそうなるのかはわからない。

雑誌の詳細が気になる？ ログイン創刊号（1982年）です。
![login.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520216/3d91fdc2-9b22-4ea0-4757-94bb8f7ed5e5.jpeg)

### 2010年、ケータイで撮った東尋坊の写真
![toujinbo_xnview.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520216/8ec40ea2-3015-3e43-884f-0862759924aa.png)
機種は東芝の[911T](https://ja.wikipedia.org/wiki/SoftBank_911T)。挙動は滝と同じ。縦長がデフォで、横長画像も縦長になってしまう。

### 2018年、スマホで撮ったサンフランシスコの写真
![sf_xnview.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520216/148b266d-cca2-858e-9ad8-a3860f71e1a7.png)
HUAWEIのVNS-L22はよく知られているところの[P9 lite](https://consumer.huawei.com/jp/support/phones/p9-lite/)のこと。
標準の姿勢では縦長画像になる（であろう）スマホのカメラ、`Orientation`は0(Unknown)となっている。縦長で表示されてしまうはずなのにありがたいことに横長で表示された。その理由はわからない。
Googleフォトも同様。

### 2018年、スマホで撮ったサンフランシスコ土産の写真
![bag_xnview.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520216/d6bfade0-cbfd-de75-3840-539076afa554.png)
こちらは横長に撮ったつもりが普通に縦長として保存されている。カメラを水平に構えて真下にある物体を撮影するにあたり、スマホの加速度センサー？を使った縦横判定が機能しなかったのだろう。
Googleフォトも同様。

# 終わりに
結局どうすればいいのかというと、よくわからない。というか、そもそも写真内のExif情報が当てにならないことが分かった。`Orientation`とは別の情報があるのだろうか。