OpenCVで画像を読み込む際にまず使うimread。引数にファイル名を渡すだけで、画像を行列として読んでくれる優れものだが、1点罠があり、ちょっとはまったので共有したいと思います。
実は、imreadでJPEGを読み込む際には、ExifのOrientationタグを見てくれて、自動的に補正して読んでくれちゃう。という仕様でした。
世の中的にはこのOrientationタグを見て画像を適切に補正するスクリプトがいろいろ解説されていますが、OpenCVのimreadはそれを勝手にやってくれます。
環境
検証に用いた環境は、Python3.6+OpenCV4.1.0.25のWindows環境で試しました。Python3.7+OpenCV3.4.1でも同じ現象でした。
実験
Exifを操作できるツール(今回F6 Exifというツールを使いました。)を使って、Orientationを編集してみました。定義は1~8まで用意されており、いろいろなページで解説されていますが、
1:そのまま
2:左右反転
3:左右上下反転(180度回転)
4:上下反転
5~8は1~4の転置
という覚え方が一番しっくりきました。以前rawpyの現像オプションで指定した向きとは異なります。定義がいろいろあって困ります。ただ後半が前半の転置。という点だけは共通しています。このタグだけ編集した8種類のJPEGファイルを作って実験してみました。
Orientationのイメージ図は以下
ちなみに転置とは、いわゆる行列の転置行列の事を言っています。x軸とy軸の走査順入れ替えるだけなので、わかりやすいです。こんなのでしたね。
$$ A = \left(\begin{array}{c}a & b & c \\ d & e & f\end{array}\right)\
A^{\top} = \left(\begin{array}{c}a & d \\ b & e \\ c & f\end{array}\right) $$
このようにして作成したファイルですが、あくまで向き指定のタグ上の話で、画像データの実体はすべて1の状態で格納されています。なので普通に画像を読み込むと1の状態で読み込まれることを期待してしまいます。
ただ、前述したようにOpenCVのimreadで読み込むと、向きを自動補正してくれて、画像の配列そのものがこの向きに変換されます。
試しに、それら8種類のファイルを
import cv2 img = cv2.imread(fname) print(img.shape) cv2.imshow("wnd",img) cv2.waitKey(0)
として表示させると、このfnameで指定したファイルのOrientationを見てくるくる回って表示されます。ちなみに、同じファイルをPillowで読み込むと、
import cv2 from PIL import Image #img = cv2.imread(fname) img = np.array(Image.open(fname), dtype=np.uint8)[:,:,[2,1,0]] print(img.shape) cv2.imshow("wnd",img) cv2.waitKey(0)
向きは変わらず一定で、すべて、データ通り1の向きで表示されました。最もRGB順になるので並び替える必要はありますが…。
罠
で、どんな罠にはまったかというと、EXIFデータのコピーを行った際にそれは発生しました。
Pythonで画像処理を行うにあたって必須ともいえるOpenCV。ただどこを見てもEXIFを操作するすべが見当たらない。”OpenCV EXIF”でググってもヒットするのはPillowを使ったEXIF操作の記事ばかり。というわけで、EXIF操作はPillow使っとくか、と思ってました。
img = cv2.imread(fname) out = 何ぞ処理(img) image = Image.open(fname) exif = image.info["exif"] # exif情報取得 Image.fromarray(out).save(output, "JPEG", exif=exif, quality=95)
こんな感じで、cv2.imreadで開いた画像を処理して、最後pillow経由で取得した同じ画像のEXIFを、処理済みの画像にコピーして保存したところ、出来上がったファイルが思わぬ向きになっていました。そりゃそうです。OpenCVが親切に向きを直してくれているのに、OrientationのタグがそのままのEXIFをコピーしたので、Windows等のビューアーが正しい向きで入っているデータを、タグに従って回転表示させ、変な向きになります。
何せ、通常のOrientation = 1の時はうまく行って見えたので調子に乗っていたらこのざまです。皆さんお気を付けください。
あともう一つ。このやり方だとサムネイル情報も引っ張ってくれます。なので、処理してガラッと印象が変わる処理をして、元のEXIFコピーすると、サムネイルと違う画像が出来上がります。これもWindows上でクリックしたサムネイルと違う画像が出てきて、ビビります。本来適宜必要なEXIFだけ抽出して付け替えるのが正しいやり方だと思います。EXIF操作だけなら、
で記述しましたが、専用のライブラリを使って操作した方が使い勝手がいいかもしれません。この記事ではrawデータについているEXIFをいじってますが、JPEGなら全然簡単にできます。
まとめ
Pillow(Image.open):EXIF操作ができる。が読み込み時にはEXIFのOrientationは見ず、データの向きで画像列を読み込む。ちなみにRGB順
OpenCV(cv2.imread):EXIF操作できない。が読み込み時はEXIFのOrientationを見て、その向きの画像列に変換して読み込む。ちなみにBGR順
読み込みの速度比較をしているサイトとか見てもOpenCVの方が若干遅いみたいなので、このデータに素直じゃない読み込みが起因しているかもしれませんね。
コメント