ImageMagick PythonMagick GraphicsMagick and pgmagick

少し前にFlickrクローンをつくろうと思ってPythonでの画像処理について調べていました。 画像処理といっても既存の変換ツールorライブラリの使い方や出力画像の質、 扱いやすさ等について調べていました。

PIL はPythonの画像操作ライブラリの代表格ですが、縮小アルゴリズムがあまり選択できず 縮小すると画像が荒くなってしまいます。 OpenCV はドキュメントが数学っぽくて理解に時間が掛かりそうだったので、 小さい画像への縮小処理に使用して、その他のもう少し大きな画像の縮小には 使用しませんでした。またPILと同じく縮小すると画像が荒くなってしまいます。 もう一つ画像処理に使用できるライブラリとして ImageMagick があります。 ImageMagickでも荒くはなるのですが鮮鋭化フィルタを同時にかけれるので、 キレイな画像で縮小画像を保存できます。 ImageMagick自体はライブラリとコマンドセット(convert, identifyコマンド等)を 提供していますが、様々な言語のインターフェースが既に存在しています。

PythonMagick

ImageMagickをPythonで使用したいと思いさらに調べました。 ImageMagick本家サイトに存在するPythonバインディングは PythonMagick です。 PythonMagickという同じ名前でGraphicsMagickのPythonインターフェースが存在 します が現在は死んでいるようです。(GraphicsMagick はImageMagickをアップデートしたもので、 速度向上も図られているようです。本当はこれのPythonバインディングが欲しい。)

Ubuntuでは python-pythonmagick パッケージをインストールすれば使用することができます。 ソースからインストールするときはboost等のインストールも必要になりやや手順が 面倒ですが、やってやれなくはないと思います。

$ apt-get install python-pythonmagick

pgmagick

GraphicsMagickのPythonバインディングがなさげなので、Imageまわりを実装した ほんとにほんとに薄いラッパーを作ってみました。

http://www.hexacosa.net/hgrepos/pgmagick/

以下のような感じで使います。ほぼPythonMagickと同じ。

from pgmagick import Image, FilterTypes
ft = FilterTypes()
im = Image('./X.jpg')
im.quality(100)
im.filterType(ft.SincFilter)
im.scale('1000x1000')
im.sharpen(1.0)
im.write('./Y.jpg')

もう少し Boost.Python を勉強しないとダメですね。

処理速度

1024x768のJPEG画像を500x500に縮小し、鮮鋭化フィルタをかけたものを 品質100で保存する処理の速度比較をしてみました。 縮小アルゴリズムは Sinc にしてみました。 PythonMagickを使用したスクリプトではフィルタの使用方法がわからなかったので、 デフォルトのアルゴリズムになっています。スクリプトは以下のような感じです。

from PythonMagick import Image

im = Image('./X.jpg')
im.quality(100)
im.scale('500x500')
im.sharpen(1.0)
im.write('./Y.jpg')

測定はtimeコマンドです。

===================================================
[pgmagick (libGraphicsMagick++ wrapper for Python)]
1.97user 0.06system 0:01.23elapsed 164%CPU (0avgtext+0avgdata 63280maxresident)k
0inputs+3440outputs (0major+10519minor)pagefaults 0swaps
1.98user 0.05system 0:01.22elapsed 165%CPU (0avgtext+0avgdata 63280maxresident)k
0inputs+3440outputs (0major+10534minor)pagefaults 0swaps
1.97user 0.06system 0:01.22elapsed 166%CPU (0avgtext+0avgdata 63280maxresident)k
0inputs+3440outputs (0major+10516minor)pagefaults 0swaps
1.97user 0.05system 0:01.20elapsed 168%CPU (0avgtext+0avgdata 63360maxresident)k
0inputs+3440outputs (0major+10524minor)pagefaults 0swaps
1.98user 0.05system 0:01.22elapsed 167%CPU (0avgtext+0avgdata 63360maxresident)k
0inputs+3440outputs (0major+10528minor)pagefaults 0swaps
-rw-r--r-- 1 hattori hattori 236239 2010-08-05 03:07 Y.jpg
===================================================
[PythonMagick(libMagick++ wrapper for Python)]
2.01user 0.10system 0:01.29elapsed 163%CPU (0avgtext+0avgdata 84032maxresident)k
0inputs+3352outputs (0major+16394minor)pagefaults 0swaps
2.05user 0.05system 0:01.27elapsed 165%CPU (0avgtext+0avgdata 84032maxresident)k
0inputs+3352outputs (0major+16394minor)pagefaults 0swaps
2.06user 0.07system 0:01.29elapsed 164%CPU (0avgtext+0avgdata 84464maxresident)k
0inputs+3352outputs (0major+16432minor)pagefaults 0swaps
2.02user 0.06system 0:01.28elapsed 163%CPU (0avgtext+0avgdata 84048maxresident)k
0inputs+3352outputs (0major+16396minor)pagefaults 0swaps
2.04user 0.05system 0:01.27elapsed 164%CPU (0avgtext+0avgdata 84032maxresident)k
0inputs+3352outputs (0major+16394minor)pagefaults 0swaps
-rw-r--r-- 1 hattori hattori 230916 2010-08-05 03:07 Y.jpg
===================================================
[ImageMagick(convert command)]
2.52user 0.06system 0:01.50elapsed 172%CPU (0avgtext+0avgdata 87808maxresident)k
0inputs+3544outputs (0major+21416minor)pagefaults 0swaps
2.50user 0.09system 0:01.52elapsed 171%CPU (0avgtext+0avgdata 87920maxresident)k
0inputs+3544outputs (0major+21380minor)pagefaults 0swaps
2.48user 0.09system 0:01.49elapsed 172%CPU (0avgtext+0avgdata 87936maxresident)k
0inputs+3544outputs (0major+21413minor)pagefaults 0swaps
2.48user 0.12system 0:01.51elapsed 171%CPU (0avgtext+0avgdata 87824maxresident)k
0inputs+3544outputs (0major+21389minor)pagefaults 0swaps
2.46user 0.12system 0:01.50elapsed 172%CPU (0avgtext+0avgdata 87824maxresident)k
0inputs+3544outputs (0major+21411minor)pagefaults 0swaps
-rw-r--r-- 1 hattori hattori 239584 2010-08-05 03:07 Y.jpg
===================================================
[GraphicsMagick(gm convert command)]
2.23user 0.04system 0:01.33elapsed 169%CPU (0avgtext+0avgdata 50208maxresident)k
0inputs+3584outputs (0major+14593minor)pagefaults 0swaps
2.21user 0.07system 0:01.31elapsed 173%CPU (0avgtext+0avgdata 50224maxresident)k
0inputs+3584outputs (0major+14621minor)pagefaults 0swaps
2.20user 0.07system 0:01.33elapsed 170%CPU (0avgtext+0avgdata 50208maxresident)k
0inputs+3584outputs (0major+14615minor)pagefaults 0swaps
2.24user 0.06system 0:01.32elapsed 174%CPU (0avgtext+0avgdata 50208maxresident)k
0inputs+3584outputs (0major+14596minor)pagefaults 0swaps
2.21user 0.06system 0:01.34elapsed 169%CPU (0avgtext+0avgdata 50208maxresident)k
0inputs+3584outputs (0major+14634minor)pagefaults 0swaps
-rw-r--r-- 1 hattori hattori 245732 2010-08-05 03:07 Y.jpg

うーむ、なんかビミョー。誤差範囲と言えなくもない。 ファイルサイズはgmコマンドがやや大きいでしょうか。。。

http://photon.hexacosa.net/photos/00000000000003d4/o

上記リンク先にあるオリジナル画像を変換した画像は以下になります。 上から、pgmagick、PythonMagick、ImageMagickコマンド、 GraphicsMagickコマンドの出力画像となります。

http://image.hexacosa.net/images/container/00000000000003d6.jpg
http://image.hexacosa.net/images/container/00000000000003d7.jpg
http://image.hexacosa.net/images/container/00000000000003d8.jpg
http://image.hexacosa.net/images/container/00000000000003d9.jpg

おわりに

現在は結局、画像処理はGraphicMagickコマンド(gm)をサブプロセスで呼ぶかたちで 使用しています。 やるやる詐欺になりそうですが、GraphicsMagickをPythonバインディングをカバー率100%まで 持っていきたいところです。処理速度を単純に比較すると、誤差範囲かもしれませんが GraphicsMagickをコマンド経由でなくライブラリとして使用した方が速くなっていそうなので。

やるとなるとBoostとかを学びながらになりそうです。