OpenCV中的傅里叶的门道

接触到傅里叶-梅林算法,需要用到傅里叶变换,于是去查了一下OpenCV中的实现方法,没想到习以为常的傅里叶变换之中的门道还不少。

//傅里叶变换https://blog.csdn.net/keith_bb/article/details/53389819
	Mat I = imread("Lena.jpg", IMREAD_GRAYSCALE);       //读入图像灰度图

	//判断图像是否加载成功
	if (I.empty())
	{
		cout << "图像加载失败!" << endl;
		return -1;
	}
	else
		cout << "图像加载成功!" << 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型,代表四个方向填充的长度,填充之后得到padded

	Mat 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] = magnitude
	Mat 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 plt

img = 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


猜你喜欢

转载自blog.csdn.net/zcg1942/article/details/81006950