這次介紹下opencv中DFT的使用,對應的例程是(EXAMPLE) dft。在圖像處理領域,通過DFT可以將圖像轉換到頻域,實現高通和低通濾波;還可以利用矩陣的卷積運算等同於其在頻域的乘法運算從而優化算法降低運算量, 即先將圖像轉換到頻域,然後做完乘法運算後,再轉換到圖像域,opencv中的模板匹配就利用了這一特性降低運算量。
下面是dft例程的源碼
#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); // opencv中用來處理命令行參數的類
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;
}
int M = getOptimalDFTSize( img.rows ); // 獲得最佳DFT尺寸,爲2的次方
int N = getOptimalDFTSize( img.cols ); //同上
Mat padded;
copyMakeBorder(img, padded, 0, M - img.rows, 0, N - img.cols, BORDER_CONSTANT, Scalar::all(0)); // opencv中的邊界擴展函數,提供多種方式擴展
Mat planes[] = {Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F)}; // Mat 數組,第一個爲擴展後的圖像,一個爲空圖像,
Mat complexImg;
merge(planes, 2, complexImg); // 合併成一個Mat
dft(complexImg, complexImg); // FFT變換, dft需要一個2通道的Mat
// compute log(1 + sqrt(Re(DFT(img))**2 + Im(DFT(img))**2))
split(complexImg, planes); //分離通道, planes[0] 爲實數部分,planes[1]爲虛數部分
magnitude(planes[0], planes[1], planes[0]); // 求模
Mat mag = planes[0];
mag += Scalar::all(1);
log(mag, mag); // 模的對數
// crop the spectrum, if it has an odd number of rows or columns
mag = mag(Rect(0, 0, mag.cols & -2, mag.rows & -2)); //保證偶數的邊長
int cx = mag.cols/2;
int cy = mag.rows/2;
// rearrange the quadrants of Fourier image //對傅立葉變換的圖像進行重排,4個區塊,從左到右,從上到下 分別爲q0, q1, q2, q3
// so that the origin is at the image center // 對調q0和q3, q1和q2
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); // 規範化值到 0~1 顯示圖片的需要
imshow("spectrum magnitude", mag);
waitKey();
return 0;
}
下圖爲運行結果,例程只介紹了將圖像轉換到頻域,那如何將其逆變換轉換成圖像呢?opencv中提供逆變換的函數爲idft,下面看下dft,idft這兩個函數的原型
//! performs forward or inverse 1D or 2D Discrete Fourier Transformation
CV_EXPORTS_W void dft(InputArray src, OutputArray dst, int flags=0, int nonzeroRows=0);
//! performs inverse 1D or 2D Discrete Fourier Transformation
CV_EXPORTS_W void idft(InputArray src, OutputArray dst, int flags=0, int nonzeroRows=0);
從描述可以看出,dft,idft分別用於實現1維,2維的傅立葉變換和傅立葉逆變換。此時你或許可能認爲opencv爲dft,idft分別寫了兩個函數實現,但如果查看idft的源碼,你會發現idft的實現也是在dft函數中,opencv中代碼的重用率還是挺高的。
void cv::idft( InputArray src, OutputArray dst, int flags, int nonzero_rows )
{
dft( src, dst, flags | DFT_INVERSE, nonzero_rows );
}
通過一個flags來控制是進行dft變換還是idft變換,這種用法在opencv中十分的普遍。我們可以在程序中添加一些代碼
Mat ifft;
idft(complexImg,ifft,DFT_REAL_OUTPUT);
normalize(ifft,ifft,0,1,CV_MINMAX);
imshow("idft",ifft);
實現DFT的逆變換
結果如下圖所示
可以看出逆變換後的圖像內容與原圖是一樣的,因爲我們進行任何處理,但是idf的大小比原圖要大一些,且有些黑色邊界,這是由於dft需要保證邊長長度爲2的次方,在這種情況下,計算速度可以加速,因此在之前的處理過程中,將原圖的大小調整到2的次方,黑色邊界便是copyMakeBoder的0值填充所形成的,這個函數在插值,濾波,掩模操作中十分常用,被用來擴展邊界以是圖像邊界上的像素也能得以處理,只不過在那些操作中,處理完的圖片與原圖一樣大小。
傅立葉變換還有一個特性經常被利用到,就是它的相位,用來確定圖像的選擇角度,例如tutorial文檔中就提到一個應用,用來確定文字的旋轉角度,我們可以通過將一張圖像旋轉一定的角度來做一個實驗,下面是結果。
可以看出沿着中心的兩個軸隨着圖像的旋轉而旋轉,因此通過確定軸的旋轉角度,便可以將圖像位置調整過來,相應的,處理的如果是文字或者是車牌,也可以通過類似的處理進行對齊和角度調整。
對頻域圖像閾值處理後,可以看得更加清楚。