OpenCV 【十七】离散傅立叶变换
目錄
1 key
2 原理
3 實(shí)例
3代碼
4運(yùn)行結(jié)果
5應(yīng)用舉例
1 key
-
什么是傅立葉變換及其應(yīng)用?
-
如何使用OpenCV提供的傅立葉變換?
-
相關(guān)函數(shù)的使用,如: copyMakeBorder(), merge(), dft(), getOptimalDFTSize(), log() 和 normalize() .
簡(jiǎn)單點(diǎn)說(shuō)就是:所有的波都可以用很多個(gè)正弦波疊加表示。
然而這些波又可以通過(guò)頻率、幅值和相位來(lái)表示。這樣你就可以從左邊那張圖中時(shí)域的視角轉(zhuǎn)化為咱們高大上的頻域視角啦。
圖引用!!!
2 原理
對(duì)一張圖像使用傅立葉變換就是將它分解成正弦和余弦兩部分。也就是將圖像從空間域(spatial domain)轉(zhuǎn)換到頻域(frequency domain)。
這一轉(zhuǎn)換的理論基礎(chǔ)來(lái)自于以下事實(shí):任一函數(shù)都可以表示成無(wú)數(shù)個(gè)正弦和余弦函數(shù)的和的形式。傅立葉變換就是一個(gè)用來(lái)將函數(shù)分解的工具。 2維圖像的傅立葉變換可以用以下數(shù)學(xué)公式表達(dá):
式中 f 是空間域(spatial domain)值, F 則是頻域(frequency domain)值。 轉(zhuǎn)換之后的頻域值是復(fù)數(shù), 因此,顯示傅立葉變換之后的結(jié)果需要使用實(shí)數(shù)圖像(real image) 加虛數(shù)圖像(complex image), 或者幅度圖像(magitude image)加相位圖像(phase image)。 在實(shí)際的圖像處理過(guò)程中,僅僅使用了幅度圖像,因?yàn)榉葓D像包含了原圖像的幾乎所有我們需要的幾何信息。 然而,如果你想通過(guò)修改幅度圖像或者相位圖像的方法來(lái)間接修改原空間圖像,你需要使用逆傅立葉變換得到修改后的空間圖像,這樣你就必須同時(shí)保留幅度圖像和相位圖像了。
3 實(shí)例
在此展示如何計(jì)算以及顯示傅立葉變換后的幅度圖像。由于數(shù)字圖像的離散性,像素值的取值范圍也是有限的。比如在一張灰度圖像中,像素灰度值一般在0到255之間。 因此,我們這里討論的也僅僅是離散傅立葉變換(DFT)。 如果你需要得到圖像中的幾何結(jié)構(gòu)信息,那你就要用到它了。請(qǐng)參考以下步驟(假設(shè)輸入圖像為單通道的灰度圖像 I):
-
將圖像延擴(kuò)到最佳尺寸. 離散傅立葉變換的運(yùn)行速度與圖片的尺寸息息相關(guān)。當(dāng)圖像的尺寸是2, 3,5的整數(shù)倍時(shí),計(jì)算速度最快。 因此,為了達(dá)到快速計(jì)算的目的,經(jīng)常通過(guò)添湊新的邊緣像素的方法獲取最佳圖像尺寸。函數(shù) getOptimalDFTSize() 返回最佳尺寸,而函數(shù) copyMakeBorder() 填充邊緣像素 ,添加的像素初始化為0.
Mat padded; ? ? ? ? ? ? ? ? ? ? ? ? //將輸入圖像延擴(kuò)到最佳的尺寸 int m = getOptimalDFTSize( I.rows ); int n = getOptimalDFTSize( I.cols ); // 在邊緣添加0 copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0)); -
為傅立葉變換的結(jié)果(實(shí)部和虛部)分配存儲(chǔ)空間. 傅立葉變換的結(jié)果是復(fù)數(shù),這就是說(shuō)對(duì)于每個(gè)原圖像值,結(jié)果是兩個(gè)圖像值。 此外,頻域值范圍遠(yuǎn)遠(yuǎn)超過(guò)空間值范圍, 因此至少要將頻域儲(chǔ)存在 float 格式中。 結(jié)果我們將輸入圖像轉(zhuǎn)換成浮點(diǎn)類(lèi)型,并多加一個(gè)額外通道來(lái)儲(chǔ)存復(fù)數(shù)部分:
Mat planes[] = {Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F)}; Mat complexI; merge(planes, 2, complexI); ? ? ? ? // 為延擴(kuò)后的圖像增添一個(gè)初始化為0的通道 -
進(jìn)行離散傅立葉變換. 支持圖像原地計(jì)算 (輸入輸出為同一圖像):
dft(complexI, complexI); ? ? ? ? ? // 變換結(jié)果很好的保存在原始矩陣中 -
將復(fù)數(shù)轉(zhuǎn)換為幅度*.復(fù)數(shù)包含實(shí)數(shù)部分(Re*)和復(fù)數(shù)部分 (imaginary - Im)。 離散傅立葉變換的結(jié)果是復(fù)數(shù),對(duì)應(yīng)的幅度可以表示為:
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]; ? 計(jì)算二維矢量的幅值:magnitude()函數(shù) 該函數(shù)用來(lái)計(jì)算二維矢量的幅值 void magnitude(InputArray x,InputArray y,OutputArray magnitude) 第一個(gè)參數(shù):InputArray類(lèi)型的x,表示矢量的浮點(diǎn)型X坐標(biāo)值,也就是實(shí)部 第二個(gè)參數(shù):InputArray類(lèi)型的y,表示矢量的浮點(diǎn)型Y坐標(biāo)值,也就是虛部 第三個(gè)參數(shù):OutputArray類(lèi)型的magnitude,輸出的幅值,它和第一個(gè)參數(shù)X有著同樣的尺寸和類(lèi)型 -
對(duì)數(shù)尺度(logarithmic scale)縮放. 傅立葉變換的幅度值范圍大到不適合在屏幕上顯示。高值在屏幕上顯示為白點(diǎn),而低值為黑點(diǎn),高低值的變化無(wú)法有效分辨。為了在屏幕上凸顯出高低變化的連續(xù)性,我們可以用對(duì)數(shù)尺度來(lái)替換線性尺度:
轉(zhuǎn)化為OpenCV代碼:
magI += Scalar::all(1); ? ? ? ? ? ? ? ? ? // 轉(zhuǎn)換到對(duì)數(shù)尺度 log(magI, magI); -
剪切和重分布幅度圖象限. 還記得我們?cè)诘谝徊綍r(shí)延擴(kuò)了圖像嗎? 那現(xiàn)在是時(shí)候?qū)⑿绿砑拥南袼靥蕹?。為了方便顯示,我們也可以重新分布幅度圖象限位置(注:將第五步得到的幅度圖從中間劃開(kāi)得到四張1/4子圖像,將每張子圖像看成幅度圖的一個(gè)象限,重新分布即將四個(gè)角點(diǎn)重疊到圖片中心)。 這樣的話原點(diǎn)(0,0)就位移到圖像中心。
magI = magI(Rect(0, 0, magI.cols & -2, magI.rows & -2)); int cx = magI.cols/2; int cy = magI.rows/2; ? Mat q0(magI, Rect(0, 0, cx, cy)); ? // Top-Left - 為每一個(gè)象限創(chuàng)建ROI 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; ? ? ? ? ? ? ? ? ? ? ? ? ? // 交換象限 (Top-Left with Bottom-Right) q0.copyTo(tmp); q3.copyTo(q0); tmp.copyTo(q3); ? q1.copyTo(tmp); ? ? ? ? ? ? ? ? ? // 交換象限 (Top-Right with Bottom-Left) q2.copyTo(q1); tmp.copyTo(q2);
-
歸一化. 這一步的目的仍然是為了顯示。 現(xiàn)在我們有了重分布后的幅度圖,但是幅度值仍然超過(guò)可顯示范圍[0,1] 。我們使用 normalize() 函數(shù)將幅度歸一化到可顯示范圍。normalize(magI, magI, 0, 1, CV_MINMAX); // 將float類(lèi)型的矩陣轉(zhuǎn)換到可顯示圖像范圍// (float [0, 1]).
3代碼
?
#include "opencv2/core.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
?
using namespace cv;
using namespace std;
?
?
?
int main(int argc, char ** argv)
{Mat I = imread("C:\\Users\\guoqi\\Desktop\\\ch7\\4.jpg", IMREAD_GRAYSCALE);if (I.empty()) {cout << "Error opening image" << endl;return EXIT_FAILURE;}
?//! [expand]Mat padded; ? ? ? ? ? ? ? ? ? ? ? ? ? //expand input image to optimal sizeint m = getOptimalDFTSize(I.rows);int n = getOptimalDFTSize(I.cols); // on the border add zero valuescopyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0));//! [expand]
?//! [complex_and_real]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//! [complex_and_real]
?//! [dft]dft(complexI, complexI); ? ? ? ? ? // this way the result may fit in the source matrix//! [dft]
?// compute the magnitude and switch to logarithmic scale// => log(1 + sqrt(Re(DFT(I))^2 + Im(DFT(I))^2))//! [magnitude]split(complexI, planes); ? ? ? ? ? ? ? ? ? // planes[0] = Re(DFT(I), planes[1] = Im(DFT(I))magnitude(planes[0], planes[1], planes[0]);// planes[0] = magnitudeMat magI = planes[0];//! [magnitude]
?//! [log]magI += Scalar::all(1); ? ? ? ? ? ? ? ? ? // switch to logarithmic scalelog(magI, magI);//! [log]
?//! [crop_rearrange]// crop the spectrum, if it has an odd number of rows or columnsmagI = 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//重新排列傅里葉圖像的象限,使原點(diǎn)位于圖像中心int cx = magI.cols / 2;int cy = magI.rows / 2;
?Mat q0(magI, Rect(0, 0, cx, cy)); ? // Top-Left - Create a ROI per quadrantMat q1(magI, Rect(cx, 0, cx, cy)); // Top-RightMat q2(magI, Rect(0, cy, cx, cy)); // Bottom-LeftMat 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);//! [crop_rearrange]
?//! [normalize]normalize(magI, magI, 0, 1, NORM_MINMAX); // Transform the matrix with float values into a// viewable image form (float between values 0 and 1).//! [normalize]
?imshow("Input Image", I); ? // Show the resultimshow("spectrum magnitude", magI);waitKey();
?return EXIT_SUCCESS;
}
?
4運(yùn)行結(jié)果
?
5應(yīng)用
離散傅立葉變換的一個(gè)應(yīng)用是決定圖片中物體的幾何方向.比如,在文字識(shí)別中首先要搞清楚文字是不是水平排列的? 看一些文字,你就會(huì)注意到文本行一般是水平的而字母則有些垂直分布。文本段的這兩個(gè)主要方向也是可以從傅立葉變換之后的圖像看出來(lái)。我們使用這個(gè) 水平文本圖像 以及 旋轉(zhuǎn)文本圖像 來(lái)展示離散傅立葉變換的結(jié)果 。
水平文本圖像:
水平文本圖像對(duì)應(yīng)的DFT變換:
旋轉(zhuǎn)文本圖像:
旋轉(zhuǎn)文本圖像對(duì)應(yīng)的DFT變換:
觀察這兩張幅度圖你會(huì)發(fā)現(xiàn)頻域的主要內(nèi)容(幅度圖中的亮點(diǎn))是和空間圖像中物體的幾何方向相關(guān)的。 通過(guò)這點(diǎn)我們可以計(jì)算旋轉(zhuǎn)角度并修正偏差。
?
6 應(yīng)用拓展機(jī)器含義
這就得出了一個(gè)結(jié)論:傅里葉變換后的白色部分(即幅度較大的低頻部分),表示的是圖像中慢變化的特性,或者說(shuō)是灰度變化緩慢的特性(低頻部分)。
傅里葉變換后的黑色部分(即幅度低的高頻部分),表示圖像中快變化的特性,或者說(shuō)是灰度變化快的特性(高頻部分)。
參考
?
?
?
?
?
總結(jié)
以上是生活随笔為你收集整理的OpenCV 【十七】离散傅立叶变换的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 神迹六龙
- 下一篇: 黄山风景区汤口南大门到屯溪老街怎么去