OpenCV —— 频率域滤波(傅里叶变换,低通和高通滤波,带通和带阻滤波,同态滤波)

频率域滤波

    • 基本概念
    • 傅里叶变换
      • 二维离散的傅里叶变换
      • 快速傅里叶变换
      • 傅里叶幅度谱与相位谱
      • 谱残差显著性检测
      • 卷积与傅里叶变换的
    • 频率域滤波
      • 低通滤波和高通滤波
      • 带通和带阻滤波
      • 同态滤波

基本概念

频率域滤波 —— 百度百科

频率域滤波是对图像进行傅里叶变换,将图像由图像空间转换到频域空间,然后在频率域中对图像的频谱作分析处理,以改变图像的频率特征。

滤波: 狭义地说,滤波是指改变信号中各个频率分量的相对大小、或者分离出来加以抑制、甚至全部滤除某些频率分量的过程。广义地说,滤波是把某种信号处理成为另一种信号的过程。空间域滤波: 以像元与周围邻域像元的空间关系为基础,通过卷积运算实现图像滤波的一种方法。

为什么要在频率域中进行图像处理?

  1. 可以利用频率成分和图像外表之间的对应关系。一些在空间域表述困难的增强任务,在频率域中变得非常普通;
  2. 滤波在频率域更为直观,它可以解释空间域滤波的某些性质;
  3. 可以在频率域指定滤波器,做反变换,然后在空间域使用结果滤波器作为空间域滤波器的指导

谈到频率域,就不得不说傅里叶变换了。傅里叶是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,M1],y[0,N1] ,其对应的傅里叶变换为:
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=0M1v=0N1F(u,v)e(M2πux+N2πvy)i,0x<M,0y<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=0M1y=0N1f(x,y)e(M2πux+N2πvy)i,0u<M,0v<N
那么 F F F f f f 的傅里叶变换,称 f f f F F F 的傅立叶逆变换,表示为 f ⟺ F f \Longleftrightarrow F fF 。虽然我们讨论的图像矩阵是实数矩阵,但是所得到的 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=0M1[y=0N1f(x,y)eN2πvyi]eM2πuxi,0u<M,0v<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=U1FV1=MN1UFV

因为图像是实数矩阵,下图为对图像进行傅里叶变换处理的基本步骤,即先对图像矩阵进行傅里叶变换,然后进行傅里叶逆变换,接着取实部,就可以恢复原图像。

在这里插入图片描述

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:

  • DFT_COMPLEX_OUTPUT:输出复数形式
  • DFT_REAL_OUTPUT:只输出实部
  • DFT_INVERSE:傅立叶逆变换
  • DFT_SCALE:是否除以 M*N
  • DFT_ROWS:输入矩阵的每行进行傅里叶变换或者逆变换

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++示例

利用函数 getOptimalDFTSizedft 完成图像矩阵的傅里叶变换。

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+iImaginary

幅度谱,又称傅里叶谱,通过一下公式计算:
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=0M1y=0N1f(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=Amplitudecos(Phase)+iAmplitude.sin(Phase)
其中 . ∗ .* . 代表矩阵的点乘,即对应位置相乘。

下面对幅度谱和相位谱进行灰度级显示,注意区别幅度谱及其灰度级和相位谱及其灰度级,两个灰度级显示的目的是为了使人眼能够观察到对幅度谱和相位谱所做的特殊处理。

C++实现

因为幅度谱的最大值在 (0, 0) 处,即左上角,通常为了便于观察,需要将其移动到幅度谱的中心,那么需要在进行傅里叶变换前,将图像矩阵乘以 ( − 1 ) r + c (-1)^{r+c} (1)r+c 。整个步骤如下图

OpenCV —— 频率域滤波(傅里叶变换,低通和高通滤波,带通和带阻滤波,同态滤波)_第1张图片

函数 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 —— 频率域滤波(傅里叶变换,低通和高通滤波,带通和带阻滤波,同态滤波)_第2张图片

如下图,一些图的幅度谱的灰度级显示结果,会发现中心化后的傅里叶谱比较亮的区域大致与原图中的主要目标垂直。

OpenCV —— 频率域滤波(傅里叶变换,低通和高通滤波,带通和带阻滤波,同态滤波)_第3张图片

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,则该函数将以度为单位计算角度,否则,将以弧度为单位进行测量。

谱残差显著性检测

生物视觉研究表明,视觉注意机制是一种具有选择性的注意,它首先由视觉内容中最显著的、与其周围其他内容相比差异更大的成分引起的,然后根据观察者的主观意识去选择注意。视觉显著性检测可以看作抽取信息中最具差异的部分或者最感兴趣或首先关注的部分,赋予对图像分析的选择性能力,对提高图像的处理效率是极为重要的。

显著性检测的方法有很多,本文介绍一种简单、高效的基于幅度谱残差的方法,只要明白幅度谱和相位谱就可以实现这种方法。

原理详解

图像的傅里叶变换可以由幅度谱和相位谱表示,注意不是幅度谱和相位谱的灰度级,也就是通过已知的幅度谱和相位谱就可以还原图像。谱残差显著性检测通过改变幅度谱和相位谱来改变图像的显示,即只显示图像的显著目标。步骤如下:

  1. 计算图像的快速傅里叶变换矩阵 F F F
  2. 计算傅里叶变换的幅度谱的灰度级 graySpectrum
  3. 计算相位谱 phaseSpectrum,然后根据相位谱计算出对应的正弦谱和余弦谱
  4. 对第二步计算出的灰度级进行均值平滑,记为 f m e a n ( graySpectrum ) f_{mean}(\text{graySpectrum}) fmean(graySpectrum)
  5. 计算谱残差(spectralResidual)。谱残差的定义是第 2 步得到的幅度谱的灰度级减去第 4 步得到的平滑结果,即 spectralResidual = graySpectrum − f m e a n ( graySpectrum ) \text{spectralResidual} = \text{graySpectrum} - f_{mean}(\text{graySpectrum}) spectralResidual=graySpectrumfmean(graySpectrum)
  6. 对谱残差进行幂指数运算 exp(spectralResidual),即对谱残差矩阵中的每一个值进行指数运算
  7. 将第 6 步得到的幂指数作为新的 “幅度谱”,仍然使用原图的相位谱,根据新的 “幅度谱”和相位谱进行傅里叶逆变换,可得到一个复数矩阵
  8. 对于第 7 步得到的复数矩阵,计算该矩阵的实部和虚部的平方和的开方,然后进行高斯平滑,最后进行灰度级的转换,即得到显著性。

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);
}

OpenCV —— 频率域滤波(傅里叶变换,低通和高通滤波,带通和带阻滤波,同态滤波)_第4张图片

至此,通过幅度谱和相位谱等性质,我们对图像的傅里叶变换有了大体的认识,接下来介绍傅里叶变换的一个重要作用。在图像平滑 [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 Ik 具有 M + m − 1 M+m-1 M+m1 N + n − 1 N+n-1 N+n1 列,这里在全卷积的运算过程中,采用 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,0r<M,0c<Nelse,0r<M+m1,0c<N+n1
同样,在卷积核 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,0r<m,0c<nelse,0r<M+m1,0c<N+n1
假设 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 Ik 的傅里叶变换就等于 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} IkFT_Ip.FT_kp,这就是卷积定理。

利用傅里变换计算卷积,主要步骤为,首先计算两个傅里叶变换的点乘,然后进行傅里叶逆变换,并只取逆变换的实部。

通过快速傅里叶变换计算卷积

  1. I I I 进行边界填充,在上侧和下侧均补充 m − 1 2 \frac{m-1}{2} 2m1 行,在左侧和右侧均补充 n − 1 2 \frac{n-1}{2} 2n1 列。扩充策略和卷积的一样,效果比较好的是对边界进行镜像扩充,扩充后的结果记为 I_padded \text{I\_padded} I_padded,且行数为 M+m-1,列数为 N+n-1
  2. I_padded \text{I\_padded} I_padded k k k 的右侧和下侧扩充 0。为了利用快速傅里叶变换,将得到的结果记为 I_padded_zeros \text{I\_padded\_zeros} I_padded_zeros k_zeros \text{k\_zeros} k_zeros
  3. 计算 I_padded_zeros \text{I\_padded\_zeros} I_padded_zeros k_zeros \text{k\_zeros} k_zeros 的傅里叶变换,分别记为 fft2_Ipz \text{fft2\_Ipz} fft2_Ipz fft2_kz \text{fft2\_kz} fft2_kz
  4. 计算上述两个复数矩阵(傅里叶变换)的点乘 fft2_Ipkz = fft2_Ipz . ∗ fft2_kz \text{fft2\_Ipkz} = \text{fft2\_Ipz} .* \text{fft2\_kz} fft2_Ipkz=fft2_Ipz.fft2_kz
  5. 计算 fft2_Ipkz \text{fft2\_Ipkz} fft2_Ipkz 的傅里叶逆变换,然后只取实部,得到的是 full 卷积的结果 fullConv = Real(ifft2)(fft2_Ipkz) \text{fullConv} = \text{Real(ifft2)(fft2\_Ipkz)} fullConv=Real(ifft2)(fft2_Ipkz)
  6. 裁剪。从 fullConv \text{fullConv} fullConv 的左上角 (m-1, n-1) 开始裁剪到右下角 (m-1+M, n-1+N) 的位置,该区域就是 same 卷积的结果。

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 后的傅里叶变换就称为频率域滤波器,只是这些核的尺寸较小,一般直接采用卷积,不需要将其转换为频率域滤波器。在数学运算和代码中,频率域滤波器的呈现就是一个矩阵。接下来详细介绍在频率域上常用的滤波器。

频率域滤波

接下来了解两个重要的概念:低频和高频。低频指的是图像的傅里叶变换“中心位置”附近的区域。高频随着到“中心位置”距离的增加而增加,即傅里叶变换中心位置的外围区域,这里的“中心位置”指的是傅里叶变换所对应的幅度谱最大值的位置。

频率域滤波器在程序或者数学运算中的呈现可以理解为一个矩阵,该矩阵的宽、高和图像的傅里叶变换的宽、高是相同的,下面所涉及的常用的低通、高通、带通、带阻等滤波的关键步骤,就是通过一定的准则构建矩阵的。下图为频率域滤波的步骤:

OpenCV —— 频率域滤波(傅里叶变换,低通和高通滤波,带通和带阻滤波,同态滤波)_第5张图片

频率域滤波关键步骤就是通过一定的标准构造 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} 0r<H,0c<C,D(r,c)=(r-maxR)2+(c-maxC)2 。下面根据以上假设,说明构建三种低通滤波器的规则。

  1. 理想低通滤波器

    理想低通滤波器。记 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

  2. 巴特沃斯低通滤波器

    巴特沃斯低通滤波器。记 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 代表阶数。

  3. 高斯低通滤波器

    高斯低通滤波器。记 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)=eD2(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);
}

OpenCV —— 频率域滤波(傅里叶变换,低通和高通滤波,带通和带阻滤波,同态滤波)_第6张图片

低通滤波的效果模糊了灰度突变的区域,保留了灰度缓慢变化的区域,而且高斯低通滤波的效果比其他两者效果更好,显示更平滑。

三种常用的高通滤波

高通滤波会衰减傅里叶变换中的低频成分而不会扰乱高频信息。布特沃斯滤波器表现为理想滤波器的锐利性和高斯滤波器的宽阔平滑间的一种过渡。

  1. 理想高通滤波器

    理想高通滤波器。记 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

  2. 巴特沃斯低通滤波器

    巴特沃斯低通滤波器。记 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)=11+[D(r,c)/radius]2n1
    其中 n 代表阶数。

  3. 高斯低通滤波器

    高斯低通滤波器。记 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)=1eD2(r,c)/2radius2

显然,高通滤波器和低通滤波器满足关系: hpFilter = 1 - lpFilter \text{hpFilter = 1 - lpFilter} hpFilter = 1 - lpFilter ,即 1 减去 createLPFilter 得到的矩阵就可以得到高通滤波器。代码与低通类似。

OpenCV —— 频率域滤波(傅里叶变换,低通和高通滤波,带通和带阻滤波,同态滤波)_第7张图片

带通和带阻滤波

三种常用的带通滤波

带通滤波是指指保留某一范围区域的频率带,假设 BW \text{BW} BW 代表带宽, D 0 D_0 D0 代表带宽的径向中心,其他符号与低通滤波相同

  1. 理想带通滤波器

    理想带通滤波器。记 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,D02BWD(r,c)D0+2BWelse

  2. 巴特沃斯带通滤波器

    巴特沃斯带通滤波器。记 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)=11+(D(r,c)2D02DBW)2n1
    其中 n 代表阶数。

  3. 高斯带通滤波器

    高斯带通滤波器。记 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)2D02)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 的带通滤波效果。

OpenCV —— 频率域滤波(傅里叶变换,低通和高通滤波,带通和带阻滤波,同态滤波)_第8张图片

三种常用的带阻滤波器

与带通滤波相反,带阻滤波是指撤销或消弱指定范围区域的频率带。

  1. 理想带阻滤波器

    理想带阻滤波器。记 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,D02BWD(r,c)D0+2BWelse

  2. 巴特沃斯带阻滤波器

    巴特沃斯带阻滤波器。记 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)2D02DBW)2n1
    其中 n 代表阶数。

  3. 高斯带阻滤波器

    高斯带阻滤波器。记 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)=1exp((D(r,c)BWD(r,c)2D02)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)(1ekD2(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()

OpenCV —— 频率域滤波(傅里叶变换,低通和高通滤波,带通和带阻滤波,同态滤波)_第9张图片

使用同态滤波处理后可以看到原图中更多的信息。


你可能感兴趣的:(OpenCV,opencv)