咱們要尋找如下問題的答案:ios
一、什麼是傅立葉變換,爲何咱們要用這個?算法
二、在OpenCV中如何作到?函數
三、例如copyMakeBorder(),merge(),dft(),getOptimalDFGSize(),log()以及normalize()函數的用法。性能
你能夠從這裏下載或者從samples/cpp/tutorial_code/core/discrete_fourier_transform/discrete找到代碼。ui
#include "opencv2/core/core.hpp"this
#include "opencv2/imgproc/imgproc.hpp"spa
#include "opencv2/highgui/highgui.hpp"code
#include <iostream>orm
using namespace cv;對象
using namespace std;
static void help(char* progName)
{
cout << endl
<< "This program demonstrated the use of the discrete Fourier transform (DFT). " << endl
<< "The dft of an image is taken and it's power spectrum is displayed." << endl
<< "Usage:" << endl
<< progName << " [image_name -- default lena.jpg] " << endl << endl;
}
int main(int argc, char ** argv)
{
help(argv[0]);
const char* filename = argc >=2 ? argv[1] : "lena.jpg";
Mat I = imread(filename, CV_LOAD_IMAGE_GRAYSCALE);
if( I.empty())
return -1;
Mat padded; //expand input image to optimal size
int m = getOptimalDFTSize( I.rows );
int n = getOptimalDFTSize( I.cols ); // on the border add zero values
copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0));
Mat planes[] = {Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F)};
Mat complexI;
merge(planes, 2, complexI); // Add to the expanded another plane with zeros
dft(complexI, complexI); // this way the result may fit in the source matrix
// compute the magnitude and switch to logarithmic scale
// => log(1 + sqrt(Re(DFT(I))^2 + Im(DFT(I))^2))
split(complexI, planes); // planes[0] = Re(DFT(I), planes[1] = Im(DFT(I))
magnitude(planes[0], planes[1], planes[0]);// planes[0] = magnitude
Mat magI = planes[0];
magI += Scalar::all(1); // switch to logarithmic scale
log(magI, magI);
// crop the spectrum, if it has an odd number of rows or columns
magI = magI(Rect(0, 0, magI.cols & -2, magI.rows & -2));
// rearrange the quadrants of Fourier image so that the origin is at the image center
int cx = magI.cols/2;
int cy = magI.rows/2;
Mat q0(magI, Rect(0, 0, cx, cy)); // Top-Left - Create a ROI per quadrant
Mat q1(magI, Rect(cx, 0, cx, cy)); // Top-Right
Mat q2(magI, Rect(0, cy, cx, cy)); // Bottom-Left
Mat q3(magI, Rect(cx, cy, cx, cy)); // Bottom-Right
Mat tmp; // swap quadrants (Top-Left with Bottom-Right)
q0.copyTo(tmp);
q3.copyTo(q0);
tmp.copyTo(q3);
q1.copyTo(tmp); // swap quadrant (Top-Right with Bottom-Left)
q2.copyTo(q1);
tmp.copyTo(q2);
normalize(magI, magI, 0, 1, CV_MINMAX); // Transform the matrix with float values into a
// viewable image form (float between values 0 and 1).
imshow("Input Image" , I ); // Show the result
imshow("spectrum magnitude", magI);
waitKey();
return 0;
}
傅里葉變換將圖像拆分爲組成它的正弦和餘弦部分。換句話說,他將一幅圖像從它的空間與轉換爲他的頻度域。這個思想來源於任何函數能夠無限接近於正弦和餘弦函數之和。傅立葉變換就是這樣的一個方法。二維圖像在數學上的傅立葉變換就是:
f是圖像在空間域的值,F是頻率域的值。變換的結果是一個複數。顯示這些可能要經過real格式的圖像和一個複數圖像或者是經過幅值圖像以及相位圖像。然而,整個圖像處理算法只有在幅值圖像中才有意思,由於這個圖像包含全部的咱們須要的圖像幾何結構的信息。
然而,若是你想要在圖像中作一些相似於這種形式的修改而且你須要從新轉換它,你就須要保留他們兩個。
在這個示例中,我會給你展現如何計算和顯示傅里葉變換產生的幅值圖像。數字圖像自己就是離散的。這就意味着他們可能從給定的域值中取值。例如在基本的灰度圖像中,數值通常在0-255之間。所以傅里葉變換一樣須要最終轉換爲離散的形式,也就是離散的傅里葉變換(DFT)。當你在須要從一個圖像的幾何點中決定圖像的類型時,你會用到它。下面就是步驟(輸入圖像I是灰度的狀況):
一、拉伸圖像到好的(optimal)尺寸。DFT的性能是依據圖像的尺寸。經過將圖像的尺寸乘以數字2,3以及5來達到最快的效果。所以,爲了達到最大性能,將邊緣值墊襯到這樣的一個數值也不是爲一種好的辦法。getOptimalDFTSize()函數返回最有尺寸而且咱們能夠而使用copyMakeBorder()函數來拉伸一個圖像的邊緣:
Mat padded;
int m=getOptimalDFRSize(I.rows);
Int n=getOptimalDFTSize(I.cols);
copyMakeBorder(I,Padded,0,m-I.rows,0,n-I.cols,BORDER_CONSTANT,Scalar::all(0));
附加的像素的數值被初始化爲0。
二、爲複數和真值騰出空間。傅立葉變換的結果是複數。這就代表對於每個ie圖像的結果的數值也就是兩個圖像的值(一個組成部分就是一個)。此外頻率域的範圍遠遠大於對應的空間(域)。所以咱們常常最起碼使用float類型來存儲它們。所以,咱們將咱們的輸入圖像轉換爲這個類型的而且使用另外的通道來存儲複數。
Mat planes[]={Mat_<float>(padded),Mat::zeros(padded.size(),CV_32F};
Mat complexI;
Merge(planes,2,complesI);
三、進行離散傅立葉變換。原地(in-place 輸入和輸出是同樣的)執行計算是可能的:
dft(complexI,complexI);
四、將真值和複數部分轉換爲幅值圖像。一個複數由真值(Re)和複數(imaginary-Im)部分組成。DFT的結果是複數。幅值圖像的DFT:
使用OpenCV代碼表示:
split(complexI,planes);
magnitude(planes[0],planes[1],planes[0]);
Mat magI=planes[0];
五、選擇一個對數範圍。傅里葉變換得出來的動態範圍的係數大到不可以在屏幕中顯示。咱們沒法在這樣的數值中觀察到小的以及一些高的變化值。所以高的數值將會被轉化爲白點,低的數值就是黑點。爲了使用灰度數值來可視化,咱們能夠說將線性範圍轉換爲對數範圍:
轉變爲OpenCV代碼:
magI+=Scalar::all(1);
log(magI,magI);
六、修剪和重排列。還記得咱們在第一步重的圖像延伸嗎?如今就到了要把那些新引入的數值扔掉的時候了。爲了可視化的目的,咱們能夠重排列結果的象限,所以origin(zero,zero)最應於圖像的中心點。
magI=magI(Rect(0,0,magI.cols&-2,magI.rows&02));
int cx=magI.cols/2;
Int cy=magI.rows/2;
Mat q0(magI,Rect(0,0,cx,cy));
Mat q1(magI,Rect(cx,0,cx,cy));
Mat q2(magI,Rect(0,cy,cx,cy));
Mat q3(magI,Rect(cx,cy,cx,cy));
Mat tmp;
q0.copyTo(tmp);
q3.copyTo(q0);
tmp.copyTo(q3);
q1.copyTo(tmp);
q2.copyTo(q1);
tmp.copyTo(q2);
七、正常化。這也是爲了可視化的目的。咱們如今擁有了幅值圖像,然而這個仍是在咱們的0-1的顯示範圍以外。咱們使用normalize()函數將咱們的數值正常化到這個範圍中。
normalize(magI,magI,0,1,CV_MINMAX);
一個應用的觀點多是決定圖像中幾何的存在的方位。例如,讓咱們發現文字是否水平。看一些文字,你會發現文字行安排爲水平,書信形式的被安排爲豎直行。這兩個主要的文字片斷組成可能一樣被看做傅立葉轉換。讓咱們作一下水平的和有文字的圖形被旋轉。
在水平文字狀況:
旋轉文字狀況:
你能夠看到頻率域的最有影響的組成部分(在幅值圖像重的最亮的點)在圖像中跟隨對象進行幾何旋轉。從這裏咱們就能夠計算出偏移量以及一張圖像旋轉去更正最後的缺失的偏移量。