OpenCV 【十七】离散傅立叶变换
目錄
1 key
2 原理
3 實例
3代碼
4運行結果
5應用舉例
1 key
-
什么是傅立葉變換及其應用?
-
如何使用OpenCV提供的傅立葉變換?
-
相關函數的使用,如: copyMakeBorder(), merge(), dft(), getOptimalDFTSize(), log() 和 normalize() .
簡單點說就是:所有的波都可以用很多個正弦波疊加表示。
然而這些波又可以通過頻率、幅值和相位來表示。這樣你就可以從左邊那張圖中時域的視角轉化為咱們高大上的頻域視角啦。
圖引用!!!
2 原理
對一張圖像使用傅立葉變換就是將它分解成正弦和余弦兩部分。也就是將圖像從空間域(spatial domain)轉換到頻域(frequency domain)。
這一轉換的理論基礎來自于以下事實:任一函數都可以表示成無數個正弦和余弦函數的和的形式。傅立葉變換就是一個用來將函數分解的工具。 2維圖像的傅立葉變換可以用以下數學公式表達:
式中 f 是空間域(spatial domain)值, F 則是頻域(frequency domain)值。 轉換之后的頻域值是復數, 因此,顯示傅立葉變換之后的結果需要使用實數圖像(real image) 加虛數圖像(complex image), 或者幅度圖像(magitude image)加相位圖像(phase image)。 在實際的圖像處理過程中,僅僅使用了幅度圖像,因為幅度圖像包含了原圖像的幾乎所有我們需要的幾何信息。 然而,如果你想通過修改幅度圖像或者相位圖像的方法來間接修改原空間圖像,你需要使用逆傅立葉變換得到修改后的空間圖像,這樣你就必須同時保留幅度圖像和相位圖像了。
3 實例
在此展示如何計算以及顯示傅立葉變換后的幅度圖像。由于數字圖像的離散性,像素值的取值范圍也是有限的。比如在一張灰度圖像中,像素灰度值一般在0到255之間。 因此,我們這里討論的也僅僅是離散傅立葉變換(DFT)。 如果你需要得到圖像中的幾何結構信息,那你就要用到它了。請參考以下步驟(假設輸入圖像為單通道的灰度圖像 I):
-
將圖像延擴到最佳尺寸. 離散傅立葉變換的運行速度與圖片的尺寸息息相關。當圖像的尺寸是2, 3,5的整數倍時,計算速度最快。 因此,為了達到快速計算的目的,經常通過添湊新的邊緣像素的方法獲取最佳圖像尺寸。函數 getOptimalDFTSize() 返回最佳尺寸,而函數 copyMakeBorder() 填充邊緣像素 ,添加的像素初始化為0.
Mat padded; ? ? ? ? ? ? ? ? ? ? ? ? //將輸入圖像延擴到最佳的尺寸 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)); -
為傅立葉變換的結果(實部和虛部)分配存儲空間. 傅立葉變換的結果是復數,這就是說對于每個原圖像值,結果是兩個圖像值。 此外,頻域值范圍遠遠超過空間值范圍, 因此至少要將頻域儲存在 float 格式中。 結果我們將輸入圖像轉換成浮點類型,并多加一個額外通道來儲存復數部分:
Mat planes[] = {Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F)}; Mat complexI; merge(planes, 2, complexI); ? ? ? ? // 為延擴后的圖像增添一個初始化為0的通道 -
進行離散傅立葉變換. 支持圖像原地計算 (輸入輸出為同一圖像):
dft(complexI, complexI); ? ? ? ? ? // 變換結果很好的保存在原始矩陣中 -
將復數轉換為幅度*.復數包含實數部分(Re*)和復數部分 (imaginary - Im)。 離散傅立葉變換的結果是復數,對應的幅度可以表示為:
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]; ? 計算二維矢量的幅值:magnitude()函數 該函數用來計算二維矢量的幅值 void magnitude(InputArray x,InputArray y,OutputArray magnitude) 第一個參數:InputArray類型的x,表示矢量的浮點型X坐標值,也就是實部 第二個參數:InputArray類型的y,表示矢量的浮點型Y坐標值,也就是虛部 第三個參數:OutputArray類型的magnitude,輸出的幅值,它和第一個參數X有著同樣的尺寸和類型 -
對數尺度(logarithmic scale)縮放. 傅立葉變換的幅度值范圍大到不適合在屏幕上顯示。高值在屏幕上顯示為白點,而低值為黑點,高低值的變化無法有效分辨。為了在屏幕上凸顯出高低變化的連續性,我們可以用對數尺度來替換線性尺度:
轉化為OpenCV代碼:
magI += Scalar::all(1); ? ? ? ? ? ? ? ? ? // 轉換到對數尺度 log(magI, magI); -
剪切和重分布幅度圖象限. 還記得我們在第一步時延擴了圖像嗎? 那現在是時候將新添加的像素剔除了。為了方便顯示,我們也可以重新分布幅度圖象限位置(注:將第五步得到的幅度圖從中間劃開得到四張1/4子圖像,將每張子圖像看成幅度圖的一個象限,重新分布即將四個角點重疊到圖片中心)。 這樣的話原點(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 - 為每一個象限創建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);
-
歸一化. 這一步的目的仍然是為了顯示。 現在我們有了重分布后的幅度圖,但是幅度值仍然超過可顯示范圍[0,1] 。我們使用 normalize() 函數將幅度歸一化到可顯示范圍。normalize(magI, magI, 0, 1, CV_MINMAX); // 將float類型的矩陣轉換到可顯示圖像范圍// (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//重新排列傅里葉圖像的象限,使原點位于圖像中心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運行結果
?
5應用
離散傅立葉變換的一個應用是決定圖片中物體的幾何方向.比如,在文字識別中首先要搞清楚文字是不是水平排列的? 看一些文字,你就會注意到文本行一般是水平的而字母則有些垂直分布。文本段的這兩個主要方向也是可以從傅立葉變換之后的圖像看出來。我們使用這個 水平文本圖像 以及 旋轉文本圖像 來展示離散傅立葉變換的結果 。
水平文本圖像:
水平文本圖像對應的DFT變換:
旋轉文本圖像:
旋轉文本圖像對應的DFT變換:
觀察這兩張幅度圖你會發現頻域的主要內容(幅度圖中的亮點)是和空間圖像中物體的幾何方向相關的。 通過這點我們可以計算旋轉角度并修正偏差。
?
6 應用拓展機器含義
這就得出了一個結論:傅里葉變換后的白色部分(即幅度較大的低頻部分),表示的是圖像中慢變化的特性,或者說是灰度變化緩慢的特性(低頻部分)。
傅里葉變換后的黑色部分(即幅度低的高頻部分),表示圖像中快變化的特性,或者說是灰度變化快的特性(高頻部分)。
參考
?
?
?
?
?
總結
以上是生活随笔為你收集整理的OpenCV 【十七】离散傅立叶变换的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 神迹六龙
- 下一篇: OpenCV 【十八】图像平滑处理/腐蚀