pythonを使ってjpeg画像をまとめて圧縮する方法

久々にwordpressの投稿関連の記事。とは言ってもpython記事だけれども。

さてwordpressユーザーの方はご存知の通り、wordpressのブログにアップできる画像の容量は1Mバイトまで。というわけでデジカメの画像はもちろん、携帯のカメラで撮影した写真でも多少サイズを小さくする必要があることが多い。

数枚ならどんな方法で圧縮してもいいのだけど、これが大量の画像になってくるとそれなりに面倒臭くなっていくもの。
いくつか良いフリーソフトウェアもあるようだけど、こんな時こそpythonを使って見るのも悪く無い。書き出しのファイルサイズを多少はコントロールできたり利点もある。

そういうわけで、この投稿ではpythonを使ってjpeg画像をまとめて1M以下にサイズ圧縮する方法について書いていきたい。

Pillowを使ってjpeg画像圧縮

画像の読み込みはpillowで。画像の読み込みができるpythonパッケージはいくつかあると思うのだけど、簡単な処理をするだけなら大して変わらないんじゃ無いかと信じている。
そんなことないよ。これのが全然早いよってのがあったら教えてください。

必要パッケージのインポート

まずは必要なパッケージをインポート。ファイル名の読み込みようにtkinter各種。それから画像処理にPIL、ファイル名の処理にosをインポート。
ioは画像サイズの処理の関係で利用。詳細は後ほど。

import os
from tkinter import Tk, ttk
import tkinter.filedialog as tkfd
import tkinter.messagebox as tkms
from PIL import Image
from io import BytesIO

tkinterで読み込みファイル名の取得

取り立てて変わったこともないけれど、一応jpgでもjepgでも読み込めるようにしてはある。画像ファイルは./ORIGINALっていうディレクトリに入れておいて一気に読み込めるような設定。

tk = Tk()
tk.withdraw()
print ('select a data files')
filenames = tkfd.askopenfilenames(filetypes= [('jpg','*.jpg'),('jpeg','*.jpeg')], initialdir='./ORIGINAL')
tkms.showinfo('file paths are',filenames)
tk.destroy()

画像サイズを1M以下になるまでjpeg圧縮

というわけでメインパート。画像を縮小するのではなく圧縮する場合だと、jpeg保存のqualityを変えていけばサイズが変わる。この場合qualityを5ずつ落としていっているけれど、そこらへんは適当に調整していただきたい。
for文でqualityを下げながら一回一回ファイルを書き出し、osのstat.st_sizeでファイルサイズのチェックをしている。
ちなみに書き出しは./REDUCEDというディレクトリの中に。

for i in range(len(filenames)):
  img = Image.open(filenames[i])
  basename = os.path.basename(filenames[i])
  for j in range(10):
    img.save('./REDUCED/'+basename+'.jpeg',quality=90-j*5,optimize=True)
    if os.stat('./REDUCED/'+basename+'.jpeg').st_size < 1000000:
    break
  img.close()

しかしこのやり方だと毎回ファイルを書き出して、読み込んでっていうのが少し気持ち悪い。

というわけで以下は実際にはファイルを書き出さない例。ioのByteIOを利用することでそんなことが可能になる。初めて使ったのでこのパッケージの詳細についてはよく把握していない。

qual = [0]*len(filenames)
  for i in range(len(filenames)):
  img = Image.open(filenames[i])
  for j in range(10):
    img_file = BytesIO()
    img.save(img_file,'jpeg',quality=90-j*5,optimize=True)
    #print (img_file.tell())
    if img_file.tell() < 1000000:
      qual[i] = 90-j*5
      break
  basename = os.path.basename(filenames[i])
  img.save('./REDUCED/'+basename+".jpeg",quality=qual[i],optimize=True)
  img.close()

実際にファイルを書きださないで処理しようと思った理由は速度があげられるかなっていうところもあった。

というわけで二つのスクリプトを1.3M、3.5M、4.5Mのjpeg画像を使って速度チェック。
ちなみに書き出されたファイルのサイズは674、989、962[KB]。元のサイズ差のせいなのか、qualityを5ずつ落としているせいなのか目が荒い。

ファイルを書き出す最初のプログラム:

4.87; 4.89; 4.91; 4.87; 4.84

ファイルをメモリに保存しとく2個目のプログラム:

5.34; 5.35; 5.61; 5.22; 5.23

というわけで意外なことに、一々ファイルを書き出すプログラムの方が早かった。メモリを食う方が速度が落ちるのかな。使っているパソコンのシステム次第で変わりそうな気もするけどどうなんだろう。
大した速度の違いがあるわけではないので、個人的には2個目の方が良いかなって思ってるんだけど、どんなもんでしょうか?

変換前後

最後に圧縮前後の画像の一例を載せようと思ったら、当たり前だけど圧縮前の画像がサイズの都合でアップできなかった。なんのためにこの投稿を書いていたのやら。画像を並べたスクリーンショットを参考までに。

関連記事

1. pythonのまとめ

2. pythonでデジカメのraw画像を一括読み込みして別形式で保存する

D