더북(TheBook)

9.3.2 임의의 각도 회전 구현

영상의 회전 변환에서는 삼각함수의 계산이 필요하다. C/C++에서는 sin, cos, tan 등의 삼각함수를 제공하며, 이들 함수를 사용하려면 먼저 <math.h> 파일을 포함(#include)해야 한다. C/C++에서 제공하는 sin, cos 함수의 원형은 다음과 같다.

double sin(double x);
double cos(double x);

이들 함수에서 인자로 받는 회전 각도 x는 라디안radian 단위를 사용하기 때문에 우리가 흔히 사용하는 형태의 각도 단위(°)를 사용하려면 다음과 같은 공식으로 변환해야 한다.

const double PI = 3.14159265358979323846;
double rad = (angle * PI) / 180.;

여기서 const double형으로 선언된 변수 PI는 원주율 π를 의미한다. 이를 이용하여 회전 각도 angle로 입력된 값은 (angle * PI) / 180 공식에 의해 라디안 값 rad로 변환된다.

그러면 영상의 회전 변환을 수행하는 함수를 작성해보자. 함수의 이름은 IppRotate를 사용하기로 하고, 다음과 같은 함수 선언을 IppGeometry.h 파일에 추가하자.

void IppRotate(IppByteImage& imgSrc, IppByteImage& imgDst, double angle);

IppRotate 함수는 입력 영상 imgSrcangle 각도만큼 시계 방향으로 회전한 후, 결과 영상을 imgDst에 저장한다. 이 함수의 앞부분에서는 회전 변환되어 생성될 결과 영상의 크기를 계산하고, 뒷부분에서는 실제 회전 변환을 수행한다. 회전 변환 시 보간법은 양선형 보간법을 사용하였다. IppRotate 함수의 전체 내용은 소스 9-9에 나타내었다.

소스 9-9 영상의 회전 변환 구현 함수(IppGeometry.cpp)
#include <math.h>
const double PI = 3.14159265358979323846;
…

void IppRotate(IppByteImage& imgSrc, IppByteImage& imgDst, double angle)
{
    int w = imgSrc.GetWidth();
    int h = imgSrc.GetHeight();

    double rad = (angle * PI) / 180.;
    double cos_value = cos(rad);
    double sin_value = sin(rad);

    // 회전 후 생성되는 영상의 크기를 계산.
    // 4개의 코너 포인트의 이동 좌표를 계산하여 최대, 최소점의 차이를 구한다.

    int nx, ny, minx, miny, maxx, maxy, nw, nh;

    minx = maxx = 0;
    miny = maxy = 0;

    nx = static_cast<int>(floor((w - 1)* cos_value + 0.5));
    ny = static_cast<int>(floor((w - 1) * sin_value + 0.5));
    minx = (minx < nx) ? minx : nx; maxx = (maxx > nx) ? maxx : nx;
    miny = (miny < ny) ? miny : ny; maxy = (maxy > ny) ? maxy : ny;

    nx = static_cast<int>(floor(-(h - 1) * sin_value + 0.5));
    ny = static_cast<int>(floor((h - 1) * cos_value + 0.5));
    minx = (minx < nx) ? minx : nx; maxx = (maxx > nx) ? maxx : nx;
    miny = (miny < ny) ? miny : ny; maxy = (maxy > ny) ? maxy : ny;

    nx = static_cast<int>(floor((w - 1) * cos_value - (h - 1) * sin_value + 0.5));
    ny = static_cast<int>(floor((w - 1) * sin_value + (h - 1) * cos_value + 0.5));
    minx = (minx < nx) ? minx : nx; maxx = (maxx > nx) ? maxx : nx;
    miny = (miny < ny) ? miny : ny; maxy = (maxy > ny) ? maxy : ny;

    nw = maxx - minx + 1;
    nh = maxy - miny + 1;

    // 실제 회전 변환

    imgDst.CreateImage(nw, nh);

    BYTE** pSrc = imgSrc.GetPixels2D();
    BYTE** pDst = imgDst.GetPixels2D();

    int i, j, x1, x2, y1, y2;
    double rx, ry, p, q, temp;

    for (j = miny; j <= maxy; j++)
    for (i = minx; i <= maxx; i++)
    {
        rx = i * cos_value + j * sin_value;
        ry = -i * sin_value + j * cos_value;

        x1 = static_cast<int>(rx);
        y1 = static_cast<int>(ry);

        // 원본 영상 내에 포함된 좌표가 아니라면 무시.
        if (x1 < 0 || x1 > w - 1 || y1 < 0 || y1 > h - 1)
            continue;

        x2 = x1 + 1; if (x2 == w) x2 = w - 1;
        y2 = y1 + 1; if (y2 == h) y2 = h - 1;

        p = rx - x1;
        q = ry - y1;

        temp = (1.0 - p) * (1.0 - q) * pSrc[y1][x1]
            + p * (1.0 - q) * pSrc[y1][x2]
            + (1.0 - p) * q * pSrc[y2][x1]
            + p * q * pSrc[y2][x2];

        pDst[j - miny][i - minx] = static_cast<BYTE>(limit(temp));
    }
}
신간 소식 구독하기
뉴스레터에 가입하시고 이메일로 신간 소식을 받아 보세요.