OpenCVのGIF codecを試してみよう！

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

TL;DR
=====
- OpeCV 4.11にGIF Codecsが追加されました！使いたい時はcmakeで有効化してね
- delay time設定が独自なので、ちょーっといやーんです！

はじめに
=======
GIF画像形式は、遥か昔「インターネット」が普及し始めた時に、共にあり、滅んだ。

後に言う「LZW特許問題」である。「LZW特許の使用料、個人のフリーソフトについては免除っていったけど、やっぱ徴収するわ！」で、(# ﾟДﾟ)という話。 
- とほほさんの[GIFの特許問題について](https://www.tohoho-web.com/wwwxx051.htm)
- Wikipediaの[特許問題とその顛末](https://ja.wikipedia.org/wiki/Graphics_Interchange_Format#%E7%89%B9%E8%A8%B1%E5%95%8F%E9%A1%8C%E3%81%A8%E3%81%9D%E3%81%AE%E9%A1%9B%E6%9C%AB)

そして、米国では2003年6月20日、日本でも2004年6月20日に、特許の有効期限が切れました。

あれから20年経過した2024年、OpenCVにもGIFサポートが追加されたのです！！

こちらのGSoC_2024の成果でございます！

https://github.com/opencv/opencv/wiki/GSoC_2024#idea-animated-images-and-other-functionality-for-imgcodecs


アニメーション画像を作る簡単な手段
===============================
例えば、連番画像を作って、imagemagikに入れたら、GIFもAPNGでもアニメーションする画像ファイル作れます。あるいは、videoio module使って、ffmpeg経由で動画ファイルも作れます。

でも、それってちょーっと、めんどくさくないですかね？ライブラリとかも必要になりますし。

もしOpenCVだけで簡単にアニメーション画像作れたら、それはそれで幸せだと思うのですよ！

よし、やっていこう！

GIF Codec有効化して、OpenCVをbuildしよう！
========================================
GIF Codecを有効化するためには、明示的に`-DWITH_IMGCODEC_GIF=ON`を指定しなければならない。なお、最新のOpenCV 4.xブランチだけあれば、外部ライブラリなどは不要。

```bash
git clone https://github.com/opencv/opencv.git opencv4
cmake -S opencv4 -B build4-main.GIF -GNinja -DWITH_IMGCODEC_GIF=ON
cmake --build build4-main.GIF
```

そうすると、Media I/O:の欄に「GIF」の文字が、やったね！

```
--   Media I/O:
--     ZLib:                        /usr/lib/x86_64-linux-gnu/libz.so (ver 1.3)
--     JPEG:                        /usr/lib/x86_64-linux-gnu/libjpeg.so (ver 80)
--     WEBP:                        /usr/lib/x86_64-linux-gnu/libwebp.so (ver encoder: 0x020f)
--     AVIF:                        /usr/lib/x86_64-linux-gnu/libavif.so.16.0.4 (ver 1.0.4)
--     PNG:                         /usr/lib/x86_64-linux-gnu/libpng.so (ver 1.6.43)
--     TIFF:                        /usr/lib/x86_64-linux-gnu/libtiff.so (ver 42 / 4.5.1)
--     JPEG 2000:                   OpenJPEG (ver 2.5.0)
--     OpenEXR:                     build (ver 2.3.0)
--     GIF:                         YES
--     HDR:                         YES
--     SUNRASTER:                   YES
--     PXM:                         YES
--     PFM:                         YES
```

あとはいつも通り、build と installすれば準備はOK!

```bash
cmake --build build4-main.GIF
sudo cmake --install build4-main.GIF
```

GIF画像を作ってみる
==================

サンプルコードはこんな感じですね

- `std::vector<cv::Mat>` に画像データをぶち込む
- `imwrite()`する

```cpp
// g++ main.cpp -o a.out -I/usr/local/include/opencv4 -lopencv_core -lopencv_imgcodecs -lopencv_imgproc

#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>

int main()
{
  cv::Mat src = cv::imread("opencv-logo-white.png", cv::IMREAD_COLOR);

  std::vector<cv::Mat> imgs;
  std::vector<int> params;
  params.push_back(cv::IMWRITE_GIF_LOOP);
  params.push_back(1);  // Do not loop
  params.push_back(cv::IMWRITE_GIF_SPEED);
  params.push_back(91); // ( 100 - (91 - 1) ) * 10 ms = 100ms

  for(int i = 1; i < 21 ; i++){
    cv::Mat frame;
    cv::blur(src, frame, cv::Size(i,i));
    char text[64]; sprintf(text, "%d", i); // std::format() is supported after C++20.
    cv::putText(frame, text, cv::Point(32,32), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(255,255,255));
    imgs.push_back(frame);
  }

  std::reverse(imgs.begin(), imgs.end());
  std::cout << "imgs.size() = " << imgs.size() << std::endl;
  cv::imwrite("test.gif", imgs, params);

  return 0;
}
```

![test.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89434/4061f00b-6525-f0d0-9038-f56b7c314f0e.gif)


ちょっとした悲しみ
=================

loop１回が指定できない！
----------------------
本当は、ループの最後でビタ！と止め絵にしたいのですが・・・。
どうにもChromiumでもFirefoxでも画像ビューワでも、LOOP指定がちゃんと動かないですね。

なお、バイナリエディアでみて指定できていることは確認済みなので、表示側のバグですかねえ・・・ 0x30d付近から

| Add | Size | Value | contents |
|-:|-:|-:|:-|
| 0x30d | 1 | 0x21 | Entension Introducer |
| 0x30e | 1 | 0xff | Entension Label |
| 0x30f | 1 | 0x0b | Block Size #1 |
| 0x310 | 8 | `NETSCAPE` | Application Identifier |
| 0x318 | 3 | `2.0` | Application Authentication Code |
| 0x31b | 1 | 0x03 | Block Size #2 |
| 0x31d | 3 | 0x01_0100 | Application Data <br/> 先頭1byteは0x01固定、残り2byteはループ回数|
| 0x31f | 1 | 0x00 | Block Terminator |

```plaintext
kmtr@kmtr-VMware-Virtual-Platform:~/work/qiita$ hexdump -C test.gif  | grep NETSCAPE -A5 -B5
000002c0  24 aa fc 24 ff fc 48 00  fc 48 55 fc 48 aa fc 48  |$..$..H..HU.H..H|
000002d0  ff fc 6c 00 fc 6c 55 fc  6c aa fc 6c ff fc 90 00  |..l..lU.l..l....|
000002e0  fc 90 55 fc 90 aa fc 90  ff fc b4 00 fc b4 55 fc  |..U...........U.|
000002f0  b4 aa fc b4 ff fc d8 00  fc d8 55 fc d8 aa fc d8  |..........U.....|
00000300  ff fc fc 00 fc fc 55 fc  fc aa fc fc ff 21 ff 0b  |......U......!..|
00000310  4e 45 54 53 43 41 50 45  32 2e 30 03 01 01 00 00  |NETSCAPE2.0.....|
00000320  21 f9 04 0c 0a 00 00 00  2c 00 00 00 00 b4 00 ee  |!.......,.......|
00000330  00 07 08 ff 00 01 08 1c  48 b0 a0 c1 83 08 13 22  |........H......"|
00000340  04 01 00 84 43 20 40 88  00 21 13 86 0c a0 42 84  |....C @..!....B.|
00000350  4a 91 2a 55 8c 58 b1 62  e5 40 96 1b 49 b2 a4 c9  |J.*U.X.b.@..I...|
00000360  93 24 41 7e f4 a8 b1 14  a1 42 80 ca 80 21 03 11  |.$A~.....B...!..|
```

`IMWRITE_GIF_SPEED`の指定が何か変
---------------------------------
フレーム毎のdelay time指定方法が直感的ではない（なんでこういうデザインにしたのですかね）

Application側での指定方法
```cpp
  params.push_back(cv::IMWRITE_GIF_SPEED);
  params.push_back(91); // ( 100 - (91 - 1) ) * 10 ms = 100ms
```

OpenCV側でのハンドリング部分
```cpp
    // confirm the params
    for (size_t i = 0; i < params.size(); i += 2) {
        switch (params[i]) {
            case IMWRITE_GIF_LOOP:
                loopCount = std::min(std::max(params[i + 1], 0), 65535); // loop count is in 2 bytes
                break;
            case IMWRITE_GIF_SPEED:
                frameDelay = 100 - std::min(std::max(params[i + 1] - 1, 0), 99); // from 10ms to 1000ms
                break;
```

- GIFフォーマットのdelay time指定は、10ms単位で、0～65535まで
- だけど、なぜか1～100の値域に絞ってる？
- しかも、最大値が1000msに限定？

10ms単位を直接指定できればいいんじゃないですかね。。。

```cpp
            case IMWRITE_GIF_DELAYTIME_10MS:
                frameDelay = std::min(std::max(params[i + 1], 0), 65535); // from 0ms to 65535 * 10ms
                break;
```

まとめ
======
- OpeCV 4.11にGIF Codecsが追加されました！使いたい時はcmakeで有効化してね
- delay time設定が独自なので、ちょーっといやーんです！

お忙しい中、ご精読頂きましてありがとうございました！！

明日、12/16は、dandelion先生の「OpenCVのzlib-ng連携で高速化を図る」です！ new generationの実力を見せていただきましょう！！（？）