Exif Orientation のうんちく


はじめに

画像を表示する際に意図せぬ回転して困る事で有名な Exif Orientation に関するうんちくです。(2021年12月5日作成記事)

Exif Orientation とは

カメラで撮影して保存する JPEG に関する主な規格として、DCF と Exif があります。
そのうち Exif の Orientation 情報は、カメラの向き等によって画像が本来意図するものと違う回転/鏡像で JPEG ファイルに保存される事を示すメタデータとしての値フィールドです。
図のように横長のカメラを縦にして撮影した場合、本来の映像を横に倒した画像が JPEG に保存される事になります。これを表示する際に元に戻すのが一般的な使われ方です。

画像ビューアによって Orientation が無視される事もあるので、世の中の画像アップローダは Orientation 補正して画像を保存する事が多いでしょう。
また多くの画像ライブラリは画像を読み込む際に自動では Orientation 補正をしないので、縦と横で違いが出るフィルタを実装する場合や、画像認識をする場合に、やはり回転補正する必要があります。

Exif Orientation 仕様

Exif Orientation の仕様はこちらに含まれます。なお Exif 2.3 当初誤記があって 2012年12月の改訂で修正されたようです。

尚、現時点(2021年12月5日)での最新は 2.32 ですが、ドラフトだけ PDF 公開されていて、正式版は売り物です。(ちなみに ¥24,574)

Exif バイナリ形式 (おまけ)

Exif バイナリ形式での Orientation フィールドの入れ方はこちらです。

タグ名称 タグ番号 タイプ カウント
画像方向 Orientation 274(0x0112) SHORT(=2byte) 1

Exif のバイナリ形式は TIFF のサブセットで、その TIFF の画像フォーマットの仕様は Adobe のサイトにあります。

ですが、まずは以下のエントリを参照すると良いでしょう。噛み砕いた説明で分かりやすいです。

サンプル画像

サンプル画像は ImageMagick で以下のように作成しました。文字を入れると鏡写しにすぐ気付けるのでお勧めです。

% convert -size 100x75 -font .New-York-Italic -pointsize 64 \
     -fill white -stroke black -strokewidth 1 -gravity center \
     \( \( xc:red  -annotate 0 R \) \( xc:green1 -annotate 0 G \) +append \) \
     \( \( xc:blue -annotate 0 B \) \( xc:yellow -annotate 0 Y \) +append \) \
     -append -depth 1 RGBY.png

Orientation の値

回転だけなら 4 種類あれば良いのですが、鏡像反転も対応するので 1〜8 の 8 種類の値を持ちます。
そのため、インカメラのように左右鏡像反転した方が直感的なケースも対応できます。
1 origin の番号体系にも注意が必要です。おそらく 0 は Exif 内に Orientation フィールド不在な場合と区別しにくいので、特別扱いしたものと思われます。(ただ、処理的には Orientation 無しと 0 と 1 は全部同じなので、区別する必要があったかというと微妙)

回転と鏡像反転 (前提知識)

実は画像の90度単位での回転は、鏡像反転処理だけで実現できます。
例えば、90度回転は上下反転と斜め反転の組み合わせで可能です。

元画像 上下反転 更に斜め反転

つまり、同じ鏡像反転を2回実行すると元に戻りますが、違う種類の鏡像反転を適用すると、回転するのです。

上下+斜め => 90度回転 上下+左右 = 180度回転 左右+斜め = 270度回転

そんな訳で、Orientation の全ては、上下/左右/斜めの鏡像反転の組み合わせで実現できますし、それら3つのフラグと相互変換もできます。

さて、具体的な画像の変換を紹介します。

変換表

JPEG ファイル内の実画像

Orientation 適用前の JPEG 実画像と、Orientation を適用した時の比較です。
Orientation の番号は回転の順には並んでいません。画像の反転をベースに考えているようです。

Orientaton JPEG 実画像(Orientation無視表示) Orientation適用表示 操作
1 無し
2 左右反転
3 上下左右反転 = 180度回転
4 上下反転
5 斜め反転
6 2 + 斜め反転 = 270回転
7 3 + 斜め反転
8 RGBY.png 4 + 斜め反転 = 90度回転

早見表

2つに分けます。

実画像の配置

Orientation 操作すると になる、元画像を以下に並べます。

1 2 3 4
5 6 7 8

変換操作

実際に Orientation 適用の実装をする場合、こちらの表が便利だと思います。
元が で、Orientation 操作した後の画像テーブルです。

1 2 3 4
5 6 7 8

一つ目の早見表との違いは、6 と 8 が入れ替わっているだけです。

Orientation と鏡像反転フラグ

画像のテーブルを見ると分かりますが、Orientation 操作は左右反転、上下反転、斜め反転の3つの鏡像反転で表現できます。
Orientation から 1 を引いてビットで分解すると、その反転のフラグに近いデータが出てきます。

1 2 3 4
そのまま 左右反転 上下左右反転 上下反転
5 6 7 8
1の斜め反転 2の斜め反転 3の斜め反転 4の斜め反転

つまり、Orientation から 1 を引くと、ビットで処理できます。
3 と 4 の意味が逆なら、もっと素直なビット処理になるのが少し残念ですが、仕方なく頑張ると、以下のようなコードで相互変換可能です。

  • Orientation から反転ビットを取り出す処理
function fromOrientation(orientation) {
    const o = orientation - 1;
    const reverse = (o & 1)? true: false;
    const vertical = (o & 2)? true: false;
    const horizontal = reverse !== vertical;  // xor
    const diagonal = (o & 4)? true: false;
    return [horizontal, vertical, diagonal];
}
  • 反転ビットから Orientation 値に戻る処理
function toOrientation(horizontal, vertical, diagonal) {
    const reverse = horizontal !== vertical;  // xor
    const o = (reverse? 1: 0) + (vertical? 2: 0) + (diagonal? 4: 0);
    return o + 1;  // orientation;
}

Orientation 値と反転フラグの関係が実感できるデモを作りました。任意の画像をドロップして試せます。