openCv page Dewarp 사다리꼴 결과물 보정

개요

이미지 디워핑을 하다보면 가끔 이미지의 가장자리 부분이 잡아 끌려와서 이상하게 처리되 사다리꼴이 된 모습을 볼 수 있다.

이미지가 사다리꼴이 된 모습

이와 같이 노이즈가 경계면에 있는 이유는 마스킹 때 이미지를 벗어난 좌표까지 기준으로 했기 때문에 그렇다.

이미지 마스킹 좌표표시

위 그림처럼 페이지를 새로 만들 부분을 이미지 경계면에서 벗어난 오른쪽 부분까지 지정한 것을 볼 수 있다. 그림에서는 이미지가 오른쪽으로 검은 부분이 더 있는 것처럼 보이지만 실제 input이미지는 책 오른쪽 검은쪽이 없다.

그렇기 때문에 해당 좌표를 기준으로 remmaping을 하면 없는 좌표의 것을 처리하기 때문에 오른쪽이 노이즈가 낀 것처럼 표시되는 것이다.

또한 책의 이미지가 사다리 꼴 모양으로 변해 가독성도 떨어지게 된다.

선 찾기

해당 이미지를 노이즈를 제거하고 사각형 모양으로 만들기 위해서는 오른쪽 이미지와 노이즈 부분의 경계를 찾을 필요가 있다.

여기서 사용한 방법은 opnecv의 선찾기 방법인 cv2.HoughLines이다.

해당 방법을 사용해 위의 결과물에 적용하면 아래와 같이 선을 찾을 수 있다.

이미지 선찾기

빨간 선이 찾은 선의 모습이다.

해당 선을 찾기 위한 소스코드는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)

edges = cv2.Canny(gray,50,50,apertureSize = 3)

lines = cv2.HoughLines(edges,1,np.pi/180,140)
if lines is None:
return src
for line in lines:
rho,theta = line[0]
a = np.cos(theta)
b = np.sin(theta)
x0 = a*rho
y0 = b*rho
x1 = int(x0 + 1000*(-b))
y1 = int(y0 + 1000*(a))
x2 = int(x0 - 1000*(-b))
y2 = int(y0 - 1000*(a))
if DEBUG_LEVEL >= 3:
cv2.line(img,(x1,y1),(x2,y2),(0,0,255),3)
if DEBUG_LEVEL >= 3:
debug_show('HoughLines', 7, 'HoughLines', img)

위 코드를 간단히 설명하면 이미지를 gray → canny로 변경하여 선찾기를 위한 전처리 과정을 진행한다.

canny로 변경한 이미지는 아래와 같다.

canny 이미지

이 이미지를 기준으로 선을 찾게 된다.

기준이 되는 좌표 찾기

사다리 꼴 모양을 똑바로 펴기 위해서는 네개의 모서리 점의 좌표가 필요하다.

일단 여기서 오른쪽의 좌표는

1
2
topLeft = [0 ,0]
bottomLeft = [0 , height]

와 같이 표현할 수 있다.

오른쪽 좌표를 찾기 위해서는 선의 끝점을 찾아야 한다.

여기서 처음에는 위에서 구한 (x1,y1), (x2,y2)가 끝점이 된다고 생각하였다.

하지만 해당 좌표를 print해보면 아래와 같은 값이 나오게 된다.

x1 = 411, y1 = -1014

x2 = 481, y2 = 983

하지만 이미지 전체의 넓이와 높이는 아래와 같다.

img_width = 496

img_height = 648

일단 y1은 -1014로 마이너스 값이기 때문에 이미지 좌표에서 벗어난다.

y2또한 983으로 이미지의 높이인 648을 훨씬 초과하였다.

그렇기 때문에 우리는 y=0일때 x좌표와 y=img_height 일때 x좌표를 구해야한다.

아래의 수식을 통해 우리는 y좌표가 정해졌을 때 x값을 구할 수 있다.

xcos(theta) + ysin(theta)= rho

xcos(theta) = rho-ysin(theta)

x = (rho - ysin(theta) ) / cos(theta)

위를 파이썬 코드로 풀어내면 다음과 같다.

1
2
x1 = int(np.ceil(rho / np.cos(theta)))
x2 = int(np.ceil((rho - img_height*np.sin(theta) ) / np.cos(theta)))

좌표는 정수여야만 하므로 올림을 했다.

이미지 와핑(Imgae Warping)하기

이제 4개의 꼭지점을 알았으니 그 점을 기준으로 이미지 와핑을 하면 된다.

여기서 사용하는 방법은 투영변환(Perspective Transformation)이다.

소스로 보면 아래와 같다.

1
2
3
4
5
src_pts = np.float32([topLeft, topRight, bottomLeft, bottomRight])
dst_pts = np.float32([[0,0], [width,0], [0, height], [width, height]])

M = cv2.getPerspectiveTransform(src_pts, dst_pts)
dst = cv2.warpPerspective(src, M, (width, height))

기준이 되는 점(src_pts)와 목적지 점(dst_pts)를 지정한다.

두점을 가지고 cv2.getPerspectiveTransform를 사용해 변환 행렬을 구한다.

구한 변환행렬을 사용해 cv2.warpPerspective를 사용해 이미지 와핑을 해주면 끝난다.

결과물은 아래와 같다.

이미지 와핑 결과물

공유하기