技術文章の執筆用に、画像の余白部分を削除するツールをPythonで作りました。
思ったよりシンプルにできたのが印象的です。いくつかやり方がありましたが、Pillowという画像処理用のモジュールを使用して「背景差分法」という手法を使って実現しています。
やりたいこと
こんな感じで、上下左右に白の余白のある画像があったとします(赤い枠線は画像の範囲がわかりやすいようにつけています)。
この余白を削除(トリミング)して、画像の必要な部分のみ切り出して新しい画像を生成します。
生成した画像は、上書きせずに新しい画像ファイルとして出力するやり方を取ることにします。
スクリプト
スクリプトの全体像
こちらが全体のスクリプトです。
from PIL import Image, ImageChops """グローバル定数""" TARGET_IMG_PATH = '/Users/massa/hogehoge/fig01.png' # 対象の画像ファイルのパス NEW_IMG_PATH = '/Users/massa/hogehoge/new_fig01.png' # 新たに作成する画像ファイルのパス BG_COLOR = (255, 255, 255) # 背景色RGBをタプルで指定 def crop_margin(): """ TARGET_IMG_PATHの画像の余白を削除してNEW_IMG_PATHとして出力する関数。 """ # 画像ファイルの取得 img = Image.open(TARGET_IMG_PATH) # 背景画像の生成 bg_img = Image.new('RGBA', img.size, BG_COLOR) # 差分画像の生成 diff_img = ImageChops.difference(img, bg_img) # 切り出し範囲の取得と実行 crop_range = diff_img.convert('RGB').getbbox() crop_img = img.crop(crop_range) # 画像ファイルの出力 crop_img.save(NEW_IMG_PATH) if __name__ == '__main__': crop_margin()
スクリプトはこちらを参考にさせていただきました!
余白切り抜きの手法についても記事内でわかりやすく解説されています。
グローバル定数
スクリプトを使用する際は、グローバル定数部分を書き換えて使用できます。
"""パラメータ""" TARGET_IMG_PATH = '/Users/massa/hogehoge/fig01.png' # 対象の画像ファイルのパス NEW_IMG_PATH = '/Users/massa/hogehoge/new_fig01.png' # 新たに作成する画像ファイルのパス BG_COLOR = (255, 255, 255) # 背景色RGBをタプルで指定
TARGET_IMG_PATHには元画像のファイルパスを、NEW_IMG_PATHには余白を削除した新しいファイルパスを指定します。
また、背景色BG_COLORには、削除したい余白のRGBの値を(n, m, l)
という形で0〜255の数値で指定します。今回は白色を指定しています。
Pillowと使用するクラス
Pillow
Pillowは、Pythonで画像処理を行うためのモジュールです。画像のリサイズや回転、トリミングなどの単純な処理を簡単に行うことができます。
モジュールをインストールしていない場合は、あらかじめターミナル等で以下のコマンドを使ってインストールしておきましょう。
pip install Pillow
今回のスクリプトでは、PillowのImageクラス、ImageChopsクラスを利用するので、このようにインポートします。
from PIL import Image, ImageChops
モジュール名はPIL
とする必要があるので、注意してください。
Imageクラス
Pillowで画像ファイルを扱う際には、Imageオブジェクトを使います。「Imageオブジェクト=画像ファイルをPython上で扱えるようにしたもの」というイメージで良いのかなと思います。
下記に、Imageクラスのよく使いそうな属性をまとめておきます。
※im
はImageオブジェクト
※★は今回のスクリプトで使用しているもの
Imageオブジェクトの読み込み・生成・保存
属性 | 解説 | 備考 |
---|---|---|
im.mode | 画像のモードを取得 | 文字列'L', 'RGB', 'CMYK'など |
★im.size | 画像のサイズを取得 | タプル(幅, 高さ) |
im.format | 画像のフォーマットを取得 | 文字列'PNG'など |
★im.open(fp, mode='r', formats=None) | 画像ファイルを読み込む | 引数fpは画像ファイルのパス 戻り値はImageオブジェクト |
★im.new(mode, size, color=0) | 単一色のImage画像を生成する | 戻り値はImageオブジェクト |
★im.save(fp, format=None, **params) | 画像を保存する | 引数fpは画像ファイルのパス 戻り値はImageオブジェクト |
im.show(title=None) | 画像をプレビュー表示する |
Imageオブジェクトの切り抜き・色の取得
属性 | 解説 | 備考 |
---|---|---|
im.getpixel(xy) | 画像の指定した座標の色を取得する | 引数xyは座標を表すタプル(x, y) 戻り値はカラーを表すタプル |
★im.getbbox() | 画像内で値が0 でない最小領域の長方形を返す | 戻り値は長方形領域を表すタプル(左, 上, 右, 下) |
★im.crop(box=None) | 画像を指定した長方形でトリミングする | 引数boxは長方形領域を表すタプル(左, 上, 右, 下) 戻り値はImageオブジェクト |
ImageChopsクラス
切り抜き範囲を指定するために、ImageChopsクラスを使います。
これはちょっとイメージが難しいのですが、ざっくり「Imageオブジェクトの各ピクセルに対して演算処理を行う」機能を提供してくれるモジュール、という感じでしょうか。chopsは「Channel Operations」の略のようですね。
今回使用するImageChopsクラスのメソッドはこちらです。
ImageChopsクラスのメソッド
メソッド | 解説 | 備考 |
---|---|---|
★ImageChops.difference(image1, image2) | 2つの画像のピクセルごとの値の差の絶対値を返す | 引数は2つのImageオブジェクト 戻り値はImageオブジェクト |
スクリプトの解説
すべて解説できませんでしたが、気になった点を補足しておきます。
Imageオブジェクトの生成
画像ファイルをパスを指定して開くには、Imageクラスのopen()
メソッド、単一色の背景画像を生成するにはImageクラスのnew()
メソッドを使用します。
# 画像ファイルの取得 img = Image.open(TARGET_IMG_PATH) # 背景画像の生成 bg_img = Image.new('RGBA', img.size, BG_COLOR)
new()
メソッドの引数は以下のように指定します。
- 第1引数には
RGBA
を指定 - 第2引数に
img.size
を指定。元画像img
と同じサイズになる - 第3引数に
BG_COLOR
を指定。今回は白の単一色画像が生成される
ちなみに、第1引数に最初はRGB
を指定したところ、僕の環境だとなんだかうまくいかず…。色々試したところ、RGBA
を指定するとうまくいきました。
差分画像を生成し切り出し範囲を決める
元画像と背景色画像との差分画像を生成し、その差分画像を使って切り出し範囲を決める、というのが背景差分法のポイントみたいです。
# 差分画像の生成 diff_img = ImageChops.difference(img, bg_img) # 切り出し範囲の取得と実行 crop_range = diff_img.convert('RGB').getbbox() crop_img = img.crop(crop_range)
まず、差分画像の生成。ここではImageChopsクラスのdifference()
メソッドを使って、元画像img
と背景色画像bg_img
との差分となるImageオブジェクトを生成しています。
画像の座標ピクセルごとに差分を取り、背景色が一致する部分は黒で埋め尽くされた差分画像が出来上がります。
次に、切り出し範囲の取得。ここでは差分画像diff_img
に対して、getbbox()
メソッドで画像の境界線を長方形で取得しています。bboxは「bounding box」から来ているようですね。
ちなみにこの際にconvert('RGB')
を適用しておかないと、うまく切り出しができませんでした。
画像ファイルの出力
Imageオブジェクトを画像ファイルとして出力するには、Imageクラスのsave()
メソッドを使用します。
# 画像ファイルの出力
crop_img.save(NEW_IMG_PATH)
ここで元の画像のパスを指定すれば、生成された画像を上書き保存するような仕様にすることができますね。
おわりに
今回はPillowというライブラリを使いましたが、OpenCVでも同じような(むしろより高度な)画像処理を行うことができるようです。
また、余白の削除には「背景差分法」のほかに「二値法」というやり方があり、そのやり方を使うと単一色でなくても切り抜きが可能そうです。画質が荒くなった画像の余白を削除するには、この二値法が使えそうですね。