openCv page Dewarp remapping을 이용한 가장자리 보정

개요

저번 포스트에서는 remapping이 어떻게 이루어 지는가사다리꼴 결과물 보정에 대해서 분석해 보았습니다.

사다리꼴 결과물 보정에서는 가장자리의 선을 찾아 그 끝점으로 이미지를 와핑하는 방법을 사용하였습니다.

하지만 아래와 같이 그림책의 경우에는 선이 너무 많고 가장자리의 선을 못찾는 경우가 발생하였습니다.

이미지 보정 전을 보면 눈으로는 가장자리 선을 확인할 수 있습니다.

보정전 이미지

하지만 정작 선찾기를 해보면 쓸대 없는 선을 매우 많이 찾습니다. 특히 우리가 원하는 오른쪽 가장자리 선은 찾지 못하고 다른 엉뚱한 세로선의 모습을 볼 수 있습니다.

선찾기

결국 이상한 선을 기준으로 자른 결과 오른쪽 내용의 상당수가 소실된채 이미지 와핑이 되게 됩니다.

보정 후 이미지

그렇기 때문에 선찾기로는 한계가 있을 수 밖에 없습니다.

그래서 remapping시 이미지가 벗어나는 부분을 찾아서 이미지 와핑의 기준을 잡기로 했습니다.

이미지 영역을 벗어나는 곳 찾기

이미지의 원본 소스를 img라고 합니다.

그러면 원본 소스의 넓이(x축)과 높이(y축)은 다음과 같습니다.

1
height, width = img.shape[:2]

원본 소스의 remap 함수는 다음과 같이 쓰였습니다.

1
2
3
remapped = cv2.remap(img, image_x_coords, image_y_coords,
cv2.INTER_CUBIC,
None, cv2.BORDER_REPLICATE)

우리는 x축에 관심이 있기 때문에 image_x_coords를 사용해서 이미지 영역 밖을 찾습니다.

이미지 영역을 찾기 위해선 numpy의 특정 요소를 찾는 np.where을 아래와 같이 써줍니다.

1
2
corners_x1 = np.where(image_x_coords[0,:].astype(int) ==img.shape[1])
corners_x2 = np.where(image_x_coords[height-1,:].astype(int) ==img.shape[1])

해당 소스의 뜻은 corners_x1은 image_x_coords에서 y가 0인 x의 값과 원본 이미지의 넓이와 같은 좌표를 찾는 것입니다.
corners_x2는 마찬가지로 y값이 remapping된 이미지의 높이일때 x의 값과 원본 이미지의 넓이와 같은 좌표를 찾는 것입니다.

이를 그림으로 표현하면 아래와 같습니다.

이미지 좌표 계산

앞서 remapping 관련 포스트에서 설명한 것 처럼 image_x_coords에는 원본 소스의 좌표가 들어 있기 때문에 그 좌표와 같은 값이 remapped 이미지의 어느 좌표와 매칭되는 가를 찾는 것입니다.

np.where의 결과는 tuple로 한번 감싸져 있습니다.

1
2
3
print("corners_x1 = ", corners_x1)

corners_x1 = (array([1249, 1250], dtype=int32),)

그렇기 때문에 결과의 가장 첫번째 값을 찾기 위해선 corners_x1[0][0]와 같이 써주면 됩니다.

위에서 corners_x1[0][0]을 해주면 1249의 값을 찾을 수 있습니다.

여기서 1249는 remapped된 이미지의 x값 좌표가 1249라는 의미입니다.

corners_x2는 다음과 같습니다.

corners_x2 = (array([1478, 1479, 1480], dtype=int32),)

위 결과를 그림으로 표현하면 아래와 같습니다.

remapped의 좌표

만약 np.where의 값이 없다면 아래와 같이 리턴됩니다.

corners_x1 = (array([], dtype=int32),)

이 값을 체크하기 위해서는 일단 np.where의 리턴은 tuple로 감싸있으므로

if corners_x1[0].size == 0

과 같이 체크할 수 있습니다.

이미지 펴기

이제 이미지를 펴기 위해 4점을 특정해 줍니다.

여기서는 책의 왼쪽 페이지로 우측에 이미지의 손실이 일어난다고 가정합니다.

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

이미지의 좌측은 원래 이미지의 기준인 (0,0), (0,height)로 잡았습니다.

우측은 우리가 구한 값인 (corners_x1[0][0], 0), (corners_x2[0][0], height)을 기준으로 잡았습니다.

이미지 4점 좌표

이제 4점을 기준으로 펴주는 작업입니다.

1
2
3
4
5
6
7
8
if corners_x1[0][0] >= corners_x2[0][0]:
dst_pts = np.float32([[0,0], [corners_x1[0][0],0], [0, height], [corners_x1[0][0], height]])
M = cv2.getPerspectiveTransform(src_pts, dst_pts)
src = cv2.warpPerspective(src, M, (corners_x1[0][0], height))
else:
dst_pts = np.float32([[0,0], [corners_x2[0][0],0], [0, height], [corners_x2[0][0], height]])
M = cv2.getPerspectiveTransform(src_pts, dst_pts)
src = cv2.warpPerspective(src, M, (corners_x2[0][0], height))

구한 두 점중 더 긴 곳을 기준으로 이미지 펴기를 하였습니다.

해당 결과는 아래와 같습니다.

이미지 펴기 결과물

공유하기