HEIF→JPEG変換

ブログへの写真アップロードの件で少し考えたのが、以前のスマートフォンをiPhoneに変えたときに写真の画像ファイルフォーマットがデフォルトではjpegではなくheifになっていたので、互換性を重視するためにということでjpegに変更していた事。コマンドラインで一括変換する仕組みを導入するにあたって、改めてheifフォーマットの利点を調べてみた。ウィキペディアなどで調べてみると、画質はそのままで圧縮率が2倍以上と、保存用途としてもかなり良いため再びheifでの保存に切り替えることとする。

で、必要になってくるものがpythonでheifを扱うためのライブラリ’pyheif’

とりあえずbrewでインストール出来るということで、まずは必要なライブラリから

$ brew install libffi libheif (libde265) # 括弧はWSL2の場合

その後にpipでpythonライブラリのインストール

$ pip install pyheif

と、WSLではうまく行ったのだが、自分の環境のMacOSではpipでインストールエラーとなった。どうやらclangのインクルードパスが/opt/local/includeを要求しているのに対し、それが無いためらしい。内容を調べてみると、/opt/homebrewというディレクトリが/opt/localであれば色々解決しそうなので、試しにシンボリックリンクを張ることとした。

$ sudo ln -s /opt/homebrew /opt/local

これで旨く/opt/local/includeを見つけることが出来、pyheifライブラリがインストール出来た。

次にサイトを参考に先日のアップロード準備用リサイズプログラムwpresizeにheifデコード機能を組み込んでみた。

from PIL import Image
import pyheif

heif_file = pyheif.read("sample.heic")
image = Image.frombytes(
    heif_file.mode, 
    heif_file.size, 
    heif_file.data,
    "raw",
    heif_file.mode,
    heif_file.stride,
    )
image.save("output.jpeg", "JPEG")

ただし上記はあくまでも画像を変換するだけで、Exif情報はコピーされない。そこでpyheifからExif情報を取り出して出力JPEGファイルに付加する方法を調べてみた。

heif_file(pyheifオブジェクト)にはその他にmetadataというプロパティがあり、メタデータを格納する辞書形式(のリスト)であると説明される。リストと言っても内容は一つしかなく、そのゼロ番目の内容を調べると

type: 'Exif'
data: xxxxxx(バイナリ情報)

であったため、このdataの中身が撮影画像のExif情報だということが分かる。これを保存時に付加することでExif付きで保存することが出来る。

.....
exif = heif_file.metadata[0]['data']
.....
image.save("output.jpeg", "JPEG", exif=exif)

GPS情報だけ消したり、Exif情報を編集したいのであれば、前回同様に一度辞書形式に変換してやれば編集可能となる。

exif = heif_file.metadata[0]['data']
exif_dict = piexif.load(exif)
del exif_dict['GPS']
exif = piexif.dump(exif_dict)
image.save("output.jpeg", "JPEG", exif=exif)

結局メタデータ自体が2重に辞書保管されるような形になっていたのが分かりにくかったです。

あともう一点注意すべき点は、Exifの記載されている画像の回転情報。カメラ構え方で画像の縦横を変えて表示したいが、オリジナル画像から縦横変換をしようとすると画素の並び順から変えなければならず、再圧縮からの点からも非効率的(勝手な想像)だと思うので、Exifの情報として画像をどの様に表示するのかの向き(回転)情報を保管している。サムネイルやビューワはこの情報をもとに画像を表示している(GIMPなどの編集ソフトはこの情報をもとにするが、向きに合わせてオリジナルのピクセル情報を並び替えるのかを聞いてくる)。

そういった意味かと思うが、heif_fileで変換されたイメージはExif情報は保存されないが、Exifの回転情報をもとにピクセルを並び替えているようで、オリジナルを右90度回転した縦長画像を変換しても、縦長画像として出力される。これに対して右90度回転したExif情報を付加すると、さらに画像回転情報が加わり横倒しになった画像になる。

この辺りの仕組みを変換時に制御出来るかどうか調べられなかったので、とりあえずは変換時にオリジナルの回転処理がされるものとして、保存するExif情報のうち回転情報のみ1(正転)に戻しておくこととした。

exif = heif_file.metadata[0]['data']
exif_dict = piexif.load(exif)
del exif_dict['GPS']
exif_dict['0th'][274] = 1  # 回転情報を保持するExifアドレス(右回転の場合6になってるので正転1とする)
exif = piexif.dump(exif_dict)
image.save("output.jpeg", "JPEG", exif=exif)

対処療法的ではあるが、とりあえずこれでExifを保管し画像の向きを保持して変換出来るようになった。

前回のwpresizeに組み込んだところがこちら(masterにコミットしてしまったので前回のリンクもheif対応の物になってますw)。

pyheifオブジェクトに関しては詳しい資料が見つからなかったので、自分で調べる形になってしまった。とりあえず結果オーライで暫く運用ということで