《OpenCV3编程入门》学习笔记5 Core组件进阶(五)离散傅里叶变换(DFT)
第5章 Core組件進階
5.5 離散傅里葉變換(Discrete Fourier Transform,DFT)
5.5.1 離散傅里葉變換原理
1.對一張圖像使用傅里葉變換就是把它分解成正弦和余弦,將圖像從空間域(spatial domain)轉換到頻域(frequency domain)
2.理論基礎:任一函數都可以表示成無數個正弦和余弦函數的和的形式
3.二維圖像的傅里葉變換數學公式表達:
??????????????,??
??f是空間域值(spatial domain),F是頻域值(frequency domain),轉換之后的頻域值是復數,因此,顯示傅里葉變換之后的結果需要使用實數圖像(real image)加虛數圖像(complex image),或幅度圖像(magitude image)加相位圖像(phase image)。
4.頻域里面,高頻部分代表圖像的細節、紋理信息;低頻部分代表圖像的輪廓結構,如果對一幅精細的圖像使用低通濾波器,那么濾波后的結果會只剩下輪廓,如果圖像受到的噪聲正好位于某個特定的“頻率”范圍內,則可以通過濾波器來恢復原來的圖像。
5.傅里葉變換在圖像處理中可以做到圖像增強與圖像去噪、圖像分割之邊緣檢測、圖像特征提取、圖像壓縮等。
5.5.2 dft()函數
1.作用:對一維或二維浮點數組進行正向或反向離散傅里葉變換
2.原型:
void dft(InputArray src,OutputArray dst,int flags=0,int nonzeroRows=0)
3.參數說明:
(1)輸入矩陣
(2)運算結果
(3)轉換標識符(默認0)
????????????
(4)參數設為非零時,函數會假設只有輸入矩陣的第一個非零行包含非零元素(沒有設置DFT_INVERSE標識符)或只有輸出矩陣的第一個非零行包含非零元素(設置了DFT_INVERSE標識符),如此函數可以對其他行進行更高效的處理,以節省時間開銷,尤其在采用DFT計算矩陣卷積時非常有效。
4.示例:用dft函數計算兩個二維實矩陣卷積
void consolveDFT(InputArray A, InputArray B, OutputArray C)
{//【1】初始化輸出矩陣C.create(abs(A.rows - B.rows) + 1, abs(A.cols - B.cols) + 1, A.type());Size dftSize;//【2】計算DFT變換尺寸dftSize.width = getOptimalDFTSize(A.cols + B.cols - 1);dftSize.height = getOptimalDFTSize(A.rows + B.rows - 1);//【3】分配臨時緩沖區并初始化置零Mat tempA(dftSize, A.type(), Scalar::all(0));Mat tempB(dftSize, B.type(), Scalar::all(0));//【4】分別復制A和B到tempA和tempB的左上角Mat roiA(tempA, Rect(0, 0, A.cols, A.rows));A.copyTo(roiA);Mat roiB(tempB, Rect(0, 0, B.cols, B.rows));B.copyTo(roiB);//【5】就地操作(in_place),進行快速傅里葉變換,并將nonzeroRows參數置為非零,以進行更快速的處理dft(tempA, tempA, 0, A.rows);dft(tempB, tempB, 0, B.rows);//【6】將得到的頻譜相乘,結果存放于tempA中mulSpectrums(tempA, tempB, tempA);//計算兩個傅里葉頻譜的每個元素的乘法//【7】將結果變換為頻域,且盡管結果行(result rows)都為非零,我們只需要其中的C.rows的第一行,所以采用nonzeroRows==C.rowsdft(tempA, tempA, DFT_INVERSE + DFT_SCALE, C.rows);//【8】將結果復制到C中tempA(Rect(0, 0, C.cols, C.rows)).copyTo(C);//所有的臨時緩沖區將被自動釋放,所以無須收尾操作
}
5.5.3 返回DFT最優尺寸大小:getOptimalDFTSize()函數
??返回給定向量尺寸的傅里葉最優尺寸大小,為了提高離散傅里葉變換的運行速度,需要擴充圖像,具體擴充多少,由此函數計算得到:
?????????????????int getOptimalDFTSize(int vecsize)
5.5.4 擴充圖像邊界:copyMakeBorder()函數
1.原型:
void copyMakeBorder(InputArray src,OutputArray dst,int top,int bottom,int left,int right,int borderType,const Scalar&value=Scalar())
2.參數說明:
(1)源圖像
(2)運算結果,需和源圖片有一樣的尺寸和類型,Size(src.cols+left+right,src.rows+top+bottom)
(3)top,bottom,left,right,分別表示在源圖像的四個方向上擴充多少像素
(4)邊界類型,常見取值為BORDER_CONSTANT
(5)默認值Scalar(),可理解為默認值為0,borderType取值為BORDER_CONSTANT,這個參數表示邊界值
5.5.5 計算二維矢量的幅值:magnitude()函數
1.原型:
void magnitude(InputArray x,InputArray y,OutputArray magnitude)
2.參數說明:
(1)表示矢量的浮點型X坐標值(實部)
(2)表示矢量的浮點型Y坐標值(虛部)
(3)輸出的幅值,它和第一個參數x有著同樣的尺寸和類型
3.原理:
????????????????
5.5.6 計算自然對數:log()函數
1.作用:計算每個數組元素絕對值的自然對數
2.原型:
void log(InputArray src,OutputArray dst)
3.參數說明:輸入圖像,得到的對數值
4.原理:
????????????????
5.5.7 矩陣歸一化:normalize()函數
1.原型:
void normalize(InputArray src,OutputArray dst,double alpha=1,double beta=0,int norm_type=NORM_L2,int dtype=-1,InputArray mask=noArray() )
2.參數說明:
(1)輸入圖像
(2)運算結果,和源圖片有一樣的尺寸和類型
(3)歸一化后的最大值,默認值1
(4)歸一化后的最大值,默認值0
(5)歸一化類型,有NORM_INF、NORM_L1、NORM_L2(默認)和NORM_MINMAX等參數可選
(6)默認值-1,當此參數取負值時,輸出矩陣和src有同樣的類型,否則它和src有同樣的通道數,且此時圖像深度為CV_MAT_DEPTH(dtype)
(7)可選的操作掩膜,默認值noArray()
5.5.8 示例程序:離散傅里葉變換
#include<opencv2/opencv.hpp>
#include<opencv2/core/core.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<iostream>
using namespace cv;
using namespace std;int main()
{//[1]以灰度模式讀取原始圖像并顯示Mat srcImage = imread("love.jpg", 0);if (!srcImage.data) { printf("讀取圖片錯誤,請確定目錄下是否有imread函數指定圖片存在~!\n"); return false; }imshow("原始圖像", srcImage);//[2]將輸入圖像延擴到最佳尺寸,邊界用0擴充(擴大圖像尺寸可以提高計算速度)int m = getOptimalDFTSize(srcImage.rows);int n = getOptimalDFTSize(srcImage.cols);//將添加的像素初始化為0Mat padded;copyMakeBorder(srcImage, padded, 0, m - srcImage.rows, 0, n - srcImage.cols, BORDER_CONSTANT, Scalar::all(0));//[3]為傅里葉變換的結果(實部和虛部)分配存儲空間//傅里葉變換結果是復數,即每個原圖像值結果會有兩個圖像值,且頻域值范圍至少存儲在float格式中,所以將輸入圖像轉換成浮點類型,并多加一個額外通道存儲復數部分//將planes數組組合合并成一個多通道的數組complexIMat planes[] = { Mat_<float>(padded),Mat::zeros(padded.size(),CV_32F) };Mat complexI;merge(planes, 2, complexI);//[4]進行就地離散傅里葉變換dft(complexI, complexI);//[5]將復數轉換為幅值,即=>log(1+sqrt(Re(DFT(I)^2+IM(DFT(I))^2))split(complexI, planes); //將多通道數組complexI分離成幾個單通道數組,planes[0]=Re(DFT(I),planes[1]=Im(DFT(I))magnitude(planes[0], planes[1], planes[0]);//planes[0]=magnitudeMat magnitudeImage = planes[0];//[6]進行對數尺寸(logarithmic scale)縮放//傅里葉變換的幅度值范圍大到不合適再屏幕顯示,高值在屏幕上顯示為白點,低值為黑點,高低值的變化無法有效分辨,為了在屏幕上凸顯高低變化的連續性,可以用對數尺度來替換線性尺度:M'=log(1+M)magnitudeImage += Scalar::all(1);log(magnitudeImage, magnitudeImage);//求自然對數//[7]剪切和重分布幅度圖象限//因為[2]中進行了圖像延擴,現在需要剔除,為了方便顯示,可以重新分布幅度圖象限位置//若有奇數行或奇數列,進行頻譜裁剪magnitudeImage = magnitudeImage(Rect(0, 0, magnitudeImage.cols & -2, magnitudeImage.rows & -2));//重新排列傅里葉圖像中的象限,使得原點位于圖像中心int cx = magnitudeImage.cols / 2;int cy = magnitudeImage.rows / 2;Mat q0(magnitudeImage, Rect(0, 0, cx, cy)); //ROI區域的左上Mat q1(magnitudeImage, Rect(cx, 0, cx, cy)); //ROI區域的右上Mat q2(magnitudeImage, Rect(0, cy, cx, cy)); //ROI區域的左下Mat q3(magnitudeImage, Rect(cx, cy, cx, cy)); //ROI區域的右下//交換象限(左上和右下進行交換)Mat tmp;q0.copyTo(tmp);q3.copyTo(q0);tmp.copyTo(q3);//交換象限(右上與左下進行交換)q1.copyTo(tmp);q2.copyTo(q1);tmp.copyTo(q2);//[8]歸一化,用0到1之間的浮點值將矩陣變換為可視的圖像格式//有了重分布后的幅度圖,但是幅度值仍然超過可顯示范圍[0,1],使用normalize函數將幅度歸一化到可顯示范圍normalize(magnitudeImage, magnitudeImage, 0, 1, NORM_MINMAX);//[9]顯示效果圖imshow("頻譜幅值", magnitudeImage);waitKey(0);return 0;
}
總結
以上是生活随笔為你收集整理的《OpenCV3编程入门》学习笔记5 Core组件进阶(五)离散傅里叶变换(DFT)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一般割包皮要多少钱?
- 下一篇: 《OpenCV3编程入门》学习笔记5 C