傅立叶变换原理:http://daily.zhihu.com/story/3935067
https://www.zhihu.com/question/22085329/answer/774074211
频域:一种描述信号在频率方面特性的坐标系
6.png
#include<opencv2/opencv.hpp>] #include<iostream> void fftshift(cv::Mat& plane0, cv::Mat& plane1); int main(int argc, char** argv) { cv::Mat test = cv::imread("D:/bb/tu/6.png", 0); test.convertTo(test, CV_32FC1); cv::Mat plane[] = { test.clone(), cv::Mat::zeros(test.size() , CV_32FC1) }; cv::Mat complexIm; cv::merge(plane, 2, complexIm); // 合并通道 cv::dft(complexIm, complexIm, 0); // 进行傅立叶变换(把空域变成频域) /* 参数1:InputArray src: 输入图像,可以是实数或虚数;必须是单通道或双通道,数据类型必须是float 对于单通道:输入被认为是实数,输出结果保存为CCS格式 对于双通道:输入分别被认为是实数和虚数部分---建议双通道,便于区分实数和虚数 第一通道是实数,第二通道是虚数 参数2:OutputArray dst: 输出图像,其大小和类型取决于第三个参数flags 参数3:int flags: 转换的标识符,有默认值0.其可取的值如下所示: DFT_INVERSE=1: 用一维或二维逆变换取代默认的正向变换 DFT_SCALE=2: 缩放比例标识符,根据数据元素个数平均求出其缩放结果,如有N个元素, 则输出结果以1/N缩放输出,常与DFT_INVERSE搭配使用 DFT_ROWS=4: 对输入矩阵的每行进行正向或反向的傅里叶变换;此标识符可在处理多种适 量的的时候用于减小资源的开销,这些处理常常是三维或高维变换等复杂操作 DFT_COMPLEX_OUTPUT=16: 对一维或二维的实数数组进行正向变换,这样的结果虽然是复数 阵列,但拥有复数的共轭对称性(CCS),可以以一个和原数组尺寸大小相同的实数 数组进行填充,这是最快的选择也是函数默认的方法。你可能想要得到一个全尺寸 的复数数组(像简单光谱分析等等),通过设置标志位可以使函数生成一个全尺寸 的复数输出数组 DFT_REAL_OUTPUT=32: 对一维二维复数数组进行逆向变换,这样的结果通常是一个尺寸相同 的复数矩阵,但是如果输入矩阵有复数的共轭对称性(比如是一个带有DFT_COMPLEX_OUTPUT 标识符的正变换结果),便会输出实数矩阵 参数4:int nonzeroRows = 0: 在绝大多数DFT的算法中,建议的尺寸都是2的指数。 cv::getOptimalDFTSize()帮助获得这个尺寸 离散傅里叶变换的运算速度与图片的尺寸息息相关,当图像的尺寸是2、3、5的整数倍时,计算速度最快 nonzeroRows表示多少Rows不参与这个简化运算 */ cv::split(complexIm, plane);// 分离通道 //即planes[0]为实部---幅度图像 //planes[1]为虚部---相位图像 fftshift(plane[0], plane[1]); cv::Mat mag, mag_log, mag_nor, mag_log_nor; cv::magnitude(plane[0], plane[1], mag); //计算二维矢量的幅值 //第一个参数:InputArray类型的x,表示矢量的浮点型X坐标值,也就是实部 //第二个参数:InputArray类型的y,表示矢量的浮点型Y坐标值,也就是虚部 //第三个参数:OutputArray类型的magnitude,输出的幅值,它和第一个参数X有着同样的尺寸和类型 //【幅值图 大于等于1是白色 小于1是黑色 幅值>0】 // 幅值对数化:log(1+m),便于观察频谱信息 mag += cv::Scalar::all(1); cv::log(mag, mag_log); cv::normalize(mag, mag_nor, 1, 0, cv::NORM_MINMAX);//归一化 //归一化之后 越靠近1越亮 cv::normalize(mag_log, mag_log_nor, 1, 0, cv::NORM_MINMAX); cv::Mat BLUR; // 再次搬移回来进行逆变换 fftshift(plane[0], plane[1]); cv::merge(plane, 2, BLUR); // 实部与虚部合并 cv::idft(BLUR, BLUR); //把频域变成空域 // idft结果也为复数---第一通道为原图像 BLUR = BLUR / BLUR.rows / BLUR.cols; //让数据正常 cv::split(BLUR, plane);//分离通道 cv::Mat p = plane[0].clone(); cv::waitKey(0); return 0; } // fft变换后进行频谱搬移-->搬移目的:将亮度集中于中心 void fftshift(cv::Mat& plane0, cv::Mat& plane1) { // 以下的操作是移动图像 (零频移到中心) int cx = plane0.cols / 2; int cy = plane0.rows / 2; cv::Mat part1_r(plane0, cv::Rect(0, 0, cx, cy)); // 元素坐标表示为(cx, cy) //part1_r与plane[0]共享数据 cv::Mat part2_r(plane0, cv::Rect(cx, 0, cx, cy)); cv::Mat part3_r(plane0, cv::Rect(0, cy, cx, cy)); cv::Mat part4_r(plane0, cv::Rect(cx, cy, cx, cy)); cv::Mat temp; part1_r.copyTo(temp); //左上与右下交换位置(实部) part4_r.copyTo(part1_r); temp.copyTo(part4_r); part2_r.copyTo(temp); //右上与左下交换位置(实部) part3_r.copyTo(part2_r); temp.copyTo(part3_r); cv::Mat part1_i(plane1, cv::Rect(0, 0, cx, cy)); //元素坐标(cx,cy) cv::Mat part2_i(plane1, cv::Rect(cx, 0, cx, cy)); cv::Mat part3_i(plane1, cv::Rect(0, cy, cx, cy)); cv::Mat part4_i(plane1, cv::Rect(cx, cy, cx, cy)); part1_i.copyTo(temp); //左上与右下交换位置(虚部) part4_i.copyTo(part1_i); temp.copyTo(part4_i); part2_i.copyTo(temp); //右上与左下交换位置(虚部) part3_i.copyTo(part2_i); temp.copyTo(part3_i); }