OpenCVでprogressiveJPEGを保存できるようにする方法
画像のプログレッシブ形式
画像をネットワーク転送して、全データ到着の前に荒い画像を表示したいことがしばしばあります。その際に使われるのが、プログレッシブ(インターレース)形式の画像です。規格としては、JPEGやGIF、PNGなどが対応しています。プログレッシブ形式のメリットを詳しく知りたい場合、[1]が参考になります。
libjpegでのプログレッシブ圧縮指定
OpenCVでは、imwrite関数で画像のファイル書き込みができ、実装としてはファイル名の拡張子により呼び出すlibjpegやlibpngなどの画像ライブラリを変更しています。つまり、JPEG形式の画像を実際に画像ファイルの読み書きをするのはlibjpeg[2]です。最近では、高速化のために派生したlibjpeg-turbo[3]が標準的に使われることが多いようです。インターフェースは同じに整えられているので、使う側は(超高速実装を目指さなければ)意識する必要がありません。
libjpegのプログレッシブ方式による圧縮の方法を確認しましょう。libjpegのソースをダウンロードしてきて、progressiveなどのキーワードで全文探索していくと、
cjpeg.cの中に、
jpeg_simple_progression(cinfo);
なる関数呼び出しを見つけることが出来ました。
opencvでのjpeg書き出し箇所
今度はOpenCVのimwrite関数がどこでlibjpegを呼び出しているかを検索して調べていくと、grfmt_jpeg.cppというファイル中のJpegEncoder::write関数で扱っているようだということが分かります。jpegの圧縮情報は、
struct jpeg_compress_struct cinfo;
という構造体で管理されているようです。このメンバ変数に、progressive_modeという名前のboolパラメータがありますので、とりあえずtrueにしてみます。次に、cjpeg中で使われていたjpeg_simple_progression関数を呼び出します。これを、JpegEncoder::write関数中ごろの
jpeg_set_defaults( &cinfo );
の下に挿入します。
cinfo.progressive_mode = true;
jpeg_simple_progression(&cinfo);
とりあえずこれだけ書き換えてOpenCVをビルドしなおしてみます。cmake等を使ってOpenCVのプロジェクトを作ったりビルドしなおしたりする方法は省略します。WEBに一杯情報があるので、調べてください。初めてのビルドだと、数時間かかります。
imwriteでprogressiveJPEGが書き出せるかテスト
適当にPNGファイルを読んで、プログレッシブJPEGとして書き出してみます。
#include "opencv2/opencv.hpp"
using namespace cv;
int main(int argc, char* argv)
{
Mat img = imread("tsukuba_r.png");
imwrite("test.jpeg", img);
return 0;
}
フリーソフトの簡易プログレッシブJPEG判定機[4]というのを借りて、プログレッシブ形式になっているかどうかを確認してみます。
出来ていそうですね。
mozillaプロジェクトがプログレッシブ圧縮をベースにしたWEB向け高圧縮JPEGエンコーダmozjpeg[5]というのを開発しているようです。今後はこれに期待ですね。Googleが作ったwebPというのもあるそうですが、こちらは新しい形式になるのでなかなかユーザが増えていないそうです。
OpenCVでもソースを書き換えれば、プログレッシブJPEGで圧縮できる方法があることが分かりました。このままではすべてのJPEGファイルをプログレッシブ圧縮してしまうので、JpegEncoder::write関数の引数のparamsでプログレッシブ圧縮するかどうかのフラグを指定できるようにすると便利が良さそうです。
highgui_c.hの中でIMWRITE用フラグがenum宣言されています。自分が使うだけなら困りませんので適当に、
CV_IMWRITE_JPEG_PROGRESSIVE=11
などと追加して、
{
CV_IMWRITE_JPEG_QUALITY =1,
CV_IMWRITE_PNG_COMPRESSION =16,
CV_IMWRITE_PNG_STRATEGY =17,
CV_IMWRITE_PNG_BILEVEL =18,
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,
CV_IMWRITE_PXM_BINARY =32,
CV_IMWRITE_WEBP_QUALITY =64,
CV_IMWRITE_JPEG_PROGRESSIVE=11
};
としてみます。
grfmt_jpeg.cppのJpegEncoder::write関数内のパラメータ指定箇所を少し書き換えます。
~省略~
int quality = 95;
bool progressive = false;
for( size_t i = 0; i < params.size(); i += 2 )
{
if( params[i] == CV_IMWRITE_JPEG_QUALITY )
{
quality = params[i+1];
quality = MIN(MAX(quality, 0), 100);
}
else if( params[i] == CV_IMWRITE_JPEG_PROGRESSIVE )
{
if(params[i+1] != 0){
progressive = true;
}
}
}
jpeg_set_defaults( &cinfo );
if(progressive){
cinfo.progressive_mode = true;
jpeg_simple_progression(&cinfo);
}
jpeg_set_quality( &cinfo, quality,
TRUE /* limit to baseline-JPEG values */ );
jpeg_start_compress( &cinfo, TRUE );
~省略~
などとしてみます。これぐらい、openCVの標準で対応してくれるとありがたいのですが、特許などの問題があるんですかね?
使い方は、
#include "opencv2/opencv.hpp"
#include <opencv2/highgui/highgui.hpp>
#pragma comment(lib, "opencv_core300.lib")
#pragma comment(lib, "opencv_highgui300.lib")
using namespace cv;
extern int CV_IMWRITE_JPEG_PROGRESSIVE = 11;
int main(int argc, char* argv)
{
std::vector<int> params(2);
params[0] = CV_IMWRITE_JPEG_PROGRESSIVE;
params[1] = 1;
Mat img = imread("tsukuba_r.png");
imwrite("test.jpeg", img, params);
return 0;
}
などのように。
なお、progressiveJPEGの読み込みはとくに何もせずにできるようです。
[1]http://mikeneko.creator.club.ne.jp/~lab/grp/png/p3.html#h3-5
[2]http://ja.wikipedia.org/wiki/Libjpeg
[3]http://libjpeg-turbo.virtualgl.org/
[4]http://limetarte.net/if_software/profile.cgi?_v=1346751019&tpl=view2