低通滤波器和高通滤波器

应用于图像低通滤波器和高通滤波器的实现

需要用到傅里叶变换


#include <opencv2/opencv.hpp>
#include <Eigen>
#include <iostream>
#include <vector>
#include <cmath>
#include <complex>

#define M_PI       3.14159265358979323846   // pi
// 对数幅度缩放
Eigen::MatrixXd logAmplitudeSpectrum(const Eigen::MatrixXd& spectrum) {
    return (spectrum.array() + 1).log();
}

//  乘幂尺度变换
Eigen::MatrixXd powerLawScaling(const Eigen::MatrixXd& spectrum, double gamma) {
    return spectrum.array().pow(gamma);
}

// 归一化 to [0, 1]
Eigen::MatrixXd normalize(const Eigen::MatrixXd& spectrum) {
    double minVal = spectrum.minCoeff();
    double maxVal = spectrum.maxCoeff();
    return (spectrum.array() - minVal) / (maxVal - minVal);
}

// 增强频谱显示
Eigen::MatrixXd enhanceSpectrumDisplay(const Eigen::MatrixXd& spectrum, double gamma = 1) {
    Eigen::MatrixXd logSpectrum = logAmplitudeSpectrum(spectrum);
    Eigen::MatrixXd powerScaledSpectrum = powerLawScaling(logSpectrum, gamma);
    return normalize(powerScaledSpectrum);
}


// 从复数矩阵中获取幅度谱和相位谱
void getAmplitudeAndPhaseSpectra(const Eigen::MatrixXcd& data, Eigen::MatrixXd& amplitude, Eigen::MatrixXd& phase) {

    amplitude = data.array().abs().matrix();
    phase = data.array().arg().matrix();
}

// 从幅度谱和相位谱重构复数矩阵
void reconstructFromAmplitudeAndPhase(const Eigen::MatrixXd& amplitude,
    const Eigen::MatrixXd& phase,
    Eigen::MatrixXcd& data)
{

    data = (amplitude.array() * (phase.array().cos() + std::complex<double>(0, 1) * phase.array().sin())).matrix();


}


// 1:**振幅谱低频移动到中心(频率平移)**:方便操作,利用象限对称互换 fft之后
Eigen::MatrixXd fftShift(const Eigen::MatrixXd &F)
{
    int M = F.rows();
    int N = F.cols();
    Eigen::MatrixXd F_shifted(M, N);

    int mid_M = M >> 1;
    int mid_N = N >> 1;

    // 交换第一象限和第三象限
    F_shifted.block(0, 0, mid_M, mid_N) = F.block(mid_M, mid_N, mid_M, mid_N);
    F_shifted.block(mid_M, mid_N, mid_M, mid_N) = F.block(0, 0, mid_M, mid_N);
    // 交换第二象限和第四象限
    F_shifted.block(0, mid_N, mid_M, mid_N) = F.block(mid_M, 0, mid_M, mid_N);
    F_shifted.block(mid_M, 0, mid_M, mid_N) = F.block(0, mid_N, mid_M, mid_N);

    return F_shifted;
}

// 2:振幅谱低频移动到中心  **图像进行-1幂操作**:然后经过fft变换后,低频会在振幅谱中间 fft之前
Eigen::MatrixXd imageShift(const Eigen::MatrixXd &image)
{
    int M = image.rows();
    int N = image.cols();
    Eigen::MatrixXd F_shifted(M, N);

    // 通过乘以 (-1)^(u+v) 来平移频率
    for (int u = 0; u < M; ++u)
    {
        for (int v = 0; v < N; ++v)
        {
            //(u + v) & 1 通过位的判断末尾如果是1 为奇数,为0,为偶数。 -1的奇次幂还是-1,偶次幂为1.
            F_shifted(u, v) = image(u, v) * ((u + v) & 1 ? -1 : 1);
        }
    }


    return F_shifted;
}

Eigen::MatrixXd tMatrixXd(const cv::Mat &img)
{
    Eigen::MatrixXd image(img.rows, img.cols);
    for (int i = 0; i < img.rows; ++i)
    {
        for (int j = 0; j < img.cols; ++j)
        {
            image(i, j) = img.at<float>(i, j);
        }
    }
    return image;
}
cv::Mat tMat(const Eigen::MatrixXd &img)
{
    cv::Mat image(img.rows(), img.cols(), CV_32FC1);
    for (int i = 0; i < img.rows(); ++i)
    {
        for (int j = 0; j < img.cols(); ++j)
        {
            image.at<float>(i, j) = img(i, j);
        }
    }
    return image;
}

//inv =true 为逆变换,false为 正变换
//FFT 使用Eigen库中的向量表示,方便二维计算
Eigen::VectorXcd FFT(const Eigen::VectorXcd& y, bool inv = false)
{
    Eigen::VectorXcd x = y;
    //数据的大小
    int N = x.size();


    // 按位反转
    for (int i = 1, j = 0; i < N; i++)
    {
        //bit = N/2
        int bit = N >> 1;
        /*
         * 我们正在查看j的二进制表示中的每一位。
            从左到右,我们检查每一位是否为1。
            对于每一个为1的位,我们将其反转为0。
            当我们遇到第一个为0的位时,循环终止。
        */
        for (; j & bit; bit >>= 1)
        {
            j ^= bit;
        }
        //进行异或运算(XOR运算的工作原理是:当两个比较的位相同时,结果是0;当两个比较的位不同时,结果是1。)
        j ^= bit;

        //当 i >j 表示前面已经替换了位置, i==j,表示位置不用变
        if (i < j)
        {
            std::swap(x[i], x[j]);
        }
    }


    // 预先计算旋转因子
    Eigen::VectorXcd w(N >> 1);
    //逆变换 = 1;傅里叶变换  = -1;
    double imag_i = inv ? 1.0 : -1.0;
    std::complex<double> tempW = std::exp(std::complex<double>(0, imag_i * 2 * M_PI / N));

    w[0] = 1;
    for (int i = 1; i < (N >> 1); ++i)
    {
        // w[i] = std::polar(1.0, imag_i * 2 * M_PI * i / N);
        //w[i] = std::pow(tempW, i);
        w[i] = w[i - 1] * tempW;
    }

    // 迭代FFT
    for (int len = 2; len <= N; len <<= 1)
    {
        int halfLen = len >> 1;
        int step = N / len;
        for (int i = 0; i < N; i += len)
        {
            for (int j = 0; j < halfLen; ++j)
            {
                std::complex<double> u = x[i + j];
                std::complex<double> v = x[i + j + halfLen] * w[j * step];
                x[i + j] = u + v;
                x[i + j + halfLen] = u - v;
            }
        }
    }

    //使用逆变换时
    if (inv)
    {
        for (std::complex<double>& a : x) {
            a /= N;
        }
    }
    return x;
}

//检查二维数据大小是否为2的幂数,不是则填充为0到2的幂数大小
Eigen::MatrixXd padToPowerOfTwo(const Eigen::MatrixXd& matrix) {
    int rows = matrix.rows();
    int cols = matrix.cols();

    // 确保输入的大小是2的幂
    int newRows = 1, newCols = 1;
    while (newRows < rows)
    {
        newRows <<= 1;
    }
    while (newCols < cols)
    {
        newCols <<= 1;
    }

    Eigen::MatrixXd paddedMatrix = Eigen::MatrixXd::Zero(newRows, newCols);
    paddedMatrix.block(0, 0, rows, cols) = matrix;

    return paddedMatrix;
}
// 离散傅里叶变换 -  二维
Eigen::MatrixXcd FFT2D(const Eigen::MatrixXcd& image, bool inv = false) {
    int rows = image.rows();
    int cols = image.cols();
    Eigen::MatrixXcd result(rows, cols);

    for (int i = 0; i < rows; i++) {
        Eigen::VectorXcd temp = image.row(i).transpose();
        result.row(i) = FFT(temp, inv);
    }

    for (int j = 0; j < cols; j++) {
        Eigen::VectorXcd temp = result.col(j);
        result.col(j) = FFT(temp, inv);
    }

    return result;
}



//创建高频  radius越小,越减少高频的部分,越大,越还原图像
Eigen::MatrixXd highFrequency (const Eigen::MatrixXd & data,int radius)
{

    int rows = data.rows();
    int cols = data.cols();

    // 创建高通滤波器(圆形掩码)
    Eigen::MatrixXd mask = Eigen::MatrixXd::Ones(rows, cols);

    //中心坐标
    int centerRow = rows >> 1;
    int centerCol = cols >> 1;


    //找到以 radius 大小的矩形范围内

    //左边位置
    int left = centerCol - radius;
    //超过边界 为0
    left = left > 0 ? left : 0;

    //右边位置
    int right = centerCol + radius;
    //超过边界 为0
    right = right < cols ? right : cols;

    //上边位置
    int top = centerRow - radius;
    //超过边界 为0
    top = top > 0 ? top : 0;

    //下边位置
    int down = centerRow + radius;
    //超过边界 为0
    down = down < rows ? down : rows;

    //在正矩形内画最大的圆
    for (int i = top; i < down; ++i)
    {
        for (int j = left; j < right; ++j)
        {
            //在图像中心画圆,半径不能超过
            double distance = std::sqrt(std::pow(i - centerRow, 2) + std::pow(j - centerCol, 2));
            if (distance <= radius)
            {
                mask(i, j) = 0.0;
            }
        }
    }
    return mask;

}
//创建高通滤波器 -
//简单的说,就是靠近频谱图中心的低频部分给舍弃掉,远离频谱图中心的高频部分保留。通常会保留物体的边界。
Eigen::MatrixXd  highPassFilter(const Eigen::MatrixXd & image, int radius = 0)
{
    int rows = image.rows();
    int cols = image.cols();

    //大小变为2的幂数
    Eigen::MatrixXd data = padToPowerOfTwo(image);
    // 计算傅里叶变换
    Eigen::MatrixXcd transformed = FFT2D(data);


    //获取幅度谱和相位谱
    Eigen::MatrixXd amplitude, phase;
    getAmplitudeAndPhaseSpectra(transformed, amplitude, phase);

    //振幅谱移动到中心(频率平移)
    amplitude = fftShift(amplitude);

    ///
    //增强振幅,用于观测 --  实际运算注释掉
    Eigen::MatrixXd amplitude1 = enhanceSpectrumDisplay(amplitude,1);
    cv::Mat highP = tMat(amplitude1);
    cv::imshow("highPassFilter",highP);
    ///

    //根据radius创建高频掩码
    Eigen::MatrixXd mask = highFrequency(amplitude, radius);
    amplitude = amplitude.array() * mask.array();


    //振幅谱移动到中心(频率平移)反转换
    amplitude = fftShift(amplitude);
    // 从幅度谱和相位谱重构复数矩阵
    reconstructFromAmplitudeAndPhase(amplitude, phase, transformed);


    // 计算逆变换
    Eigen::MatrixXcd reconstructed = FFT2D(transformed, true);

    return reconstructed.real().block(0, 0, rows, cols);
}
//创建低频  radius越小,越还原图像,越大,减少低频的部分,
Eigen::MatrixXd lowFrequency(const Eigen::MatrixXd & data,int radius)
{

    int rows = data.rows();
    int cols = data.cols();
    // 创建低通滤波器(圆形掩码)
    Eigen::MatrixXd mask = Eigen::MatrixXd::Zero(rows, cols);

    //中心坐标
    int centerRow = rows >> 1;
    int centerCol = cols >> 1;


    //找到以 radius 大小的矩形范围内

    //左边位置
    int left = centerCol - radius;
    //超过边界 为0
    left = left > 0 ? left : 0;

    //右边位置
    int right = centerCol + radius;
    //超过边界 为0
    right = right < cols ? right : cols;

    //上边位置
    int top = centerRow - radius;
    //超过边界 为0
    top = top > 0 ? top : 0;

    //下边位置
    int down = centerRow + radius;
    //超过边界 为0
    down = down < rows ? down : rows;

    //在正矩形内画最大的圆
    for (int i = top; i < down; ++i)
    {
        for (int j = left; j < right; ++j)
        {
            //在图像中心画圆,半径不能超过
            double distance = std::sqrt(std::pow(i - centerRow, 2) + std::pow(j - centerCol, 2));
            if (distance <= radius)
            {
                mask(i, j) = 1.0;
            }
        }
    }
    return mask;

}
//创建低通滤波器 -
//简单的说,就是靠近频谱图中心的低频部分给保留,远离频谱图中心的高频部分给去除掉。但是这会影响图像的清晰度。
Eigen::MatrixXd  lowPassFilter(const Eigen::MatrixXd & image, int radius = 0)
{
    int rows = image.rows();
    int cols = image.cols();

    //大小变为2的幂数
    Eigen::MatrixXd data = padToPowerOfTwo(image);
    // 计算傅里叶变换
    Eigen::MatrixXcd transformed = FFT2D(data);

    //获取幅度谱和相位谱
    Eigen::MatrixXd amplitude, phase;
    getAmplitudeAndPhaseSpectra(transformed, amplitude, phase);


    //振幅谱移动到中心(频率平移)
    amplitude = fftShift(amplitude);


    ///
//    //增强振幅,用于观测 --  实际运算注释掉
//    Eigen::MatrixXd amplitude1 = enhanceSpectrumDisplay(amplitude,1);
//    cv::Mat highP = tMat(amplitude1);
//    cv::imshow("lowPassFilter",highP);
    ///

    //根据radius创建高频掩码
    Eigen::MatrixXd mask = lowFrequency(amplitude, radius);
    amplitude = amplitude.array() * mask.array();


    //振幅谱移动到中心(频率平移)反转换
    amplitude = fftShift(amplitude);

    // 从幅度谱和相位谱重构复数矩阵
    reconstructFromAmplitudeAndPhase(amplitude, phase, transformed);


    // 计算逆变换
    Eigen::MatrixXcd reconstructed = FFT2D(transformed, true);

    return reconstructed.real().block(0, 0, rows, cols);
}



int main()
{

    cv::Mat img = cv::imread("193560523230866.png");
    if (img.empty())
    {
        std::cout << "请确定是否输入正确的图像文件" << std::endl;

    }
    cv::Mat gray;
    cvtColor(img, gray, cv::COLOR_BGR2GRAY);
    //图像转换CV_32F储存
    gray.convertTo(gray, CV_32F, 1 / 255.0, 0);

    //图像太大,用直接计算 耗时太长,缩小比例
    //resize(gray, gray, cv::Size(80, 80));

    //Mat  转 MatrixXd
    Eigen::MatrixXd image = tMatrixXd(gray);

    // 记录开始时间
    auto start = std::chrono::high_resolution_clock::now();

    //低频
    Eigen::MatrixXd low = lowPassFilter(image,50);
    //高频
    Eigen::MatrixXd high = highPassFilter(image, 50);
    // 记录结束时间
    auto stop = std::chrono::high_resolution_clock::now();
    // 计算持续时间
    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(stop - start);
    qDebug() << "代码运行时长: " << duration.count() << " 微秒" ;

     cv::Mat lowP = tMat(low);
    //+0.5 增加显示效果
    high = high.array() + 0.5;
    cv::Mat highP = tMat(high);

    cv::imshow("lowP",lowP);
    cv::imshow("highP",highP);

	return 0;
}

在这里插入图片描述

在这里插入图片描述

  • 振幅增强
    在这里插入图片描述
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/91025.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C语言】喝汽水问题

大家好&#xff01;今天我们来学习C语言中的喝汽水问题&#xff01; 目录 1. 题目内容&#xff1a; 2. 思路分析 2.1 方法一 2.2 方法二 2.3 方法三 3. 代码实现 3.1 方法一 3.2 方法二 3.3 方法三 1. 题目内容 喝汽水&#xff0c;1瓶汽水1元&#xff0c;2个空瓶可以…

Nacos配置管理服务

统一配置管理 功能&#xff1a;对配置文件相同的微服务进行配置文件的统一管理。 统一配置管理是解决场景&#xff1a;普通情况下&#xff0c;多个相同功能的微服务实例&#xff0c;更改配置的话得一个一个更改后重启的情况。 核心配置放在配置管理服务中&#xff0c;启动时…

隐秘的角落:Java连接Oracle提示Connection timed out

前言 这个报错相信各位后端开发都不陌生&#xff0c;大体的原因就那么几种&#xff1a; 检查网络连接&#xff1a;确保您的计算机与数据库服务器之间的网络连接正常。尝试通过其他方式验证您的网络连接是否正常。 检查数据库服务器状态&#xff1a;确保数据库服务器正在运行&…

研磨设计模式day09原型模式

目录 场景 代码实现 有何问题 解决方案 代码改造 模式讲解 原型与new 原型实例与克隆出来的实例 浅度克隆和深度克隆 原型模式的优缺点 思考 何时选用&#xff1f; 相关模式 场景 代码实现 定义订单接口 package com.zsp.bike.day08原型模式;/*** 订单的接口*…

计算机msvcp120.dll丢失的解决方法,非常靠谱的三个解决方法

今天&#xff0c;我将为大家分享一个关于电脑报错msvcp120.dll解决方法的话题。在日常生活中&#xff0c;我们可能会遇到这样的问题&#xff1a;电脑突然出现“程序无法正常运行”的提示&#xff0c;然后要求我们重新安装某个软件或者升级系统。这时候&#xff0c;我们很可能会…

【C++】做一个飞机空战小游戏(十一)——游戏过关、通关、结束的设置

[导读]本系列博文内容链接如下&#xff1a; 【C】做一个飞机空战小游戏(一)——使用getch()函数获得键盘码值 【C】做一个飞机空战小游戏(二)——利用getch()函数实现键盘控制单个字符移动【C】做一个飞机空战小游戏(三)——getch()函数控制任意造型飞机图标移动 【C】做一个飞…

函数的参数传递和返回值-PHP8知识详解

本文学习的是《php8知识详解》中的《函数的参数传递和返回值》。主要包括&#xff1a;向函数传递参数值、向函数传递参数引用、函数的返回值。 1、向函数传递参数值 函数是一段封闭的程序&#xff0c;有时候&#xff0c;程序员需要向函数传递一些数据进行操作。可以接受传入参…

Docker容器与虚拟化技术:Gitlab账户注册

目录 一、实验 1.gitlab 一、实验 1.gitlab (1) 概念 GitLab 是一个用于仓库管理系统的开源项目&#xff0c;使用Git作为代码管理工具&#xff0c;并在此基础上搭建起来的Web服务。 &#xff08;2&#xff09;官网 The DevSecOps Platform | GitLab &#xff08;3&#…

算法笔记:KD树

1 引入原因 K近邻算法需要在整个数据集中搜索和测试数据x最近的k个点&#xff0c;如果一一计算&#xff0c;然后再排序&#xff0c;开销过大 引入KD树的作用就是对KNN搜索和排序的耗时进行改进 2 KD树 2.1 主体思路 以空间换时间&#xff0c;利用训练样本集中的样本点&…

图神经网络与分子表征:番外——基组选择

学过高斯软件的人都知道&#xff0c;我们在撰写输入文件 gjf 时需要准备输入【泛函】和【基组】这两个关键词。 【泛函】敲定计算方法&#xff0c;【基组】则类似格点积分中的密度&#xff0c;与计算精度密切相关。 部分研究人员借用高斯中的一系列基组去包装输入几何信息&am…

Linux线程 --- 生产者消费者模型(C语言)

在学习完线程相关的概念之后&#xff0c;本节来认识一下Linux多线程相关的一个重要模型----“ 生产者消费者模型” 本文参考&#xff1a; Linux多线程生产者与消费者_红娃子的博客-CSDN博客 Linux多线程——生产者消费者模型_linux多线程生产者与消费者_两片空白的博客-CSDN博客…

Discuz!论坛发帖标题字数限制80字符可以修改吗?修改发帖标题字数的方法

Discuz!论坛发帖标题字数限制80字符修改方法 1.数据库修改2.修改JS验证字符数文件3.修改模板中写死的字符限制数4.修改函数验证文件5.修改语言包文件6.更新缓存 Discuz X3.4论坛网站帖子标题字数限制80字符&#xff0c;当我们想使用长标题的时候就得一删再删&#xff0c;实在是…

JAVA switch case 穿透问题

1&#xff0c;前提 其实开发中很少会用到switch &#xff0c;一般更倾向于if-else&#xff0c; 但是最近接手的项目&#xff0c;前人写的代码都用switch &#xff0c; 但是我一直以来对switch 的理解就跟if一样&#xff0c; 然后项目运用的时候才发现这玩意居然还有穿透问题 …

nginx(七十八)nginx配置http2

一 ngx_http_v2模块 1、本文不讲解HTTP2的知识2、只讲解nginx中如何配置HTTP2 ① 前置条件 1、openssl的版本必须在1.0.2e及以上2、开启https加密,目前http2.0只支持开启了https的网站编译选项&#xff1a;--with-http_ssl_module --with-http_v2_module 特点&#xff1a…

[管理与领导-51]:IT基层管理者 - 8项核心技能 - 6 - 流程

前言&#xff1a; 管理者存在的价值就是制定目标&#xff0c;即目标管理、通过团队&#xff08;他人&#xff09;拿到结果。 要想通过他人拿到结果&#xff1a; &#xff08;1&#xff09;目标&#xff1a;制定符合SMART原则的符合业务需求的目标&#xff0c;团队跳一跳就可以…

Vue3 [Day11]

Vue3的优势 create-vue搭建Vue3项目 node -v npm init vuelatest npm installVue3项目目录和关键文件 Vetur插件是Vue2的 Volarr插件是Vue3的 main.js import ./assets/main.css// new Vue() 创建一个应用实例 > createApp() // createRouter() createStore() // 将创建实…

基于JAYA算法优化的BP神经网络(预测应用) - 附代码

基于JAYA算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码 文章目录 基于JAYA算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码1.数据介绍2.JAYA优化BP神经网络2.1 BP神经网络参数设置2.2 JAYA算法应用 4.测试结果&#xff1a;5.Matlab代码 摘要…

容器技术,1. Docker,2. Kubernetes(K8s):

目录 容器技术 1. Docker&#xff1a; 2. Kubernetes&#xff08;K8s&#xff09;&#xff1a; Docker和Kubernetes 容器的主要应用场景有哪些&#xff1f; 容器技术 有效的将单个操作系统的资源划分到孤立的组中&#xff0c;以便更好的在孤立的组之间平衡有冲突的资源使…

Kotlin协程flow发送时间间隔debounce

Kotlin协程flow发送时间间隔debounce debounce的作用是让连续发射的数据之间间隔起来。典型的应用场景是搜索引擎里面的关键词输入&#xff0c;当用户输入字符时候&#xff0c;有时候&#xff0c;并不希望用户每输入任何一个单字就触发一次后台真正的查询&#xff0c;而是希望…

《Dive into Deep Learning》

《Dive into Deep Learning》&#xff1a;https://d2l.ai/ Interactive deep learning book with code, math, and discussionsImplemented with PyTorch, NumPy/MXNet, JAX, and TensorFlowAdopted at 500 universities from 70 countries 《动手学深度学习》中文版&#xff1…