新しいcodecsの仲間、AVIFのご紹介

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

■ TL;DR
=======
- OpenCVからAVIF Encoder/Decoderが使えるようになりました！
- チューニングしないデフォルトだと、かなり重い＋サイズがでかい…
- 今回は、画像評価はやってないから、もしかするとサイズが大きい分綺麗かも？
- 今後の使いこなしが課題。

■ imgcodecsに新しい仲間、AVIFが追加されたよ!
==========================================
2023/5 AVIF supportがOpenCV imgcodecsに追加されました、やったー！今回はこの紹介をしていく。

https://github.com/opencv/opencv/pull/23596

◯AVIFとは何ものぞ？
-------------------
まずは、AVIFとは何かを簡単にまとめる。

https://ja.wikipedia.org/wiki/AV1

ざっくり言うと、AVIFはAV1 Codec技術を静止画に適用した画像フォーマットである。
なお、コンテナ形式とファイル形式が若干入り組んでいる点には要注意。

- AV1のフレームを、HEIFコンテナ形式で保存している　⇒　AVIFファイル
- H.265のフレームを、HEIFコンテナ形式で保存している　⇒　HEIFファイル

| 静止画 | 動画   |
|:-----:|:------:|
| AVIF  | AV1    |
| WebP  | VP8    |
| HEIF  | H.265  |

AVIFは、後発の強みを生かし、機能は盛りだくさんである。

|特徴                    | AVIF      | JPEG                       |PNG       |
|:----------------------:|:---------:|:--------------------------:|:--------:|
| HDR(ハイダイナミック)  | Supported | N/S                        |次期仕様でSupported予定[^PNGHDR]　|
| 色深度                 | 8,10,12   | 8(12,16は一般的ではない)   |8,16      |
| Lossy圧縮              | Supported  | Supported                  |N/S       |
| Lossless圧縮           | Supported | 一般的ではない             |Supported |
| Alpha                  | Supported | Not Supported              |Supported |
| Depth                  | Supported | Not Supported              |Supported |
| ICC Profile            | Supported | Supported                  |Supported |
| Subsampling            | Supported | Supported                  |N/S       |

PNGHDRについては、こちらのコメントで教えていただきました、ありがとうございます！ https://qiita.com/hon_no_mushi/items/5309a8d576e9f3cd07b1#comment-4fc4f5fc457e06a48682

[^PNGHDR]: こちらのコメントで教えていただきました、ありがとうございます！ https://qiita.com/hon_no_mushi/items/5309a8d576e9f3cd07b1#comment-4fc4f5fc457e06a48682


■ AVIFサポートを有効にしたOpenCV のbuild
======================================

◯AVIF Libraryのインストール
---------------------------
Ubuntu 23.10を使っているならば、libavifというパッケージを利用できます。OpenCVのbuildに使うので、開発用の-devを入れましょう。

```bash
sudo apt install libavif-dev libavif15
```

◯OpenCVのbuildファイルを作成するためにcmakeコマンドを実行する
----------------------------------------------------------

`cmake`を実行するときに、`-DWITH_AVIF=ON`を指定する事。明示的に指定をしないと無視されます（OFFになっていることさえも表示されない）。

```bash
cmake -S opencv -B build4-main -GNinja -DWITH_AVIF=ON
```

Build Configurationの結果はこんな感じになる。

```plaintext
-- General configuration for OpenCV 4.8.0-dev =====================================
--   Version control:               4.8.0-570-g953dddd26b
--
--   Platform:
--     Timestamp:                   2023-12-23T07:08:47Z
--     Host:                        Linux 6.5.0-14-generic x86_64
--     CMake:                       3.27.4
--     CMake generator:             Ninja
--     CMake build tool:            /usr/bin/ninja
--     Configuration:               Release

--   Media I/O:
--     ZLib:                        /usr/lib/x86_64-linux-gnu/libz.so (ver 1.2.13)
--     JPEG:                        /usr/local/lib/libjpeg.so (ver 62)
--     WEBP:                        build (ver encoder: 0x020f)
--     AVIF:                        /usr/lib/x86_64-linux-gnu/libavif.so.15.0.1
--     PNG:                         /usr/lib/x86_64-linux-gnu/libpng.so (ver 1.6.40)
--     TIFF:                        build (ver 42 - 4.2.0)
--     JPEG 2000:                   build (ver 2.5.0)
--     OpenEXR:                     build (ver 2.3.0)
--     HDR:                         YES
--     SUNRASTER:                   YES
--     PXM:                         YES
--     PFM:                         YES

```

◯OpenCVをbuild
---------------
これはいつものbuildですね

```bash
cmake --build build4-main
```


◯性能を測定してみよう
--------------------
さて、どれくらいの性能かな？ということで、測定プログラムを組み込んでみよう。
このファイルを置いて、build4-main上で`cmake . ; ninja` でできる・・・はず。

```cpp
kmtr@kmtr-None:~/work/build4-main$ cat ../opencv/modules/imgcodecs/perf/perf_common.cpp
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include "perf_precomp.hpp"

namespace opencv_test
{
using namespace perf;

typedef TestBaseWithParam<tuple<String,ImreadModes,Size>> CodecsCommon;

PERF_TEST_P_(CodecsCommon, Decode)
{
    String codecExt = get<0>(GetParam());
    ImreadModes immode = get<1>(GetParam());
    Size dstSize = get<2>(GetParam());

    String filename = getDataPath("perf/2560x1600.png");
    cv::Mat src = imread(filename, immode);
    cv::Mat dst;
    cv::resize(src, dst, dstSize);

    vector<uchar> buf;
    imencode(codecExt.c_str(), dst, buf);

    declare.in(buf).out(dst);

    TEST_CYCLE() imdecode(buf, immode);

    SANITY_CHECK_NOTHING();
}

PERF_TEST_P_(CodecsCommon, Encode)
{
    String codecExt = get<0>(GetParam());
    ImreadModes immode = get<1>(GetParam());
    Size dstSize = get<2>(GetParam());

    String filename = getDataPath("perf/2560x1600.png");
    cv::Mat src = imread(filename, immode);
    cv::Mat dst;
    cv::resize(src, dst, dstSize);

    vector<uchar> buf;
    imencode(codecExt.c_str(), dst, buf); // To recode datasize
    declare.in(dst).out(buf);

    TEST_CYCLE() imencode(codecExt.c_str(), dst, buf);

    SANITY_CHECK_NOTHING();
}

INSTANTIATE_TEST_CASE_P(/* */,
    CodecsCommon,
    ::testing::Combine(
        ::testing::Values(
            ".bmp"
#ifdef HAVE_JPEG
            , ".jpg"
#endif
#ifdef HAVE_PNG
            , ".png"
#endif
#ifdef HAVE_TIFF
//            , ".tif"
#endif
#ifdef HAVE_AVIF
            , ".avif"
#endif
#ifdef HAVE_WEBP
            , ".webp"
#endif
        ),
        ::testing::Values(
            ImreadModes(IMREAD_COLOR)
//            , IMREAD_GRAYSCALE
        ),
        ::testing::Values(
            Size(640,480)
            , Size(1920,1080)
//            , Size(2560,1600)
            , Size(3840,2160)
        )
    )
);

} // namespace
```

◯テストを実行する
-----------------
できた実行ファイルを起動する。この時、gtest_output指定がおすすめ。

`./bin/opencv_perf_imgcodecs --gtest_filter="*Common*"  --gtest_output=xml`

実行するとこんな感じでダラダラーって流れて、ここから情報を抜き出すのは大変。これを出力ファイルに出すためのオプションが、gtest_outputである。

```
OpenCL is disabled
Note: Google Test filter = *Common*
[==========] Running 30 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 30 tests from CodecsCommon
[ RUN      ] CodecsCommon.Decode/0, where GetParam() = (".bmp", 1, 640x480)
[ PERFSTAT ]    (samples=100   mean=0.06   median=0.06   min=0.06   stddev=0.01 (15.7%))
[       OK ] CodecsCommon.Decode/0 (62 ms)
[ RUN      ] CodecsCommon.Decode/1, where GetParam() = (".bmp", 1, 1920x1080)
[ PERFSTAT ]    (samples=100   mean=0.99   median=0.99   min=0.91   stddev=0.04 (3.9%))
[       OK ] CodecsCommon.Decode/1 (164 ms)
[ RUN      ] CodecsCommon.Decode/2, where GetParam() = (".bmp", 1, 3840x2160)
[ PERFSTAT ]    (samples=25   mean=3.36   median=3.34   min=3.26   stddev=0.08 (2.4%))
[       OK ] CodecsCommon.Decode/2 (215 ms)
[ RUN      ] CodecsCommon.Decode/3, where GetParam() = (".jpg", 1, 640x480)
[ PERFSTAT ]    (samples=13   mean=1.35   median=1.34   min=1.31   stddev=0.04 (2.6%))
```

◯実行結果をまとめてみる
----------------------

では得られたxmlファイルを整理してみる…

**AVIFくん、Decodeが特に遅すぎるんですけどー！ファイルサイズも大きいんですけどー** という結果になった。とほほ...

（私の使う機材が古くて）まあ、HWアシストがないとこんなものですかね。
また、チューニングを何もしていないので、そのあたりやったらまた結果が変わると思います。

※ i7 4770/VMWare/ubuntu上でのざっくりした測定結果。

|File  | Size      | Decode(ms)  | Encode(ms)  |　File |
|:----:|----------:|------------:|------------:|------:|
|bmp   | 640x480   |    0.06    |    0.10    | 921,654 |
|      | 1920x1080 |    1.02    |    1.23    | 6,220,854 |
|      | 3840x2160 |    3.32    |    5.23    | 24,883,254 |
|jpg   | 640x480   |    1.33    |    1.27    | 50,678 |
|      | 1920x1080 |    7.24    |    7.48    | 202,354 |
|      | 3840x2160 |    26.06   |    29.18   | 539,857 |
|png   | 640x480   |    3.30    |    5.60    | 134,998 |
|      | 1920x1080 |    20.87   |    31.58   | 601,918 |
|      | 3840x2160 |    78.12   |   112.09   | 1,689,892 |
|avif  | 640x480   |   17.52    |   56.35    | 173,418 |
|      | 1920x1080 |   82.77    |   309.59   | 746,933 |
|      | 3840x2160 |  255.88    |   1360.44  | 2,028,269 |
|webp  | 640x480   |   2.42     |  141.84    | 90,704 |
|      | 1920x1080 |   12.71    |  653.10    | 391,082 |
|      | 3840x2160 |   43.82    |  2415.63   | 1,091,642 |

<details><summary>生データ</summary>

```bash
kmtr@kmtr-None:~/work/build4-main$ python3 ../opencv/modules/ts/misc/report.py test_detail.xml  | sort -k 1,1 -k 3n


                                              collected samples outliers
                Name of Test                      Number of     Number of    Min       Median   Geometric mean    Mean    Standard deviation
Decode::CodecsCommon::(".avif", 1, 640x480)          10             0      17.19 ms   17.52 ms     17.56 ms     17.56 ms       0.32 ms
Decode::CodecsCommon::(".avif", 1, 1920x1080)        10             0      81.89 ms   82.77 ms     83.19 ms     83.21 ms       1.49 ms
Decode::CodecsCommon::(".avif", 1, 3840x2160)        13             1     253.73 ms  255.88 ms    255.82 ms    255.82 ms       1.22 ms
Decode::CodecsCommon::(".bmp", 1, 640x480)           100            8      0.06 ms    0.06 ms      0.06 ms      0.06 ms        0.01 ms
Decode::CodecsCommon::(".bmp", 1, 1920x1080)         100            8      0.92 ms    1.02 ms      1.02 ms      1.02 ms        0.05 ms
Decode::CodecsCommon::(".bmp", 1, 3840x2160)         38             3      3.18 ms    3.32 ms      3.32 ms      3.32 ms        0.10 ms
Decode::CodecsCommon::(".jpg", 1, 640x480)           100            8      1.27 ms    1.33 ms      1.34 ms      1.34 ms        0.05 ms
Decode::CodecsCommon::(".jpg", 1, 1920x1080)         10             0      7.11 ms    7.24 ms      7.25 ms      7.25 ms        0.11 ms
Decode::CodecsCommon::(".jpg", 1, 3840x2160)         13             1      25.43 ms   26.06 ms     26.03 ms     26.04 ms       0.40 ms
Decode::CodecsCommon::(".png", 1, 640x480)           25             2      3.23 ms    3.30 ms      3.32 ms      3.32 ms        0.09 ms
Decode::CodecsCommon::(".png", 1, 1920x1080)         13             1      20.53 ms   20.87 ms     20.96 ms     20.97 ms       0.33 ms
Decode::CodecsCommon::(".png", 1, 3840x2160)         13             1      76.62 ms   78.12 ms     78.35 ms     78.36 ms       1.11 ms
Decode::CodecsCommon::(".webp", 1, 640x480)          13             1      2.34 ms    2.42 ms      2.44 ms      2.44 ms        0.06 ms
Decode::CodecsCommon::(".webp", 1, 1920x1080)        10             0      12.46 ms   12.71 ms     12.75 ms     12.75 ms       0.21 ms
Decode::CodecsCommon::(".webp", 1, 3840x2160)        25             2      43.15 ms   43.82 ms     44.10 ms     44.11 ms       0.72 ms
Encode::CodecsCommon::(".avif", 1, 640x480)          10             0      55.49 ms   56.35 ms     56.37 ms     56.37 ms       0.53 ms
Encode::CodecsCommon::(".avif", 1, 1920x1080)        10             0     305.32 ms  309.59 ms    308.87 ms    308.88 ms       2.79 ms
Encode::CodecsCommon::(".avif", 1, 3840x2160)        10             0     1338.88 ms 1360.44 ms   1360.16 ms   1360.27 ms      17.94 ms
Encode::CodecsCommon::(".bmp", 1, 640x480)           100            8      0.10 ms    0.10 ms      0.11 ms      0.11 ms        0.01 ms
Encode::CodecsCommon::(".bmp", 1, 1920x1080)         100            8      1.14 ms    1.23 ms      1.23 ms      1.24 ms        0.05 ms
Encode::CodecsCommon::(".bmp", 1, 3840x2160)         13             1      4.97 ms    5.23 ms      5.20 ms      5.20 ms        0.12 ms
Encode::CodecsCommon::(".jpg", 1, 640x480)           10             0      1.20 ms    1.27 ms      1.27 ms      1.27 ms        0.04 ms
Encode::CodecsCommon::(".jpg", 1, 1920x1080)         10             0      7.34 ms    7.48 ms      7.49 ms      7.49 ms        0.08 ms
Encode::CodecsCommon::(".jpg", 1, 3840x2160)         10             0      28.72 ms   29.18 ms     29.26 ms     29.26 ms       0.38 ms
Encode::CodecsCommon::(".png", 1, 640x480)           10             0      5.48 ms    5.60 ms      5.61 ms      5.61 ms        0.10 ms
Encode::CodecsCommon::(".png", 1, 1920x1080)         10             0      31.31 ms   31.58 ms     31.78 ms     31.78 ms       0.50 ms
Encode::CodecsCommon::(".png", 1, 3840x2160)         10             0     110.25 ms  112.09 ms    111.99 ms    112.00 ms       1.19 ms
Encode::CodecsCommon::(".webp", 1, 640x480)          10             0     141.37 ms  141.84 ms    142.21 ms    142.21 ms       1.27 ms
Encode::CodecsCommon::(".webp", 1, 1920x1080)        10             0     651.46 ms  653.10 ms    653.34 ms    653.34 ms       1.51 ms
Encode::CodecsCommon::(".webp", 1, 3840x2160)        10             0     2394.42 ms 2415.63 ms   2411.83 ms   2411.85 ms      10.99 ms
test_detail.xml
```

</details>

◯画像評価は？
-------------
今回は時間の都合で、画像評価は割愛です。

もしかするとこの画像サイズに見合った分、画質が綺麗なのかもしれない！

◯使いこなしが課題
-----------------
AVIF対応に伴い、IMWRITEのオプションが増えました。

https://docs.opencv.org/4.x/d8/d6a/group__imgcodecs__flags.html#ga292d81be8d76901bff7988d18d2b42ac

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89434/b96c6751-a5a1-fb13-9413-3b26f255227d.png)

（んんんん？最高速設定がデフォルト仕様に見えるでござる、、、）

■ まとめ
=======
- OpenCVからAVIF Encoder/Decoderが使えるようになりました！
- チューニングしないデフォルトだと、かなり重い＋サイズがでかい…
- 今回は、画像評価はやってないから、もしかするとサイズが大きい分綺麗かも？
- 今後の使いこなしが課題。

それでは、ご精読ありがとうございました！！
あしたはアドベントカレンダー最終日となります！！