接触到傅里叶-梅林算法,需要用到傅里叶变换,于是去查了一下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