기하학적 변환(geometric transform)
기하학적 변환은 영상의 좌표에 기하학적인 연산을 가해서 변환된 새로운 좌표를 얻는 것을 말한다.
영상에 기하학적 변환을 하면 이동, 확대, 축소, 회전 등 일상생활에서 흔히 접하는 변환에서부터
볼록 거울에 비친 모습이나 일렁이는 물결에 비친 모습과 같은 여러가지 왜곡된 모양으로도 변환할 수 있다.
이동, 확대/축소, 회전
영상의 기하학적 변환은 기존의 영상을 원하는 모양이나 방향 등으로 변환하기 위해 각 픽셀을 새로운 위치로 옮기는 것이 작업의 대부분이다.
그러기 위해서는 각 픅셀의 x, y 좌표에 대해 옮기고자 하는 새로운 좌표x’, y’을 구하는 연산이 필요하다.
이때 효율적으로 연산을 하기 위해 행렬을 이용한다.
이동
처음 점의 좌표를 p(x,y) 라고 하고 이를 d_x와 d_y 만큼 옮기면 새로운 위치의 좌표를 구할 수 있다.
이것을 수식으로 작성하면 아래와 같다.
위의 방정식을 행렬식으로 바꾸어 표현하면 아래와 같다.
dst = cv2.warpAffine(src, mtrx, dsize [, dst, flags, borderMode, borderValue ] )
- src : 원본 영상, Numpy 배열
- mtrx : 2 X 3 변환행렬, Numpy 배열, dtype=float32
- dsize : 겨로가 이미지 크기, tuple(width, height)
- flags : 보간법 알고리즘 선택 플래그
- cv2.INTER_LINEAR : 기본값, 인접한 4개 픽셀 값에 거리 가중치 사용
- cv2.INTER_NEAREST : 가장 가까운 픽셀 값 사용
- cv2.INTER_AREA : 픽셀 영역 관계를 이용한 재샘플링
- cv2.INTER_CUBIC : 인접한 16개 픽셀 값에 거리 가중치 사용
- cv2.INTER_LANCZOS4 : 인접한 8개 픽셀을 이용한 란초의 알고리즘
- borderMode : 외곽 영역 보정 플래그
-
cv2.BORDER_CONSTANT : 고정 색상 값(999 12345 999) -
cv2.BORDER_REPLICATE : 가장자리 복제(111 12345 555) -
cv2.BORDER_WRAP : 반복(345 12345 123) -
cv2.BORDER_REFLECT : 반사(321 12345 543)
-
- borderValue : cv2.BORDER_CONSTANT 의 경우 사용할 색상 값(기본값 = 0)
- dst : 결과 이미지, numpy 배열
import cv2
import numpy as np
img = cv2.imread('./img/fish.jpg')
rows, cols = img.shape[0:2] # 영상의 크기
dx, dy = 100, 50 # 이동할 픽셀 거리
# 변환행렬 생성 1
mtrx = np.float32([[1, 0, dx],
[0, 1, dy]])
# 단순 이동
dst = cv2.warpAffine(img, mtrx, (cols+dx, rows+dy))
# 탈락된 외각 픽셀을 파란색으로 보정
dst2 = cv2.warpAffine(img, mtrx, (cols+dx, rows+dy), None,
cv2.INTER_LINEAR, cv2.BORDER_CONSTANT, (255,0,0))
#dst4 = cv2.warpAffine(img, mtrx, (cols+dx, rows+dy), None, cv2.INTER_NEAREST, cv2.BORDER_CONSTANT, (255,0,0))
#dst5 = cv2.warpAffine(img, mtrx, (cols+dx, rows+dy), None, cv2.INTER_AREA, cv2.BORDER_CONSTANT, (255,0,0))
#dst6 = cv2.warpAffine(img, mtrx, (cols+dx, rows+dy), None, cv2.INTER_CUBIC, cv2.BORDER_CONSTANT, (255,0,0))
#dst7 = cv2.warpAffine(img, mtrx, (cols+dx, rows+dy), None, cv2.INTER_LANCZOS4, cv2.BORDER_CONSTANT, (255,0,0))
# 탈락된 외곽 픽셀을 원본을 반사시켜서 보정
dst3 = cv2.warpAffine(img, mtrx, (cols+dx, rows+dy), None,
cv2.INTER_LINEAR, cv2.BORDER_REFLECT)
cv2.imshow('original', img)
cv2.imshow('trans', dst)
cv2.imshow('BORDER_CONSTANT', dst2)
#cv2.imshow('BORDER_NEAREST / Nearest', dst4)
#cv2.imshow('BORDER_NEAREST / Area', dst5)
#cv2.imshow('BORDER_NEAREST / Cubic', dst6)
#cv2.imshow('BORDER_NEAREST / Lanczos4', dst7)
cv2.imshow('BORDER_REFLECT', dst3)
cv2.waitKey(0)
cv2.destroyAllWindows()
확대/축소
영상을 확대 또는 축소하려면 원래 있던 좌표에 원하는 비율만큼 곱해서 새로운 좌표를 구할 수 있다.
이때 확대/축소 비율을 가로와 세로 방향으로 각각 α, β라고 하면 변환행렬은 아래와 같다.
- 행렬을 이용한 확대와 축소
import cv2
import numpy as np
img = cv2.imread('./img/fish.jpg')
height, width = img.shape[:2]
# 0.5배 축소 변환행렬
m_small = np.float32([[0.5, 0, 0],
[0, 0.5, 0]])
# 2배 확대 변환행렬
m_big = np.float32([[2, 0, 0],
[0, 2, 0]])
# 보간법 적용 없이 확대/축소
dst1 = cv2.warpAffine(img, m_small, (int(height*0.5), int(width*0.5)))
dst2 = cv2.warpAffine(img, m_big, (int(height*2), int(width*2)))
# 보간법 적용한 확대/축소
dst3 = cv2.warpAffine(img, m_small, (int(height*0.5), int(width*0.5)), None, cv2.INTER_AREA)
dst4 = cv2.warpAffine(img, m_big, (int(height*2), int(width*2)), None, cv2.INTER_CUBIC)
cv2.imshow('original', img)
cv2.imshow('small', dst1)
cv2.imshow('big', dst2)
cv2.imshow('small INTER_AREA', dst3)
cv2.imshow('big INTER_CUBIC', dst4)
cv2.waitKey(0)
cv2.destroyAllWindows()
resize( ) 함수 이용
dst = cv2.resize(src, dsize, dst, fx, fy, interpolation)
- src : 입력 영상, numpy 배열
- dsize : 출력 영상 크기(확대/축소 목표 크기), 생략하면 fx, fy를 적용
- (width, height)
- fx, fy : 크기 배율, 생략하면 dsize를 적용
- interpolation : 보간법 알고리즘 선택 플래그(xc2.warpAffine()과 동일)
- dst : 결과 영상, Numpy 배열
import cv2
import numpy as np
img = cv2.imread('./img/fish.jpg')
height, width = img.shape[:2]
dst1 = cv2.resize(img, (int(width*2), int(height*2)), interpolation=cv2.INTER_AREA)
dst2 = cv2.resize(img, None, None, 2, 2, cv2.INTER_CUBIC)
cv2.imshow('original', img)
cv2.imshow('small', dst1)
cv2.imshow('big', dst2)
cv2.waitKey(0)
cv2.destroyAllWindows()
회전
영상을 회전하려면 삼각함수를 써야한다. 회전을 행렬식으로 표현하면 다음과 같다.
import cv2
import numpy as np
img = cv2.imread('./img/fish.jpg')
rows, cols = img.shape[0:2]
# 라디안 각도 계산
d45 = 45.0*np.pi/180
d90 = 90.0*np.pi/180
# 회전을 위한 변환행렬 생성
m45 = np.float32( [[np.cos(d45), -1*np.sin(d45), rows//2],
[np.sin(d45), np.cos(d45), -1*cols//4]])
m90 = np.float32( [[np.cos(d90), -1*np.sin(d90), rows],
[np.sin(d90), np.cos(d90), 0]])
# 회전변환 적용
r45 = cv2.warpAffine(img, m45, (cols, rows))
r90 = cv2.warpAffine(img, m90, (cols, rows))
cv2.imshow('origin', img)
cv2.imshow('d45', r45)
cv2.imshow('d90', r90)
cv2.waitKey(0)
cv2.destroyAllWindows()
회전(opencv 제공함수 이용)
mtrx = cv2.getRotationMatrix2D(center, angle, scale)
- center : 회전 푹 중심좌표, 튜플(x, y)
- angle : 회전 각도, 60진법
- scale : 확대/축소 배율
import cv2
img = cv2.imread('./img/fish.jpg')
rows, cols = img.shape[0:2]
# 회전을 위한 변환행렬 구하기
# 회전 푹 : 중앙 / 각도 : 45 / 배율 : 0.5
m45 = cv2.getRotationMatrix2D((cols/2, rows/2),45.0,0.5)
# 최전 축: 중앙 / 각도 : 90 / 배율 : 1.5
m90 = cv2.getRotationMatrix2D((cols/2, rows/2),90.0,1.5)
# 변환행렬 적용
img45 = cv2.warpAffine(img, m45, (cols, rows))
img90 = cv2.warpAffine(img, m90, (cols, rows))
cv2.imshow('origin', img)
cv2.imshow('45', img45)
cv2.imshow('90', img90)
cv2.waitKey(0)
cv2.destroyAllWindows()
뒤틀기
어핀 변환(affine transform)
어핀 변환은 앞의 이동, 확대/축소, 회전을 포함하는 변환으로 직선, 길이의 비율, 평햇성을 보존하는 변환을 말한다.
import cv2
import numpy as np
from matplotlib import pyplot as plt
file_name = './img/fish.jpg'
img = cv2.imread(file_name)
rows, cols = img.shape[:2]
# 변환전, 후 각 3개의 좌표 생성
pts1 = np.float32([[100, 50], [200, 50], [100, 200]])
pts2 = np.float32([[80, 70], [210, 60], [250, 120]])
# 변환 전 좌표를 이미지에 표시
cv2.circle(img, (100,50), 5, (255,0), -1)
cv2.circle(img, (200,50), 5, (0,255,0), -1)
cv2.circle(img, (100,200), 5, (0,0,255), -1)
# 짝지은 3개의 좌표로 변환행렬 계산
mtrx = cv2.getAffineTransform(pts1, pts2)
# 어핀변환 적용
dst = cv2.warpAffine(img, mtrx, (int(cols*1.5), rows))
cv2.imshow('origin', img)
cv2.imshow('affin', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
원근 변환(perspective transform)
원근변환(perspective transform)은 보는 사람의 시각에 따라 같은 물체도 먼 은 작게, 가까운 것은 크게 보이는 현상인 원근감을 주는 변환을 말한다.
2차원인 영상을 3차우너으로 느끼게 차원 간의 차이를 보정해 줄 추가 연산과 시스템이 필요한데,
이때 사용하는 좌표계를 동차 좌표(homogeneous coordinates)라고 한다. 이때문에 원근 변환을 다른 말로 호모그래피(Homography)라고 한다.
간단히 설명하면 2차원 좌표 (x, y) 에 상수항을 추가해(wx, wy, w)로 표현하고 이것을 2차원 좌표로 바꿀 때는 다시 상수항 w로 나누어 (x/w, y/w)로 표현한다.
원근 변환을 하려면 (x,y,1)꼴의 좌표계가 필요하고 아래와 같은 변환행렬식이 필요하다.
mtrx = cv2.getPerspectiveTransform(ppts1, pts2)
- pts1 : 변환 이전 영상의 좌표 4개, 4X2 numpy 배열(float32)
- pts2 : 변환 이전 영상의 좌표 4개, pts1 과 동일
- mtrx : 변환행렬 반환, 3X3 행렬
dst = cv2.warpPerspective(src, mtrx, dsize [, dst, flags, borderMode, borderValue] ) : 모든 파라미터와 반환값은 cv2.warpAffine( )과 동일
import cv2
import numpy as np
file_name = './img/fish.jpg'
img = cv2.imread(file_name)
rows, cols = img.shape[:2]
# 원근 변환 전후 4개 좌표
pts1 = np.float32([[0,0], [0,rows], [cols,0], [cols,rows]])
pts2 = np.float32([[100,50], [10,rows-50], [cols-100,50], [cols-10,rows-50]])
# 변환 전 좌표를 원본 이미지에 표시
cv2.circle(img, (0,0), 10, (255,0,0), -1)
cv2.circle(img, (0,rows), 10, (0,255,0), -1)
cv2.circle(img, (cols,0), 10, (0,0,255), -1)
cv2.circle(img, (cols,rows), 10, (0,255,255), -1)
# 원근 변환행렬 계산
mtrx = cv2.getPerspectiveTransform(pts1, pts2)
# 원근 변환 적용
dst = cv2.warpPerspective(img, mtrx, (cols, rows))
cv2.imshow('origin', img)
cv2.imshow('perspective', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
- 마우스와 원근 변환으로 문서스캔효과 내기
import cv2
import numpy as np
win_name = 'scanning'
img = cv2.imread('./img/paper.jpg')
rows, cols = img.shape[:2]
draw = img.copy()
pts_cnt = 0
pts = np.zeros((4,2), dtype=np.float32)
def onMouse(event, x, y, flags, param): # 마우스 이벤트 콜백 함수 구현
global pts_cnt # 마우스로 찍은 좌표의 개수 저장
if event == cv2.EVENT_LBUTTONDOWN:
cv2.circle(draw, (x,y), 10, (0,255,0), -1) # 좌표에 초록색 동그라미 표시
cv2.imshow(win_name, draw)
pts[pts_cnt] = [x,y] # 마우스 좌표 저장
pts_cnt+=1
if pts_cnt==4: # 마우스 좌표가 4개이면
sm = pts.sum(axis=1) # 4쌍의 좌표 각각 x+y 계산
diff = np.diff(pts, axis=1) # 4쌍의 좌표 각각 x-y 계산
topLeft = pts[np.argmin(sm)] # x+y가 가장 작은값이 좌상단 좌표
bottomRight = pts[np.argmax(sm)] # x+y가 가장 큰값이 우하단 좌표
topRight = pts[np.argmin(diff)] # x-y가 가장 작은값이 우상단 좌표
bottomLeft = pts[np.argmax(diff)] # x-y가 가장 작은값이 좌하단 좌표
# 변환전 4개의 좌표
pts1 = np.float32([topLeft, topRight, bottomRight, bottomLeft])
# 변환 후 영상에 사용할 서류의 폭과 높이 계산
w1 = abs(bottomRight[0] - bottomLeft[0]) # 상단 좌우 좌표 간의 거리
w2 = abs(topRight[0] - topLeft[0]) # 하단 좌우 좌표 간의 거리
h1 = abs(topRight[1] - bottomRight[1]) # 우측 상하 좌표 간의 거리
h2 = abs(topLeft[1] - bottomLeft[1]) # 좌측 상하 좌표 간의 거리
width = int(max([w1, w2])) # 두 좌우 거리 간의 최대 값이 서류의 폭
height = int(max([h1, h2])) # 두 상하 거리 간의 최대 값이 서류의 높이
# 변환 후 4개의 좌표
pts2 = np.float32([[0,0], [width-1,0],
[width-1,height-1], [0,height-1]])
# 변환생렬 계산
mtrx = cv2.getPerspectiveTransform(pts1, pts2)
# 원근 변환 적용
result = cv2.warpPerspective(img, mtrx, (width, height)) # 마우스 콜백 함수를 GUI윈도에 등록
cv2.imshow('scanned', result)
cv2.imshow(win_name, img)
cv2.setMouseCallback(win_name, onMouse)
cv2.waitKey(0)
cv2.destroyAllWindows()
삼각형 어핀 변환
x,y,w,h = cv2.boundingRect(pts)
- pts : 다각형 좌표
- x,y,w,h : 외접 사각형의 좌표와 폭과 높이
cv2.fillConvexPoly(img, points, color [, lineType])
- img : 입력 영상
- points : 다각형 꼭짓점 좌표
- color : 채우기에 사용할 색상
- lineType : 선 그리기 알고리즘 선택 플래그
import cv2
import numpy as np
img = cv2.imread('./img/taekwonv1.jpg')
img2 = img.copy()
draw = img.copy()
# 변환 전, 후 삼각형 좌표
pts1 = np.float32([[188,14], [85,202], [294,216]])
pts2 = np.float32([[128,40], [85,307], [306,167]])
# 각 삼각형을 완전히 감싸는 사각형 좌표 구하기
x1,y1,w1,h1 = cv2.boundingRect(pts1)
x2,y2,w2,h2 = cv2.boundingRect(pts2)
# 사각형을 이용한 관심영역 설정
roi1 = img[y1:y1+h1, x1:x1+w1]
roi2 = img2[y2:y2+h2, x2:x2+w2]
# 관심영역을 기준으로 좌표 계산
offset1 = np.zeros((3,2), dtype=np.float32)
offset2 = np.zeros((3,2), dtype=np.float32)
for i in range(3):
offset1[i][0], offset1[i][1] = pts1[i][0]-x1, pts1[i][1]-y1
offset2[i][0], offset2[i][1] = pts2[i][0]-x2, pts2[i][1]-y2
# 관심영역을 주어진 삼각형 좌표로 어핀 변환
mtrx = cv2.getAffineTransform(offset1, offset2)
warped = cv2.warpAffine(roi1, mtrx, (w2, h2), None,
cv2.INTER_LINEAR, cv2.BORDER_REFLECT_101)
# 어핀 변환 후 삼각형만 골라 내기 위한 마스크 생성
mask = np.zeros((h2, w2), dtype = np.uint8)
cv2.fillConvexPoly(mask, np.int32(offset2), (255))
# 삼각형 영역만 마스킹해서 합성
warped_masked = cv2.bitwise_and(warped, warped, mask=mask)
roi2_masked = cv2.bitwise_and(roi2, roi2, mask=cv2.bitwise_not(mask))
roi2_masked = roi2_masked + warped_masked
img2[y2:y2+h2, x2:x2+w2] = roi2_masked
# 관심영역과 삼가경에 선을 그려서 출력
cv2.rectangle(draw, (x1, y1), (x1+w1, y1+h1), (0,255,0), 1)
cv2.polylines(draw, [pts1.astype(np.int32)], True, (255,0,0), 1)
cv2.rectangle(img2, (x2, y2), (x2+w2, y2+h2), (0,255,0), 1)
cv2.imshow('origin', draw)
cv2.imshow('warped triangle', img2)
cv2.waitKey(0)
cv2.destroyAllWindows()
렌즈 왜곡(lens distortion)
행렬식으로 표현할 수 없는 모양의 변환도 필요할 때가 있다. 물잔을 통해 비친 장면이나 반사된 모습 같은것이 에시이다.
이런 렌즈 왜곡 변환에 대해 알아보겠다.
리매핑
opencv는 규칙성 없이 마음대로 모양을 변환해 주는 함수로 cv2.remap()을 제공한다.
dst = cv2.remap(src, mapx, mapy, interpolation [, dst, borderMode, borderValue ] )
- src : 입력 영상
- mapx, mapy : x축과 y축으로 이동할 좌표(index), src와 동일한 크기, dtype=float32
- 나머지 인자는 cv2.warpAffine()과 동일
- dst : 결과 영상
cv2.remap()함수의 mapx, mapy는 입력 영상인 src와 같은 배열인 float32로 만들어야하고,
배열의 각 요소는 src의 같은 인덱스의 픽셀이 각각 x축과 y축으로 옮겨갈 새로운 인덱스를 갖게해야한다.
mapx[0][0] = 10, mapy[0][0] = 5 로 지정했다면 src의 (0,0)픽셀을 (10,5)로 옮기라는 의미이다.
이때 사용하는 함수가 np.indices()함수이다. 배열을 주어진 크기로 생성하는데, 자신의 인덱스 값으로 초기화해서 3차원 배열로 반환한다.
아래와 같이 사용한다.
mapx, mapy = np.indices( (rows, cols), dtype=np.float32)
여기서 영상을 뒤집기 위한 행열식과 연산은 다음과 같다.
- x’ = cols -x -1
-
y’ = rows -x -1
- 변환 행렬과 리매핑으로 영상 뒤집기
import cv2
import numpy as np
import time
img = cv2.imread('./img/girl.jpg')
rows, cols = img.shape[:2]
# 변환행렬로 구현
st = time.time()
mflip = np.float32([ [-1, 0, cols-1], [0, -1, rows-1]]) # 변환행렬 생성
fliped1 = cv2.warpAffine(img, mflip, (cols, rows)) # 변환 적용
print('matrix:', time.time()-st)
# remap 함수로 구현
st2 = time.time()
mapy, mapx = np.indices((rows, cols), dtype=np.float32) # 매핑 배열 초기화 생성
mapx = cols - mapx -1 # x축 좌표 뒤집기 연산
mapy = rows - mapy -1 # y축 좌표 뒤집기 연산
fliped2 = cv2.remap(img,mapx,mapy,cv2.INTER_LINEAR) # remap 적용
print('remap:', time.time()-st2)
cv2.imshow('origin', img)
cv2.imshow('fliped1', fliped1)
cv2.imshow('fliped2', fliped2)
cv2.waitKey()
cv2.destroyAllWindows()
matrix: 0.0014691352844238281
remap: 0.004033088684082031
- 삼각함수를 이용한 비선형 리매핑
import cv2
import numpy as np
l = 20 # 파장(wave length)
amp = 15 # 진폭(amplitude)
img = cv2.imread('./img/taekwonv1.jpg')
rows, cols = img.shape[:2]
# 초기 매핑 배열 생성
mapy, mapx = np.indices((rows, cols), dtype=np.float32)
# sin, cos함수를 잉요한 변형 매핑 연산
sinx = mapx + amp * np.sin(mapy/l)
cosy = mapy + amp * np.cos(mapx/l)
# 영상 리매핑
img_sinx = cv2.remap(img, sinx, mapy, cv2.INTER_LINEAR)
img_cosy = cv2.remap(img, mapx, cosy, cv2.INTER_LINEAR)
# x, y 축 모두 sin, cos 곡선 적용 및 외각 영역 보정
img_both = cv2.remap(img, sinx, cosy, cv2.INTER_LINEAR, None, cv2.BORDER_REPLICATE)
cv2.imshow('origin', img)
cv2.imshow('sinx', img_sinx)
cv2.imshow('cosy', img_cosy)
cv2.imshow('sin cos', img_both)
cv2.waitKey()
cv2.destroyAllWindows()
오목렌즈와 볼록렌즈 효과
r, theta = cv2.cartToPolar(x, y) : 직교좌표 -> 극좌표 변환
x, y = cv2.polarToCart(r, theta) : 극좌표 -> 직교좌표 변환
- x,y : x,y 좌표 배열
- r : 원점과의 거리
- theta : 각도 값
import cv2
import numpy as np
img = cv2.imread('./img/taekwonv1.jpg')
rows, cols = img.shape[:2]
exp = 0.5
#exp = 1.5 # 볼록, 오목 지수(오목:0.1~1, 볼록: 1.1~)
scale = 1 # 변환 영역 크기(0~1)
# 매핑 배열 생성
mapy, mapx = np.indices((rows, cols), dtype=np.float32)
# 좌상단 기준좌표에서 -1~1로 정규화된 중심점 기준 좌표로 변경
mapx = 2*mapx/(cols - 1) - 1
mapy = 2*mapy/(rows - 1) - 1
# 직교좌표를 극좌표로 변환
r, theta = cv2.cartToPolar(mapx, mapy)
# 왜곡 영역만 중심 확대/축소 지수 적용
r[r< scale] = r[r<scale] **exp
# 극좌표를 직교좌표로 변환
mapx, mapy = cv2.polarToCart(r, theta)
# 중심점 기준에서 좌상단 기준으로 변경
mapx = ((mapx + 1)*cols - 1)/2
mapy = ((mapy + 1)*rows - 1)/2
# 리매핑 변환
distorted = cv2.remap(img,mapx,mapy,cv2.INTER_LINEAR)
cv2.imshow('origin', img)
cv2.imshow('distorted', distorted)
cv2.waitKey()
cv2.destroyAllWindows()
방사왜곡
대부분의 영상은 카메라로 촬영한다. 렌즈는 동그랗고 영상은 사각형이다 보니 렌즈 가장자리에는 왜곡이 생기고,
이런 돼곡을 배럴 왜곡(barrel distortion)이라고 한다. 배럴 왜곡은 다음과 같은 식으로 해결한다.
- rd : 왜곡 변형 후
- ru : 왜곡 변형 전
- k1, k2, k3 : 왜괙 계수
왜곡 계수에 따라 밖으로 튀어나오는 배럴 왜곡이 나타나기도 하고, 안으로 들어가는 핀쿠션 왜곡이 일어나기도 한다.
import cv2
import numpy as np
k1, k2, k3 = 0.5, 0.2, 0.0 # 배럴 왜곡
#k1, k2, k3 = -0.3, 0, 0 # 핀큐션 왜곡
img = cv2.imread('./img/girl.jpg')
rows, cols = img.shape[:2]
# 매핑 배열 생성
mapy, mapx = np.indices((rows, cols), dtype=np.float32)
# 중앙점 좌표로 -1~1 정규화 및 극좌표 변환
mapx = 2*mapx/(cols - 1) - 1
mapy = 2*mapy/(rows - 1) - 1
r, theta = cv2.cartToPolar(mapx, mapy)
# 방사 왜곡 변형 연산
ru = r*(1+k1*(r**2) + k2*(r**4) + k3*(r**6))
# 직교좌표 및 좌상단 기준으로 복원
mapx, mapy = cv2.polarToCart(ru, theta)
mapx = ((mapx + 1)*cols - 1)/2
mapy = ((mapy + 1)*rows - 1)/2
# 리매핑
distored = cv2.remap(img,mapx,mapy,cv2.INTER_LINEAR)
cv2.imshow('original', img)
cv2.imshow('distored', distored)
cv2.waitKey()
cv2.destroyAllWindows()
opencv에서는 배럴왜곡 연산 공식으로 cv2.undistort() 함수를 제공한다
dst = cv2.undistort(src, cameraMtrix, distCoeffs)
- src : 입력 원본 영상
-
cameraMatrix : 카메라 매트릭스
- distCoeffs : 왜곡 계수, 최소 4개 또는 5, 8, 12, 14개
- (k1, k2, p1, p2[, k3]
다음은 방사 왜곡(radial distortion)현상을 cv2.undistort()함수로 구현한 예제이다.
import cv2
import numpy as np
# 격자 무늬 생성
img = np.full((300,400,3), 255, np.uint8)
img[::10, :, :] = 0
img[:, ::10, :] = 0
width = img.shape[1]
height = img.shape[0]
# 왜곡 계수 설정
#k1, k2, p1, p2 = 0.001, 0, 0, 0 # 배럴 왜곡
k1, k2, p1, p2 = -0.0005, 0, 0, 0 # 핀쿠션 왜곡
distCoeff = np.float64([k1, k2, p1, p2])
# 임의의 값으로 카메라 매트릭스 설정
fx, fy = 10, 10
cx, cy = width/2, height/2
camMtx = np.float32([[fx,0,cx],
[0,fy,cy],
[0,0,1]])
# 왜곡 변형
dst = cv2.undistort(img,camMtx,distCoeff)
cv2.imshow('original',img)
cv2.imshow('dst',dst)
cv2.waitKey()
cv2.destroyAllWindows()