OpenCVで使える画像圧縮フォーマットの比較

この記事は[OpenCV Advent Calendar 2024](https://qiita.com/advent-calendar/2024/opencv)の2日目の記事です．
他の記事は目次にまとめられています．

# はじめに
OpenCVを使って画像を読み書き取るときには，様々な画像フォーマットで読み込めます．

* bmp, dib, pbm, pgm, ppm, pnm, pxm, pfm, sr（非圧縮ラスタデータ）
* PNG (可逆圧縮)
* TIFF (可逆にも非可逆にも使用可)
* JPEG
* JPEG2000
* WebP
* AVIF

この中でも，非圧縮データ以外は画像情報が圧縮されて元よりも小さくなります．
この記事では，なんのフォーマットをどういう時に使えばよいかを記載します．

# 各種画像フォーマット
## 非圧縮フォーマット
データが何も加工されないで保存されます．
使用しているOSで簡単に見られる画像フォーマットで保存すればなんでもよいです．
情報量として何も変わりません．

なお，float型のデータ（CV_32F）を保存する場合はpfmを指定しましょう．float型の画像情報を保存してくれます．

## TIFFフォーマット
TIFFは画像圧縮形式というよりも，様々な画像圧縮の状態を保存するコンテナのフォーマットです．
画像圧縮のオプションとして色々選ぶことができ，どんな圧縮・非圧縮アルゴリズムで保存されているかは拡張子だけからはわかりません．

例えばOpenCVのオプションを読めば下記の圧縮フォーマットが使えるようです．
実際に試したわけではないため何がどこまで有効化されているかはわかりませんが，JPEG，WebPから最新のJPEG-XLまで対応しているようです．
```
 cv::IMWRITE_TIFF_COMPRESSION_NONE = 1 ,
  cv::IMWRITE_TIFF_COMPRESSION_CCITTRLE = 2 ,
  cv::IMWRITE_TIFF_COMPRESSION_CCITTFAX3 = 3 ,
  cv::IMWRITE_TIFF_COMPRESSION_CCITT_T4 = 3 ,
  cv::IMWRITE_TIFF_COMPRESSION_CCITTFAX4 = 4 ,
  cv::IMWRITE_TIFF_COMPRESSION_CCITT_T6 = 4 ,
  cv::IMWRITE_TIFF_COMPRESSION_LZW = 5 ,
  cv::IMWRITE_TIFF_COMPRESSION_OJPEG = 6 ,
  cv::IMWRITE_TIFF_COMPRESSION_JPEG = 7 ,
  cv::IMWRITE_TIFF_COMPRESSION_T85 = 9 ,
  cv::IMWRITE_TIFF_COMPRESSION_T43 = 10 ,
  cv::IMWRITE_TIFF_COMPRESSION_NEXT = 32766 ,
  cv::IMWRITE_TIFF_COMPRESSION_CCITTRLEW = 32771 ,
  cv::IMWRITE_TIFF_COMPRESSION_PACKBITS = 32773 ,
  cv::IMWRITE_TIFF_COMPRESSION_THUNDERSCAN = 32809 ,
  cv::IMWRITE_TIFF_COMPRESSION_IT8CTPAD = 32895 ,
  cv::IMWRITE_TIFF_COMPRESSION_IT8LW = 32896 ,
  cv::IMWRITE_TIFF_COMPRESSION_IT8MP = 32897 ,
  cv::IMWRITE_TIFF_COMPRESSION_IT8BL = 32898 ,
  cv::IMWRITE_TIFF_COMPRESSION_PIXARFILM = 32908 ,
  cv::IMWRITE_TIFF_COMPRESSION_PIXARLOG = 32909 ,
  cv::IMWRITE_TIFF_COMPRESSION_DEFLATE = 32946 ,
  cv::IMWRITE_TIFF_COMPRESSION_ADOBE_DEFLATE = 8 ,
  cv::IMWRITE_TIFF_COMPRESSION_DCS = 32947 ,
  cv::IMWRITE_TIFF_COMPRESSION_JBIG = 34661 ,
  cv::IMWRITE_TIFF_COMPRESSION_SGILOG = 34676 ,
  cv::IMWRITE_TIFF_COMPRESSION_SGILOG24 = 34677 ,
  cv::IMWRITE_TIFF_COMPRESSION_JP2000 = 34712 ,
  cv::IMWRITE_TIFF_COMPRESSION_LERC = 34887 ,
  cv::IMWRITE_TIFF_COMPRESSION_LZMA = 34925 ,
  cv::IMWRITE_TIFF_COMPRESSION_ZSTD = 50000 ,
  cv::IMWRITE_TIFF_COMPRESSION_WEBP = 50001 ,
  cv::IMWRITE_TIFF_COMPRESSION_JXL = 50002
  ```

また，TIFFは16bitや32bitの情報も保存できます．
詳しくは下記を参照してください．
[OpenCV imread mode 指定](https://qiita.com/yoya/items/b2fab73698d7aa35f5a7)

## PNGフォーマット
典型的な可逆圧縮フォーマットです．
品質劣化を一切させたくない場合や，パワーポイント等の人工的な画像の場合はこれを使います．
以下の非可逆圧縮よりも圧縮性能はかなり低いため，劣化していい場合にはこのフォーマットは選択肢に入りません．

OpenCVでは下記オプションで圧縮率，アルゴリズム，ビット深度を指定できます．
```
  cv::IMWRITE_PNG_COMPRESSION = 16 ,//ハフマン符号化の最適化レベル
  cv::IMWRITE_PNG_STRATEGY = 17 ,//予測アルゴリズムの選択
  cv::IMWRITE_PNG_BILEVEL = 18 ,//ビット深度
```

予測アルゴリズムは下記から選べます．デフォルトはRLEで，最適化も最速オプションが設定されています．
画像圧縮率を高めたい場合は，FILTEREDを使って最適化レベルを最大に指定するとよいでしょう．
最大設定でもそんなに重たくありません．

なお，PNGOptなどのPNG最適化ツールは圧縮アルゴリズムを画像の行単位で最善なものを選ぶようにしてファイルを小さくします．
```
cv::ImwritePNGFlags {
  cv::IMWRITE_PNG_STRATEGY_DEFAULT = 0 ,
  cv::IMWRITE_PNG_STRATEGY_FILTERED = 1 ,
  cv::IMWRITE_PNG_STRATEGY_HUFFMAN_ONLY = 2 ,
  cv::IMWRITE_PNG_STRATEGY_RLE = 3 ,
  cv::IMWRITE_PNG_STRATEGY_FIXED = 4
}
```
## JPEG
デファクトスタンダートな非可逆圧縮フォーマットです．
これを使っておけば，間違いは少ないでしょう．
しかし，圧縮性能はよくありません．

OpenCVでは下記オプションが指定できます．
```
  cv::IMWRITE_JPEG_QUALITY = 1 ,//品質指定
  cv::IMWRITE_JPEG_PROGRESSIVE = 2 ,//プログレッシブデコード有効化．圧縮率アップ．デコード速度ダウン．
  cv::IMWRITE_JPEG_OPTIMIZE = 3 ,//ハフマン最適化有効化．圧縮率アップ．デコード速度ダウン．
  cv::IMWRITE_JPEG_RST_INTERVAL = 4 ,//パケットロスの耐性向上．
  cv::IMWRITE_JPEG_LUMA_QUALITY = 5 ,//輝度（Y）の圧縮品質指定．ない場合は1のオプションに相関した値にセット
  cv::IMWRITE_JPEG_CHROMA_QUALITY = 6 ,//色差（CrCb）の圧縮品質指定．ない場合は1のオプションに相関した値にセット
  cv::IMWRITE_JPEG_SAMPLING_FACTOR = 7 ,//YUVのダウンサンプル方法（444，422,440,420,411）の指定．
```
YUVダウンサンプルは下記で指定できます．
```
enum  	cv::ImwriteJPEGSamplingFactorParams {
  cv::IMWRITE_JPEG_SAMPLING_FACTOR_411 = 0x411111 ,//水平に1/4（あえて使うことはほとんどない．）
  cv::IMWRITE_JPEG_SAMPLING_FACTOR_420 = 0x221111 ,//水平・垂直にそれぞれ1/2．デフォルトはこれ．
  cv::IMWRITE_JPEG_SAMPLING_FACTOR_422 = 0x211111 ,//水平が1/2
  cv::IMWRITE_JPEG_SAMPLING_FACTOR_440 = 0x121111 ,//垂直が1/2
  cv::IMWRITE_JPEG_SAMPLING_FACTOR_444 = 0x111111//ダウンサンプル無し
}
```
## JPEG2000
JPEGにとって代わろうとして，普及に失敗したフォーマットです．
デジタルシネマや医用画像等に使われているフォーマットです．
JPEGよりも圧縮率が高いことが多いです．
計算時間はJPEGよりも圧倒的に長いです．
OpenCVでは下記オプションで圧縮品質（0-1000）が指定できます．
```
 cv::IMWRITE_JPEG2000_COMPRESSION_X1000 = 272 ,
```

やっぱりJPEGを置き換えたいという希望は強く，JPEG-XL（エクステンション・ロングターム）が提案されていますがまだOpenCVには入っていません．

## WebP
JPEGを置き換えるべく登場したGoogleからの画像フォーマットです．
JPEGよりも圧縮率が高く，可逆圧縮にも対応し，アニメーションにも透過チャネルにも対応します．
主要なブラウザはすべてWebPを表示できます． ([Can I Use](https://caniuse.com/webp))．

OpenCVでは下記オプションで品質を指定できます．
ライブラリを直接いじれば圧縮アルゴリズムの選択等もできますが，OpenCVでは品質指定だけにとどまります．
```
  cv::IMWRITE_WEBP_QUALITY = 64 ,
```

## AVIF
AVIF（AV1 Image File Format）で画像を保存するフォーマットです．
OpenCVで使える画像フォーマット中では圧縮率が最大です．
なお，計算時間もぶっちぎりで最大です．
主要なブラウザはすべてAVIFを表示できます．([Can I Use](https://caniuse.com/avif))

OpenCVでは下記オプションで品質，ビット深度，アルゴリズム（スピードいう名前）を選択可能です．
```
cv::IMWRITE_AVIF_QUALITY = 512 ,
cv::IMWRITE_AVIF_DEPTH = 513 ,
cv::IMWRITE_AVIF_SPEED = 514
```
スピードのオプションはデフォルトではほぼ最速のオプションが選択されています．
最大圧縮が可能なオプションを指定すると，バグがあったのか？と思うほど遅いですが圧縮率は高いです．

また，配布されているデフォルト環境ではAVIFを使えないためcmakeでlibavifをリンクしてOpenCVライブラリを作る必要があります．

# 非可逆圧縮フォーマットの品質比較（PSNR）
JPEGやWebPと言った非可逆画像圧縮は，目立たない情報を減らして圧縮率を上げるため，デコードすると元の画像には完全には戻りません．
しかし，情報の減らし方は画像フォーマットによって違うため，たとえ同じファイルサイズであってもフォーマットによって出力画像の品質は違います．

このような状況を評価するときにはRD (rate-distiortion) 曲線で評価することが多いです．
横軸に圧縮された度合い（bit per pixel，1画素あたりのビット量），縦軸に画像品質（PSNRやSSIM）を取って，圧縮すればするほどどれくらい画質が劣化したかを評価します．

この時，縦軸は色々な品質評価のメトリクスで圧縮を評価します．
代表的なメトリクスはPSNRとSSIMです．
おまけで，OpenCVでは扱えませんが，JPEG圧縮ツールにに[MOZJPEG](https://github.com/mozilla/mozjpeg)と [JPEGLI](https://github.com/google/jpegli)を加えてます．
ついでに[JPEG-XL](https://jpeg.org/jpegxl/)も[HEIF](https://github.com/strukturag/libheif)も入れちゃいましょう．
データセットは，[CID22](https://cloudinary.com/labs/cid22)の一部を使いました．解像度は512x512です．

結果は以下になります．キャプションの意味は下記になります．また線が被って見づらいのでJPEGデフォルト設定からの変化でもリプロットします．
* JPEGTB_Default JPEGTorbo(OpenCVのJPEG保存)のデフォルトオプション
* JPEGTB_444PH JPEGTorboでプログレッシブハフマン符号化を使ったYUV444フォーマット
* JPEGMZ_Default MozJPEGのデフォルトオプション
* JPEGLI_Default JPEGLIのデフォルトオプション
* WebP4 WebPのデフォルトオプション
* AVIF420_9 AVIFのデフォルトオプション
* AVIF444_0 AVIFの最大圧縮オプション（デフォルト環境だとAVIF444はOpenCVから使えないので少しコードをいじっています）

![RD_PSNR.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67908/6edcf57b-a7c9-96b6-6b7d-1b2c0b325417.png)
![RD_PSNRDiff.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67908/46f9f3f0-20db-03bc-2cd0-ba643fe5b322.png)

AVIF444設定（OpenCVデフォルトでは設定できない）が最高性能で，HEIF（OpenCVでは設定できない）と続き，JPEG-XL（OpenCVでは設定できない）と続きます．
OpenCV環境で使える中では，WebPとAVIF420がほぼ並んでトップです（若干WebPのほうが良い）．
bppが上がるほどPSNRが上がっているグラフはすべてYUV444設定で，高品質状態ではYUV444でないと性能が出ていません．
ですが，YUV444が設定できるものは現在のOpenCV環境だとJPEGだけになります．

# 非可逆圧縮フォーマットの品質比較（ほかの評価）
PSNRは人の主観評価からかなり離れています．
また，SSIMも画質評価指標として性能は高くなく，PSNRよりはいいってくらいです．
なので他のメトリクスでも見てみましょう．
なお，JPEG2000は完全に逸脱値のところしか点がないので削除して代わりにHEIFのYUV444を追加しておきます．

SSIM，GMSD，VMAF，Butteraugli，SSIMULACRA2の評価を示します．
なお，[GMSD](https://docs.opencv.org/4.x/d8/d81/classcv_1_1quality_1_1QualityGMSD.html)はOpenCVでも使えます．
Butteraugli，SSIMULACRA2は[JPEG-XL](https://github.com/libjxl/libjxl)のレポジトリに入っています．
[VMAF](https://github.com/Netflix/vmaf)はネットフリックスのレポジトリにいます
この中で人の主観評価に一番高いとされているのがSSIMULACRA2，次にButteraugliです．

Butteraugli指標，SSIMULACRA2指標ともに低ビット帯ではAVIF444が最善で，高ビット帯ではJPEG-XLが最善です．
（残念ながら，どちらもOpenCVのデフォルト設定では使えません．）
なので，OpenCVのオプションで使えるAVIFとWebPに限って圧縮アルゴリズムを振ってみます．


![RD_SSIM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67908/5bd950a0-ae0a-8d67-45a5-74b1cb265ab0.png)
![RD_GMSD.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67908/8c2294b8-43e6-4034-08a2-64c727714a58.png)
![RD_butteraugli.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67908/c3cd8d48-35c4-bb8c-21be-f1c3fc8a9be7.png)
![RD_VMAF.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67908/743b5f3b-7919-794d-c40e-e4d49ce1a88f.png)
![SSIMULACRA2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67908/297661c8-bd62-f28a-9b2e-eb1e9115dde6.png)

## OpenCVで使える符号化アルゴリズムでの比較
OpenCV縛りの符号化性能比較です．
なお参考までにAVIF444の最大圧縮オプションと最速圧縮オプションも追記しています．
このOpenCVで縛って使うのであれば，AVIF420の最も圧縮率が高いオプション0の性能が良いことが分かります．
なお，AVIFはオプション6，9と数が増えるごとに性能が低下していきますが，その分圧縮速度が上がります．
次で説明しますが，AVIFのオプション0は512x512というそれほど大きくない画像に対しても1分以上の計算時間がかかります．

WebPはPSNRで見たときはそんなにAVIFと比べてそん色ないのですが，人の主観に近いメトリクスにするほど，思ったよりも性能でません．

![butcv.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67908/ec31f78a-d5f7-ab86-6c27-239da78767c4.png)
![ssimcv.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67908/ba8bd54b-571e-2fa1-a29c-d1136f9ef05e.png)


# 計算時間
最後に計算時間を示します．使った計算機はAMDスレッドリッパー3990Xの1コア設定です．
1.0bpp 程度になるようにクオリティを設定したエンコード・デコードの合算時間です．
AVIFはアルゴリズムの指定により指数的に重たくなるため，細かくアルゴリズムのオプション設定を取りました．単位はmsecです．

AVIFは分オーダー（68秒や79秒）までいく一方で，JPEGデフォルトは2.69msです．
方法間の速度差は，結構大きいことが分かります．

| Method | OpenCV|Time [msec] |
| ---- | ---- |----|
| JPEGデフォ  |〇| 2.69 |
| JPEG444 PH |〇|2.85 |
| JPEGLIデフォ| ×|10.71|
| MOZ JPEGデフォ|×| 28.33|
| ---- | ----|--ここまでJPEG互換-- |
| WebP| △|13.5-15.8-20.8-53.0-53.12-5883.9 （アルゴリズム0-6のうちデフォの4のみ設定可）|
| JPEG2000|〇|141.1|
| JPEG-XL| ×|27.8-383.5 |
| HEIF420| ×|85.7-283.4|
| HEIF444| ×|88.9-468.5|
| AVIF420| 〇|086-243-546-0956-4315-6049-13690-20938-26393-68806|
| AVIF444| ×|108-283-626-1244-6344-8882-18582-28041-36390-79271（OpenCVライブラリのコード改変しないとYUV444設定不可）|
* PH: プログレッシブモードのハフマン最適化したJPEGを意味する
* -でつないでいるものは，最速設定-最大圧縮設定の時間を表示
* WebPは0-6までアルゴリズムが選択でき，デフォルト設定のアルゴリズム4が選択されています
* AVIFは0-9までSPEEDオプションでアルゴリズムが選択できます
* AVIF444はOpenCVのソースコードレベルから変更しないと設定できません

# おわりに
OpenCVで使える圧縮オプションの比較をやりました．
OpenCVで画像保存する場合，速度さえ無視すれば，新しめの品質評価尺度上でAVIFが圧縮効率いいことが分かりました．

OpenCVだと直接使えないですがJPEG圧縮する場合は，JPEGLIによるJPEG圧縮はMozJPEGよりもbutteraugliやSSIMULACRAといった指標だと優れています．


明日は，@dandelion1124 さんの「NVIDIA製ライブラリ関連のOpenCV CMakeオプションパーフェクトガイド」です！