Pythonで画像の余白を削除する(OpenCV編)
PythonのOpenCVモジュールを使って、画像の余白部分を削除(クロップ、トリミング)します。
背景差分法と2値化の2種類について述べます。画像によって適切な方法を選択してください。
Pillowを使う方法については、こちらを参照してください。
目次
サンプル画像
本稿でクロップする画像はこちらです。
プロ生ちゃんこと暮井慧さんです。
OpenCVで背景差分法によるクロップを行う
背景差分法というのは、クロップ(トリミング)したい画像と背景画像の差分からクロップする範囲を求める方法です。
OpenCVには背景差分を計算する便利な関数が用意されています。ですが、本稿ではそれらを使わずに背景差分を計算してクロップします。
大凡のな手順は下記です。
背景部分の色を求める。
背景色で元画像と同じ大きさの画像を作成する。
元画像と背景色画像の差を計算する。(差分画像の生成)
差分画像の輪郭を抽出する。
輪郭のボックスを計算する。
求めたボックスで元画像をクロップする。
PythonのOpenCVモジュールはMATをNumPyのndarrayで表現しますので、ndarrayとしての操作も活用します。
背景差分法のコード例
import numpy as np
import cv2
img = cv2.imread('test.jpg')
# 元画像と同じサイズのndarray(背景画像)の生成
bg_img = np.zeros(img.shape, np.uint8)
# 元画像の(0,0)の画素の色を背景画像にコピー
bg_img[:,:,:] = img[0,0,:]
# 元画像と背景画像の差分の計算
diff_img = cv2.absdiff(img, bg_img)
# 差分画像から黒色部分を抜き出し、白黒反転
mask_img = cv2.bitwise_not(cv2.inRange(diff_img, np.array([0,0,0]),np.array([0,0,0])))
# 輪郭抽出
contours, h_ = cv2.findContours(mask_img, cv2.RETR_EXTERNAL, method=cv2.CHAIN_APPROX_NONE)
# 輪郭からバウンディングボックスの計算
rect = cv2.boundingRect(contours[-1])
# バウンディングボックスの座標に合わせてndarrayを切り出す
crop_img = img[rect[1]:rect[1]+rect[3]-1, rect[0]:rect[0]+rect[2]-1]
cv2.imwrite('crop_img.jpg', crop_img)
背景差分法の実施例
元画像は347x347のJpegファイルで、(0,0)画素の色は白(255,255,255)です。ですから、背景画像は347x347の白色の画像になります。
背景画像と元画像の差分は、このような画像になります。
背景(余白)部分が黒になっています。
この画像から黒部分だけを抜き出して、さらに白黒反転すると、このような画像になります。
この画像から輪郭を抽出すると、抽出された輪郭はこのようになります。
抽出された輪郭から外接する矩形を計算して、元画像にプロットすると、このようになります。
得られた矩形の座標をもとに、元画像のndarrayから行列の切り出し(スライス)をします。そうすると、このような画像になります。
少し余白が残ってますね。元画像がJpegだからかもしれません。
この方法は背景との差分からクロップ範囲を計算しますので、背景が完全に単一色であればどのような色にも応用できると思います。
OpenCVで2値化によるクロップを行う
背景が白に近い色であるならば、2値化してクロップの範囲を決めてしまいましょう。
大凡のな手順は下記です。
画像を2値化する。
2値化した画像の輪郭を抽出する。
輪郭のボックスを計算する。
求めたボックスで元画像をクロップする。
2値化の場合のコード例
import numpy as np
import cv2
img = cv2.imread('test.jpg')
# 元画像をグレースケールに変換
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# グレースケール画像を白黒に変換
ret_, bw_img = cv2.threshold(gray_img, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
# 白黒画像の輪郭を抽出
contours, h_ = cv2.findContours(bw_img, cv2.RETR_EXTERNAL, method=cv2.CHAIN_APPROX_NONE)
# 輪郭からバンディングボックスを計算
rect = cv2.boundingRect(contours[-1])
# バウンディングボックスの座標で元画像をクロップ
crop_img = img[rect[1]:rect[1]+rect[3]-1, rect[0]:rect[0]+rect[2]-1]
cv2.imwrite('crop_img.jpg', crop_img)
2値化での実施例
元画像をグレースケールに変換すると、このような画像になります。
このグレースケールの画像を2値化します。2値化の際に、次の輪郭抽出のために白黒反転します。閾値は大津の方法にしました。そうすると、このような画像になります。
この2値化画像から輪郭を抽出します。こんな感じです。
輪郭からバウンディングボックスを計算して、元画像に描画すると、こんな感じになります。
このバウンディングボックスに従って元画像をクロップすると、このような画像になります。
この2値化での方法は、背景が白に近い色で、かつある程度のコントラストがある画像(例えば書類のスキャン画像)であることが前提になると思います。
主なメソッド
cv2.findContours()の使い方については、輪郭を検出する記事を参考にしてください。
cv2.boundingRect()の使い方については、外接矩形を描く記事を参考にしてください。
cv2.threshold()の使い方については、画像を2値化する記事を参考にしてください。
cv2.absdiff
import cv2
dst = cv2.absdiff(src1, src2, [dst])
変数 |
型 |
内容 |
---|---|---|
src1 |
ndarray |
画像を示す行列1 |
src2 |
ndarray |
画像を示す行列2 |
dst |
ndarray |
差分の行列 |
行列の各要素の差分の絶対値を計算して行列で返す関数です。画像データの要素の値は0~255の整数のことが多いと思いますが、単純にこの差分をとるとマイナスの値になる場合がありますので、差分の絶対値を使用することで計算結果がマイナスにならないようにします。
cv2.inRange
import cv2
dst = cv2.inRange(src, lower, upper, [dst])
変数 |
型 |
内容 |
---|---|---|
src |
ndarray |
入力画像を示す行列 |
lower |
ndarray, scalar |
下限を示す行列または数値 |
upper |
ndarray, scalar |
上限を示す行列または数値 |
dst |
ndarray |
判定結果の行列 |
行列の要素が指定した上下限の間にあるかどうかを確認します。出力はsrcと同じサイズの行列です。各要素の値は、上下限の範囲内の場合255に、範囲外の場合は0になります。
公開日
広告
Pythonで画像処理カテゴリの投稿
- Python (Pillow)でグラデーション画像を作る
- PythonでGIFアニメを作る(PILでファイルをつなげる編)
- PythonでOpenCVの画像データをMatplotlibで表示する
- PythonでPDFを画像に変換する(Windows10)
- PythonでPSDファイルをJPEGやPNGに変換する
- Pythonでカラー画像をグレイスケールに変換する(OpenCV編)
- Pythonでフォルダ内に同じ画像がないか検索してみた
- Pythonでマスク画像を作る方法(3選)
- Pythonで図形の外接矩形を描く(OpenCV編)
- Pythonで図形の輪郭の大きさを調べる(OpenCV編)
- Pythonで画像の余白を削除する(OpenCV編)
- Pythonで画像の余白を削除する(Pillow編)
- Pythonで画像の大きさやフォーマットを調べる(Pillow編)
- Pythonで画像の減色をする
- Pythonで画像をトリミングする(Pillow編)
- Pythonで画像を他の画像にペーストする(Pillow編)
- Pythonで画像を回転する(Pillow編)
- Pythonで画像を拡大縮小(リサイズ)する(Pillow編)
- Pythonで画像を読み込み、表示し、保存する(OpenCV編)
- Pythonで画像を読み込み、表示し、保存する(Pillow編)
- Pythonで画像を2値化する(OpenCV編)
- Pythonで記号の輪郭を検出する(OpenCV編)
- PythonとOpenCVで画像をグレースケールに変換してみた
- PythonとOpenCVで画像をネガポジ反転してみた