소스 뷰어

어파인 변환의 연결

import numpy as np, math, cv2
def bilinear_value(img, pt):
    x, y = np.int32(pt)
    if y >= img.shape[0]-1: y = y - 1
    if x >= img.shape[1]-1: x = x - 1

    P1, P2, P3, P4 = np.float32(img[y:y+2,x:x+2].flatten())
    alpha, beta = pt[1] - y,  pt[0] - x                   # 거리 비율

    M1 = P1 + alpha * (P3 - P1)                      # 1차 보간
    M2 = P2 + alpha * (P4 - P2)
    P  = M1 + beta  * (M2 - M1)                     # 2차 보간
    return np.clip(P, 0, 255)                       # 화소값 saturation후 반환
def contain(p, shape):                              # 좌표(y,x)가 범위내 인지 검사
    return 0<= p[0] < shape[0] and 0<= p[1] < shape[1]
def affine_transform(img, mat):
    rows, cols = img.shape[:2]
    inv_mat = cv2.invertAffineTransform(mat)  # 어파인 변환의 역행렬
    ## 리스트 생성 방식
    pts = [np.dot(inv_mat, (j, i, 1)) for i in range(rows) for j in range(cols)]
    dst = [bilinear_value(img, p) if contain(p, size) else 0 for p in pts]
    dst = np.reshape(dst, (rows, cols)).astype('uint8')

    return dst
def getAffineMat(center, degree, fx = 1, fy = 1, translate = (0,0)):
    cen_trans = np.eye(3, dtype=np.float32)
    org_trans = np.eye(3, dtype=np.float32)
    scale_mat = np.eye(3, dtype=np.float32)         # 크기 변경 행렬
    trans_mat = np.eye(3, dtype=np.float32)         # 평행 이동 행렬
    rot_mat   = np.eye(3, dtype=np.float32)         # 회전 변환 행렬

    radian = (degree/180.0) * np.pi                 # 회전 각도 - 라디언  계산
    rot_mat[0] = [ np.cos(radian), np.sin(radian), 0]
    rot_mat[1] = [-np.sin(radian), np.cos(radian), 0]

    cen_trans[:2, 2] = center                       # 중심 좌표를 기준으로 회전
    org_trans[:2, 2] = np.multiply(center[0], -1)   # 원점으로 이동
    trans_mat[:2, 2] = translate                    # 평행 이동 행렬의 원소 지정
    scale_mat[0, 0], scale_mat[1, 1] = fx, fy       # 크기 변경 행렬의 원소 지정

    ret_mat = cen_trans.dot(rot_mat.dot(trans_mat.dot(scale_mat.dot(org_trans))))
    # ret_mat = cen_trans.dot(rot_mat.dot(scale_mat.dot(trans_mat.dot(org_trans))))
    return np.delete(ret_mat, 2, axis=0)            # 행 제거 ret_mat[0:2,:]
image = cv2.imread('img/affine.jpg', cv2.IMREAD_GRAYSCALE)
if image is None: raise Exception("영상 파일 읽기 에러")
size = image.shape[::-1]
center = np.divmod(size, 2)[0]                  # 회전 중심 좌표 - 크기는 행,열의 역순
angle, tr = 45.0, (200, 0)                                      # 각도와 평행이동
aff_mat1 = getAffineMat(center, angle)                          # 중심 좌표 기준 회전
aff_mat2 = getAffineMat((0,0), 0, 2.0, 1.5)                     # 크기 변경 - 확대
aff_mat3 = getAffineMat(center, angle, 0.7, 0.7)                # 회전 및 축소
aff_mat4 = getAffineMat(center, angle, 0.7, 0.7, tr)            # 복합 변환
dst1 = cv2.warpAffine(image, aff_mat1, size)             # OpenCV 함수
dst2 = cv2.warpAffine(image, aff_mat2, size)
dst3 = affine_transform(image, aff_mat3)                        # 사용자 정의 함수
dst4 = affine_transform(image, aff_mat4)
cv2.imshow("image", image)
cv2.imshow("dst1_only_rotate", dst1)
cv2.imshow("dst2_only_scaling", dst2)
cv2.imshow("dst3_rotate_scaling", dst3)
cv2.imshow("dst4_rotate_scaling_translate", dst4)
cv2.waitKey(0)
-1

result

  • 실행 결과에서 dst1은 중심점에서 45도의 회전만 수행하고, dst2는 크기변경만 수행한다. 그리고 dst3은 회전, 크기 변경을 적용했으며, dst4는 dst3에 x방향으로 200화소 평행이동을 한다. 여기서 dst4가 dst3에서 x 방향으로 평행이동을 수행하도록 했는데, 결과에서는 대각선 방향으로 평행이동이 된다, 이것은 st4에 회전 변환 후에 평행 이동이 적용되기 대문에 45도 기울어진 방향으로 평행이도 되기 대문이다. 즉 각 변환 행렬을 적용하여 하나의 어파인 행렬로 만들고, 이것을 영상에 적용할 경우에 변환 행렬을 곱하는 순서가 중요함을 보여주는 것이다.