본문 바로가기
OpenCV/OpenCV-Chapter

CH05 Opencv-Python 기하학적(Geometry) 변환

by kiimy 2021. 12. 18.
728x90
728x90

영상의 기하학적 변환(geometric transformation)이란?

• 영상을 구성하는 픽셀의 배치 구조를 변경함으로써 전체 영상의 모양을 바꾸는 작업

• Image registration, removal of geometric distortion, etc

 

이동 변환(Translation transformation)

• 가로 또는 세로 방향으로 영상을 특정 크기만큼 이동시키는 변환

• x축과 y축 방향으로의 이동 변위를 지정

덧셈을 곱셈으로 하나로 표현하기 위함

영상의 전단 변환(Shear transformation)

- 층 밀림 변환. x축과 y축 방향에 대해 따로 정의

cv2.warpAffine(src, M, dsize, dst=None, flags=None, 
				borderMode=None, borderValue=None) -> dst
'''
M: 2x3 어파인 변환 행렬. 실수형
dsize: 결과 영상 크기. (w, h) 튜플. (0, 0)이면 src와 같은 크기로 설정.
flags: 보간법. 기본값은 cv2.INTER_LINEAR
borderMode: 가장자리 픽셀 확장 방식. 기본값은 cv2.BORDER_CONSTANT
borderValue: cv2.BORDER_CONSTANT일 때 사용할 상수 값. 기본값은 0
'''
# Shift(이동 변환)
aff = np.array([[1, 0, 200],
                [0, 1, 100]], dtype=np.float32)

aff1 = np.float32([[1, 0, 200],
                   [0, 1, 100]])

# cv2.warpAffine(src, M, dsize)
# 가로로 200pixel, 세로로 100pixel 이동했다.
dst = cv2.warpAffine(src, aff, (0,0))
dst1 = cv2.warpAffine(src, aff1, (0,0))

# Shear(전단 변환)
# array 변경
# m = 0.5, 값이 커질수록 더 x축으로 더 길게 늘어남(공식)
aff_shear = np.float32([[1, 0.5, 0],
                        [0, 1, 0]])
(h,w) = src.shape[:2]
# *크기 지정은 정수형태로*
# dst_shear1은 불러온 영상 크기와 같기 때문에 짤림
# ==> h(세로)는 고정 w(가로) 늘리기
dst_shear = cv2.warpAffine(src, aff_shear, (w + int(h*0.5), h))
dst_shear1 = cv2.warpAffine(src, aff_shear, (0,0))

영상의 크기 변환

cv2.resize(src, dsize, dst=None, fx=None, fy=None, interpolation=None) -> dst

# src.shape=(320, 480) h*w
# resize= dsize(w, h)
'''
dsize 절대크기 (0,0) 설정시 fx, fy(상대크기) 무조건 설정
dszie 설정시 fx, fy 설정 필요 없음

interpolation (default: cv2.INTER_LINEAR)
= 결과영상의 퀄리티, 자연스럽냐 부자연스럽냐를 결정짓는 파라미터
= 원래이미지의 pixel 윤곽선을 잘 표현하냐 못 하냐
'''
# dst1 -> dst2 -> dst3 -> dst4(품질이 가장 좋은, but 연산이 오래걸림)
# dst1 = 픽셀자체가 큰 사각형이 된느낌? = 계단현상, 블럭현상
dst1 = cv2.resize(src, (0,0), fx=4, fy=4, interpolation=cv2.INTER_NEAREST)
dst2 = cv2.resize(src, (480*4, 320*4)) # cv2.INTER_LINEAR
dst3 = cv2.resize(src, (1920, 1280), interpolation=cv2.INTER_CUBIC)
dst4 = cv2.resize(src, (1920, 1280), interpolation=cv2.INTER_LANCZOS4)

cv2.imshow('src', src)
cv2.imshow('dst1', dst1[500:900, 400:800]) # h*w
cv2.imshow('dst2', dst2[500:900, 400:800])
cv2.imshow('dst3', dst3[500:900, 400:800])
cv2.imshow('dst4', dst4[500:900, 400:800])
cv2.waitKey()

* interpolation 보간법

- 결과 영상의 퀄리티를 결정 짓는 파라미터

- 원래 이미지의 pixel 윤곽선을 잘 표현하느냐 못 하느냐

cv2.INTER_NEAREST 최근방 이웃 보간법( 가장 안 좋아 )
cv2.INTER_LINEAR 양선형 보간법 (2x2 이웃 픽셀 참조)
cv2.INTER_CUBIC 3차회선 보간법 (4x4 이웃 픽셀 참조)
cv2.INTER_LANCZOS4 Lanczos 보간법 (8x8 이웃 픽셀 참조)
cv2.INTER_AREA 영상 축소 시 효과적

영상의 축소 시 고려할 사항

• 영상 축소 시 디테일이 사라지는 경우가 발생 (e.g. 한 픽셀로 구성된 선분)

• 입력 영상을 부드럽게 필터링한(Gaussian) 후 축소, 다단계 축소

• OpenCV의 cv2.resize() 함수에서는 cv2.INTER_AREA 플래그를 사용


이미지 피라미드(Image Pyramid)

• 하나의 영상에 대해 다양한 해상도의 영상 세트를 구성한 것

보통 가우시안 블러링 & 다운샘플링 형태로 축소하여 구성(자동으로 됨)

== cv2.resize와의 차이

Why?
어떤 객체를 찾고 싶을 때 객체의 크기는 정말 다양하다
How?
1. 객체의 크기가 다양하게 설정
2. 이미지 크기를 resize해가면서 여러번 객체를 찾는 방법
내가 설정한 고양이의 얼굴 크기를 100*100으로 했을 때
입력 이미지의 고양이 얼굴은 설정한 값보다 클 경우 설정한 값(100*100)으로
입력 이미지의 고양이 얼굴을 찾을 수가 없다.
cv2.pyrDown(src, dst=None, dstsize=None, borderType=None) -> dst
cv2.pyrUp(src, dst=None, dstsize=None, borderType=None) -> dst
▪ 이후 짝수 행과 열을 제거하여 작은 크기의 영상을 생성
'''
영상 축소시 디테일이 사라지는 경우 발생
= 입력 영상을 부드럽게 filtering한 후 축소(다단계축소)
= cv2.resize(interpolation=cv2.INTER_AREA) 사용
= cv2.pyrDown, pyrUp = 5*5 Gaussianblur 적용됨
'''

# x, y, w, h
rc = (250, 120, 200, 200)  # rectangle tuple
'''
그림
x,y 기준으로 w = (x+200), y = (y+200)
'''

# 사본 영상에 그리기(객체 찾기)
src_copy = src.copy()
cv2.rectangle(src_copy, rc, (0,0,255), 1)

for i in range(1, 4):
    src = cv2.pyrDown(src)
    cpy = src.copy()
    # shift = 이미지가 downsampling되면 사각형이 같이 작아짐
    # 마지막 이미지의 잔상이 남음(cv2.destroyWindow() 설정)
    cv2.rectangle(cpy, rc, (0,0,255), 1, shift=i)
    cv2.imshow('src', cpy)
    cv2.waitKey()
    # cv2.destroyWindow('src')


영상의 회전

회전 변환(rotation transformation)

- 문서 이미지의 OCR(광학문자인식) 성능을 향상시키기 위함(Optical Character Recognition)

 

cv2.getRotationMatrix2D(center, angle, scale) -> retval
'''
retval: 2x3 어파인 변환 행렬. 실수형.
'''

# 직접 구현 code
# radian = degree * (pi / 180)
# degree(양수) = 반시계방향
# degree(음수) = 시계방향
rad = 20 * (np.pi / 180)


# 보통 영상의 중심점을 잡고 회전을 시킴
# 회전 절차 확인(.pdf)
aff = np.float32([[np.cos(rad), np.sin(rad), 0],
                  [-np.sin(rad), np.cos(rad), 0]])

dst = cv2.warpAffine(src, aff, (0,0))

# cv2.getRotationMatrix2D(center, angle, scale)
# scale = 영상의 크기 조절
(h,w) = src.shape[:2]
center = (h / 2, w/2)
rot = cv2.getRotationMatrix2D(center, 20, 1)
dst1 = cv2.warpAffine(src, rot, (0,0)) #dsize = (w,h)

1. 영상의 중심을 잡는다.
2. 영상의 중심 값을 빼서 영상을 원점(0,0)으로 이동 변환
3. 이동 변환 한 상태에서 회전
4. 회전한 것을 중심점까지 다시 이동변환
5. 출력 영상을 입력 영상의 크기와 동일하게 지정했기 때문에 검은색 부분이 나옴
== warpAffine에서 BorderValue로 설정 가능

투시변환(Perspective Transform)

Parallelograms= 평행사변형 

Perspective = 평행사변형보다는 사다리꼴 or 임의의 사각형

자유도(Degrees of freedom); 피사계 심도( Depth of field )
Affine = 6개의 미지수 / Perspective = 8개의 미지수

즉, 자유도가 더 높다는 말

# Affine
cv2.getAffineTransform(src, dst) -> retval
cv2.warpAffine(src, M, dsize, dst=None, flags=None, 
				borderMode=None, borderValue=None) -> dst
# Perspective
cv2.getPerspectiveTransform(src, dst, solveMethod=None) -> retval
cv2.warpPerspective(src, M, dsize, dst=None, flags=None, 
					borderMode=None, borderValue=None) -> dst
'''
Affine = retval: 2x3 투시 변환 행렬
Perspective = retval: 3x3 투시 변환 행렬
'''

# 명함의 비율이 9:5
# 비율에 맞게 출력할 이미지 사이즈 정함
w, h = 810, 450

# 이미지의 각 모서리 좌표
# 좌측상단, 우측상단, 우측하단, 우측하단( 설정하기 나름 )
srcQuad = np.float32([[325, 307], [760, 369], [718, 611], [231, 515]])

# mapping할 좌표(그래프 확인)
dstQuad = np.float32([[0, 0], [w-1, 0], [w-1, h-1], [0, h-1]])

# cv2.getPerspectiveTransform(src, m)
# cv2.warpPerspective(src, M, dsize, dst=None, flags=보간법(interpolation), 
# borderMode=None, borderValue=None
pers = cv2.getPerspectiveTransform(srcQuad, dstQuad)
dst = cv2.warpPerspective(src, pers, (w,h))

리매핑(remapping)

- 영상의 특정 위치 픽셀을 다른 위치에 재배치하는 일반적인 프로세스

- 어파인 변환, 투시 변환을 포함한 다양한 변환을 리매핑으로 표현 가능

왼쪽은 이동변환, 오른쪽은 대칭변환

* 이동변환
(x-200)은 -200 이동하는 것이 아니라 x방향으로 200이동 한다는 의미
(y-100) 또한 y방향으로 100이동

* 대칭변환
y좌표는 그대로
x가 0이면 w-1 = 가로 끝점 좌표참조 
cv2.remap(src, map1, map2, interpolation, dst=None, borderMode=None, 
			borderValue=None) -> dst
'''
map1: 결과 영상의 (x, y) 좌표가 참조할 입력 영상의 x좌표.
	입력 영상과 크기는 같고, 타입은 np.float32인 numpy.ndarray.
map2: 결과 영상의 (x, y) 좌표가 참조할 입력 영상의 y좌표
'''

h, w = src.shape[:2]

# 이미지에 적용할 mapping 설정
# y좌표 index, x좌표 index = np.indices()
# 어떤 행렬의 index값( 행렬의 x좌표값, y좌표값 반환)
map2, map1 = np.indices((h,w), np.float32)
map2 = map2 + (10 * np.sin(map1 / 32))

# cv2.remap(src, map1, map2, interpolation, borderMode, borderValue)
# map1 = 결과 영상의 (x, y) 좌표가 참조할 입력 영상의 x좌표
# map2 = 결과 영상의 (x, y) 좌표가 참조할 입력 영상의 y좌표
'''
*borderMode*
cv2.BORDER_DEFAULT: 영상 바깥쪽에 가상의 픽셀이 있기 때문에 자연스러움
                    ==> 금방의 색으로 대체됨(대칭)
cv2.BORDER_CONSTANT(default): borderValue가 0(default) 영상 바깥쪽 검은색
'''
dst = cv2.remap(src, map1, map2, interpolation=cv2.INTER_CUBIC,\
                borderMode= cv2.BORDER_DEFAULT)

cv2.BORDER_CONSTANT(default)

728x90

댓글