OpenCV中的傅里叶的门道
接觸到傅里葉-梅林算法,需要用到傅里葉變換,于是去查了一下OpenCV中的實現方法,沒想到習以為常的傅里葉變換之中的門道還不少。
//傅里葉變換https://blog.csdn.net/keith_bb/article/details/53389819Mat I = imread("Lena.jpg", IMREAD_GRAYSCALE); //讀入圖像灰度圖//判斷圖像是否加載成功if (I.empty()){cout << "圖像加載失敗!" << endl;return -1;}elsecout << "圖像加載成功!" << endl << endl;Mat padded; //以0填充輸入圖像矩陣int m = getOptimalDFTSize(I.rows);//為了使dft()運算更高效,改變尺寸,注意參數是行/列int n = getOptimalDFTSize(I.cols);//一個數組尺寸是2、3、5的倍數(例如:300 = 5*5*3*2*2)同樣有很高的處理效率//填充輸入圖像I,輸入矩陣為padded,上方和左方不做填充處理copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0));//中間四個參數是int型,代表四個方向填充的長度,填充之后得到paddedMat planes[] = { Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F) };//Mat_<float>對應的是CV_32F 這是創建了一個Mat類型的數組,兩個元素大小都是padded,類型都是float為延擴后的圖像增添一個初始化為0的通道Mat complexI;merge(planes, 2, complexI); //將planes融合合并成一個多通道數組complexI//這個函數將單通道的數組融合起來,第一個參數是指向mat的指針/*imshow("complexI", complexI);waitKey();*///會溢出,因為complex是mat數組,用于存放實部和虛部dft(complexI, complexI); //進行傅里葉變換 src不能是padded//注意這里的輸入圖像不是原圖也不是padded,而是融合得到的complexI//支持圖像原地計算 (輸入輸出為同一圖像)//計算幅值,轉換到對數尺度(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))//即planes[0]為實部,planes[1]為虛部magnitude(planes[0], planes[1], planes[0]); //將求得的幅度放在planes[0],planes[0] = magnitudeMat magI = planes[0];magI += Scalar::all(1);//加1(為了取對數之后全為整數吧0之后才取對數,為了將灰度值跨度縮小,增強可視化效果log(magI, magI); //線性尺度轉換到對數尺度(logarithmic scale)//剪切和象限變換//如果有奇數行或列,則對頻譜進行裁剪magI = magI(Rect(0, 0, magI.cols&-2, magI.rows&-2));//這個按位與什么意思?//x&-2代表x與-2按位相與,-2的二進制形式為:1111 1110,在x與-2按位相與后,不管x是奇數還是偶數,最后x都會變成一個偶數https://blog.csdn.net/autocyz/article/details/48803859//重新排列傅里葉圖像中的象限,使得原點位于圖像中心int cx = magI.cols / 2;int cy = magI.rows / 2;Mat q0(magI, Rect(0, 0, cx, cy)); //左上角圖像劃定ROI區域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之間的浮點數將矩陣變換為可視的圖像格式Mat magI01;normalize(magI, magI01, 0, 1, CV_MINMAX);//0和1分別是下限和上限,范圍歸一化,而不是數值歸一化imshow("輸入圖像", I);imshow("頻譜圖", magI01);//imshow對float數據會擴大255倍,這也是歸一化到0~1的原因imwrite("Fouier0101.jpg", magI01);傅里葉變換后求得的幅值沒有經過對數尺度變換,而直接范圍歸一化在0~1:
沒有對數尺度變換,也沒有范圍歸一化:
可以看到直接求得的幅值都很大,即頻率成分都很高。
對數尺度變換后,但沒有范圍歸一化:
看不出來什么差別,不過中心點黑點變得不明顯了。
對數尺度變換后歸一化在0~1:
沒有對數尺度變換,直接歸一化在0~255:
對數尺度變換后歸一化在0~255:
對數尺度變換后歸一化在0~2:
Normalize之后imshow問題:
將圖像范圍歸一化在0~1,圖像會是近似成全黑的,imwrite保存到本地后查看圖像也驗證了確實如此,但為什么opencv打開的頻譜圖效果良好呢?(OpenCV的工作路徑是項目下的,不是解決方案)
咨詢了一下師兄,師兄認為是imshow的問題,因為matlab中的imshow就可選對不同數據進行不同的展示方法。猜測imshow對normalize之后是0~1之間的浮點數有一個特別的操作,而不是簡單的將0~1之間的浮點數作為最終展示的圖像的灰度值。查了一下資料果然如此:
??? 如果載入的圖像是8位無符號類型(8-bitunsigned),就顯示圖像本來的樣子。
??? 如果圖像是16位無符號類型(16-bitunsigned)或32位整型(32-bit integer),便用像素值除以256。也就是說,值的范圍是[0,255 x 256]映射到[0,255]。
如果圖像是32位浮點型(32-bitfloating-point),像素值便要乘以255。也就是說,該值的范圍是[0,1]映射到[0,255]。有個術語稱之為亮度圖像。但為什么歸一化到0~1再乘255和直接歸一化在0~255的效果不同呢,這是因為取對數后的頻譜依然存在大量大于255的幅值,如果直接歸一化在0~255,這些大于255的數值依然是255.歸一化在0~1再乘255相當于進行了縮放,先縮小范圍在0~1(盡管大量的值依然集中在1附近),然后按比例放大到圖像灰度值的范圍。
有的資料甚至說OpenCV的imshow不允許處理除了uchar(8-bitunsigned)以外的數據類型。如果保存成jpeg格式,每個像素用24bit真彩表示,JPG是有損壓縮。JPG是有損壓縮,PNG是無損壓縮,bmp沒有經過壓縮。
DFT函數問題:
dft(complexI,complexI);DFT要分別計算實部和虛部,把要處理的圖像作為輸入的實部、一個全零的圖像作為輸入的虛部。dft()輸入和輸出應該分別為單張圖像,所以要先用merge()把實虛部圖像合并,分別處于圖像comImg的兩個通道內。計算得到的實虛部仍然保存在comImg的兩個通道內。
傅里葉變換的最優尺寸問題
getOptimalDFTSize函數得到最優的行列數,然后使用copyMakeBorder進行填充。數組的大小是2的整數次冪的情況,dft變換的速度是最快的。當然,大部分情況下,數組的大小不會是2的次冪,但是如果其大小可以分解為2、3、5的累成也是能夠提高dft的效率的。
附dft在python中的實現:
#!/usr/bin/env.python #!/usr/bin/env.python import cv2 as cv import numpy as np from matplotlib import pyplot as pltimg = cv.imread('Joseph_Fourier_250.jpg', 0) f = np.fft.fft2(img) # 快速傅里葉變換算法得到頻率分布 fshift = np.fft.fftshift(f) # 默認結果中心點位置是在左上角,轉移到中間位置fimg = np.log(np.abs(fshift)+1) # fft 結果是復數,求絕對值結果才是振幅(化為實數) cv.imwrite("dft.bmp",fimg); fimg0=np.log(np.abs(f)+1) #取對數可以將數據變化范圍縮小# 展示結果 plt.subplot(131), plt.imshow(img, 'gray'), plt.title('Original Fourier') plt.subplot(132), plt.imshow(fimg0, 'gray'), plt.title('Fourier Fourier') plt.subplot(133), plt.imshow(fimg, 'gray'), plt.title('Fourier Fourier-shift') plt.show()Python代碼沒有圖像填充等操作,直接對圖像進行dft變換,中心平移后取對數。但保存頻譜圖再查看會發現保存的頻譜圖的灰度值依然在0~1區間。
Reference:
1.??????代碼opencv\sources\samples\cpp\dft.cpp
2.??????乘以255https://blog.csdn.net/honey1992/article/details/50055615
3.??????imshow不允許除uchar以外https://blog.csdn.net/kelvin_yan/article/details/50033981
4.??????JPG、PNGhttps://www.cnblogs.com/skyfsm/p/7136709.html
5.??????http://johnhany.net/2013/11/dft-based-text-rotation-correction/
6.??????補碼按位與偶數https://blog.csdn.net/autocyz/article/details/48803859
總結
以上是生活随笔為你收集整理的OpenCV中的傅里叶的门道的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android之Bundle类
- 下一篇: Caffe中的卷积实现