美文网首页OpenCV 学习笔记
OpenCV 笔记(35):频域低通滤波——高斯低通滤波器、巴特

OpenCV 笔记(35):频域低通滤波——高斯低通滤波器、巴特

作者: fengzhizi715 | 来源:发表于2024-07-06 21:49 被阅读0次

1. 高斯低通滤波器

高斯低通滤波器(GLPF)是一种具有平滑频域特性、较慢衰减速度和良好截止频率附近衰减效果的滤波器。在图像处理中有着广泛的应用。

高斯低通滤波器的传播函数有如下的形式:

H(u,v)= e^{-D^2(u,v)/2\sigma^2}

其中,D(u,v) 表示中心点到频域中心的距离,即 D(u,v) = \sqrt{[(u-P/2)^2+(v-Q/2)^2]}

\sigma 是关于中心分离度的测度。令 \sigma = D_0 ,则:H(u,v)= e^{-D^2(u,v)/2D_0^2}

其中,D_0 是截止频率,控制着滤波器的截止范围。

D(u,v) = D_0时,高斯低通滤波器下降到它最大值的 0.607 处。

D_0 值越大,允许通过的频率越高,滤波效果越弱;D_0 值越小,允许通过的频率越低,滤波效果越强。

高斯低通滤波器具有以下特性:

  • 平滑的频率响应: GLPF 的频率响应曲线呈高斯形状,在截止频率附近平滑衰减,在截止频率以上迅速衰减至零。这种平滑的频率响应使得 GLPF 能够有效地去除高频噪声而又不失真低频信号。
  • 良好的边缘保持能力: 由于 GLPF 的频率响应在截止频率附近比较平滑,因此它不会对图像的边缘造成明显的振铃效应,从而能够较好地保持图像的边缘细节。
  • 可控的截止频率: GLPF 的截止频率可以通过调整高斯函数的方差来控制,从而可以根据不同的应用需求来选择合适的截止频率。

下面的例子,展示了高斯低通滤波器的实现代码

#include <opencv2/opencv.hpp>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <random>

using namespace std;
using namespace cv;

void addSaltNoise(Mat &src, int num, Mat &dst)
{
    dst = src.clone();

    // 随机数产生器
    std::random_device rd; //种子
    std::mt19937 gen(rd()); // 随机数引擎

    auto rows = src.rows; // 行数
    auto cols = src.cols * src.channels();

    for (int i = 0; i < num; i++)
    {
        auto row = static_cast<int>(gen() % rows);
        auto col = static_cast<int>(gen() % cols);

        auto p = dst.ptr<uchar>(row);
        p[col++] = 255;
        p[col++] = 255;
        p[col] = 255;
    }
}

// 高斯低通滤波核函数
cv::Mat gaussian_low_pass_kernel(cv::Mat scr, float sigma)
{
    cv::Mat gaussianBlur(scr.size(), CV_32FC1);
    float d0 = sigma;
    for (int i = 0; i < scr.rows; i++) {
        for (int j = 0; j < scr.cols; j++) {
            float d = pow(float(i - scr.rows / 2), 2) + pow(float(j - scr.cols / 2), 2);//分子,计算pow必须为float型
            gaussianBlur.at<float>(i, j) = expf(-d / (2 * d0*d0));
        }
    }
    return gaussianBlur;
}

// fft 变换后进行频谱中心化
void fftshift(cv::Mat &plane0, cv::Mat &plane1)
{
    int cx = plane0.cols / 2;
    int cy = plane0.rows / 2;
    cv::Mat q0_r(plane0, cv::Rect(0, 0, cx, cy));  // 元素坐标表示为(cx, cy)
    cv::Mat q1_r(plane0, cv::Rect(cx, 0, cx, cy));
    cv::Mat q2_r(plane0, cv::Rect(0, cy, cx, cy));
    cv::Mat q3_r(plane0, cv::Rect(cx, cy, cx, cy));

    cv::Mat temp;
    q0_r.copyTo(temp);  //左上与右下交换位置(实部)
    q3_r.copyTo(q0_r);
    temp.copyTo(q3_r);

    q1_r.copyTo(temp);  //右上与左下交换位置(实部)
    q2_r.copyTo(q1_r);
    temp.copyTo(q2_r);

    cv::Mat q0_i(plane1, cv::Rect(0, 0, cx, cy));  //元素坐标(cx,cy)
    cv::Mat q1_i(plane1, cv::Rect(cx, 0, cx, cy));
    cv::Mat q2_i(plane1, cv::Rect(0, cy, cx, cy));
    cv::Mat q3_i(plane1, cv::Rect(cx, cy, cx, cy));

    q0_i.copyTo(temp);  //左上与右下交换位置(虚部)
    q3_i.copyTo(q0_i);
    temp.copyTo(q3_i);

    q1_i.copyTo(temp);  //右上与左下交换位置(虚部)
    q2_i.copyTo(q1_i);
    temp.copyTo(q2_i);
}

// 频率域滤波
cv::Mat frequency_filter(cv::Mat &src, cv::Mat &blur)
{
    Mat mask = src == src;
    src.setTo(0.0f, ~mask);

    // 创建一个双通道矩阵 planes,用来储存复数的实部与虚部
    Mat planes[] = {src.clone(), cv::Mat::zeros(src.size() , CV_32FC1) };

    Mat complexI;
    merge(planes, 2, complexI); // 合并通道 (把两个矩阵合并为一个2通道的Mat类容器)
    dft(complexI, complexI); // 进行傅立叶变换,结果保存在自身

    // 分离通道(数组分离)
    cv::split(complexI, planes);

    // 频谱中心化
    fftshift(planes[0], planes[1]);

    //  H(u, v) * F(u, v)
    Mat blur_r, blur_i, dst;
    multiply(planes[0], blur, blur_r);  // 滤波(实部与滤波器模板对应元素相乘)
    multiply(planes[1], blur, blur_i);  // 滤波(虚部与滤波器模板对应元素相乘)
    Mat planes1[] = {blur_r, blur_i };

    // 频谱中心化
    fftshift(planes1[0], planes1[1]);
    merge(planes1, 2, dst); // 实部与虚部合并

    // 傅里叶逆变换
    idft(dst, dst);       // idft 结果也为复数
    dst = dst / dst.rows / dst.cols;

    split(dst, planes1);//分离通道,主要获取通道

    return planes1[0];
}

int main()
{
    Mat src = imread(".../girl.jpg");
    imshow("src", src);

    Mat gray;
    cvtColor(src, gray, COLOR_BGR2GRAY);
    imshow("gray", gray);

    addSaltNoise(gray,100000,gray);
    imshow("add salt", gray);

    // 扩充边界
    int w = cv::getOptimalDFTSize(src.cols); // 获取DFT变换的最佳宽度
    int h = cv::getOptimalDFTSize(src.rows); // 获取DFT变换的最佳高度

    cv::Mat padded;
    // 常量法扩充图像边界,常量 = 0
    cv::copyMakeBorder(gray, padded, 0, h - src.rows, 0, w - src.cols, cv::BORDER_CONSTANT, cv::Scalar::all(0));
    padded.convertTo(padded, CV_32FC1);

    float d0 = 30.0f;
    cv::Mat gaussian_kernel = gaussian_low_pass_kernel(padded, d0);
    cv::Mat dst = frequency_filter(padded, gaussian_kernel);

    convertScaleAbs(dst, dst);
    imshow("dst", dst);

    waitKey(0);
    return 0;
}
灰度图像vs椒盐噪声vs高斯低通滤波的效果.png

2. 巴特沃斯低通滤波器

巴特沃斯低通滤波器(BLPF)是一种经典的滤波器类型,因其在通频带内的最大平坦幅度响应而得名,也称为最大平坦滤波器。其主要特点是通频带内的频率响应曲线最大限度平坦,没有起伏,而在阻频带则逐渐下降为零。

H(u,v)=\frac{1}{1+[\frac{D(u,v)}{D_0}]^{2n}}

巴特沃斯低通滤波器具有以下特性:

  • 在截止频率 D_0 以下的频率分量,其幅度基本保持不变,即滤波器对这些频率分量没有衰减;
  • 在截止频率 D_0 以上的频率分量,其幅度随着频率的增加而迅速衰减,衰减速度由滤波器阶数 n 决定。阶数越高,衰减速度越快。
  • 频域特性曲线平滑,没有振铃效应。

D_0 值越大,允许通过的频率越高,滤波效果越弱,图像越清晰;D_0 值越小,允许通过的频率越低,滤波效果越强,图像越模糊。

巴特沃斯低通滤波器的阶数的选择取决于具体的应用需求。

  • 如果需要快速去除高频噪声,应选择较高的滤波器阶数。 较高的滤波器阶数可以使滤波器对高频分量的衰减速度更快,从而更有效地去除高频噪声。
  • 如果需要保留更多图像细节,应选择较低的滤波器阶数。 较低的滤波器阶数可以使滤波器对低频分量的衰减速度更慢,从而保留更多图像细节。

下面的例子,展示了巴特沃斯低通滤波器的实现代码

#include <opencv2/opencv.hpp>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <random>

using namespace std;
using namespace cv;

void addSaltNoise(Mat &src, int num, Mat &dst)
{
    dst = src.clone();

    // 随机数产生器
    std::random_device rd; //种子
    std::mt19937 gen(rd()); // 随机数引擎

    auto rows = src.rows; // 行数
    auto cols = src.cols * src.channels();

    for (int i = 0; i < num; i++)
    {
        auto row = static_cast<int>(gen() % rows);
        auto col = static_cast<int>(gen() % cols);

        auto p = dst.ptr<uchar>(row);
        p[col++] = 255;
        p[col++] = 255;
        p[col] = 255;
    }
}

// 巴特沃斯低通滤波核函数
cv::Mat butterworth_low_kernel(cv::Mat &scr, float sigma, int n)
{
    cv::Mat butterworth_low_pass(scr.size(), CV_32FC1);
    float D0 = sigma;
    for (int i = 0; i < scr.rows; i++) {
        for (int j = 0; j < scr.cols; j++) {
            float d = sqrt(pow(float(i - scr.rows / 2), 2) + pow(float(j - scr.cols / 2), 2));
            butterworth_low_pass.at<float>(i, j) = 1.0f / (1.0f + pow(d / D0, 2 * n));
        }
    }
    return butterworth_low_pass;
}

// fft 变换后进行频谱中心化
void fftshift(cv::Mat &plane0, cv::Mat &plane1)
{
    int cx = plane0.cols / 2;
    int cy = plane0.rows / 2;
    cv::Mat q0_r(plane0, cv::Rect(0, 0, cx, cy));  // 元素坐标表示为(cx, cy)
    cv::Mat q1_r(plane0, cv::Rect(cx, 0, cx, cy));
    cv::Mat q2_r(plane0, cv::Rect(0, cy, cx, cy));
    cv::Mat q3_r(plane0, cv::Rect(cx, cy, cx, cy));

    cv::Mat temp;
    q0_r.copyTo(temp);  //左上与右下交换位置(实部)
    q3_r.copyTo(q0_r);
    temp.copyTo(q3_r);

    q1_r.copyTo(temp);  //右上与左下交换位置(实部)
    q2_r.copyTo(q1_r);
    temp.copyTo(q2_r);

    cv::Mat q0_i(plane1, cv::Rect(0, 0, cx, cy));  //元素坐标(cx,cy)
    cv::Mat q1_i(plane1, cv::Rect(cx, 0, cx, cy));
    cv::Mat q2_i(plane1, cv::Rect(0, cy, cx, cy));
    cv::Mat q3_i(plane1, cv::Rect(cx, cy, cx, cy));

    q0_i.copyTo(temp);  //左上与右下交换位置(虚部)
    q3_i.copyTo(q0_i);
    temp.copyTo(q3_i);

    q1_i.copyTo(temp);  //右上与左下交换位置(虚部)
    q2_i.copyTo(q1_i);
    temp.copyTo(q2_i);
}

// 频率域滤波
cv::Mat frequency_filter(cv::Mat &src, cv::Mat &blur)
{
    Mat mask = src == src;
    src.setTo(0.0f, ~mask);

    // 创建一个双通道矩阵 planes,用来储存复数的实部与虚部
    Mat planes[] = {src.clone(), cv::Mat::zeros(src.size() , CV_32FC1) };

    Mat complexI;
    merge(planes, 2, complexI); // 合并通道 (把两个矩阵合并为一个2通道的Mat类容器)
    dft(complexI, complexI); // 进行傅立叶变换,结果保存在自身

    // 分离通道(数组分离)
    cv::split(complexI, planes);

    // 频谱中心化
    fftshift(planes[0], planes[1]);

    //  H(u, v) * F(u, v)
    Mat blur_r, blur_i, dst;
    multiply(planes[0], blur, blur_r);  // 滤波(实部与滤波器模板对应元素相乘)
    multiply(planes[1], blur, blur_i);  // 滤波(虚部与滤波器模板对应元素相乘)
    Mat planes1[] = {blur_r, blur_i };

    // 频谱中心化
    fftshift(planes1[0], planes1[1]);
    merge(planes1, 2, dst); // 实部与虚部合并

    // 傅里叶逆变换
    idft(dst, dst);       // idft 结果也为复数
    dst = dst / dst.rows / dst.cols;

    split(dst, planes1);//分离通道,主要获取通道

    return planes1[0];
}

int main()
{
    Mat src = imread(".../girl.jpg");
    imshow("src", src);

    Mat gray;
    cvtColor(src, gray, COLOR_BGR2GRAY);
    imshow("gray", gray);

    addSaltNoise(gray,100000,gray);
    imshow("add salt", gray);

    // 扩充边界
    int w = cv::getOptimalDFTSize(src.cols); // 获取DFT变换的最佳宽度
    int h = cv::getOptimalDFTSize(src.rows); // 获取DFT变换的最佳高度

    cv::Mat padded;
    // 常量法扩充图像边界,常量 = 0
    cv::copyMakeBorder(gray, padded, 0, h - src.rows, 0, w - src.cols, cv::BORDER_CONSTANT, cv::Scalar::all(0));
    padded.convertTo(padded, CV_32FC1);

    float d0 = 160.0f;
    int n = 2;
    cv::Mat kernel = butterworth_low_kernel(padded, d0, n);
    cv::Mat dst = frequency_filter(padded, kernel);

    convertScaleAbs(dst, dst);
    imshow("dst", dst);

    waitKey(0);
    return 0;
}
灰度图像vs椒盐噪声vs巴特沃斯低通滤波的效果.png

巴特沃斯低通滤波器介于理想低通滤波器和高斯低通滤波器之间,阶数越高,其滤波特性越接近理想低通滤波器;阶数越低,其滤波特性越接近高斯低通滤波器。巴特沃斯低通滤波器可以根据需要选择不同的阶数来达到不同的滤波效果。

理想低通滤波器、高斯低通滤波器、巴特沃斯低通滤波器三者的区别:

滤波器 频域特性 优点 缺点 衰减速度 截止频率附近衰减 实现难度 应用
理想低通滤波器 矩形 最快的衰减速度 难以实现,存在振铃效应 最快 较少使用
高斯低通滤波器 高斯函数 没有振铃效应 截止频率附近的衰减效果较弱 平缓 良好 图像去噪、图像模糊
巴特沃斯低通滤波器 平滑的衰减曲线 没有振铃效应,截止频率附近的衰减效果好 中等 更好 中等 图像去噪、图像模糊、边缘检测

3. 总结

高斯低通滤波器(GLPF)和巴特沃斯低通滤波器(BLPF)都是常用的数字图像处理滤波器,用于平滑图像、去除噪声。两种滤波器都具有平滑的频域特性和良好的截止频率附近衰减效果。

高斯低通滤波器的频域特性呈高斯函数形状,衰减速度平缓,没有振铃效应。巴特沃斯低通滤波的频域特性介于理想低通滤波器和高斯低通滤波器之间,具有平滑的衰减曲线,没有振铃效应,且在截止频率附近的衰减效果比高斯低通滤波器更好。

相关文章

网友评论

    本文标题:OpenCV 笔记(35):频域低通滤波——高斯低通滤波器、巴特

    本文链接:https://www.haomeiwen.com/subject/vohncjtx.html