Perspective transformation
Perspective 변환(투시 변환)은 이미지 또는 이미지 내의 객체를 나란히 직선으로 만들 때 매우 유용한 변환이다. Perspective 변환을 적용하는 아주 좋은 예시는 테이블 위의 문서를 가지런하게 만드는 것이다.
Perspective 변환을 시작하기 앞서 간단히 어떠한 방식으로 이러한 작업을 할 수 있는지 알아보자.
우선 원본 이미지 상에서 사변형의 좌표와 최종적으로 변형된 사변형의 좌표를 알아야 한다. 두 사변형의 좌표들을 통해 이들의 관계를 변환 행렬로 나타낼 수 있다. 이 변환 행렬을 통해 원본 이미지에 적용해 원하는 결과물을 얻을 수 있다. (변환 행렬을 구하는 수식은 생략한다)
조금 더 직관적으로 접근하자면, 우리가 책상위에 있는 인쇄물(object)을 카메라(eye)로 찍는 각도에 따라 화면(screen)상에서 보이는 인쇄물의 모양이 달라보이는 것을 상상해보면 조금 더 이해 하기 쉬울것이다. 결국 3차원 상의 객체를 2차원(평면 이미지)에 투영(projection) 하는 것이 이 포스팅에서 말하고자 하는 핵심이고, 이를 수학적으로 계산한 결과가 (변환)행렬로 나온다.
OpenCV에서는 이 행렬을 구해주는 다음과 같은 함수를 제공한다.
Mat getPerspectiveTransform(Mat src, Mat dst)
src : 원본 이미지에서 4개의 좌표 dst : 출력 이미지에서 4개의 좌표
변환행렬을 구했다면 warpPerspective 함수를 통해 원하는 결과 영상을 얻을 수 있게 된다.
warpPerspective(Mat src, Mat dst, Mat M, Size dsize)
src : 원본 이미지 dst : 출력 이미지 M : 변환 행렬 dsize : 출력할 결과 영상의 사이즈
// A4 용지 크기: 210x297cm val width = 210.0 val height = 297.0 // 원본 영상 val src:Mat = .. // 원본 이미지 상의 4개 좌표 val srcQuad = MatOfPoint2f( Point(58.0, 130.0), Point(420.0, 130.0), Point(88.0, 710.0), Point(570.0, 635.0) ) // 출력 이미지상의 4개 좌표 val dstQuad = MatOfPoint2f( Point(0.0, 0.0), Point(width - 1, 0.0), Point(0.0, height - 1), Point(width - 1, height - 1), ) // 투시 변환 행렬 구하기 val perspectiveTransform = Imgproc.getPerspectiveTransform(srcQuad, dstQuad) // 인쇄물 기준으로 가지런하게 만들기 var dst = Mat() Imgproc.warpPerspective(src, dst, perspectiveTransform, Size(0.0, 0.0)) // 인쇄물만 추출 dst = dst.submat(0, height.toInt(), 0, width.toInt())
가지런히 출력된 인쇄물 영상 (예제코드)
스캐너 앱 만들기
Perspective 변환을 통해 간단한 문서(명함) 스캐너 앱을 만들어 보자. (예제코드)
- View에 TouchListener를 추가하여 ACTION_DOWN 이벤트 좌표에 인접한 앵커가 있는지 확인
- 특정 앵커가 선택 되었다면 ACTION_MOVE 이벤트 시 앵커의 좌표를 갱신
- ACTION_UP 이벤트 시의 앵커 좌표를 기록
- ‘완료’ 버튼을 누를 때 앵커들의 좌표와 미리 입력해둔 명함 좌표를 통해 투시 변환 행렬을 계산
- 계산 된 투시 변환 행렬을 이용하여 원본이미지를 투시 변환하여 이미지뷰에 출력
1개의 댓글
ggg · 2023년 5월 11일 2:21 오후
혹시 이 예제코드 링크는 존재하지 않다고 하는데 볼 수 있는 방법은 따로 없을까요…?