opencvdft离散傅立叶变换(把空域变成频域)

傅立叶变换原理: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);
}

原文地址:https://www.cnblogs.com/liming19680104/p/15571744.html