この記事はOpenCV Advent Calendar 2024の15日目の記事です。
他の記事は目次にまとめられています。
TL;DR
- OpeCV 4.11にGIF Codecsが追加されました!使いたい時はcmakeで有効化してね
- delay time設定が独自なので、ちょーっといやーんです!
はじめに
GIF画像形式は、遥か昔「インターネット」が普及し始めた時に、共にあり、滅んだ。
後に言う「LZW特許問題」である。「LZW特許の使用料、個人のフリーソフトについては免除っていったけど、やっぱ徴収するわ!」で、(# ゚Д゚)という話。
- とほほさんのGIFの特許問題について
- Wikipediaの特許問題とその顛末
そして、米国では2003年6月20日、日本でも2004年6月20日に、特許の有効期限が切れました。
あれから20年経過した2024年、OpenCVにもGIFサポートが追加されたのです!!
こちらのGSoC_2024の成果でございます!
アニメーション画像を作る簡単な手段
例えば、連番画像を作って、imagemagikに入れたら、GIFもAPNGでもアニメーションする画像ファイル作れます。あるいは、videoio module使って、ffmpeg経由で動画ファイルも作れます。
でも、それってちょーっと、めんどくさくないですかね?ライブラリとかも必要になりますし。
もしOpenCVだけで簡単にアニメーション画像作れたら、それはそれで幸せだと思うのですよ!
よし、やっていこう!
GIF Codec有効化して、OpenCVをbuildしよう!
GIF Codecを有効化するためには、明示的に-DWITH_IMGCODEC_GIF=ON
を指定しなければならない。なお、最新のOpenCV 4.xブランチだけあれば、外部ライブラリなどは不要。
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!
cmake --build build4-main.GIF
sudo cmake --install build4-main.GIF
GIF画像を作ってみる
サンプルコードはこんな感じですね
-
std::vector<cv::Mat>
に画像データをぶち込む -
imwrite()
する
// 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;
}
ちょっとした悲しみ
loop1回が指定できない!
本当は、ループの最後でビタ!と止め絵にしたいのですが・・・。
どうにも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 先頭1byteは0x01固定、残り2byteはループ回数 |
0x31f | 1 | 0x00 | Block Terminator |
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側での指定方法
params.push_back(cv::IMWRITE_GIF_SPEED);
params.push_back(91); // ( 100 - (91 - 1) ) * 10 ms = 100ms
OpenCV側でのハンドリング部分
// 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単位を直接指定できればいいんじゃないですかね。。。
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の実力を見せていただきましょう!!(?)