pythonとOpenCVで歪み補正
書類をスキャンしたときは、画像が傾いているのみならず、歪んでいる場合がある。
この歪みをpythonで直すためのプログラム。ただし、大きな枠があり、歪みが小さい場合に限る。
大まかな流れ :
イ)四角い枠の角の座標を取得する。(一番外側の線は紙そのものの輪郭)
ロ)台形補正で歪みを直す。
以下のサイトを参考にした。(と言うかほとんどそのままです。本当ごめんなさい。)
opencv で マーカー付き用紙の向きを直してみる - 機械学習備忘録
opencv で カードの向きを直してみる - 機械学習備忘録
1)角の座標の取得
(1−1)輪郭検出
適当な画像を用意し(ここでは、test.png)、以下のコードを実行する。
import cv2
import numpy as np
img = cv2.imread("test.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
ret, contours, hierarchy = cv2.findContours(thresh , cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE )
ここでは、画像を読み込んだ後、白黒に二値化し、輪郭(contours)やら何やらを検出している。
(1−2)面積の大きい順に輪郭を並べ替える
上に続いて以下のコードを実行する。
menseki=[ ]
for i in range(0, len(contours)):
menseki.append([contours[i],cv2.contourArea(contours[i])])
menseki.sort(key=lambda x: x[1], reverse=True)
ここでは、
menseki=[[輪郭1, 輪郭1の面積], [輪郭2, 輪郭2の面積], …]
を作り、これを面積の大きい順に並べ替えている。
(1−3)角の座標の取得
上に続いて以下のコードを実行する。
epsilon = 0.1*cv2.arcLength(menseki[1][0],True)
approx = cv2.approxPolyDP(menseki[1][0],epsilon,True)
cv2.drawContours(img, approx, -1,(0, 0, 255),10)
cv2.imwrite("result.png",img)
epsilon, approxの説明はここの「輪郭の近似」を参照のこと。二値化した時に枠線がギザギザしてしまい、それを点として認識するのを避けている。
ここではapproxに角の4点が格納されている。
また、一番目に大きな輪郭(menseki[0][0])は紙そのものの輪郭なので、二番目に大きな輪郭(menseki[1][0])の点を取得している。
生成された画像(result.png)で角を取得できていることを確認。
以下参照
【OpenCV; Python】findcontours関数のまとめ
https://imagingsolution.net/program/opencv/cvfindcontours-labelling/
(1−4)角の点の並び替え
approxには角の点が格納されているものの、その順番はバラバラである。
そこで以下のコードで左下、左上、右下、右上に対応させる。
approx=approx.tolist()
left = sorted(approx,key=lambda x:x[0]) [:2]
right = sorted(approx,key=lambda x:x[0]) [2:]
left_down= sorted(left,key=lambda x:x[0][1]) [0]
left_up= sorted(left,key=lambda x:x[0][1]) [1]
right_down= sorted(right,key=lambda x:x[0][1]) [0]
right_up= sorted(right,key=lambda x:x[0][1]) [1]
コードの概略としては以下の通りである。
イ)numpy配列をただのlistに変換する。
ロ)x座標の小さい順に並び替えることにより角の4点を左右に分ける。
ハ)それらをy座標の小さい順に並び替えることにより、上下に分ける。
全ての四角形に対応するわけでは無いが、スキャンならこれで十分のはず。
2)台形補正
perspective1 = np.float32([left_down,right_down,right_up,left_up])
perspective2 = np.float32([[0, 0],[1654, 0],[1654, 2340],[0, 2340]])
psp_matrix = cv2.getPerspectiveTransform(perspective1,perspective2)
img_psp = cv2.warpPerspective(img, psp_matrix,(1654,2340))
ここで、perspective1は補正前の角の座標を、perspective2は補正後の角の座標をそれぞれ示す。ただし、perspective2としてA4の解像度(2340×1640)を用いている。
また、psp_matrixは台形補正に必要な変換行列を示す。
生成された画像(image_modified.png)で、歪み補正されていることを確認できる。
以下参照 (と言うかそのまま使ってごめんなさい)
Python OpenCV3で透視変換 | from umentu import stupid
全コードは以下の通り。
###modify image file because of distortion by scanning###
### obtatin edge###
import cv2
import numpy as np
img = cv2.imread("test.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(gray,100,255,cv2.THRESH_BINARY)
ret, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE )
menseki=[ ]
for i in range(0, len(contours)):
menseki.append([contours[i],cv2.contourArea(contours[i])])
menseki.sort(key=lambda x: x[1], reverse=True)
epsilon = 0.1*cv2.arcLength(menseki[1][0],True)
approx = cv2.approxPolyDP(menseki[1][0],epsilon,True)
cv2.drawContours(img, approx, -1,(0, 0, 255),10)
cv2.imwrite("result.png",img)
approx=approx.tolist()
left = sorted(approx,key=lambda x:x[0]) [:2]
left_down= sorted(left,key=lambda x:x[0][1]) [0]
left_up= sorted(left,key=lambda x:x[0][1]) [1]
right = sorted(approx,key=lambda x:x[0]) [2:]
right_down= sorted(right,key=lambda x:x[0][1]) [0]
right_up= sorted(right,key=lambda x:x[0][1]) [1]
### modify distortion ###
perspective1 = np.float32([left_down,right_down,right_up,left_up])
perspective2 = np.float32([[0, 0],[1654, 0],[1654, 2340],[0, 2340]])
psp_matrix = cv2.getPerspectiveTransform(perspective1,perspective2)
img_psp = cv2.warpPerspective(img, psp_matrix,(1654,2340))
cv2.imwrite("image_modified.png",img_psp)
関連