【画像処理】Pillowを使って画像周りの余白削除(トリミング)を自動化してみた！（２）

#概要
以下の記事では，OpenCVを使って，画像周りの余白削除を行いました．

[【画像処理】OpenCVを使って画像周りの余白削除(トリミング)を自動化してみた！（１）](https://qiita.com/trami/items/e25eb70a59a51ae4f7ba)

今回は，Pillowを用いて同様のことをやってみようと思います．どちらが簡単に実装できるのでしょうか？
それではやっていってみましょう！

#動作環境
- Windows10(64bit)
- Python 3.7.2
- Pillow 6.0.0 

#やりたいこと
上の画像を下の画像のようにするのが目標です．少しわかりにくいかもしれませんが，
下の画像では，上の画像の周りの余白を除いた部分が切り取られ，枠ぴったりに物体が収まっています．
ちなみにこれは非同期式6進ダウンカウンタです。

![6thDownCounter.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/433790/f4b0fdd1-3210-81d4-62ab-b7f1b5b28f33.png)

↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

![6thDownCounter.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/433790/5f464fb1-1f27-aac6-e4a5-3cc2706942d4.png)

#どのようにして余白削除を行うのか

ざっくり，実装の流れを説明すると，

1.画像ファイルを開く
2.背景色画像を作成する
3.差分画像を作成する
4.背景色との境界を求めて画像を切り抜く

のように行います．これはいわゆる背景差分法です．動画の解析などでもよく出てきます．
自然言語処理では，Word2Vecでは言葉をベクトル化して，演算を可能にしますが，背景差分法もそのようなイメージでしょうか？
元の画像から背景画像を引いて，残った部分が物体で，あとはそれに沿って切り出します．

#余白を削除する関数
まずは，余白を削除する関数を定義します．これがこのプログラムの核となる部分です．

```py
# 余白を削除する関数
def cropImage(image): #引数は画像の相対パス
    # 画像ファイルを開く
    img = Image.open(image)

    # 周りの部分は強制的にトリミング
    w, h = img.size
    box = (w*0.05, h*0.05, w*0.95, h*0.95)
    img = img.crop(box)

    # 背景色画像を作成
    bg = Image.new("RGB", img.size, img.getpixel((0, 0)))
    # bg.show()

    # 背景色画像と元画像の差分画像を作成
    diff = ImageChops.difference(img, bg)
    # diff.show()

    # 背景色との境界を求めて画像を切り抜く
    croprange = diff.convert("RGB").getbbox()
    crop_img = img.crop(croprange)
    # crop_img.show()

    return crop_img
```

#解説
では解説をしていきます．

- 画像ファイルを開く

```py
    img = Image.open(image)
```

- 強制トリミング

これは，本来は必要ないのですが，今回の画像は一番外側に座標のようなものが入り込んでしまっているため，
輪郭抽出でそこを抽出しないようにするため，強制的にそこの部分を削除しています．
crop()は(左端のx座標, 上端のy座標, 右端のx座標, 下端のy座標)という形式のタプル引数に取ります．

```py
    w, h = img.size
    box = (w*0.05, h*0.05, w*0.95, h*0.95)
    img = img.crop(box)
```

- 背景色画像の作成

bgはbackgroundの略です．これはshow()すると，わかりますが，真っ白な画像になっています．

```py
    bg = Image.new("RGB", img.size, img.getpixel((0, 0)))
```

- 差分画像の作成

差分はdifferenceです．difference()関数を使えば，差分画像を生成することができます．

```py
    diff = ImageChops.difference(img, bg)
```

- 背景色との境界を求めて切り抜く

getbbox()は画像内で値が0でない最小領域を返します、値がすべて0の画像はNoneを返します。

```py
    croprange = diff.convert("RGB").getbbox()
    crop_img = img.crop(croprange)
```

あとはこの関数の返り値に対して，画像を表示したり保存したりすれば，所望の画像を保存することができます．

#複数の画像に対して処理をまとめて行う
では複数の画像に対して，一気に余白削除をやってみようと思います．
余白を削除したい画像がimages_for_cropディレクトリにあり，拡張子は.pngであるとします．
そして保存先のディレクトリはcropped_imagesとします．

```py
import os
# import time
import glob

from PIL import Image, ImageChops

INPUTDIR = 'images_for_crop'
OUTPUTDIR = 'cropped_images'
EXT = 'png'

# 編集後の画像の保存ディレクトリの作成
if not os.path.isdir(OUTPUTDIR):
    os.mkdir(OUTPUTDIR)

# INPUTDIR内の全ての画像に対してループ
for image in glob.glob(INPUTDIR + '/*.' + EXT):
    crop_img = crop(image)
    
    # ファイル名を取得
    image = os.path.basename(image)

    # 画像の表示
    # crop_img.show()
    # time.sleep(0.5)

    # 切り取った画像を保存
    crop_img.save(OUTPUTDIR + '/' + image)
```

#まとめ
いかがだったでしょうか？Pillowを使って，画像の不要な周りの余白を削除してみました．実行速度はOpenCV使ってもあまり変わらない感じでしょうか？ただ，OepenCVだとnumpy.ndarrayで画像を処理するので，若干使いやすい感じがありますかね．あとはOpenCVに比べて情報が少なめなのがPillowの難点でしょうか？
ちなみにに，OpenCVを使って背景差分法で余白削除をすることもできそうです．また機会があれば余白削除ではなく，何らかの形で背景差分法を扱ってみたいと思います．
プログラムに関する建設的なフィードバックもお待ちしています！