Processing math: 100%

주어진 함수 y(x)를 구간 0x<L에서 일정한 간격으로 샘플링해서 얻은 N개의 data {yn=y(nL/N)|n=0,1,...,N1}의 discrete Fourier transform(DFT) Yk

Yk=1NN1n=0yne2πiNnk,

로 정의된다. 역변환(IDFT)은 Yk에서 yn을 얻는 과정으로 다음 식으로 계산된다:

yn=N1k=0Yke+2πiNkn.

이산적인 data yn만을 가지고 xn=nL/N에서 미분계수를 구하려면 어떻게 해야 할까? 샘플링 점 사이 구간에서 값을 추정할 수 있도록 보간 함수를 만들어 사용해야 한다. 간단하게는 IDFT 식에서 n번째 샘플링 위치인 xn=n(L/N)을 실수 x로 대체하여 연속함수를 얻는 방법을 생각할 수 있다. 그리고 IDFT 식에서 Yke+2πiNkn 대신에 추가적으로 위상 요소를 곱해서 Yke+2πiN(k+mkN)n, mk=integer 로 바꾸더라도 동일한 원 데이터 yn을 얻는다는 사실을 이용하도록 하자. 이 두 가지 관찰을 이용해서 만든 보간 함수는

y(x)=N1k=0Yke+2πiL(k+mkN)x

이다. 추가 위상이 yn은 바꾸지 않지만 어떤 선택을 하는지에 따라 샘플링 사이 구간에서 다른 진동을 보이므로 미분계수가 영향을 받게 된다. 그러면 어떤 정수 mk을 선택해야 하는가? 여러 가지 선택이 있을 수 있지만 전구간에서 도함수의 제곱 평균을 최소로 만드는 mk을 선택하도록 하자: 

argminmk 1LL0|y(x)|2dx

그런데 1LL0|y(x)|2dx=(2πL)2 N1k=0(k+mkN)2|Yk|2 이므로 각 k에 대해서 |k+mkN|2이 최소가 되도록 선택해야 한다. k<N/2일 때는 mk=0을 선택하면 되고, k>N/2일 때는 mk=1을 선택하면 된다. N=even일 때는 mN/2=0 or 1이 같은 결과를 주는데, 두 기여를 균등하게 되도록 선택한다: 

YN/2e2πiL(N/2+mN/2N)x12YN/2(eπiLNx+eπiLNx)=YN/2cos(πLNx) 따라서 mk 선택은 정리하면 다음과 같다:

mk={0,0k<N/21,N/2<k<N

따라서 주어진 샘플링 데이터 {yn}의 DFT 계수 {Yn}을 구하면 샘플링 데이터를 보간하는 함수(Fourier interpolation)는

y(x)=Y0+0<k<N/2(Yke+2πiLkx+YNke2πiLkx)+YN/2cos(πLNx)

로 얻어진다(마지막 항은 N이 짝수일 때만 더해짐). 이 보간함수의 도함수는 

y(x)=0<k<N/22πiLk(Yke+2πiLkxYNke2πiLkx)πLNYN/2sin(πLNx)

로 계산된다(마지막 항은 N이 짝수일 때만 더해짐). 샘플링 위치에서 미분계수는

yn=y(nL/N)=0<k<N/22πiLk(Yke+2πiNknYNke2πiNkn)=N1k=0Yke+2πiNkn

처럼 쓸 수 있는데 이 식은 Yk의 IDFT 형태이다. 

따라서 DFT를 이용해서 미분계수를 구하는 전체적인 알고리즘은

1. DFT로 Yk을 구하고,

2. YkYk={2πiLk×Yk0k<N/22πiL(kN)×Ykk>N/20k=N/2 when N=even

3. Yk에 IDFT를 해서 yn을 구한다.

 

위에서 구한 보간 함수를 이용하면 이차 미분 값도 DFT를 이용해서 구할 수 있다.

 

아래 그림은 6개의 주파수 fk=2,5,9,11,21,29Hz가 섞인 신호 함수의 미분을 FFT를 이용해서 구한 결과를 정확한 도함수(green)와 함께 보여준다.

f(x)=5k=0(k+1)cos(2πfkx)

0x<1=L 구간에서 일정한 간격으로 샘플링한 N=64개의 데이터를 사용하였다. 

void fftgrad1d() {
    const double TWOPI = 8.0 * atan(1.0);
    const int samples = 64;
    double re[samples], im[samples];
    const int nfreqs = 6;
    const double freq[nfreqs] = {2, 5, 9, 11, 21, 29};
    for ( int i = 0; i < samples; i++ ) {
        double ti = double(i) / samples;
        double signal = 0;
        for ( int k = 0; k < nfreqs; k++ )
            signal += (k + 1) * cos ( TWOPI * freq[k] * ti);
        re[i] = signal;
        im[i] = 0.;
    }
    // int fft1d(int dir, int nn, double x[], double y[]);
    fft1d(1, samples, re, im);
    const double norm = TWOPI / 1.0;
    for (int i = 0; i < samples; i++) {
        double rv = re[i];
        double iv = im[i];
        double fac = norm * double (i > samples / 2 ? i - samples: i);  
        if (i == samples / 2) fac = 0;
        re[i] = - fac * iv;
        im[i] = fac * rv;
    }
    //invers FFT: re[] stores derivatives.
    fft1d(-1, samples, re, im);
}

이차원에서 DFT를 이용해서 gradient을 구하는 경우는 복소함수 f=Re(x,y)+iIm(x,y)의 gradient가

(x+iy)(Re+iIm)=i(Rey+Imx)(Rex+Imy)

임을 이용하면 된다.

영상(실수 함수)의 IDFT 식에 적용하면 미분 연산자가 우변에 전체적으로 i를 곱하는 효과를 주게 되고, 우변식의 실수부와 허수가 각각 영상의 x-방향/ y-방향 미분에 해당한다.

spectrum
Canny edge

 

void FFTGrad(CRaster& raster, CRaster& out) {
    const double TWOPI = 8.0 * atan(1.0);
    CSize sz = raster.GetSize();
    int nx = sz.cx, ny = sz.cy;
    int stride = nx << 1;
    std::vector<double> data(stride * sz.cy);
    for (int y = 0; y < ny; y++) {
        BYTE *p = (BYTE *)raster.GetLinePtr(y);
        double *re = &data[y * stride];
        double *im = re + nx;
        for (int x = 0; x < nx; x++) {
            re[x] = *p++;
            im[x] = 0;
        }
    }
    // forward FFT(1): https://kipl.tistory.com/22
    fft2d(&data[0], nx, ny, 1);
    double normx = TWOPI / double(nx);
    double normy = TWOPI / double(ny);
    int hnx = nx >> 1;
    int hny = ny >> 1;
    for (int y = 0; y < ny; y++) {
        double *re = &data[y * stride];
        double *im = re + nx;
        for (int x = 0; x < nx; x++) {
            double rv = re[x];
            double iv = im[x];
            double xx = normx * double(x > hnx ? x - nx: x);
            double yy = normy * double(y > hny ? y - ny: y);
            if (x == hnx) xx = 0;  // N = even;
            if (y == hny) yy = 0;
            re[x] =  yy * rv + xx * iv;
            im[x] = -xx * rv + yy * iv;
        }
    }
    /* inverse FFT of {Y'_k} */
    fft2d(&data[0], nx, ny, -1);
    std::vector<double> mag(nx * ny);
    double magmax = 0, magmin = 1.e+20;
    for (int y = 0, k = 0; y < ny; y++) {
        double *re = &data[y * stride];
        double *im = re + nx;
        for (int x = 0; x < nx; x++) {
            double m = hypot(re[x], im[x]);
            if (m > magmax) magmax = m;
            if (m < magmin) magmin = m;
            mag[k++] = m;
        }
    }
    // stretch the magnitude;
    for (int y = 0, k = 0; y < ny; y++)
        for (int x = 0; x < nx; x++) {
            double a = 255 * (mag[k++] - magmin) / (magmax - magmin);
            out.SetPixel(x, y, 255-int(a));
        }
}
728x90

'Image Recognition > Fundamental' 카테고리의 다른 글

Valley emphasis Otsu threshold  (0) 2022.02.23
Image rotation by FFT  (0) 2022.02.18
SVD Fitting  (0) 2022.02.07
Color Histogram Equalization  (4) 2022.02.07
Least Squares Fitting of Ellipses  (0) 2022.01.27
,