메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

에릭하게만 시리즈 3 - 변환 행렬

한빛미디어

|

2002-05-15

|

by HANBIT

14,973

저자: 스티브 피긴스(Stephen Figgins), 역 전순재

이번 기사에는 NumPy와 DISLIN을 사용하여 기본적인 기하학적 이미지를 만들고, 표시하며, 조작해 보겠다. 이미지 처리의 핵심에는 특수한 행렬이 사용되어 다양한 효과들을 만들어 낼 수 있으며 이러한 효과들을 개별적으로 사용하거나 또는 결합하면 더욱 복잡한 처리도 할 수 있다. 본 기사의 초점은 이차원 처리에 맞추겠지만, 그 개념은 삼차원 이상으로 확장된다. 행렬 처리는 컴퓨터 게임과 컴퓨터-보조 디자인(CAD) 프로그램을 비롯하여 많은 애플리케이션에 기본이 된다. 행렬 처리는 선형 대수학 그 자체의 핵심이다.

도대체 벡터란 무엇인가?

선형 대수학(Linear algebra)은 숫자들이 모여 일정 모양을 이룬 집단(shaped collections of numbers)에 관한 수학이다. 우리가 다루는 숫자와 가장 익숙한 개념의 수학에서는 스칼라(scalars)라고 부르는 숫자들을 다룬다. 실생활에 많이 사용되는 스칼라 값으로는 예금 계좌에 들어 있는 잔고 ($10.08), 자동차를 모는 속도(시속 65 마일), 또는 팝콘을 전자레인지에서 튀겨내는데 걸리는 시간(3 분 40 초)등이 있는데 모두 스칼라로 표현할 수 있다. 우리는 이러한 각각의 값들을 일정량의 크기에 비례하여 관심을 두고 구분한다. 가끔은 값들을 조합하여 더욱 복잡한 정보를 표현하기도 한다. 예를 들어 (자동차에서처럼) 한 객체의 속력(speed)과 속도(velocity)사이에는 약간 차이가 있다. 속력(speed)은 단순한 크기(magnitude, 얼마나 빨리 움직이는가)이고 속도(velocity)는 속력에 방향이라는 개념도 함축하여 생각해야 한다. 속력은 음수일 수 없지만 속도는 음수일 수 있다. 예를 들어 자동차가 거꾸로 움직인다고 생각해보자. 속력만큼 움직이고는 있지만, 방향은 "거꾸로(backwards)"이며 이때의 속도는 마이너스 값을 갖게 되는 것이다. 따라서 속도는 음의 값을 얻을 수 있다. 속력(Speed)은 스칼라 값이지만, 속도(velocity)는 방향과 크기 모두를 함축하는 숫자 두 개를 조합한 벡터로 표현된다. 자동차가 평평한 도로를 달린다고 생각할 때 숫자 두 개만 있으면 움직이는 방향도 표현할 수 있다.

세계를 평면으로 간주하면, 그 평면에 참조 방향을 두 개 놓을 수 있다. 아래 보이는 x와 y로 라벨을 단 화살표는 벡터 에 대하여 참조점을 제공한다.

벡터 는 x 방향으로 4단위 y 방향으로 3단위로 정의된다(이 경우에 단위(units)는 달린 마일 수). 따라서 어떻게 자동차의 움직임으로부터 벡터 에 도달하는가? 자, 우리는 그 자동차의 속력(velocity)이 시간당 마일이라고 할 수 있다. 이것은 곧 지난 한 시간 동안에 자동차가 x 방향으로 4마일 움직이고 y 방향으로 3마일 움직였다는 것을 뜻한다. 그 자동차의 실제 움직임은 벡터 길이(length)이며 다음을 통하여 계산된다:

이것이 바로 어느 곳에서나 튀어 나오는 그 유명한 피타고라스의 정리이다! 예제를 계속하여 살펴보면, 자동차는 한시간 동안에 총 5마일을 움직였다. 즉 시간당 5마일을 갔다는 계산이다.

만약 차원을 더 높여 움직일 수 있는 한 객체의 움직임을 기술하고 싶다면(비행기를 예로 들면 "위로" 그리고 "아래"로도 움직일 수 있으므로), 우리는 더 많은 원소를 가진 벡터를 사용해야 한다(비행기에 대해서는 3개가 사용된다).

벡터는 많은 것들을 표현하는데 사용될 수 있다. 자동차의 속도는 그저 실례 하나에 불과할 뿐이다. 벡터의 개념은 추상적이다. 벡터를 다루기 위해 벡터가 무엇인지 꼭 알아야 할 필요는 없지만 어떤 때는 그 개념을 알아 두면 도움이 되는 일도 많다. 벡터를 조작하기 위한 수학은 구체적인 문제의 구현과는 별개로 간주될 수 있다.

속도(velocity)를 그래프로 표현하는 대신, 우리는 벡터를 사용하여 기본적인 이미지를 그릴 것이다. 그리고 나서 이 이미지를 특수 행렬과 행렬 수학을 통하여 조작하는 방법을 중점적으로 살펴볼 생각이다.

벡터 수학

벡터 수학이 어떻게 작동하는지 알아 보기 위해 벡터에 관한 기본 연산부터 검토해 보기로 한다. 두 벡터의 덧셈은 그래프로 보여줄 수 있으며 덧셈은 예상대로 작동한다. 두 벡터 를 더하면 이 된다. 아래 그림은 이것을 그래프로 나타낸 것이다.

첫 번째 벡터가 원점 (0,0)에서 시작했다는 것을 주목하기 바란다. 두 번째 벡터는 이제 자신의 꼬리(화살표 머리가 없는 끝쪽)를 첫 번째 벡터의 머리에 위치시킨다. 그 두 벡터를 조합하면 원점에서 시작하는 벡터와 두 번째 벡터의 머리를 연결한 것과 똑같다. 뺄셈도 비슷한 방식으로 계산하지만 두 번째 벡터의 방향을 반대로 해주어야 한다.

이와 같이 덧셈을 그래프로 표현하면 벡터들의 집단(각 벡터는 줄 한 개를 표현함)을 사용하여 점들을 연결한 벡터 버전으로 기본적인 이미지를 만들 수 있다. 지금 방금 살펴본 예제는 정말 간단하지만, 벡터를 여러 개 조합하면 복잡한 그림들도 만들어 낼 수 있다.

예제 하나를 들어 무엇을 만들 수 있는지 시험해보자. 다음은 완전한 파이썬 프로그램으로서 수치처리 모듈(NumPy)과 pxDislin모듈 모두를 사용한다. 지난 번 기사에서 다루지 않은 것이 있다면 dMultiLine 함수 사용뿐으로 이 함수는 x_list인수와 y_list인수 사이의 점들을 연결한 줄(vector) 하나를 그린다. 본질적으로 dMultiLine 함수는 벡터 덧셈 연산을 수행한다. 이 함수는 실제로 수학이라고 하기는 그렇지만 그 연산을 그래픽적으로 표현해 줄 수 있다. 선택된 점들은 도표의 중심축을 기준으로 간단한 상자 구조의 집을 하나 그려낸다.
from pxDislin import *
from Numeric import *

plot = dPlot()
axis = dAxis(-20,20,-20,20)
plot.add_axis(axis)

house_pts = array([[5,5,-5,-5,5,4,4,2,2,5,5,0,-5],
                      [-5,5,5,-5,-5,-5,-1,-1,-5,-5,5,8,5]])

house     = dMultiLine(house_pts[0],house_pts[1])
plot.add_object(house)

plot.show()
만들어진 도표는 다음과 같다:

집을 그려낸 점들은 본질적으로 2ⅹ13 행렬이다.

x 좌표가 있는 곳은 최상위 행이고 y 좌표들은 최하위 행에 있다. 행렬의 각 열들은 2ⅹ1 벡터로 취할 수 있으며, 그렇게 하여 그 행렬은 13개의 열 벡터로 이루어진 집단이 된다.

행렬 곱셈

행렬 곱셈(Matrix multiplication)은 이미지 처리의 기본으로 짝짓기 함수(mapping function)이다. 참조가 되는 하나의 프레임에서 다른 프레임으로 백터를 "짝짓기 해줄 수 있다". 이 시리즈 기사의 첫번재 기사(에릭 하게만 시리즈1 - 수치처리 파이썬의 기초)를 다시 살펴보면 두 행렬의 곱셈은 다음과 같았음을 알 수 있다.

이 공식을 사용하여 2ⅹ2 행렬과 2ⅹ1 벡터를 곱하면 어떻게 되는지 살펴보자. 다음은 이러한 행렬들에 있는 값들을 어떻게 참조할 것인지 보여준다.

결과는 행렬 A에 의해서 변형된 2ⅹ1 벡터이다. 위에 기술한 수학을 아래에 확대하여 본다. 각 항들 사이의 상호작용을 주목하라. 결과로 나온 각 값은 입력으로 들어온 여러 값들을 조합한 것이다.

행렬 A는 변환 행렬(transformation matrix)이다. 행렬 A의 값들이 벡터 b에 매핑 되면 벡터 b를 변환한다. 기본적으로 변형 행렬을 올바르게 선택해야만 우리가 만든 2ⅹ13 집 행렬에 대하여 올바른 효과를 줄 수 있다.

13개로 이루어진 각 모둠에서 2ⅹ1 벡터를 각각 개별적으로 조작할 수도 있겠지만 그렇게 하는 것이 지루하다. 그렇지만 선형 대수학(Linear algebra) 덕분에 우리는 그 권태에서 벗어 날 수 있다. 우리가 이전에 살펴본 행렬 곱셈 문제는 더 큰 행렬로 쉽게 확장될 수 있다. 2ⅹ2 행렬에 2ⅹ1 벡터가 곱해지면 결과는 2ⅹ1 벡터가 된다. 그러나 2ⅹ2 행렬과 2ⅹ13 행렬을 곱하면 또다른 2ⅹ13 행렬이 된다.

본질적으로 원래 행렬의 각 열은 행렬 곱셈에 의해 변환되어 결과 행렬에서 그에 상응하는 열이 된다. 행렬 수학을 사용하면 단 한번에 그 변환을 처리 할 수 있다!

이미지 조작

도표를 만들기 전에 변형 행렬 하나를 샘플 코드에 추가해보자. 간단히 하기 위해 당분간 항등행렬(identity matrix)을 사용하겠다. 항등행렬을 사용한다는 것은 곧 모든 것에다 1을 곱하는 것과 동등하다(일종의 "아무 것도 하지 않는" 연산). 그렇지만 항등행렬이 제공하는 방법을 사용해서도 "수학이 응용되는 방법"을 충분히 확인 할 수 있다. 게다가 항등행렬은 대부분의 변형행렬의 기초이다.
from pxDislin import *
from Numeric import *

plot = dPlot()
trans = array([[1,0],
                  [0,1]])

axis = dAxis(-20,20,-20,20)
plot.add_axis(axis)

house_pts_orig = array([[5,5,-5,-5,5,4,4,2,2,5,5,0,-5],
                  [-5,5,5,-5,-5,-5,-1,-1,-5,-5,5,8,5]])

house_pts = matrixmultiply(trans,house_pts_orig)

house     = dMultiLine(house_pts[0],house_pts[1])
plot.add_object(house)

plot.show()
여기에서 만들어진 도표는 위에 보여준 첫번째 도표와 똑같다.

변형

우리는 여기에서 네 가지 변형을 살펴볼 것이다. 이러한 간단한 연산으로부터 더욱 복잡한 기능들을 구성할 수 있다.

회전(Rotation)

회전 행렬(rotation matrix)을 사용하면 이미지를 원점 주위로 시계방향 또는 시계반대방향으로 회전시킬 수 있다. 이 행렬은 삼각함수 사인(sine)과 코사인(cosine)을 사용한다. 삼각 함수는 인수로 회전 각도를 취한다(양각은 시계반대방향으로 회전시킬 것이고, 음각의 값은 시계방향으로 회전시킬 것임). 각은 보통 눈금 두 개로 측정한다. 사람들은 대부분 도(degrees)라는 단위를 사용한다. 완전한 원은 360도이다. 그렇지만 컴퓨터는 각도를 호도(radians) 단위로 잰다. 컴퓨터는 원이 호도를 가지는 것으로 간주한다. 우리는 각도에 파이(pi)를 곱하고 180으로 나누어서 각도를 호도로 변환할 것이다. 이렇게 하면 우리가 사용할 행렬에 정확한 값들을 얻을 수 있다. 회전 행렬 그 자체는 다음과 같이 정의된다.

여기에서 는 우리가 원하는 회전 각도를 표시한다.

예제 코드에서 그 항등행렬을 다음 코드로 대치한 다음 그 코드를 실행하고 나면, 아까 보았던 집은 이제 30도 만큼 시계반대방향으로 회전되어 있음을 볼 수 있다.
angle = 30 * pi/180
trans     = array([[cos(angle), -sin(angle)],
                   [sin(angle), cos(angle)]])

회전 행렬(rotation matrix)을 사용하면, 어떤 점이라도 중심 점(0,0) 주위로 쉽게 회전시킬 수 있다.

반사(Reflection)

다음으로 변환 행렬의 목록에는 반사 행렬(reflection matrix)이 있다. 반사 행렬은 이미지를 뒤집거나 또는 중심축을 가로질러 반사시키는 것이다. 다음 반사행렬 예제는 모든 y 값들을 부인(negated)하게 만든다 그리하여 이미지를 x 축을 가로질러 뒤집는 결과를 도출해 낸다.

회전 행렬을 반사 행렬로 전환하면 다음 그림과 같이 된다.

x 좌표들만을 부인하는 반사 행렬을 생각해 보자. x와 y 좌표를 동시에 부인하는 행렬은 존재할 수 있는가?

신축(Scale)

위에서 언급했던 것 이외의 간단한 변형행렬으로 신축행렬이 있다. 항등행렬(identity matrix)이 변경되어 대각선상에 하나 이상의 값들이 있을 수 있다. 그리하여 그 항등행렬은 신축행렬(scale matrix)이 되어 그림의 크기만을 변경한다. 이 행렬을 사용하면 크기를 크게(a>1) 또는 작게(a<1) 할 수 있다.

기울임(Shear)

우리가 살펴볼 마지막 행렬은 "기울임(shear)" 행렬이다. 이 행렬은 특정한 차원에 기울이기 동작 효과를 낼 수 있다.

k=2일 때의 기울임 행렬에 대한 결과는 아래와 같다.

변형행렬들을 결합하기

변형행렬을 하나 이상 적용하면 여러 동작들을 유도해 낼 수 있다. 게다가 개별적인 변형행렬들이 행렬 곱셈을 통하여 결합되면 복잡한 동작들도 형성할 수 있다. 다음은 기울임 행렬(shear matrix)에 반사 행렬(reflection matrix)을 결합한 결과이다.

여러분 스스로도 여러 조합을 시도해 보기 바란다. 그리고 무엇을 만들어 낼 수 있는지 살펴보자.

평행이동

현재 우리가 만든 도표에서 집은 도표의 원점(0,0)에 중앙정렬되어 있다. 벡터 연산을 사용하면 그 집을 중앙에서 평행이동(translation)을 통하여 다른 곳으로 이동시킬 수 있다. 평행이동은 단지 행렬에서 각 열에 거리 벡터(offset vector)를 더하기만 하면된다. 다음 벡터 를 더하면 아래 그림과 같이 집이 이동시킬 수 있다.

평행이동(translation)과 변형(transformation)의 조합을 시도해 보자. 평행 이동된 이미지를 회전하면 무슨 일이 일어나는가?

게임 끝

바라건데 이 기사가 선형 대수학에 관하여 더 공부하고 싶은 욕구와 NumPy를 사용해보고자 하는 욕구를 돋구는데 도움이 되었기를 바란다. 여기에서 보여주지는 않았지만 이러한 테크닉들을 더 높은 차원으로 확대하면(보통은 3차원이 가장 보편적이다) 더욱 흥미로운 애플리케이션을 만들 수 있다. 3차원에서는 모양을 가진 객체를 모델링할 수 있으며 회전과 반사를 여러축 주위를 따라 만들기 때문에 객체의 복잡한 움직임도 만들어 낼 수 있다. 그러한 움직임은 그래픽 그리기, 모델링 패키지, 그리고 3D 게임에 근본적인 요소이다. 선형 대수학이라는 수학이 추상적일 수도 있겠지만 이것은 애플리케이션에 보편적으로 사용된다. NumPy는 이러한 움직임들을 명료하게 구현하는데 도움을 주며, 이 덕분에 훨씬 더 쉽게 이 주제를 이해할 수 있다. 더 복잡한 예제를 살펴보고 싶다면 선형 대수학과 관련된 기초 교과서를 참조하거나 그래픽 또는 이지미 처리에 관한 책을 참고하기 바란다.

다음 기사 예고

다음 기사에서는 기본적인 NumPy 함수들을 뛰어 넘어서 표준 패키지에 추가로 제공되는 모듈들을 살펴 보겠다. 이러한 모듈들을 사용하면 광범위한 애플리케이션에 대하여 더욱 복잡한 프로그램을 작성할 수 있다.
에릭 하게만(Eric Hagemann)은 임베드에서 메인프레임까지 모든 종류의 컴퓨터에서 숫자를 빨리 처리하기 위한 알고리즘에 정통한 전문가이다.
TAG :
댓글 입력
자료실

최근 본 상품0