본문 바로가기

학습컨텐츠/이미지프로세싱

기하학적 처리 - (1) 확대


  이미지를 확대하는 가장 간단한 방법은 확대하고자 하는 배율만큼 픽셀을 복제하는 것이다. 예를 들어 두 배씩 확대하는 경우에는 아래처럼 픽셀을 각 방향으로 두 배씩 복사하면 된다.

사용자 삽입 이미지

  이 방법은 그래픽 편집 프로그램에서 픽셀 단위로 세밀한 작업을 할 때 유용하게 사용된다. Windows의 그림판에서 돋보기 메뉴를 이용하여 그림을 확대했을 때 이와 같은 방식으로 그림이 확대되어 보이게 된다.


(그림 28) 픽셀 복제 방법으로 5배 확대한 고양이 사진


  정수가 아닌 배율에 대해서도 이미지를 확대할 수 있게 하기 위해, 역방향 사상의 개념이 사용된다. 역방향 사상은 결과 이미지의 특정 좌표가 입력 이미지의 어떤 좌표를 바탕으로 해야하는지를 역으로 생각하는 방법이다. 영상을 가로 세로 방향으로 5배씩 확대하는 경우 결과 이미지 좌표에 대한 입력 이미지 좌표는 아래와 같이 쓸 수 있다.



  이 식에 따르면, 5배 확대한 이미지의 (10,10)좌표에 있는 픽셀은 입력 이미지의 (2,2)좌표에서 구해야 하는 것을 알 수 있다. 이것까지는 문제가 없지만, (7,7)과 같은 좌표에 넣을 픽셀을 구하려면 문제가 생긴다. 입력 이미지의 (1.4 , 1.4) 좌표는 존재하지 않기 때문이다. 이를 극복하는 가장 간단한 방법은 좌표의 소수 부분을 무시하는 것이다. 정수 배율로 확대했을 때, 이 방법은 픽셀 복제와 같아진다. 그림판에서 원하는 영역을 선택하고 테두리를 드래그로 확대해서 키우면 이같은 방식으로 확대가 이루어진다. 이 방법을 nearest neighbor method라고 한다.

  하지만 이것만으로는 부족하다. 이런 식으로 이미지를 확대하면 픽셀 한 개가 이루는 사각 영역이 너무 커져서 눈에 보이게 되는 계단 현상이 문제가 된다. 이것을 보완하기 위한 방법으로 양선형 보간법(bilinear interpolation)이 있다. 이 방법은 다음과 같이 실수 좌표의 픽셀값을 구한다.


- ( 0.5 , 0.5 )좌표에서의 픽셀값을 구하려고 한다.
- 우선 ( 0 , 0.5 )에서의 픽셀값을 구한다. ( 0 , 0 )과 ( 0 , 1 )에서의 픽셀값의 평균으로 잡자
- 같은 방식으로 ( 1 , 0.5 )에서의 평균값을 구한다.
- ( 0.5 , 0.5 )의 픽셀값은 이제 ( 0 , 0.5 )와 ( 1 , 0.5 )에서 구한 좌표값의 평균으로 하자.


  이런 식으로 하면 2배 확대하는 데에는 문제없다. 좀더 일반적으로는 가중평균을 이용해야 하는데, 그림으로 나타내면 아래와 같다. 소수부분이 α와 β인 X 좌표에서의 픽셀값을 구한다고 하고, 인접한 픽셀들을 A, B, C, D라고 생각하자. 다음과 같이 계산하면 X의 보간된 좌표를 구할 수 있다.


(그림 29) 양선형 보간법의 식


  수식에서 볼 수 있듯이, 더 가까운 픽셀에 더 큰 가중치(Weight)를 둠으로써 부드러운 이미지를 생성할 수 있게 되었다. 식이 와닿지 않는 사람은 (그림 30)을 보며 이렇게 생각해 보자. A, B, C, D에서 각각의 픽셀값만큼의 높이를 가지는 막대그래프를 그리고(파란색 막대), A와 B, C와 D를 선으로 연결하고, E와 F의 픽셀값은 E와 F의 위치에서 그 선분의 높이라고 하자(초록색 막대). 이제 E와 F를 선으로 연결하고 X의 픽셀값은 X의 위치에서 그 선분의 높이라고 하면 된다(빨간 막대).

  이같이 두 개의 방향으로(bi-) 선을 그어서(-linear) 적절한 픽셀값을 구하는 방법이기 때문에 이것을 양선형 보간법(bilinear interpolation)이라고 부른다.

(그림 30) 막대그래프로 이해한 양선형 보간법


  양선형 보간법을 파이썬으로 구현하여 보자. 다음 소스에서 함수 Magnify는 첫 번째 인수로 주어진 이미지를 두 번째 인수로 주어진 배율만큼 확대한 새 이미지를 반환해주는 함수이다. 이 함수는 image의 실수좌표 coord에서의 픽셀값을 계산하는 biliear()함수를 이용한다.


import Image;

def bilinear(image, coord):
    "image의 실수좌표 coord에서의 픽셀값을 구한다."
    pixel=image.load()

    if(coord[0]<0) : coord=(0,coord[1])
    if(coord[0]>image.size[0]-2) : coord=(image.size[0]-2,coord[1])
    if(coord[1]<0) : coord=(coord[0],0)
    if(coord[1]>image.size[1]-2) : coord=(coord[0],image.size[1]-2)
    #coord가 범위를 벗어나면 범위 안쪽으로 들어오게 한다

    if(coord[0]==int(coord[0]) and coord[1]==int(coord[1])) : return pixel[coord]
    #coord가 정수좌표이면 그 위치를 그대로 반환한다

    left=int(coord[0])
    right=int(coord[0])+1
    top=int(coord[1])
    bottom=int(coord[1])+1
    # 왼쪽, 오른쪽의 x좌표, 위, 아래의 y좌표

    A=pixel[left,top]
    B=pixel[right,top]
    C=pixel[left,bottom]
    D=pixel[right,bottom]
    # A, B, C, D의 픽셀을 구한다 ]

    a=coord[0]-int(coord[0])
    b=coord[1]-int(coord[1])
    # 그림 29에서의 알파와 베타를 구한다

    E=( A[0]+a*(B[0]-A[0]) , A[1]+a*(B[1]-A[1]) , A[2]+a*(B[2]-A[2]) )
    F=( C[0]+a*(D[0]-C[0]) , C[1]+a*(D[1]-C[1]) , C[2]+a*(D[2]-C[2]) )
    X=( E[0]+b*(F[0]-E[0]) , E[1]+b*(F[1]-E[1]) , E[2]+b*(F[2]-E[2]) )
    # 그림 29의 방법대로 E, F, X의 픽셀값을 구한다.

    return X

def Magnify(image, magnitude):
    newsize=( int(image.size[0]*magnitude) , int(image.size[1]*magnitude) )
    newimg=Image.new("RGB",newsize)
    newpixel=newimg.load()
    for i in range(newsize[0]):
        for j in range(newsize[1]):
            coord=( i/magnitude , j/magnitude )
            newpixel[i,j]=bilinear(image, coord)
        print i
    return newimg

  이제 다음 소스를 이용해서 2.5배 확대된 고양이 사진을 만들어 보자. 시간이 엄청나게 오래걸릴 것이다. 덜 지루하게 하기 위해 처리하고 있는 가로좌표를 출력하도록 했다(print i).


im=Image.open("d:\\cat.bmp")
im2=Magnify(im,2.5)
im2.save("d:\\bigcat.bmp")





[목차] 이미지프로세싱 - 시작
[이전] 기하학적 처리 - intro
[다음] 기하학적 처리 - (2) 축소