ちょっと微妙かもしれない・・・
最近画像変換周りを触っています。
実は既にImageMagickで並列画像変換するDockerイメージを用意してたりするのですが、ImageMagickをそろそろ卒業しようと思いまして、今回PythonのPillow-SIMDで全部書き直す事にしました。
Simple fast image converter
何ができるの?
環境変数で画像パスを指定してあげて、docker-compose upすると、再帰的に画像フォルダを検索して、拡張子毎に異なる圧縮品質で、並列に画像変換(圧縮)を行う事ができます。
可逆圧縮はそもそも圧縮がほとんど効かずコスパが悪いので、jpg,jpeg辺りの非可逆圧縮のみを対象とすると、非常に高速に圧縮できます。
ちなみにリサイズは行わず、あくまで圧縮のみを行うものです。リサイズしてサムネイルも作成したい場合は、リポジトリ内の scripts/converter.py のimage.save周辺で作成しちゃって下さい。
技術要素
- Docker v17
- docker-compose v1.16
- Alpine Linux v3.6
- Python v3.6.3
- Pillow-SIMD v4.3.0
Alpine linuxベースのpythonイメージに、頑張ってPillow-SIMDをインストールしてイメージを生成してます。依存ライブラリが多いので、228MBというデブデブなイメージになってしまいました。。。
並列処理についてはPython側でmultiprocessingのProcessで、マルチプロセスで行っています。Dockerに割り当てられているCPU個数を取得して、コア数分自動で並列処理されます。
もしdocker for macやdocker for windowsをご利用の方は、DockerへのCPU割当数を増やして実行すると、CPUをフルに使い切って高速に変換可能になります。
どんな時に使うの?
主にCIサーバで以下のフローで使う事を想定しています。
- Jenkinsでgit clone。
- cloneしたファイル群に対して、画像変換をかけて圧縮。
- ビルド。
- デプロイ。
- やったね。
Jenkinsの場合はWORKSPACEという環境変数に自動でパスが設定されますよね。本dockerイメージもWORKSPACEという環境変数を設定するようにしたので、Jenkinsフレンドリーだと思います。(多分)
もし対象の画像パスを変えたい場合は、以下のように任意のパスに書き換える事ができます。
export WORKSPACE=/tmp/images
拡張子毎に圧縮品質を調整したい場合は、settings.txtを編集して下さい。
SUPPORT_EXTENSIONS=.jpg,.jpeg DEFAULT_IMAGE_QUALITY=80 JPEG=70 GIF=70
↑のJPEG・GIFといった部分が、フォーマット毎の圧縮品質値で、これはPillow-SIMDに完全に依存しています。詳細は http://pillow.readthedocs.io/en/3.4.x/handbook/image-file-formats.html をご覧下さい。
ちなみに画像変換されたファイルは上書きされますのでご注意下さい。元々CIで使う事を想定しているので、上書きにしちゃってます。
ログ
↓こんな感じに標準出力されます。システム情報と、対象ファイルの「拡張子」「(本当の)画像フォーマット」「圧縮品質」「圧縮時間」「画像パス」を出力します。
8c40001ff7da_simple-fast-image-converter | 2017-11-05 07:19:10,378 INFO === SYSTEM INFO ========================================= 8c40001ff7da_simple-fast-image-converter | 2017-11-05 07:19:10,385 INFO System : Linux 8c40001ff7da_simple-fast-image-converter | 2017-11-05 07:19:10,386 INFO Release : 4.9.49-moby 8c40001ff7da_simple-fast-image-converter | 2017-11-05 07:19:10,386 INFO Version : #1 SMP Wed Sep 27 23:17:17 UTC 2017 8c40001ff7da_simple-fast-image-converter | 2017-11-05 07:19:10,386 INFO Machine : x86_64 8c40001ff7da_simple-fast-image-converter | 2017-11-05 07:19:10,386 INFO Processor : 8c40001ff7da_simple-fast-image-converter | 2017-11-05 07:19:10,386 INFO Python version : 3.6.3 8c40001ff7da_simple-fast-image-converter | 2017-11-05 07:19:10,386 INFO Compiler : GCC 6.3.0 8c40001ff7da_simple-fast-image-converter | 2017-11-05 07:19:10,387 INFO Docker cpu core : 4 8c40001ff7da_simple-fast-image-converter | 2017-11-05 07:19:10,387 INFO ========================================================= 8c40001ff7da_simple-fast-image-converter | 2017-11-05 07:19:10,463 INFO ext=.jpg, format=JPEG, quality=70, time=0.06s, path=/images/1124191045763.jpg 8c40001ff7da_simple-fast-image-converter | 2017-11-05 07:19:10,493 INFO ext=.jpg, format=JPEG, quality=70, time=0.03s, path=/images/background.jpg 8c40001ff7da_simple-fast-image-converter | 2017-11-05 07:19:10,499 INFO elapsed_time = 0.11s
Pillowとの速度比較
jpg, png混在で、21362ファイル、合計268.2MByte の画像が存在するフォルダに対して、画像変換をかけてみました。
さてさて、PillowとPillow-SIMDはどう違いが出るでしょうか。
PillowとPillow-SIMDは、Dockerイメージでpip installする際にpillowとするか、pillow-simdとするか、で切り分けています。
ということで、同じ条件でそれぞれ5回づつ変換してかかった時間を計測してみました。
Pillow
1回目 | 25.66s |
---|---|
2回目 | 29.70s |
3回目 | 27.16s |
4回目 | 28.62s |
5回目 | 28.30s |
Pillow-SIMD
1回目 | 27.79s |
---|---|
2回目 | 27.79s |
3回目 | 27.70s |
4回目 | 27.85s |
5回目 | 30.54s |
んんん?対して変わらない???AVX2に対応していないCPUだからですかね。うーむ。。。
TIPS
OSError: [Errno 24] No file descriptors available
手持ちのiMacのDocker for macで画像変換を試しても、全くエラーが起きなかったのですが、Linuxサーバ上のDockerで試すと、以下のエラーが発生しました。
Traceback (most recent call last): File "/tmp/scripts/converter.py", line 118, in <module> convert_parallel(src_file_path_units) File "/tmp/scripts/converter.py", line 106, in convert_parallel job.start() File "/usr/local/lib/python3.6/multiprocessing/process.py", line 105, in start self._popen = self._Popen(self) File "/usr/local/lib/python3.6/multiprocessing/context.py", line 223, in _Popen return _default_context.get_context().Process._Popen(process_obj) File "/usr/local/lib/python3.6/multiprocessing/context.py", line 277, in _Popen return Popen(process_obj) File "/usr/local/lib/python3.6/multiprocessing/popen_fork.py", line 20, in __init__ self._launch(process_obj) File "/usr/local/lib/python3.6/multiprocessing/popen_fork.py", line 66, in _launch parent_r, child_w = os.pipe() OSError: [Errno 24] No file descriptors available
なんかファイルディスクリプタが足りないとの事です。ホストOSはAmazonLinuxで、ファイルディスクリプタは65536にしてあります。
となると足りていないのはDockerのコンテナ側ですね。という事で以下のように設定すると、コンテナ内のファイルディスクリプタを増やす事ができ、無事、ファイルディスクリプタ不足が起きなくなりました。
ulimits: nproc: 65535 nofile: soft: 20000 hard: 40000
気づいた事
この画像変換イメージを作り、自分で使っていて気づいたのですが・・・
拡張子偽装が結構見つかる
という点です。
例えば、ファイル名が「xxx.png」だったとします。しかし、Pillowで画像フォーマットを確認してみると「JPEG」と表示されます。拡張子というものはいくらでも偽装して嘘を付けるのですが、以外なほどポロポロ見つかります。
一番困るのがPNGが.png以外の拡張子に偽装されているケースです。pngは可逆圧縮ですが、圧縮をかけてもほとんどファイルサイズは変わりません。ほとんど圧縮できないのに、圧縮にはJPEGよりも数倍時間を要します。可逆圧縮はコストパフォーマンスは最悪なので、可能な限り可逆圧縮の圧縮は避けるべきです。
可逆圧縮フォーマットを、非可逆圧縮の拡張子に偽装しているケースは非常にいやらしいですね。今の実装だと偽装拡張子を無視する形にしていますが、pngの圧縮が混入してしまうと、圧縮速度が段違いに遅くなってしまうので、偽装されている場合は処理をスキップしようかな〜?なんて考えてます。
なんせjpgは0.1〜0.3秒程度で変換できるのに、pngだと1〜10秒かかる事もあるので、少しの混入で相当遅くなってしまうのです。会社の業務プロジェクトのアセット内ですら偽装拡張子がポロポロでてくるので、ネットから拾った壁紙のような画像群に対して変換を書けてしまうと、偽装pngのせいで処理速度が大幅劣化しそうです。
雑感
私はPythonはfabricで少し書いた程度の知識しか無いのですが、今のPython v3.6.3は、型アノテーションというものがあるのですね。知りませんでした。微妙にkotlinと記法が似てて、v2系の頃とは大分様変わりしていて、ちょっとビックリしました。
昔は型が無い事を売りにしていたと思うのですが、今となっては真逆ですね。どの言語も後付で型を導入したり、最初から型有りきで登場したり。後付で型を導入するケースの場合、やはりIDEとの連携が弱い(最初から型があるものと比較して)ので、強力な連携をしてくれるIDEを使わないと、逆に書きにくくなってしまうジレンマがあるような、無いような。
今回Pythonをちょっと触ったわけですが、競合である?Rubyは私はさっぱりです。Ruby系のツール、例えばCapistrano・Chef等は全部敬遠しており、唯一使っているのがVagrantでVagrantfileをいじる時くらいでしょうか。
完全に好き嫌いの話ですが、私はrubyって好きではないし、好きになれないです。私は型や文法がカッチリしている方が好きなので、「色々な書き方ができる!」「ワンライナーでこれだけ書ける!」といったものは避けたく、一定のルールで一定の記述ができる方を好みます。
かといってよく触るJavaだと堅苦しいのは間違い無いので、最近は専らkotlinかtypescript辺りを好んで使っています。この辺は型がありつつ推論で緩く記述する事もできて、ちょうどいい感じです。
IntelliJ IDEAハンズオン――基本操作からプロジェクト管理までマスター
- 作者: 山本裕介,今井勝信
- 出版社/メーカー: 技術評論社
- 発売日: 2017/11/08
- メディア: 大型本
- この商品を含むブログを見る
- 作者: Dmitry Jemerov,Svetlana Isakova,長澤太郎,藤原聖,山本純平,yy_yank
- 出版社/メーカー: マイナビ出版
- 発売日: 2017/10/31
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
Kotlinスタートブック -新しいAndroidプログラミング
- 作者: 長澤太郎
- 出版社/メーカー: リックテレコム
- 発売日: 2016/07/13
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
Kotlin Webアプリケーション 新しいサーバサイドプログラミング
- 作者: 長澤太郎
- 出版社/メーカー: リックテレコム
- 発売日: 2017/10/06
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る