本当は速いImageMagick: サムネイル画像生成を10倍速くする方法

一般的に ImageMagick のサムネイル画像生成は遅いとされており、パフォーマンスが求められるシーンでは Imlib2 などのより高速な画像処理ライブラリが使われることが多いです。
Imlib2 の高速さについては、以前「Imlib2でImageMagickより3倍高速かつ美しいサムネイル画像の生成 - 床のトルストイ、ゲイとするとのこと」という記事で紹介しました。この記事のベンチマークにおいて、Imlib2 によるサムネイル画像の生成は、 ImageMagick の3倍程高速でした。
しかし、 ImageMagick は Imlib2 より画質がよく、高機能で使いやすく、今も頻繁にメンテナンスされており、とてもよく出来ています。その点 Imlib2 は、2004年からメンテナンスされておらず、セキュリティホールが見つかっても、各Linuxディストリビューションがそれぞれパッチを当てているような状況です。 ImageMagick がもし十分に速ければ、 Imlib2 から脱却することができます。

そんな中、 ImageMagick について調べているうちに、 ある条件下で Imlib2 よりも高速にサムネイル生成する方法を見つけたので、紹介します。

いつもの convert に「-define jpeg:size=...」をつけるだけで10倍速くなる

「ある条件下で高速に」と書きましたが、その条件とは、以下の2つです。

  • 条件1: 元ファイルが JPEG 画像であること
  • 条件2: 元ファイルを開くより前に、変換後の縦横pxが分かっていること

たとえば、前回の記事 の例と同じ、4288x2848のJPG画像(4.8MB)から180x120pxのサムネイル画像を作成することを考えます。
普段なら、 ImageMagick の convert コマンドを使って

convert -resize 180x120 src.jpg dst.jpg

などとするとおもうんですが、ここで、高速化のためのオプションを付けます。

convert -define jpeg:size=180x120 -resize 180x120 src.jpg dst.jpg

速くする方法というのは、実は、これだけです。*1

ベンチマーク

4288x2848のJPG画像(4.8MB)から180x120pxのサムネイル画像を以下の条件でそれぞれ10回ずつ生成し、変換にかかる時間を比較しました。

  1. ImageMagick での普通のリサイズ(縮小アルゴリズムはデフォルトのLanczos)
  2. ImageMagick で -define jpeg:size オプションを付けた場合のリサイズ(同様にLanczos)
  3. Imlib2 (imlib2-ruby) でリサイズ

ベンチマークのコードはこちら(gist: 488401)
結果は以下のとおりです。*2

条件 1枚あたりの平均変換時間(sec) 変換結果(quality=90)
1. ImageMagick(jpeg:size なし) 2.735397
2. ImageMagick(jpeg:size あり) 0.275900
3. Imlib2 0.832415

以上のように、ImageMagick のサムネイル生成では、 jpeg:size オプションを指定することで、指定しない場合に比べておよそ10倍高速になりました。Imlib2 と比べても約3倍ほど高速になっています。

画質については、jpeg:size オプションをつけたことによる変化はほとんどありませんでした。

画質はほぼそのままで高速なら、このオプションを付けない理由はないですね。

なんで速くなるのか

なぜ jpeg:size で画像の大きさを指定しただけで、これほどまでに JPEG の縮小が速くなったのかを説明します。
そもそもこのオプションはなんなのかというと、公式ドキュメントに以下のような記述があります。

(以下、Common Formats -- IM v6 Examplesより意訳)

-define jpeg:size={width}x{height}

JPEGライブラリに、JPEGファイルの読み込みのヒントを与えます。このとき、作成される画像は、少なくともこの width x height より大きな画像になります。
もし入力画像が巨大なら、このヒントを与えることで、ImageMagickは小さな画像として開くため、画像読み込み時のメモリを大幅に節約でき、演算を劇的に高速化できるでしょう。
このオプションはあくまでヒントでしかなく、実際にその大きさの画像を得ることができるわけではないという点を忘れないでください。このヒントで指定したサイズに近い、かつそれ以上の大きさの画像を得ることになります。

この公式の説明ではよくわかりませんが、とにかく速くなりそうなことは伝わってきます。このオプションの挙動について、詳しく調べてみました。
先程の例、4288x2848 のJPEG画像を 180x120 にリサイズする例で説明します。使ったコマンドは、以下のとおりでした。

convert -define jpeg:size=180x120 -resize 180x120 src.jpg dst.jpg

この時の挙動は次のようになります。

  1. ImageMagickは、-define jpeg:size で与えられた 180x120 を src.jpg のサイズとして記憶する
  2. ImageMagickは src.jpg を開き、実際のサイズが 4288x2848 であることを知る
  3. ImageMagickは、実際のサイズの 1/2(2144x1424)、1/4(1608x1068)、1/8(536x356)の中から、 180x120 に一番近く、かつ 180x120 より大きいサイズを選ぶ(この場合は1/8
  4. ImageMagickは、 src.jpg を536x356の画像として開く
  5. ImageMagickは、 src.jpg を 536x356 から 180x120 にリサイズし、 dst.jpg として保存する

図にすると次のようになります。
f:id:mirakui:20110123181251p:image
ImageMagick は、 -define jpeg:size={width}x{height} というオプションが与えられると、実際の画像サイズの 1/2、1/4、1/8 のサイズの中から、オプションで指定されたサイズに近いものを選び、そのサイズとして画像を開きます。どうしてそんなことが可能なのかというと、その仕組はJPEGのデータ構造に由来しています。JPEG画像は、8x8(場合によっては16x16)pxの小さな正方形の画像の集合からなっています。詳しい説明は他の資料に任せますが、この構造を活かして、ImageMagickで利用しているJPEGライブラリのlibjpegでは、1/2、1/4、1/8のサイズへの縮小は高速に計算できるのです。

ImageMagickは画像をすべてメモリ上に展開するという特性があるので、元画像が巨大であるほどメモリ消費は激しくなり、そのためのメモリ確保に時間がかかります。それが ImageMagick が遅いと言われる原因のひとつです。リサイズ処理自体は、高速なアルゴリズム(デフォルトではLanczos)で行われるので、実は Imlib2 などの他のライブラリに比べても遅いわけではないのです。

この仕組みを簡単に理解するために、convert コマンドに、デバッグ出力をする「-debug」オプションを加えてみます。
まずは、 -define jpeg:size=180x120 をつけない場合です。

$ convert -debug Coder -log %e -resize 180x120 src.jpg dst.jpg
Profile: exif, 45952 bytes
Profile: xmp, 5079 bytes
Profile: iptc, 76 bytes
Interlace: nonprogressive
Data precision: 8
Geometry: 4288x2848
   :

Geometry という項目が、 4288x2848 と出力されています。これは、 src.jpg が本来の 4288x2848 として開かれたことを示しています。
次に、 -define jpeg:size=180x120 を付けてみます。

$ convert -debug Coder -log %e -define jpeg:size=180x120 -resize 180x120 src.jpg dst.jpg
Profile: exif, 45952 bytes
Profile: xmp, 5079 bytes
Profile: iptc, 76 bytes
Scale factor: 23.733333333333334281
Interlace: nonprogressive
Data precision: 8
Geometry: 536x356
   :

このとき、 Geometry は 536x356 となっており、この src.jpg は一旦、元画像の 1/8 である 536x356 として開かれたことがわかります。

まとめ

今回は、 ImageMagick で、JPEG画像の縮小を通常の10倍速くする方法を紹介しました。使えるシーンではどんどん -define jpeg:size をつけてみましょう。

おまけ: 古いバージョンのImageMagickでは?

この -define jpeg:size オプションは、 ImageMagick-6.5.x 以降から使うことができます。それより前のバージョンでは、-sizeオプションを使うことで同様の効果が得られます。

おまけ: MagickWand では?

convert コマンドのオプションは -define jpeg:size=... ですが、同様の効果を MagickWand から得るためには、 MagickReadImage等 の前に

MagickSetOption(wand, "jpeg:size", "180x120");

を呼べばOKです。

おまけ: GraphicsMagick はどうなの?

ImageMagick の話をすると、必ず「ImageMagick から派生した GraphicsMagick のほうが速いよ!」と教えてくれる人が出てくるんですが、リサイズに関しては公式のベンチマークでも別にそんな速くないし、手元でもセットアップが大変なわりに有意な速度差が出ませんでした。あとブランチ元の ImageMagick のバージョンが古すぎて非常に使いづらいです。

*1:ImageMagick-6.4.9 かそれ以前のバージョンでは、 -define jpeg:size=... でなく -size=... とします

*2:前回の記事よりCPUが貧弱な MacBook Air 11inch で計測しているのでやや遅めです