opencv例程解讀——dft(離散傅里葉變換)

廢話不多說,直接上代碼,代碼中我都註明了註釋,有些講不清楚的,會在代碼的後面專門拿出來講。

下面這個cpp文件不是我自己寫的程序,是opencv提供的關於dft變換的例程,文件一般會包含在你的opencv路徑下opencv\sources\samples\cpp下面,可以自行查找。

#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdio.h>

using namespace cv;
using namespace std;

static void help()
{
    printf("\nThis program demonstrated the use of the discrete Fourier transform (dft)\n"
           "The dft of an image is taken and it's power spectrum is displayed.\n"
           "Usage:\n"
            "./dft [image_name -- default lena.jpg]\n");
}

const char* keys =
{
    "{1| |lena.jpg|input image file}"
};

int main(int argc, const char ** argv)
{
    help();
    CommandLineParser parser(argc, argv, keys);
    string filename = parser.get<string>("1");

    Mat img = imread(filename.c_str(), CV_LOAD_IMAGE_GRAYSCALE);
    if( img.empty() )
    {
        help();
        printf("Cannot read image file: %s\n", filename.c_str());
        return -1;
    }
//這裏獲取符合快速傅里葉變換(FFT)的大小,m和n都可以分解爲2、3、5相乘的組合,參見 注1
    int M = getOptimalDFTSize( img.rows );
    int N = getOptimalDFTSize( img.cols );
    Mat padded;
//將原圖像的大小變爲m*n的大小,補充的位置填0,
    copyMakeBorder(img, padded, 0, M - img.rows, 0, N - img.cols, BORDER_CONSTANT, Scalar::all(0));
//這裏是獲取了兩個mat,一個用於存放dft變換的實部,一個用於存放虛部,初始的時候,實部就是圖像本身,虛部全爲0
    Mat planes[] = {Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F)};
    Mat complexImg;
//將幾個單通道的mat融合成一個多通道的mat,這裏融合的complexImg即有實部,又有虛部
    merge(planes, 2, complexImg);
//dft變換,因爲complexImg本身就是兩個通道的mat,所以dft變換的結果也可以保存在其中
    dft(complexImg, complexImg);
    //將complexImg重新拆分成兩個mat,一個是實部,一個是虛部
    split(complexImg, planes);

    // compute log(1 + sqrt(Re(DFT(img))**2 + Im(DFT(img))**2))
    //這一部分是爲了計算dft變換後的幅值,以便於顯示幅值的計算公式如上
    magnitude(planes[0], planes[1], planes[0]);//將兩個mat對應位置相乘
    Mat mag = planes[0];
    mag += Scalar::all(1);
    log(mag, mag);

    // crop the spectrum, if it has an odd number of rows or columns
    //修剪頻譜,如果圖像的行或者列是奇數的話,那其頻譜是不對稱的,因此要修剪
    //這裏爲什麼要用  &-2這個操作,我會在代碼後面的 注2 說明
    mag = mag(Rect(0, 0, mag.cols & -2, mag.rows & -2));
    Mat _magI = magI.clone();
    normalize(_magI, _magI, 0, 1, CV_MINMAX);
    imshow("before rearrange ",_magI)

    int cx = mag.cols/2;
    int cy = mag.rows/2;
    // rearrange the quadrants of Fourier image
    // so that the origin is at the image center
    Mat tmp;
    Mat q0(mag, Rect(0, 0, cx, cy));
    Mat q1(mag, Rect(cx, 0, cx, cy));
    Mat q2(mag, Rect(0, cy, cx, cy));
    Mat q3(mag, Rect(cx, cy, cx, cy));

    q0.copyTo(tmp);
    q3.copyTo(q0);
    tmp.copyTo(q3);

    q1.copyTo(tmp);
    q2.copyTo(q1);
    tmp.copyTo(q2);

    normalize(mag, mag, 0, 1, CV_MINMAX);

    imshow("spectrum magnitude", mag);
    waitKey();
    return 0;
}

注1:

opencv官方文檔是這樣說的:

 the function supports arrays of arbitrary size. But only those arrays are 
prime numbers (2, 3, and 5 in the current implementation). Such an efficient 
DFT size can be calculated using the getOptimalDFTSize() method.

就是說dft這個函數雖然對於輸入mat的尺寸不做要求,但是如果其行數和列數可以分解爲2、3、5的乘積,那麼對於dft運算的速度會加快很多。
關於getOptimalDFTSize()這個函數:

DFT performance is not a monotonic function of a vector size. Therefore, 
when you calculate convolution of two arrays or perform the spectral 
analysis of an array, it usually makes sense to pad the input data with 
zeros to get a bit larger array that can be transformed much faster than the 
original one. Arrays whose size is a power-of-two (2, 4, 8, 16, 32, ...) are 
the fastest to process. Though, the arrays whose size is a product of 2’s, 
3’s, and 5’s (for example, 300= 5*5*3*2*2) are also processed quite 
efficiently.

The function getOptimalDFTSize returns the minimum number N that is greater 
than or equal to vecsize so that the
DFT of a vector of size N can be processed efficiently. In the current 
implementation N = 2 p * 3 q * 5 r for some integerp, q, r.

上面這一段話是說,數組的大小是2的整數次冪的情況,dft變換的速度是最快的。當然,大部分情況下,數組的大小不會是2的次冪,但是如果其大小可以分解爲2、3、5的累成也是能夠提高dft的效率的。

DFT算法的原理對於輸入信號的長度不做要求,但是爲了可以使用快速傅里葉變換算法(FFT算法)進行加速。所以程序中使用copyMakeBorder填充0使橫縱長度變爲可以由2、3、5累乘的數,這裏行和列的長度不一定是樣的,只要都滿足條件即可。

注2:

我們知道x&-2代表x與-2按位相與,而-2的二進制形式是2的二進制取反加一的結果(這是補碼的問題)。2 的二進制結果是(假設用8位表示,實際整型是32位,但是描述方式是一樣的,爲便於描述,用8位表示)0000 0010,則-2的二進制形式爲:1111 1110,在x與-2按位相與後,不管x是奇數還是偶數,最後x都會變成一個偶數。

注3:

我相信很多人,包括我自己都會很疑惑,爲什麼要對傅里葉變換的結果進行座標轉換,才能看到那種中間亮,周圍暗的頻率圖。我也研究了很久,後來發現,這是因爲對於正常的傅里葉變換,其變換的結果都是在0~2*pi之間的,如果將其轉換爲-pi~pi之間,實質上是沒有變化的,但是爲了方便人觀察,纔將結果轉換到-pi~pi中。實際上如果拿四張一模一樣的沒有經過座標變換的圖拼在一起,會發現拼接處的圖形模樣與座標變換後是一樣的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章