图像傅立叶频谱分析
参考:http://cns-alumni.bu.edu/~slehar/fourier/fourier.html#filtering 很棒
分析:
如果输入二维图像数据,则显示的图像是输入的灰度分布,傅立叶频谱是输入的频率分布,频谱图中心对称。
图像频谱即二维频谱图通过对原图像进行水平和竖直两个方向的所有扫描线处一维傅立叶变换的叠加得到
频谱图中以图中心为圆心,圆的相位对应原图中频率分量的相位,半径对应频率高低,低频半径小,高频半径大,中心为直流分量,某点亮度对应该频率能量高低。
从测试案例中更清楚的提现以上几点
以下为几个测试案例:
1.水平方向为sin函数,竖直方向没有变化,对应频谱如图为零相位相对低频的一个亮点,而其他位置基本为黑色,说明没有其他频率分量(水平方向其他小亮点是由于原图不是严格的sin函数而产生的噪声)
2.相对1频率变高,频谱图中亮点位置半径变大
3.sin函数方向改变,频谱图的方向也改变,两种解释。可以理解为水平方向和竖直方向单独扫描都是sin函数,对应频谱叠加所得。也可是理解为原图sin函数方向(相位)改变,频谱图相位变化
4.3图叠加1图
5.频率再次升高,频谱图半径再次增大
6.1图叠加2图,频谱图也叠加
7.原图为一条竖直线,每个行扫描需要若干频率(几乎所有频率)正弦曲线去叠加逼近,所以频谱图包含了所有频率,竖直扫描依然没有变化,频谱图只有一条横线。
8.以下两图对比,第一个图变化不明显,对比度低,频谱图中心亮周围暗,主要为低频分量
第二个图相对对比度高,频谱周围部分比第一张图的频谱要亮的多,第二张图高频分量更多,但原图背景仍然是灰色图,所以频谱图也是中心最亮,低频分量最高。
代码:
之前的博文其实已经归纳过这方面的内容了。我们常用的图像平滑处理,其实就是一个低通滤波,一定程度上去除高频信号,可以使得图像变得柔和(也就是平滑)。但是,在去除周期性噪声时候,空间域内的滤波(卷积)就不是那么好操作了。所以,这里时候,无论是理解起来方便,还是其他原因,都需要在频域内进行滤波。
详细的叙述还是在下面的博文里面啦!!!!
[数字图像处理]频域滤波(1)–基础与低通滤波器
[数字图像处理]频域滤波(2)–高通滤波器,带阻滤波器与陷波滤波器
这部分的内容,主要就是使用OpenCV自带的函数 void cvDFT( const CvArr* src, CvArr* dst, int flags, int nonzero_rows=0 )
去求取图像的傅里叶变换。这里,输出结果CvArr* dst由两个通道组成,分别代表了实部与虚部。我们再根据如下算式,就可以得到傅里叶频谱了。
|F(u,v)|=R2(u,v)+F2(u,v)‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾√2
我自己也参考了很多人的代码,然后实现的代码如下。
IplImage* fft2(IplImage* image_input)
{
int dftWidth = getOptimalDFTSize(image_input->width);
int dftHeight = getOptimalDFTSize(image_input->height);
//cout<< " Width" << image_input->width << " " << dftWidth << "\n";
//cout<< "Height" << image_input->height << " " << dftHeight << "\n";
IplImage* image_padded = cvCreateImage(cvSize(dftWidth,dftHeight),
IPL_DEPTH_8U,
1);
cvCopyMakeBorder( image_input, image_padded, cvPoint(0,0), IPL_BORDER_CONSTANT,cvScalarAll(0));
IplImage *image_Re =0 , *image_Im = 0, *image_Fourier = 0;
image_Re = cvCreateImage(cvSize(dftWidth,dftHeight),IPL_DEPTH_64F,1);
image_Im = cvCreateImage(cvSize(dftWidth,dftHeight),IPL_DEPTH_64F,1);
image_Fourier = cvCreateImage(cvSize(dftWidth,dftHeight),IPL_DEPTH_64F,2);
//image_Re <--- image_padded
cvConvertScale(image_padded,image_Re);
//image_Im <--- 0
cvZero(image_Im);
//image_Fourier[0] <--- image_Re
//image_Fourier[1] <--- image_Im
cvMerge(image_Re,image_Im,0,0,image_Fourier);
cvDFT(image_Fourier,image_Fourier,CV_DXT_FORWARD);
//image_Fourier[0] ---> image_Re
//image_Fourier[1] ---> image_Im
cvSplit(image_Fourier,image_Re,image_Im,0,0);
//Mag = sqrt(Re^2 + Im^2)
cvPow(image_Re,image_Re,2.0);
cvPow(image_Im,image_Im,2.0);
cvAdd(image_Re,image_Im,image_Re);
cvPow(image_Re,image_Re,0.5);
// log (1 + Mag)
cvAddS(image_Re,cvScalar(1),image_Re );
cvLog (image_Re,image_Re);
// |-----|-----| |-----|-----|
// | 1 | 3 | | 4 | 2 |
// |-----|-----| ---> |-----|-----|
// | 2 | 4 | | 3 | 1 |
// |-----|-----| |-----|-----|
IplImage *Fourier = cvCreateImage(cvSize(dftWidth,dftHeight),IPL_DEPTH_64F,1);
cvZero(image_Fourier);
int cx = image_Re->width/2;
int cy = image_Re->height/2;
cvSetImageROI(image_Re,cvRect( 0, 0,cx,cy)); // 1
cvSetImageROI( Fourier,cvRect(cx,cy,cx,cy)); // 4
cvAddWeighted(image_Re,1,Fourier,0,0,Fourier);
cvSetImageROI(image_Re,cvRect(cx,cy,cx,cy)); // 4
cvSetImageROI( Fourier,cvRect( 0, 0,cx,cy)); // 1
cvAddWeighted(image_Re,1,Fourier,0,0,Fourier);
cvSetImageROI(image_Re,cvRect(cx, 0,cx,cy)); // 3
cvSetImageROI( Fourier,cvRect( 0,cy,cx,cy)); // 2
cvAddWeighted(image_Re,1,Fourier,0,0,Fourier);
cvSetImageROI(image_Re,cvRect( 0,cy,cx,cy)); // 2
cvSetImageROI( Fourier,cvRect(cx, 0,cx,cy)); // 3
cvAddWeighted(image_Re,1,Fourier,0,0,Fourier);
cvResetImageROI(image_Re);
cvResetImageROI( Fourier);
cvNormalize(Fourier,Fourier,1,0,CV_C,NULL);
return(Fourier);
}
从这里开始,还是简单的分析一下代码吧。
int dftWidth = getOptimalDFTSize(image_input->width);
int dftHeight = getOptimalDFTSize(image_input->height);
IplImage* image_padded = cvCreateImage(cvSize(dftWidth,dftHeight),
IPL_DEPTH_8U,
1);
cvCopyMakeBorder( image_input, image_padded, cvPoint(0,0), IPL_BORDER_CONSTANT,cvScalarAll(0));
这里参考了文献[2]中的说法,在尺寸数为2,3,5的倍数的场合,计算的速度是最快的。所以使用函数getOptimalDFTSize()
来寻找最匹配的尺寸,然后再同伙cvCopyMakeBorder()
进行多余部分的填充,这里选的配置是将图放在从点(0,0)开始的位置,其余不足的地方,用0进行填充。
IplImage *image_Re =0 , *image_Im = 0, *image_Fourier = 0;
image_Re = cvCreateImage(cvSize(dftWidth,dftHeight),IPL_DEPTH_64F,1);
image_Im = cvCreateImage(cvSize(dftWidth,dftHeight),IPL_DEPTH_64F,1);
image_Fourier = cvCreateImage(cvSize(dftWidth,dftHeight),IPL_DEPTH_64F,2);
//image_Re <--- image_padded
cvConvertScale(image_padded,image_Re);
//image_Im <--- 0
cvZero(image_Im);
//image_Fourier[0] <--- image_Re
//image_Fourier[1] <--- image_Im
cvMerge(image_Re,image_Im,0,0,image_Fourier);
cvDFT(image_Fourier,image_Fourier,CV_DXT_FORWARD);
//image_Fourier[0] ---> image_Re
//image_Fourier[1] ---> image_Im
cvSplit(image_Fourier,image_Re,image_Im,0,0);
其实这里的很好理解的,将填充到最适尺寸的图像赋值给image_Re,将image_Im赋值为0。让后将这两层图复制到image_Fourier的两个通道里,然后使用函数cvDFT()
进行傅里叶变换。得到结果还是存在于image_Fourier的两个通道里,分别代表实部与虚部,然后通过cvSplit()
将其抽出到image_Re与image_Im里。
//Mag = sqrt(Re^2 + Im^2)
cvPow(image_Re,image_Re,2.0);
cvPow(image_Im,image_Im,2.0);
cvAdd(image_Re,image_Im,image_Re);
cvPow(image_Re,image_Re,0.5);
// log (1 + Mag)
cvAddS(image_Re,cvScalar(1),image_Re );
cvLog (image_Re,image_Re);
以上代码,实现了以下计算。
|F(u,v)|=R2(u,v)+F2(u,v)‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾√2
还有就是进行了一个对数变换,这个也没的说,看傅里叶频谱的标配操作。
// |-----|-----| |-----|-----|
// | 1 | 3 | | 4 | 2 |
// |-----|-----| ---> |-----|-----|
// | 2 | 4 | | 3 | 1 |
// |-----|-----| |-----|-----|
IplImage *Fourier = cvCreateImage(cvSize(dftWidth,dftHeight),IPL_DEPTH_64F,1);
cvZero(image_Fourier);
int cx = image_Re->width/2;
int cy = image_Re->height/2;
cvSetImageROI(image_Re,cvRect( 0, 0,cx,cy)); // 1
cvSetImageROI( Fourier,cvRect(cx,cy,cx,cy)); // 4
cvAddWeighted(image_Re,1,Fourier,0,0,Fourier);
cvSetImageROI(image_Re,cvRect(cx,cy,cx,cy)); // 4
cvSetImageROI( Fourier,cvRect( 0, 0,cx,cy)); // 1
cvAddWeighted(image_Re,1,Fourier,0,0,Fourier);
cvSetImageROI(image_Re,cvRect(cx, 0,cx,cy)); // 3
cvSetImageROI( Fourier,cvRect( 0,cy,cx,cy)); // 2
cvAddWeighted(image_Re,1,Fourier,0,0,Fourier);
cvSetImageROI(image_Re,cvRect( 0,cy,cx,cy)); // 2
cvSetImageROI( Fourier,cvRect(cx, 0,cx,cy)); // 3
cvAddWeighted(image_Re,1,Fourier,0,0,Fourier);
cvResetImageROI(image_Re);
cvResetImageROI( Fourier);
cvNormalize(Fourier,Fourier,1,0,CV_C,NULL);
return(Fourier);
其实重头戏在这里,这里需要一个交换操作。至于为何所求得的傅里叶频谱为什么需要交换的原因是,这个代码求得的结果其实是范围[0,2π]内的傅里叶变换。而我们所需要的是[−π,π]内的结果。详细的原因,还是在我以前的博客里有说明。
[数字图像处理]频域滤波(1)–基础与低通滤波器
这里,我使用了ROI操作与cvAddWeighted()
函数进行了实现。其运行的结果如下所示。
恩,基本可以看出来,直流分量也被我移动到了中心,以上代码实现了傅里叶频谱的计算与显示。
使用MATLAB去求取尺寸为M×N图像f的傅里叶频谱时候,通常会用fft2(f,2*M,2*N)
。使用此函数求得的福利叶变换,其实还是[0,2π]范围内的傅里叶变换。要想使得傅里叶频谱的DC分量位于中心的话,其实还是需要一些别的操作的。冈萨雷斯的《数字图像处理》一书中,对于这个问题,是利用了傅里叶变换的评议特性,即
f(x,y)=f(x,y)×(−1)x+y
然后再对函数f(x,y)进行福利叶变换,所得到结果,就是所需要的是[−π,π]内傅里叶变换的结果。具体的原理与推导,还是参看冈萨雷斯的《数字图像处理》英文原本的p258页左右,中文译本的p148左右。当然,嫌麻烦的话,还可以看我的博文,博文中也简明推导了平移特性。
[数字图像处理]频域滤波(1)–基础与低通滤波器
为此,实现的代码变为了如下形式。
IplImage* fft2_New(IplImage* image_input)
{
int dftWidth = getOptimalDFTSize(image_input->width);
int dftHeight = getOptimalDFTSize(image_input->height);
cout<< " Width" << image_input->width << " " << dftWidth << "\n";
cout<< "Height" << image_input->height << " " << dftHeight << "\n";
IplImage* image_padded = cvCreateImage(cvSize(dftWidth,dftHeight),
IPL_DEPTH_8U,
1);
cvCopyMakeBorder( image_input, image_padded, cvPoint(0,0), IPL_BORDER_CONSTANT,cvScalarAll(0));
IplImage *image_Re =0 , *image_Im = 0, *image_Fourier = 0;
image_Re = cvCreateImage(cvSize(dftWidth,dftHeight),IPL_DEPTH_64F,1);
image_Im = cvCreateImage(cvSize(dftWidth,dftHeight),IPL_DEPTH_64F,1);
image_Fourier = cvCreateImage(cvSize(dftWidth,dftHeight),IPL_DEPTH_64F,2);
//image_Re = image_padded .* (-1)^(x+y);
double pixel;
for(int y=0;yheight;y++)
{
for(int x=0;xwidth;x++)
{
pixel = cvGetReal2D(image_padded,x,y);
pixel = ((x+y)%2 == 0)?(pixel):((-1)*pixel);
cvSetReal2D(image_Re,x,y,pixel);
}
}
//image_Im <--- 0
cvZero(image_Im);
//image_Fourier[0] <--- image_Re
//image_Fourier[1] <--- image_Im
cvMerge(image_Re,image_Im,0,0,image_Fourier);
cvDFT(image_Fourier,image_Fourier,CV_DXT_FORWARD);
//image_Fourier[0] ---> image_Re
//image_Fourier[1] ---> image_Im
cvSplit(image_Fourier,image_Re,image_Im,0,0);
//Mag = sqrt(Re^2 + Im^2)
cvPow(image_Re,image_Re,2.0);
cvPow(image_Im,image_Im,2.0);
cvAdd(image_Re,image_Im,image_Re);
cvPow(image_Re,image_Re,0.5);
// log (1 + Mag)
cvAddS(image_Re,cvScalar(1),image_Re );
cvLog (image_Re,image_Re);
cvNormalize(image_Re,image_Re,1,0,CV_C,NULL);
return(image_Re);
}
在这里,由于考虑到计算的原因,我将f(x,y)=f(x,y)×(−1)x+y这个计算,用下面代码去进行了实现。
for(int y=0;yheight;y++)
{
for(int x=0;xwidth;x++)
{
pixel = cvGetReal2D(image_padded,x,y);
pixel = ((x+y)%2 == 0)?(pixel):((-1)*pixel);
cvSetReal2D(image_Re,x,y,pixel);
}
}
其实也就相当于,x+y是偶数的话f(x,y)不变,x+y是奇数的话f(x,y)变为负。用这样一个简单的判断去实现。其运行的结果,如下所示。
从实验结果看来,可以看出以下两点