【opencv】教程代码 —ImgProc (8) 通过傅里叶变换对图像去除周期性噪声

8. periodic_noise_removing_filter.cpp通过傅里叶变换对图像去除周期性噪声

704fa1df8ec9cc434d0e45eedef74b81.png

0f192bb2ca83504b4bf61c93f412062c.png

/**
* @brief 你将学会如何在傅立叶域中去掉周期性噪声
* @author Karpushin Vladislav, karpushin@ngs.ru, https://github.com/VladKarpushin
*/
#include <iostream>   // 引入C++的标准输入输出库
#include "opencv2/highgui.hpp"   // 引入opencv库,用于图像的高级GUI操作
#include <opencv2/imgcodecs.hpp>   // 引入opencv库,用于读写图片
#include <opencv2/imgproc.hpp>  // 引入opencv库,用于图像处理


using namespace cv;  // 使用OpenCV的命名空间
using namespace std;   // 使用标准的命名空间


//声明函数原型
void fftshift(const Mat& inputImg, Mat& outputImg);
void filter2DFreq(const Mat& inputImg, Mat& outputImg, const Mat& H);
void synthesizeFilterH(Mat& inputOutput_H, Point center, int radius);
void calcPSD(const Mat& inputImg, Mat& outputImg, int flag = 0);


//定义一个字符串变量,用于存储命令行参数
const String keys =
"{help h usage ? |             | print this message   }"
"{@image          |period_input.jpg | input image name     }"
;


int main(int argc, char* argv[])   // 定义主函数,输入参数为命令行的参数数量及详细内容
{
    CommandLineParser parser(argc, argv, keys);   // 创建一个命令行解析对象
    string strInFileName = parser.get<String>("@image");   // 获取输入的图像文件名
    samples::addSamplesDataSearchSubDirectory("doc/tutorials/imgproc/periodic_noise_removing_filter/images");   //添加搜索子目录


    Mat imgIn = imread(samples::findFile(strInFileName), IMREAD_GRAYSCALE);  // 以灰度模式读取输入的图像文件
    if (imgIn.empty()) // 检查图像是否加载成功
    {
        cout << "ERROR : Image cannot be loaded..!!" << endl;   // 如果加载不成功,输出错误信息
        return -1;  // 并返回错误代码
    }


    imshow("Image corrupted", imgIn);   // 显示原始图像
    imgIn.convertTo(imgIn, CV_32F);   // 将图像格式转换为浮点型


    // 只处理偶数像素的图像
    Rect roi = Rect(0, 0, imgIn.cols & -2, imgIn.rows & -2);   // 创建一个矩形,该矩形的大小为偶数像素的图像大小
    imgIn = imgIn(roi);   // 将矩形应用于图像,只保留在矩形内的像素


    // 计算图像的功率谱密度(PSD)
    Mat imgPSD;   // 定义一个Mat对象,用于存储PSD
    calcPSD(imgIn, imgPSD);   // 计算PSD
    fftshift(imgPSD, imgPSD);   // 进行快速傅立叶变换
    normalize(imgPSD, imgPSD, 0, 255, NORM_MINMAX);   // 对PSD进行归一化处理


    // 计算傅立叶变换的H函数
    Mat H = Mat(roi.size(), CV_32F, Scalar(1));   // 定义一个Mat对象,用于存储H函数
    const int r = 21;   // 定义一个变量,表示半径
    synthesizeFilterH(H, Point(705, 458), r);   // 生成H函数
    synthesizeFilterH(H, Point(850, 391), r);
    synthesizeFilterH(H, Point(993, 325), r);


    // 进行滤波操作
    Mat imgOut;   // 定义一个Mat对象,用于存储滤波后的图像
    fftshift(H, H);    // 对H函数进行快速傅立叶变换
    filter2DFreq(imgIn, imgOut, H);   // 对图像进行滤波操作
    imgOut.convertTo(imgOut, CV_8U);   // 将滤波后的图像类型转换为8位无符号整型
    normalize(imgOut, imgOut, 0, 255, NORM_MINMAX);   // 对滤波后的图像进行归一化处理
    imwrite("result.jpg", imgOut);   // 将滤波后的图像写入到result.jpg文件中
    imwrite("PSD.jpg", imgPSD);   // 写入PSD到PSD.jpg文件中
    fftshift(H, H);   // 对H函数进行快速傅立叶变换
    normalize(H, H, 0, 255, NORM_MINMAX);   //对H函数进行归一化处理
    imshow("Debluring", imgOut);   // 显示去噪后的图像
    imwrite("filter.jpg", H);   // 将H函数写入到filter.jpg文件中
    waitKey(0);   // 等待用户按键,如果用户不按键,程序会一直等待
    return 0;   // 主函数结束,返回0
}


//! [fftshift]
void fftshift(const Mat& inputImg, Mat& outputImg) // 定义一个fftshift函数,用于将图像的四个象限进行位置交换,参数为输入的图像inputImg和输出的图像outputImg
{
    outputImg = inputImg.clone();  // 克隆输入图像到输出图像
    int cx = outputImg.cols / 2;   // 计算图像列数的一半,即x轴上的中心点
    int cy = outputImg.rows / 2;   // 计算图像行数的一半,即y轴上的中心点


    //定义四个矩形块,均以左上角为起始点,大小为图像的四分之一,分别代表四个象限
    Mat q0(outputImg, Rect(0, 0, cx, cy));    // q0是左上角的区域
    Mat q1(outputImg, Rect(cx, 0, cx, cy));   // q1是右上角的区域
    Mat q2(outputImg, Rect(0, cy, cx, cy));   // q2是左下角的区域
    Mat q3(outputImg, Rect(cx, cy, cx, cy));  // q3是右下角的区域


    Mat tmp;   // 定义一个临时的Mat对象,用于在交换象限时临时存储数据
    q0.copyTo(tmp);  // 将q0复制到临时对象tmp
    q3.copyTo(q0);   // 将q3复制到q0
    tmp.copyTo(q3);  // 将tmp复制到q3, 到此q0与q3象限的图像数据已交换完毕


    q1.copyTo(tmp);  // 将q1复制到临时对象tmp
    q2.copyTo(q1);   // 将q2复制到q1
    tmp.copyTo(q2);  // 将tmp复制到q2,  此步操作在于交换q1与q2象限的图像数据,此时fftshift函数执行完毕
}
//! [fftshift]


//! [filter2DFreq]
void filter2DFreq(const Mat& inputImg, Mat& outputImg, const Mat& H) // 定义一个filter2DFreq函数,用于在傅立叶域进行滤波,参数包括输入图像inputImg,输出图像outputImg,和频率域滤波函数H
{
    Mat planes[2] = { Mat_<float>(inputImg.clone()), Mat::zeros(inputImg.size(), CV_32F) };  // 创建两个Mat对象数组,即平面,第一个平面复制输入图像的像素数据,第二个平面为同等大小的零矩阵,用于存储傅立叶变换的虚部
    Mat complexI;  // 定义一个复数Mat,用于存放复数形式的图像像素数据
    merge(planes, 2, complexI);  // 将前面创建的两个平面合并为一个Mat,即复数形式的图像像素数据
    
    dft(complexI, complexI, DFT_SCALE);  // 对复数平面的图像像素进行傅立叶变换


    Mat planesH[2] = { Mat_<float>(H.clone()), Mat::zeros(H.size(), CV_32F) };  // 对滤波器也进行相同的平面创建操作
    Mat complexH;   // 定义一个复数Mat,用于存放复数形式的滤波器像素数据
    merge(planesH, 2, complexH);  // 将滤波器的两个平面合并为一个Mat,即复数形式的滤波器像素数据
    
    Mat complexIH;  // 定义一个结果复数Mat,用于存放傅立叶变换后的图像像素与滤波器的乘积
    mulSpectrums(complexI, complexH, complexIH, 0);  // 对两个进行傅立叶变换后的Mat对象进行乘积操作,结果赋值为complexIH


    idft(complexIH, complexIH);  //对乘积操作后的结果执行逆傅立叶变换,以便恢复原图像
    
    split(complexIH, planes);  // 将逆变换后的复数Mat拆分为两个平面
    outputImg = planes[0];  // 结果中实数部分的平面即为滤波后的图像
}
//! [filter2DFreq]


//! [synthesizeFilterH]
void synthesizeFilterH(Mat& inputOutput_H, Point center, int radius)  // 定义synthesizeFilterH函数,用于在滤波器中添加半径为radius的平面波,该平面波位于center, 参数包括滤波器的引用inputOutput_H,平面波的中心点center和半径radius。
{
    Point c2 = center, c3 = center, c4 = center;  // 定义中心点的映射点c2,c3,c4,这里以中心点center为基准
    c2.y = inputOutput_H.rows - center.y;  // c2是center相对于y轴的对称点
    c3.x = inputOutput_H.cols - center.x;  // c3是center相对于x轴的对称点
    c4 = Point(c3.x,c2.y);  // c4是center相对于原点的对称点


    //在滤波器上画出4个圆,并将其值设为0,其中圆心为center, 半径为radius,这四个圆对应频域上应该抑制的频率分量,对应抑制图像的噪声。
    circle(inputOutput_H, center, radius, 0, -1, 8);
    circle(inputOutput_H, c2, radius, 0, -1, 8);
    circle(inputOutput_H, c3, radius, 0, -1, 8);
    circle(inputOutput_H, c4, radius, 0, -1, 8);
}
//! [synthesizeFilterH]


// 函数通过FFT计算功率谱密度(PSD)
// flag = 0 表示返回PSD
// flag = 1 表示返回log(PSD) 
//! [calcPSD]
void calcPSD(const Mat& inputImg, Mat& outputImg, int flag)
{
   // 定义一个包含两个Mat的数组planes,第一个平面为输入图像的复制物,第二个平面为输入图像尺寸相同的全零矩阵
    Mat planes[2] = { Mat_<float>(inputImg.clone()), Mat::zeros(inputImg.size(), CV_32F) };
    Mat complexI;  // 定义一个复数型的Mat对象complexI
    merge(planes, 2, complexI); // 调用merge函数将平面数组合并成一个多通道数组complexI


    dft(complexI, complexI); // 利用dft对合并后的数组进行傅立叶变换


    split(complexI, planes); // 利用split函数将傅立叶变换后的复数型数组拆分到两个通道,planes[0]为复数的实部,planes[1]为复数的虚部


    // 将两个平面的(0,0)像素值设为0,消除直流分量
    planes[0].at<float>(0) = 0;  
    planes[1].at<float>(0) = 0;


    // 计算PSD = sqrt(Re(DFT(I))^2 + Im(DFT(I))^2)^2
    Mat imgPSD;  // 定义一个Mat对象imgPSD,用来存放功率谱密度图像
    magnitude(planes[0], planes[1], imgPSD);   // 计算每个像素的幅度值,其值为复数的模,即sqrt(Re^2 + Im^2)
    pow(imgPSD, 2, imgPSD); // 对幅度矩阵全体元素再求平方,得到PSD
    outputImg = imgPSD; // 将计算得到的功率谱密度图赋值给输出图像outputImg


    // flag=1时,PSD经过log处理,即logPSD = log(1 + PSD),log对数变换可以扩展低值区域的对比度,使图像更易观察。
    if (flag)
    {
        Mat imglogPSD;
        imglogPSD = imgPSD + Scalar::all(1); // calc log(1 + PSD) = logPSD
        log(imglogPSD, imglogPSD); // 取PSD的对数,得到log(PSD+1)
        outputImg = imglogPSD; // 将计算得到的logPSD赋值给输出图像outputImg
    }
}
//!

代码的主要功能是在傅立叶域中消除图像的周期性噪声。这是通过动态生成和应用滤波函数实现的,通过对影响图像质量的特定频率成分进行消除,达到了消除周期性噪声的效果。代码中同时实现了傅立叶逆变换,以便将处理结果重新转换回图像空间,生成最终的去噪结果。

94e2c509a449725fdfcdea6f01aa4e8a.png

  • 9
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是opencv通过傅里叶变换去除图像余弦噪声的C++代码: ```c++ #include <opencv2/opencv.hpp> using namespace cv; int main() { Mat img = imread("lena.jpg", IMREAD_GRAYSCALE); Mat noise(img.size(), CV_32F); randn(noise, 0, 32); // 添加高斯噪声 Mat noisyImg; img.convertTo(noisyImg, CV_32F); noisyImg += noise; // 添加噪声 Mat planes[] = {Mat_<float>(noisyImg), Mat::zeros(noisyImg.size(), CV_32F)}; Mat complexImg; merge(planes, 2, complexImg); // 构造复数图像 dft(complexImg, complexImg); // 傅里叶变换 Mat mag, phase; split(complexImg, planes); // 分离实部和虚部 magnitude(planes[0], planes[1], mag); // 计算幅值 phase = Mat(planes[1].size(), CV_32F); phase = atan2(planes[1], planes[0]); // 计算相位 Mat filteredMag; GaussianBlur(mag, filteredMag, Size(5, 5), 0); // 高斯滤波 Mat planes2[] = {filteredMag.mul(cos(phase)), filteredMag.mul(sin(phase))}; Mat complexImg2; merge(planes2, 2, complexImg2); // 构造复数图像 idft(complexImg2, complexImg2); // 傅里叶反变换 Mat planes3[] = {Mat::zeros(img.size(), CV_32F), Mat::zeros(img.size(), CV_32F)}; split(complexImg2, planes3); // 分离实部和虚部 Mat restoredImg; magnitude(planes3[0], planes3[1], restoredImg); // 计算幅值 normalize(restoredImg, restoredImg, 0, 255, NORM_MINMAX, CV_8U); // 归一化 imshow("Original Image", img); imshow("Noisy Image", noisyImg); imshow("Restored Image", restoredImg); waitKey(); return 0; } ``` 这段代码首先读入一张灰度图像,然后添加高斯噪声。接着,将图像转换为浮点型,并构造出复数图像。对复数图像进行傅里叶变换,得到实部和虚部。分别计算幅值和相位。对幅值进行高斯滤波,并根据相位重新构造出复数图像。对复数图像进行傅里叶反变换,得到恢复后的图像。最后,将恢复后的图像归一化并显示出来。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值