つれづれなる備忘録

日々の発見をあるがままに綴る

OpenCVの使い方9 ~ 台形補正2

 前回のOpenCVの台形補正で、変換座標を自動的に抽出して台形補正処理を行う方法について紹介する。前回は台形補正する領域の4点座標をペイントなどで見つけていたが、今回は輪郭抽出を用いることで画像から自動的に座標を抽出する。

1. 画像読み込みから輪郭抽出まで

 今回はネット上の名刺のサンプル画像の台形補正処理を試す。画像を読み込んだ後は以前紹介した2値化処理を行った後で輪郭抽出を実行する。

atatat.hatenablog.com

サンプル画像読み込み

import cv2
import numpy as np
from matplotlib import pyplot as plt
from google.colab import files
uploaded_file = files.upload()
uploaded_file_name = next(iter(uploaded_file))
orig = cv2.imread(uploaded_file_name)
src = cv2.cvtColor(orig, cv2.COLOR_BGR2RGB)
plt.imshow(src)

"テスト名刺画像"
テスト名刺画像

上記のテスト画像を以下の処理で2値化画像に変換する。

ret,th1 = cv2.threshold(gray,230,255,cv2.THRESH_BINARY)
plt.imshow(th1,cmap='gray',vmin = 0, vmax = 255)

"2値化処理画像"
2値化処理画像

2値化画像を用いて輪郭抽出処理を行う。輪郭抽出処理の詳細は今後別途記事にする予定だが、コードとしては以下を参考にしている。

qiita.com

まず輪郭を抽出するにはcv2.findContours()を用いて輪郭の座標を取得する。なおcv2.findContours()の引数の意味や種類は オブジェクト輪郭検出 | OpenCV / findContours を使用して画像中のオブジェクトの輪郭を検出する方法などを参照。

上記のコードでOpenCVのVer4.1以上では仕様の変更があり以下の部分のコードを用いた。(python - OpenCV version 4.1.0 drawContours - Stack Overflow)

tmp= cv2.findContours(th1, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contours = tmp[0] if len(tmp) == 2 else tmp[1]

cv2.CHAIN_APPROX_SIMPLEと指定すると輪郭点のみに省略され、平行四辺形では4点の座標値が返される。

検出された輪郭のうち面積の大きいもののみ識別する。

areas = []
for cnt in contours:
    area = cv2.contourArea(cnt)
    if area > 10000:
        epsilon = 0.1*cv2.arcLength(cnt,True)
        approx = cv2.approxPolyDP(cnt,epsilon,True)
        areas.append(approx)

検出された輪郭を画像上に重ねるにcv2.drawContours()を用いる。

cv2.drawContours(src,areas,-1,(255,0,0),3)
plt.imshow(src)

以下のように名刺部分の輪郭が正しく抽出できていることがわかる。

"輪郭抽出画像"
輪郭抽出画像

2. 台形補正処理と効果

 画像から輪郭抽出された座標を用いて、台形補正処理を行う。補正後の座標は、前回と同様に輪郭点同士の長さを算出することで幅、高さ情報を得る。 ここで抽出された座標が左上、右上、左下、右下といった具合に、補正後の座標点と対応させるために並び替える必要がある。例えば左に傾いた名刺であれば以下のようにして自動的に並び替える。 ただし、右や奥など任意の傾きに関しては、もう少し方法に工夫が必要。

pt1ind=np.argmin(areas[0],axis=0)[0][0]
pt1=areas[0][pt1ind] #左上
pt2ind=np.argmin(areas[0],axis=0)[0][1]
pt2=areas[0][pt2ind] #右上
pt3ind=np.argmax(areas[0],axis=0)[0][1]
pt3=areas[0][pt3ind] #左下
pt4ind=np.argmax(areas[0],axis=0)[0][0]
pt4=areas[0][pt4ind] #右下
pts = np.float32(np.array([pt1,pt2,pt3,pt4]))
o_width = np.linalg.norm(pt2 - pt1)
o_width=int(np.floor(o_width))
o_height = np.linalg.norm(pt3 - pt1)
o_height=int(np.floor(o_height))
dst_cor=np.float32([[0,0],[o_width,0],[0, o_height],[o_width, o_height]])

以下は前回と同じで、輪郭座標と変換後座標を指定して、台形補正処理を行う変換行列を取得して元画像に適用する。

M = cv2.getPerspectiveTransform(pts,dst_cor)
dst = cv2.warpPerspective(src,M,(o_width,o_height))
plt.imshow(dst)

少し歪みがあるものの(元画像の名刺が少し反っている影響?)名刺の文字が正しく表示されていることがわかる。

"台形補正処理後のテスト名刺画像"
台形補正処理後のテスト名刺画像

文字が少し薄いので、2値化画像に対して台形補正処理すると、少し潰れているものの(2値化処理の閾値の加減で調整)文字がはっきりとわかる。

dst2 = cv2.warpPerspective(th1,M,(o_width,o_height))
plt.imshow(dst2,cmap='gray')

"台形補正処理後の2値化画像"
台形補正処理後の2値化画像

3. まとめ

 OpenCVの台形補正で、変換座標を自動的に抽出して台形補正処理を行う方法について紹介した。基本的には輪郭抽出がうまくいかないと座標抽出ができないので、今回の名刺画像のように背景と補正対象物のコントラストがはっきり区別できる場合は有効である。