频率域滤波 —— 百度百科
频率域滤波是对图像进行傅里叶变换,将图像由图像空间转换到频域空间,然后在频率域中对图像的频谱作分析处理,以改变图像的频率特征。
滤波: 狭义地说,滤波是指改变信号中各个频率分量的相对大小、或者分离出来加以抑制、甚至全部滤除某些频率分量的过程。广义地说,滤波是把某种信号处理成为另一种信号的过程。空间域滤波: 以像元与周围邻域像元的空间关系为基础,通过卷积运算实现图像滤波的一种方法。
为什么要在频率域中进行图像处理?
谈到频率域,就不得不说傅里叶变换了。傅里叶是18世纪法国的一位伟大的数学家。他最大的贡献在于指出任何周期函数都可以表示为不同频率的正弦和或者余弦和的形式,每个正弦或者余弦乘以不同的系数(也就是被大家所熟知的傅里叶级数)。无论函数有多复杂,只要它是周期性的,并且满足一定的数学条件,就一定可以用这样的正弦和或者余弦和的形式来表示。甚至在有些情况下,非周期函数也可以用正弦和或者余弦和的形式来表示。用傅里叶级数或变换表示的函数特征可以完全通过傅里叶反变换来重建,而不会丢失任何信息。而正是所谓的“傅里叶变换”使得我们可以工作于频率域。
参考博客:数字图像处理-频率域滤波原理
假设有 M 行 N列的复数矩阵 f f f,其中 f ( x , y ) f(x,y) f(x,y) 代表 f f f 第 x x x 行第 y y y 列对应的值, x ∈ [ 0 , M − 1 ] , y ∈ [ 0 , N − 1 ] x\in [0, M-1], y \in [0, N-1] x∈[0,M−1],y∈[0,N−1] ,其对应的傅里叶变换为:
f ( x , y ) = 1 M N ∑ u = 0 M − 1 ∑ v = 0 N − 1 F ( u , v ) e ( 2 π M u x + 2 π N v y ) i , 0 ≤ x < M , 0 ≤ y < N f(x, y) = \frac{1}{MN}\sum_{u=0}^{M-1} \sum_{v=0}^{N-1} F(u, v) e^{(\frac{2\pi}{M}ux+\frac{2\pi}{N}vy)i}, 0 \leq x < M, 0 \leq y < N f(x,y)=MN1u=0∑M−1v=0∑N−1F(u,v)e(M2πux+N2πvy)i,0≤x<M,0≤y<N
可求的复数矩阵 F F F 为:
F ( u , v ) = ∑ x = 0 M − 1 ∑ y = 0 N − 1 f ( x , y ) e − ( 2 π M u x + 2 π N v y ) i , 0 ≤ u < M , 0 ≤ v < N F(u, v) = \sum_{x=0}^{M-1} \sum_{y=0}^{N-1} f(x, y) e^{-(\frac{2\pi}{M}ux+\frac{2\pi}{N}vy)i}, 0 \leq u < M, 0 \leq v < N F(u,v)=x=0∑M−1y=0∑N−1f(x,y)e−(M2πux+N2πvy)i,0≤u<M,0≤v<N
那么 F F F 为 f f f 的傅里叶变换,称 f f f 为 F F F 的傅立叶逆变换,表示为 f ⟺ F f \Longleftrightarrow F f⟺F 。虽然我们讨论的图像矩阵是实数矩阵,但是所得到的 F F F 一般都会有复数元素。
二维离散的傅里叶变换可以分解为一维离散的傅里叶变换:
F ( u , v ) = ∑ x = 0 M − 1 [ ∑ y = 0 N − 1 f ( x , y ) e 2 π N v y i ] e − 2 π M u x i , 0 ≤ u < M , 0 ≤ v < N F(u, v) = \sum_{x=0}^{M-1} [\sum_{y=0}^{N-1} f(x, y) e^{\frac{2\pi}{N}vyi} ]e^{-\frac{2\pi}{M}uxi}, 0 \leq u < M, 0 \leq v < N F(u,v)=x=0∑M−1[y=0∑N−1f(x,y)eN2πvyi]e−M2πuxi,0≤u<M,0≤v<N
方括号中的项表示在图像的行上计算傅里叶变换,方括号外边的求和则表示在行傅里叶变换的基础上在列上计算傅里叶变换。用矩阵的形式可以写为 F = U f V F = UfV F=UfV,其中 U , V U,V U,V 为复数矩阵,而 f = U − 1 F V − 1 = 1 M N U ∗ F V ∗ f = U^{-1}F V^{-1} = \frac{1}{MN}U^*FV^* f=U−1FV−1=MN1U∗FV∗。
因为图像是实数矩阵,下图为对图像进行傅里叶变换处理的基本步骤,即先对图像矩阵进行傅里叶变换,然后进行傅里叶逆变换,接着取实部,就可以恢复原图像。
OpenCV函数
OpenCV 函数 dft
可以实现矩阵的傅里叶(逆)变换,通过参数 flags 说明是傅里叶变换还是傅里叶逆变换。
void dft(InputArray src, OutputArray dst, int flags = 0, int nonzeroRows = 0)
参数 | 解释 |
---|---|
src | 输入矩阵,只支持 CV_32F 或 CV_32F 的单通道或双通道矩阵 |
dst | 输出矩阵 |
flags | 转换标志 |
如果输入矩阵 src 是单通道的,则代表实数矩阵;如果是双通道的,则代表复数矩阵。而输出矩阵 dst 是单通道的还是双通道的,则需要参数 flags 指定,其中 flags 的值可以组合使用,在进行傅里叶逆变换时,常用的组合为 DFT_REAL_OUTPUT+DFT_INVERSE+DFT_SCALE
。
参数 flags:
C++示例
int main()
{
string outdir = "./images/";
// 输入图像
Mat img = imread("img.png");
Mat gray;
Mat fGray;
cvtColor(img, gray, COLOR_BGR2GRAY);
// 数据类型转换
gray.convertTo(fGray, CV_64F);
// 傅里叶变换
Mat F;
dft(fGray, F, DFT_COMPLEX_OUTPUT);
// 傅里叶逆变换,只取实部
Mat iF;
dft(F, iF, DFT_REAL_OUTPUT+DFT_INVERSE+DFT_SCALE);
// 计算的if是浮点型,转换为 CV_8U
Mat I;
iF.convertTo(I, CV_8U);
imwrite(outdir+"test.jpg", I);
}
傅里叶变换理论上需要 O ( M N ) 2 O(MN)^2 O(MN)2 次运算,这是非常耗时的,并极大地降低了傅里叶变换在图像处理中的应用。当 M = 2 m , N = 2 n M = 2^m, N=2^n M=2m,N=2n 次时,或者对于任意的 M M M 和 N N N ,傅里叶变换通过 O ( M N log ( M N ) ) O(MN\log(MN)) O(MNlog(MN)) 次运算就可以完成,这就是“快速傅里叶变换”。
在 OpenCV 中实现的傅里叶变换的快速算法是针对行数和列数均满足可以分解为 2 p × 3 q × 5 r 2^p \times 3^q \times 5^r 2p×3q×5r 的情况的,所以在计算二维矩阵的快速傅里叶变换时需要先对原矩阵进行扩充,在矩阵的右侧和下侧补 0,以满足该规则,对于补多少行多少列的 0,可以使用函数 getOptimalDFTSize
进行计算,该函数返回一个不小于 vecsize
且可以分解为 2 p × 3 q × 5 r 2^p \times 3^q \times 5^r 2p×3q×5r 的整数。
int cv::getOptimalDFTSize(int vecsize)
//Python:
retval = cv.getOptimalDFTSize(vecsize)
快速傅里叶(逆)变换的步骤如下图,对图像完成快速傅里叶变换后,再通过快速傅里叶逆变换,接着取实部,然后裁剪,即可恢复原图像。
C++示例
利用函数 getOptimalDFTSize
和 dft
完成图像矩阵的傅里叶变换。
void fft2Image(Mat& I, Mat& F)
{
int rows = I.rows;
int cols = I.cols;
// 满足快速傅里叶变换的最优行数和列数
int rPadded = getOptimalDFTSize(rows);
int cPadded = getOptimalDFTSize(cols);
// 左侧和下侧补零
Mat f;
copyMakeBorder(I, f, 0, rPadded-rows, 0, cPadded-cols, BORDER_CONSTANT, Scalar::all(0));
// 快速傅里叶变换(双通道,用于存储实部和虚部)
dft(f, F, DFT_COMPLEX_OUTPUT);
}
int main()
{
string outdir = "./images/";
// 输入图像
Mat img = imread("img.png");
Mat gray;
Mat fGray;
cvtColor(img, gray, COLOR_BGR2GRAY);
gray.convertTo(fGray, CV_64F);
// 傅里叶变换
Mat F;
fft2Image(fGray, F);
// 傅里叶逆变换,只取实部
Mat iF;
dft(F, iF, DFT_REAL_OUTPUT+DFT_INVERSE+DFT_SCALE);
// 剪裁傅里叶逆变换的实部
Mat I = iF(Rect(0, 0, img.cols, img.rows)).clone();
// 计算的if是浮点型,转换为 CV_8U
I.convertTo(I, CV_8U);
imwrite(outdir+"test.jpg", I);
}
既然对图像进行快速傅里叶变换后得到复数矩阵 F F F,那么接下来就通过幅度谱和相位谱两个度量来了解该复数矩阵。分别几 Real \text{Real} Real 为矩阵 F F F 的实部, Imaginary \text{Imaginary} Imaginary 为矩阵 F F F 的虚部,即 F = Real + i ∗ Imaginary F = \text{Real} + i * \text{Imaginary} F=Real+i∗Imaginary 。
幅度谱,又称傅里叶谱,通过一下公式计算:
Amplitude = Real 2 + Imaginary 2 \text{Amplitude} = \sqrt{\text{Real}^2 + \text{Imaginary}^2} Amplitude=Real2+Imaginary2
其中 Amplitude ( u , v ) = Real ( u , v ) 2 + Imaginary ( u , v ) 2 \text{Amplitude}(u,v) = \sqrt{\text{Real}(u,v)^2 + \text{Imaginary}(u,v)^2} Amplitude(u,v)=Real(u,v)2+Imaginary(u,v)2 。根据傅里叶变换公式可知 F ( 0 , 0 ) = ∑ x = 0 M − 1 ∑ y = 0 N − 1 f ( x , y ) F(0,0) = \sum_{x=0}^{M-1} \sum_{y=0}^{N-1}f(x,y) F(0,0)=∑x=0M−1∑y=0N−1f(x,y) ,则 Amplitude ( 0 , 0 ) = F ( 0 , 0 ) \text{Amplitude}(0,0) = F(0,0) Amplitude(0,0)=F(0,0) ,即在 (0,0) 处的值等于输入矩阵 f f f 的所有值的和,这个值很大,是幅度谱中最大的值,它可能比其他项大几个数量级。
相位谱通过以下公式计算:
Phase = arctan ( Imaginary Real ) \text{Phase} = \arctan(\frac{\text{Imaginary}}{\text{Real}}) Phase=arctan(RealImaginary)
其中 Phase ( u , v ) = arctan ( Imaginary ( u , v ) Real ( y , v ) ) \text{Phase}(u,v) = \arctan(\frac{\text{Imaginary}(u,v)}{\text{Real}(y,v)}) Phase(u,v)=arctan(Real(y,v)Imaginary(u,v)).
显然,复数矩阵 F F F 可以由幅度谱和相位谱表示
F = Amplitude ∗ cos ( Phase ) + i ∗ Amplitude . ∗ sin ( Phase ) F = \text{Amplitude} * \cos(\text{Phase}) + i * \text{Amplitude} .* \sin(\text{Phase}) F=Amplitude∗cos(Phase)+i∗Amplitude.∗sin(Phase)
其中 . ∗ .* .∗ 代表矩阵的点乘,即对应位置相乘。
下面对幅度谱和相位谱进行灰度级显示,注意区别幅度谱及其灰度级和相位谱及其灰度级,两个灰度级显示的目的是为了使人眼能够观察到对幅度谱和相位谱所做的特殊处理。
C++实现
因为幅度谱的最大值在 (0, 0) 处,即左上角,通常为了便于观察,需要将其移动到幅度谱的中心,那么需要在进行傅里叶变换前,将图像矩阵乘以 ( − 1 ) r + c (-1)^{r+c} (−1)r+c 。整个步骤如下图
函数 magnitude
可以直接计算两个矩阵对应位置平方和的平方根
void cv::magnitude (InputArray x,
InputArray y,
OutputArray magnitude
)
//Python:
magnitude = cv.magnitude(x, y[, magnitude])
参数 | 解释 |
---|---|
x | 浮点型矩阵 |
y | 浮点型矩阵 |
magnitude | 幅度谱 |
计算出的幅度矩阵中的值大部分比较大,往往大于255,如果简单截断为255,那么幅度谱呈现的信息会很少。所以一般采用对数函数对幅度谱进行数值压缩,再进行归一化,这样得到的幅度谱的灰度级显示的对比度会比较高。
// 计算幅度谱
void amplitudeSpectrum(Mat& _srcFFT, Mat& _dstSpectrum)
{
// 判断傅里叶变换有两个通道
CV_Assert(_srcFFT.channels() == 2);
// 分离通道
vector<Mat> FFT2Channel;
split(_srcFFT, FFT2Channel);
// 计算傅里叶变换的幅度谱
magnitude(FFT2Channel[0], FFT2Channel[1], _dstSpectrum);
}
Mat graySpectrum(Mat spectrum)
{
Mat dst;
log(spectrum+1, dst);
// 归一化
normalize(dst, dst, 0, 1, NORM_MINMAX);
// 为了进行灰度级显示,做类型转换
dst.convertTo(dst, CV_8UC1, 255, 0);
return dst;
}
// 计算相位谱
Mat phaseSpectrum(Mat _srcFFT)
{
// 相位谱
Mat phase;
phase.create(_srcFFT.size(), CV_64FC1);
// 分离通道
vector<Mat> FFT2Channel;
split(_srcFFT, FFT2Channel);
// 计算相位谱
for(int r = 0; r < phase.rows; r++)
{
for(int c = 0; c < phase.cols; c++)
{
// 实部 虚部
double real = FFT2Channel[0].at<double>(r, c);
double imaginary = FFT2Channel[1].at<double>(r,c);
// atan2 的返回值范围: [0, 180], [-180, 0]
phase.at<double>(r,c) = atan2(imaginary, real);
}
}
return phase;
}
int main()
{
string outdir = "./images/";
// 输入图像
Mat img = imread("img1.jpg");
Mat gray;
Mat fGray;
cvtColor(img, gray, COLOR_BGR2GRAY);
gray.convertTo(fGray, CV_64F);
// 中心化
for(int r = 0; r < fGray.rows; r++)
{
for(int c = 0; c < fGray.cols; c++)
{
if((r+c)%2)
{
fGray.at<double>(r, c) *= -1;
}
}
}
// 快速傅里叶变换
Mat F;
fft2Image(fGray, F);
// 求幅度谱以及灰度级显示
Mat amplitude;
amplitudeSpectrum(F, amplitude);
Mat ampSpectrum = graySpectrum(amplitude);
// 相位谱的灰度级显示
Mat phaseSpe = phaseSpectrum(F);
Mat phaseSpeGray = graySpectrum(phaseSpe);
imwrite(outdir+"幅度谱.jpg", ampSpectrum);
imwrite(outdir+"相位谱.jpg", phaseSpeGray);
}
如下图,一些图的幅度谱的灰度级显示结果,会发现中心化后的傅里叶谱比较亮的区域大致与原图中的主要目标垂直。
OpenCV函数
相位谱函数 phase
void cv::phase (InputArray x,
InputArray y,
OutputArray angle,
bool angleInDegrees = false
)
//Python:
angle = cv.phase(x, y[, angle[, angleInDegrees]])
参数 | 解释 |
---|---|
x | 傅里叶变换的实部矩阵 |
y | 傅里叶变换的虚部矩阵 |
angle | 输出矩阵 |
angleInDegrees | 如果为true,则该函数将以度为单位计算角度,否则,将以弧度为单位进行测量。 |
生物视觉研究表明,视觉注意机制是一种具有选择性的注意,它首先由视觉内容中最显著的、与其周围其他内容相比差异更大的成分引起的,然后根据观察者的主观意识去选择注意。视觉显著性检测可以看作抽取信息中最具差异的部分或者最感兴趣或首先关注的部分,赋予对图像分析的选择性能力,对提高图像的处理效率是极为重要的。
显著性检测的方法有很多,本文介绍一种简单、高效的基于幅度谱残差的方法,只要明白幅度谱和相位谱就可以实现这种方法。
原理详解
图像的傅里叶变换可以由幅度谱和相位谱表示,注意不是幅度谱和相位谱的灰度级,也就是通过已知的幅度谱和相位谱就可以还原图像。谱残差显著性检测通过改变幅度谱和相位谱来改变图像的显示,即只显示图像的显著目标。步骤如下:
C++实现
int main()
{
string outdir = "./images/";
// 输入图像
Mat img = imread("img4.jpg");
Mat gray;
Mat fGray;
cvtColor(img, gray, COLOR_BGR2GRAY);
gray.convertTo(fGray, CV_64F, 1.0/255);
// 快速傅里叶变换
Mat F;
fft2Image(fGray, F);
// 幅度谱
Mat amplitude;
amplitudeSpectrum(F, amplitude);
Mat ampSpectrum = graySpectrum(amplitude);
// 对幅度谱进行对数运算
Mat logAmplitude;
log(amplitude + 1.0, logAmplitude);
// 均值平滑
Mat meanLogAmplitude;
blur(logAmplitude, meanLogAmplitude, Size(3,3), Point(-1, -1));
// 谱残差
Mat spectralResidual = logAmplitude - meanLogAmplitude;
// 相位谱
Mat phase = phaseSpectrum(F);
// 余弦谱 cos(phase)
Mat cosSpectrum(phase.size(), CV_64FC1);
// 正弦谱
Mat sinSpectrum(phase.size(), CV_64FC1);
for(int r = 0; r < phase.rows; r++)
{
for(int c = 0; c < phase.cols; c++)
{
cosSpectrum.at<double>(r, c) = cos(phase.at<double>(r,c));
sinSpectrum.at<double>(r, c) = sin(phase.at<double>(r,c));
}
}
// 指数运算
exp(spectralResidual, spectralResidual);
Mat real = spectralResidual.mul(cosSpectrum);
Mat imaginary = spectralResidual.mul(sinSpectrum);
vector<Mat> realAndImg;
realAndImg.push_back(real);
realAndImg.push_back(imaginary);
Mat complex;
merge(realAndImg, complex);
// 快速傅里叶变换
Mat ifft2;
dft(complex, ifft2, DFT_COMPLEX_OUTPUT+DFT_INVERSE);
// 傅里叶逆变换的幅度
Mat ifft2Amp;
amplitudeSpectrum(ifft2, ifft2Amp);
// 平方运算
pow(ifft2Amp, 2.0, ifft2Amp);
// 高斯平滑
GaussianBlur(ifft2Amp, ifft2Amp, Size(11, 11), 2.5);
// 显著性显示
normalize(ifft2Amp, ifft2Amp, 1.0, 0, NORM_MINMAX);
// 提升对比度,进行伽马变换
pow(ifft2Amp, 0.5, ifft2Amp);
// 数据类型转换
Mat saliencyMap;
ifft2Amp.convertTo(saliencyMap, CV_8UC1, 255);
imwrite(outdir+"img4_显著性.jpg", saliencyMap);
}
至此,通过幅度谱和相位谱等性质,我们对图像的傅里叶变换有了大体的认识,接下来介绍傅里叶变换的一个重要作用。在图像平滑 [1] [2] 中使用的卷积运算,一般利用定义和矩阵法进行计算,而当核的尺寸较大时,这两个计算方法都非常耗时。通过卷积和傅里叶变换的某种关系,也可以利用傅里叶变换进行计算。
卷积定理
假设 I I I 是 M M M 行 N N N 列的图像矩阵, k k k 是 m m m 行 n n n 列的卷积核,那么 I I I 与 k k k 的全卷积 I ★ k I \bigstar k I★k 具有 M + m − 1 M+m-1 M+m−1 行 N + n − 1 N+n-1 N+n−1 列,这里在全卷积的运算过程中,采用 0 扩充边界的策略。
在 I I I 的右侧和下侧进行补 0,且将 I I I 的尺寸扩充到与全卷积的尺寸相同,即
I_padded ( r , c ) = { I ( r , c ) , 0 ≤ r < M , 0 ≤ c < N 0 , e l s e , 0 ≤ r < M + m − 1 , 0 ≤ c < N + n − 1 \text{I\_padded}(r,c) = \begin{cases} I(r,c), & 0 \leq r < M,0\leq c < N \\ 0, & else \end{cases} , 0 \leq r < M+m-1, 0\leq c < N+n-1 I_padded(r,c)={I(r,c),0,0≤r<M,0≤c<Nelse,0≤r<M+m−1,0≤c<N+n−1
同样,在卷积核 k k k 的右侧和下侧进行补 0, 且将 k k k 的尺寸扩充到与全卷积的尺寸相同即:
k_padded ( r , c ) = { k ( r , c ) , 0 ≤ r < m , 0 ≤ c < n 0 , e l s e , 0 ≤ r < M + m − 1 , 0 ≤ c < N + n − 1 \text{k\_padded}(r,c) = \begin{cases} k(r,c), & 0 \leq r < m,0\leq c < n \\ 0, & else \end{cases} , 0 \leq r < M+m-1, 0\leq c < N+n-1 k_padded(r,c)={k(r,c),0,0≤r<m,0≤c<nelse,0≤r<M+m−1,0≤c<N+n−1
假设 FT_Ip \text{FT\_Ip} FT_Ip 和 FT_kp \text{FT\_kp} FT_kp 分别是 I_padded \text{I\_padded} I_padded 和 k_padded \text{k\_padded} k_padded 的傅里叶变换,那么 I ★ k I \bigstar k I★k 的傅里叶变换就等于 FT_Ip . ∗ FT_kp \text{FT\_Ip} .* \text{FT\_kp} FT_Ip.∗FT_kp,即 I ★ k ⟺ FT_Ip . ∗ FT_kp I \bigstar k \Longleftrightarrow \text{FT\_Ip} .* \text{FT\_kp} I★k⟺FT_Ip.∗FT_kp,这就是卷积定理。
利用傅里变换计算卷积,主要步骤为,首先计算两个傅里叶变换的点乘,然后进行傅里叶逆变换,并只取逆变换的实部。
通过快速傅里叶变换计算卷积
C++实现
复数矩阵的点乘可以使用函数 mulSpectrums
来实现
void cv::mulSpectrums(InputArray a,
InputArray b,
OutputArray c,
int flags,
bool conjB = false
)
//Python:
c = cv.mulSpectrums(a, b, flags[, c[, conjB]]
参数 | 解释 |
---|---|
a | 双通道矩阵1 |
b | 双通道矩阵2 |
c | 复数矩阵 a 和 b 点乘的结果 |
flags | 操作标志; 当前,唯一支持的标志是 cv::DFT_ROWS,它指示src1和src2的每一行都是独立的一维傅里叶频谱。 如果不想使用此标志,则只需添加一个0作为值。 |
conjB | 是否对 b 共轭,默认 false |
Mat fft2Conv(Mat I, Mat kernel, int borderType=BORDER_DEFAULT, Scalar value=Scalar())
{
// I 的宽高
int R = I.rows;
int C = I.cols;
// 卷积核的宽高
int r = kernel.rows;
int c = kernel.cols;
// 卷积核的半径
int tb = (r - 1) / 2;
int lr = (c - 1) / 2;
/* 1.边界扩充 */
Mat I_padded;
copyMakeBorder(I, I_padded, tb, tb, lr, lr, borderType, value);
/* 2.补0以满足快速傅里叶变换的行数和列数 */
// 满足二维快速傅里叶变换的行数列数
int rows = getOptimalDFTSize(I_padded.rows + r - 1);
int cols = getOptimalDFTSize(I_padded.cols + c - 1);
// 补0
Mat I_padded_zeros, kernel_zeros;
copyMakeBorder(I_padded, I_padded_zeros, 0, rows-I_padded.rows, 0, cols - I_padded.cols, BORDER_CONSTANT, Scalar(0, 0, 0));
copyMakeBorder(kernel, kernel_zeros, 0, rows-kernel.rows, 0, cols-kernel.cols, BORDER_CONSTANT, Scalar(0,0,0));
/* 3.快速傅里叶变换 */
Mat fft2_Ipz, fft2_kz;
dft(I_padded_zeros, fft2_Ipz, DFT_COMPLEX_OUTPUT);
dft(kernel_zeros, fft2_kz, DFT_COMPLEX_OUTPUT);
/* 4. 两个傅里叶变换点乘 */
Mat Ipz_kz;
mulSpectrums(fft2_Ipz, fft2_kz, Ipz_kz, DFT_ROWS);
/* 5.傅里叶逆变换,并只取实部 */
Mat ifft2;
dft(Ipz_kz, ifft2, DFT_INVERSE+DFT_SCALE+DFT_REAL_OUTPUT);
/* 6.裁剪,与所输入的图像矩阵的尺寸相同 */
Mat sameConv = ifft2(Rect(c-1, r-1, C+c-1, R+r-1));
return sameConv;
}
当卷积核较小时,通过快速傅里叶变换计算卷积没有明显的优势;只有当卷积较大时,利用傅里叶变换的快速算法计算卷积才会表现出明显的优势。
之前提到的图像平滑和边缘检测这些操作统称为图像的空间域滤波。基于卷积运算的空间域滤波也可以在频率域上完成,即在空间域上卷积,大体上就是两个傅里叶变换点乘后的傅里叶逆变换。一般称高斯卷积算子、Sobel边缘检测算子等为空间域滤波器,这些核按照以上规则补 0 后的傅里叶变换就称为频率域滤波器,只是这些核的尺寸较小,一般直接采用卷积,不需要将其转换为频率域滤波器。在数学运算和代码中,频率域滤波器的呈现就是一个矩阵。接下来详细介绍在频率域上常用的滤波器。
接下来了解两个重要的概念:低频和高频。低频指的是图像的傅里叶变换“中心位置”附近的区域。高频随着到“中心位置”距离的增加而增加,即傅里叶变换中心位置的外围区域,这里的“中心位置”指的是傅里叶变换所对应的幅度谱最大值的位置。
频率域滤波器在程序或者数学运算中的呈现可以理解为一个矩阵,该矩阵的宽、高和图像的傅里叶变换的宽、高是相同的,下面所涉及的常用的低通、高通、带通、带阻等滤波的关键步骤,就是通过一定的准则构建矩阵的。下图为频率域滤波的步骤:
频率域滤波关键步骤就是通过一定的标准构造 Filter 矩阵以完成图像在频率域上的滤波。
针对图像的傅里叶变换,低频信息表示图像中灰度值缓慢变化的区域;而高频区域则正好相反,表示灰度值变化迅速的部分,如边缘。低通滤波,保留傅里叶变换的低频信息,或者削弱傅里叶变换的高频信息;而高通滤波正好相反,保留傅里叶变换的高频信息,移除或者削弱傅里叶变换的低频信息。
三种常用的低通滤波器
一幅图像中的边缘和其他急剧灰度变化(如噪声)主要影响其傅里叶变换的高频内容。低通滤波,可用来平滑(模糊)图像。这三种滤波涵盖了从非常急剧(理想)的滤波到非常平滑(高斯)的滤波范围。布特沃斯滤波器的阶数,当阶数较高时,布特沃斯滤波器接近理想滤波器,对于较低的阶数,布特沃斯滤波器更像高斯滤波器。布特沃斯滤波器可以视为两种“极端”滤波器的过渡。
构建低通滤波器的过程,本质上是按照某些规则构建一个矩阵的过程。假设 H , W H, W H,W 分别代表图像快速傅里叶变换的高、宽,傅里叶谱的最大值(中心点)的位置在 (maxR, maxC),radius 代表截断频率, D ( r , c ) D(r,c) D(r,c) 代表中心位置的距离,其中 0 ≤ r < H , 0 ≤ c < C , D ( r , c ) = (r-maxR) 2 + (c-maxC) 2 0 \leq r < H, 0 \leq c < C, D(r,c) =\sqrt{\text{(r-maxR)}^2+\text{(c-maxC)}^2} 0≤r<H,0≤c<C,D(r,c)=(r-maxR)2+(c-maxC)2。下面根据以上假设,说明构建三种低通滤波器的规则。
理想低通滤波器
理想低通滤波器。记 ilpFilter = [ilpFilter ( r , c ) ] H × W \text{ilpFilter = [ilpFilter}(r,c)]_{H\times W} ilpFilter = [ilpFilter(r,c)]H×W,根据以下准则生成:
ilpFilter(r,c) = { 1 , D ( r , c ) ≤ radius 0 , D ( r , c ) > radius \text{ilpFilter(r,c)} = \begin{cases} 1, & D(r,c) \leq \text{radius}\\ 0, & D(r,c) > \text{radius} \end{cases} ilpFilter(r,c)={1,0,D(r,c)≤radiusD(r,c)>radius
巴特沃斯低通滤波器
巴特沃斯低通滤波器。记 blpFilter = [blpFilter ( r , c ) ] H × W \text{blpFilter = [blpFilter}(r,c)]_{H\times W} blpFilter = [blpFilter(r,c)]H×W,根据以下准则生成:
blpFilter(r,c) = 1 1 + [ D ( r , c ) / radius ] 2 n \text{blpFilter(r,c)} = \frac{1}{1 + [D(r,c)/ \text{radius}]^{2n}} blpFilter(r,c)=1+[D(r,c)/radius]2n1
其中 n 代表阶数。
高斯低通滤波器
高斯低通滤波器。记 glpFilter = [glpFilter ( r , c ) ] H × W \text{glpFilter = [glpFilter}(r,c)]_{H\times W} glpFilter = [glpFilter(r,c)]H×W,根据以下准则生成:
glpFilter(r,c) = e − D 2 ( r , c ) / 2 radius 2 \text{glpFilter(r,c)} = e^{-D^2(r,c)/ 2\text{radius}^2} glpFilter(r,c)=e−D2(r,c)/2radius2
这三种滤波器越靠近中心点位置的值越接近 1,越远离中心位置的值越小于 1,与傅里叶变换相乘后,相当于保留了低频信息,削弱或者移除了高频信息。
C++实现
构建滤波器
Mat createLPFilter(Size size, Point center, float radius, int type, int n)
{
Mat lpFilter = Mat::zeros(size, CV_32FC1);
int rows = size.height;
int cols = size.width;
if(radius <= 0)
{
return lpFilter;
}
// 构建理想低通滤波器
if(type == 0)
{
for(int r = 0; r < rows; r++)
{
for(int c = 0; c < cols; c++)
{
float norm2 = pow(abs(float(r - center.y)), 2) + pow(abs(float(c - center.x)), 2);
if(sqrt(norm2) < radius)
{
lpFilter.at<float>(r ,c) = 1;
}
else
{
lpFilter.at<float>(r ,c) = 0;
}
}
}
}
// 构建巴特沃斯低通滤波器
if(type == 1)
{
for(int r = 0; r < rows; r++)
{
for(int c = 0; c < cols; c++)
{
float norm2 = pow(abs(float(r - center.y)), 2) + pow(abs(float(c - center.x)), 2);
lpFilter.at<float>(r, c) = float(1.0 / (1.0 + pow(sqrt(norm2)/radius, 2.0*n)));
}
}
}
// 构建 高斯低通滤波器
if (type == 2) {
for(int r = 0; r < rows; r++)
{
for(int c = 0; c < cols; c++)
{
float norm2 = pow(abs(float(r - center.y)), 2) + pow(abs(float(c - center.x)), 2);
lpFilter.at<float>(r, c) = float(exp(-norm2 / (2 * pow(radius, 2.0))));
}
}
}
return lpFilter;
}
低通滤波实现,通过调整进度条(滤波器和截断频率)来观察低通滤波效果。
Mat I; // 输入的图像矩阵
Mat F; //图像的傅里叶变换
Point maxLoc; // 傅里叶谱的最大值的坐标
int radius = 20; // 截断频率
const int MAX_RADIUS = 100; //最大截断频率
Mat lpFilter; // 低通滤波器
int lpType = 0; // 低通滤波器的类型
const int MAX_LPTYPE = 2;
Mat F_lpFilter; // 低通傅里叶变换
Mat FlpSpectrum; // 低通傅里叶变换的傅里叶谱的灰度级
Mat result; // 低通滤波后的效果
string lpFilterspectrum = "1";
string outdir = "./images/";
// 回调函数:调整低通滤波器的类型及截断频率
void callback_lpFilter(int, void*);
int main()
{
/* 1.读取图像矩阵 */
I = imread("I1.jpg");
Mat gray;
Mat fGray;
cvtColor(I, gray, COLOR_BGR2GRAY);
gray.convertTo(fGray, CV_32FC1, 1.0, 0.0);
/* 2. 乘以 (-1)^(r+c) */
for(int r = 0; r < fGray.rows; r++)
{
for(int c = 0; c < fGray.cols; c++)
{
if((r+c)%2)
{
fGray.at<float>(r, c) *= -1;
}
}
}
/* 3,4.快速傅里叶变换 */
fft2Image(fGray, F);
// 幅度谱
Mat amplSpec;
amplitudeSpectrum(F, amplSpec);
Mat spectrum = graySpectrum(amplSpec);
imshow("spectrum", spectrum);
imwrite(outdir+"spectrum.jpg", spectrum);
// 找到幅度谱的最大值的坐标
minMaxLoc(spectrum, NULL, NULL, NULL, &maxLoc);
/* 低通滤波 */
namedWindow(lpFilterspectrum, WINDOW_AUTOSIZE);
createTrackbar("type", lpFilterspectrum, &lpType, MAX_LPTYPE, callback_lpFilter);
createTrackbar("radius", lpFilterspectrum, &radius, MAX_RADIUS, callback_lpFilter);
callback_lpFilter(0, 0);
waitKey(0);
return 0;
}
void callback_lpFilter(int, void*)
{
/* 5.构建低通滤波器 */
lpFilter = createLPFilter(F.size(), maxLoc, radius, lpType, 2);
/* 6.低通滤波器和图像的傅里叶变换点乘 */
F_lpFilter.create(F.size(), F.type());
for(int r = 0; r < F_lpFilter.rows; r++)
{
for(int c = 0; c < F_lpFilter.cols; c++)
{
//分别取出当前位置的快速傅里叶变换和理想低通滤波器的值
Vec2f F_rc = F.at<Vec2f>(r, c);
float lpFilter_rc = lpFilter.at<float>(r ,c);
// 低通滤波器和图像的快速傅里叶变换的对应位置相乘
F_lpFilter.at<Vec2f>(r, c) = F_rc * lpFilter_rc;
}
}
// 低通傅里叶变换的傅里叶谱
amplitudeSpectrum(F_lpFilter, FlpSpectrum);
// 低通傅里叶谱的灰度级显示
FlpSpectrum = graySpectrum(FlpSpectrum);
imshow(lpFilterspectrum, FlpSpectrum);
imwrite(outdir + "FlpSpectrum.jpg", FlpSpectrum);
/* 7,8. 对低通傅里叶变换执行傅里叶逆变换,并只取实部 */
dft(F_lpFilter, result, DFT_SCALE+DFT_INVERSE+DFT_REAL_OUTPUT);
/* 9.乘以 (-1)^(r+c) */
for(int r = 0; r < result.rows; r++)
{
for(int c = 0; c < result.cols; c++)
{
if((r+c)%2)
{
result.at<float>(r, c) *= -1;
}
}
}
// 将结果转换为 CV_8U 类型
result.convertTo(result, CV_8UC1, 1.0, 0);
/* 10.截取左上部分,其大小与输入图像大小相同 */
result = result(Rect(0, 0, I.cols, I.rows)).clone();
imshow("result", result);
imwrite(outdir + "lF.jpg", result);
}
低通滤波的效果模糊了灰度突变的区域,保留了灰度缓慢变化的区域,而且高斯低通滤波的效果比其他两者效果更好,显示更平滑。
三种常用的高通滤波
高通滤波会衰减傅里叶变换中的低频成分而不会扰乱高频信息。布特沃斯滤波器表现为理想滤波器的锐利性和高斯滤波器的宽阔平滑间的一种过渡。
理想高通滤波器
理想高通滤波器。记 ihpFilter = [ihpFilter ( r , c ) ] H × W \text{ihpFilter = [ihpFilter}(r,c)]_{H\times W} ihpFilter = [ihpFilter(r,c)]H×W,根据以下准则生成:
ihpFilter(r,c) = { 0 , D ( r , c ) ≤ radius 1 , D ( r , c ) > radius \text{ihpFilter(r,c)} = \begin{cases} 0, & D(r,c) \leq \text{radius}\\ 1, & D(r,c) > \text{radius} \end{cases} ihpFilter(r,c)={0,1,D(r,c)≤radiusD(r,c)>radius
巴特沃斯低通滤波器
巴特沃斯低通滤波器。记 bhpFilter = [bhpFilter ( r , c ) ] H × W \text{bhpFilter = [bhpFilter}(r,c)]_{H\times W} bhpFilter = [bhpFilter(r,c)]H×W,根据以下准则生成:
bhpFilter(r,c) = 1 − 1 1 + [ D ( r , c ) / radius ] 2 n \text{bhpFilter(r,c)} = 1- \frac{1}{1 + [D(r,c)/ \text{radius}]^{2n}} bhpFilter(r,c)=1−1+[D(r,c)/radius]2n1
其中 n 代表阶数。
高斯低通滤波器
高斯低通滤波器。记 ghpFilter = [ghpFilter ( r , c ) ] H × W \text{ghpFilter = [ghpFilter}(r,c)]_{H\times W} ghpFilter = [ghpFilter(r,c)]H×W,根据以下准则生成:
ghpFilter(r,c) = 1 − e − D 2 ( r , c ) / 2 radius 2 \text{ghpFilter(r,c)} = 1 - e^{-D^2(r,c)/ 2\text{radius}^2} ghpFilter(r,c)=1−e−D2(r,c)/2radius2
显然,高通滤波器和低通滤波器满足关系: hpFilter = 1 - lpFilter \text{hpFilter = 1 - lpFilter} hpFilter = 1 - lpFilter ,即 1 减去 createLPFilter
得到的矩阵就可以得到高通滤波器。代码与低通类似。
三种常用的带通滤波
带通滤波是指指保留某一范围区域的频率带,假设 BW \text{BW} BW 代表带宽, D 0 D_0 D0 代表带宽的径向中心,其他符号与低通滤波相同
理想带通滤波器
理想带通滤波器。记 ibpFilter = [ibpFilter ( r , c ) ] H × W \text{ibpFilter = [ibpFilter}(r,c)]_{H\times W} ibpFilter = [ibpFilter(r,c)]H×W,根据以下准则生成:
ibpFilter(r,c) = { 1 , D 0 − BW 2 ≤ D ( r , c ) ≤ D 0 + BW 2 0 , e l s e \text{ibpFilter(r,c)} = \begin{cases} 1, & D_0 - \frac{\text{BW}}{2} \leq D(r,c) \leq D_0 + \frac{\text{BW}}{2}\\ 0, & else \end{cases} ibpFilter(r,c)={1,0,D0−2BW≤D(r,c)≤D0+2BWelse
巴特沃斯带通滤波器
巴特沃斯带通滤波器。记 bbpFilter = [bbpFilter ( r , c ) ] H × W \text{bbpFilter = [bbpFilter}(r,c)]_{H\times W} bbpFilter = [bbpFilter(r,c)]H×W,根据以下准则生成:
bbpFilter(r,c) = 1 − 1 1 + ( D ∗ BW D ( r , c ) 2 − D 0 2 ) 2 n \text{bbpFilter(r,c)} = 1- \frac{1}{1 + (\frac{D*\text{BW}}{D(r,c)^2 - D_0^2})^{2n}} bbpFilter(r,c)=1−1+(D(r,c)2−D02D∗BW)2n1
其中 n 代表阶数。
高斯带通滤波器
高斯带通滤波器。记 gbpFilter = [gbpFilter ( r , c ) ] H × W \text{gbpFilter = [gbpFilter}(r,c)]_{H\times W} gbpFilter = [gbpFilter(r,c)]H×W,根据以下准则生成:
gbpFilter(r,c) = exp ( − ( D ( r , c ) 2 − D 0 2 D ( r , c ) ∗ BW ) 2 ) \text{gbpFilter(r,c)} = \exp({-(\frac{D(r,c)^2 - D_0^2}{D(r,c)*\text{BW}})^2}) gbpFilter(r,c)=exp(−(D(r,c)∗BWD(r,c)2−D02)2)
下图为带通滤波效果。前两张为 D 0 = 18 , BW = 23 D_0 = 18, \text{BW} = 23 D0=18,BW=23 的理想带通滤波效果。后几张为 D 0 = 50 , BW = 40 D_0 = 50, \text{BW} = 40 D0=50,BW=40 的带通滤波效果。
三种常用的带阻滤波器
与带通滤波相反,带阻滤波是指撤销或消弱指定范围区域的频率带。
理想带阻滤波器
理想带阻滤波器。记 ibrFilter = [ibrFilter ( r , c ) ] H × W \text{ibrFilter = [ibrFilter}(r,c)]_{H\times W} ibrFilter = [ibrFilter(r,c)]H×W,根据以下准则生成:
ibrFilter(r,c) = { 0 , D 0 − BW 2 ≤ D ( r , c ) ≤ D 0 + BW 2 1 , e l s e \text{ibrFilter(r,c)} = \begin{cases} 0, & D_0 - \frac{\text{BW}}{2} \leq D(r,c) \leq D_0 + \frac{\text{BW}}{2}\\ 1, & else \end{cases} ibrFilter(r,c)={0,1,D0−2BW≤D(r,c)≤D0+2BWelse
巴特沃斯带阻滤波器
巴特沃斯带阻滤波器。记 bbrFilter = [bbrFilter ( r , c ) ] H × W \text{bbrFilter = [bbrFilter}(r,c)]_{H\times W} bbrFilter = [bbrFilter(r,c)]H×W,根据以下准则生成:
bbrFilter(r,c) = 1 1 + ( D ∗ BW D ( r , c ) 2 − D 0 2 ) 2 n \text{bbrFilter(r,c)} = \frac{1}{1 + (\frac{D*\text{BW}}{D(r,c)^2 - D_0^2})^{2n}} bbrFilter(r,c)=1+(D(r,c)2−D02D∗BW)2n1
其中 n 代表阶数。
高斯带阻滤波器
高斯带阻滤波器。记 gbrFilter = [gbrFilter ( r , c ) ] H × W \text{gbrFilter = [gbrFilter}(r,c)]_{H\times W} gbrFilter = [gbrFilter(r,c)]H×W,根据以下准则生成:
gbrFilter(r,c) = 1 − exp ( − ( D ( r , c ) 2 − D 0 2 D ( r , c ) ∗ BW ) 2 ) \text{gbrFilter(r,c)} = 1- \exp({-(\frac{D(r,c)^2 - D_0^2}{D(r,c)*\text{BW}})^2}) gbrFilter(r,c)=1−exp(−(D(r,c)∗BWD(r,c)2−D02)2)
下图为带阻滤波效果
同态滤波背后的图像处理原理是基于图像由反射分量和入射分量乘积而形成的,是把频率滤波和空域灰度变换结合起来的一种图像处理方法,它根据图像的照度/反射率模型作为频域处理的基础,利用压缩亮度范围和增强对比度来改善图像的质量。步骤如下:
同态滤波与频率域滤波的不同之处是,它在最开始对输入的图像矩阵进行对数运算,在最后一步进行对数运算的逆运算,即指数运算,其中间步骤就是频率域滤波的步骤。
构建高频增强滤波器
heFilter ( r , c ) = (high-low) ∗ ( 1 − e − k ∗ D 2 ( r , c ) / 2 radius 2 ) + low \text{heFilter}(r ,c) = \text{(high-low)} * (1 - e^{-k*D^2(r,c)/2\text{radius}^2}) +\text{low} heFilter(r,c)=(high-low)∗(1−e−k∗D2(r,c)/2radius2)+low
其中 high > 1, low < 1。
Python 实现
# 第一步:读入图像
I = cv2.imread("I.jpg", 0)
cv2.imshow("I", I)
cv2.imwrite("I.jpg", I)
# 第二步:取对数
lI = np.log(I + 1.0)
lI = lI.astype(np.float32)
# 第三步:每一元素乘以 (-1)^(r+c)
fI = np.copy(lI)
for r in range(I.shape[0]):
for c in range(I.shape[1]):
if (r + c) % 2:
fI[r][c] = -1 * fI[r][c]
# 第四、五步:补零和快速傅里叶变换
fft2 = fft2Image(fI)
# 第六步:构造高频增强滤波器( high-emphasis Filter)
# 找到傅里叶谱中的最大值的位置
amplitude = amplitudeSpectrum(fft2)
minValue, maxValue, minLoc, maxLoc = cv2.minMaxLoc(amplitude)
# 滤波器的高和宽
rows, cols = fft2.shape[:2]
r, c = np.mgrid[0:rows:1, 0:cols:1]
c -= maxLoc[0]
r -= maxLoc[1]
d = np.power(c, 2.0) + np.power(r, 2.0)
high, low, k, radius = 2.5, 0.5, 1, 300
heFilter = (high - low) * (1 - np.exp(-k * d / (2.0 * pow(radius, 2.0)))) + low
# 第七步:快速傅里叶变换与高频增强滤波器的点乘
fft2Filter = np.zeros(fft2.shape, fft2.dtype)
for i in range(2):
fft2Filter[:rows, :cols, i] = fft2[:rows, :cols, i] * heFilter
# 第八、九步:高频增强傅里叶变换执行傅里叶逆变换,并只取实部
ifft2 = cv2.dft(fft2Filter, flags=cv2.DFT_REAL_OUTPUT + cv2.DFT_INVERSE + cv2.DFT_SCALE)
# 第十步:裁剪,和输入图像的尺寸一样
ifI = np.copy(ifft2[:I.shape[0], :I.shape[1]])
# 第十一步:每一元素乘以 (-1)^(r+c)
for i in range(ifI.shape[0]):
for j in range(ifI.shape[1]):
if (i + j) % 2:
ifI[i][j] = -1 * ifI[i][j]
# 第十二步:取指数
eifI = np.exp(ifI) - 1
# 第十三步:归一化,并进行数据类型转换
eifI = (eifI - np.min(eifI)) / (np.max(eifI) - np.min(eifI))
eifI = 255 * eifI
eifI = eifI.astype(np.uint8)
cv2.imshow("homomorphicFilter", eifI)
cv2.imwrite("homomorphicFilter.jpg", eifI)
cv2.waitKey(0)
cv2.destroyAllWindows()
使用同态滤波处理后可以看到原图中更多的信息。