OpenCV实现Matlab的fft2、ifft2函数

看标题就知道跟我的上一篇博客差不多了,本来没打算写这篇的,因为之前在网上搜过,有现成的OpenCV实现fft2和ifft2代码,而且我也试过fft2确实和Matlab计算的结果一致。但是项目后来又用到了ifft2,这次发现计算结果跟Matlab怎么都对不上,实在没办法,我只能自己摸索,后面也对网上的fft2代码进行了完善,现在一起发出来。

要用OpenCV实现傅里叶变换,首先要知道复数在OpenCV中是如何表示的,然后才能理解fft2和ifft2的实现。

一、复数在OpenCV中的表示方式

OpenCV的mat类没有专门的复数数据类型,可以将两个二维的Mat,一个存储实部,一个存储虚部,merge在一起,形成存储虚数的Mat。或者直接定义类似CV_64FC2格式的Mat,如下所示:

// 第一种方式,定义实部和虚部
Mat real = Mat::zeros(3, 3, CV_64F);
Mat imag = Mat::zeros(3, 3, CV_64F);
for (int i = 0; i < 3; i++)
{
    for (int j = 0; j < 3; j++)
    {
        real.at<double>(i, j) = i * 3 + j;
        imag.at<double>(i, j) = i * 3 + j;
    }
}
Mat planes[2] = { real, imag };
Mat complex;
merge(planes, 2, complex);

// 第二种方式,直接定义“复数”格式的Mat
Mat complex2 = Mat::zeros(3, 3, CV_64FC2);
for (int i = 0; i < 3; i++)
{
    for (int j = 0; j < 3; j++)
    {
        complex2.at<Vec2d>(i, j)[0] = i * 3 + j;
        complex2.at<Vec2d>(i, j)[1] = i * 3 + j;
    }
}

虽然第二种方式更简捷,但OpenCV的Mat矩阵运算基本上都不支持复数格式的数据,往往需要把复数拆分成实部和虚部,再分别进行计算,所以第二种还是需要转换成第一种的形式。另外再补充一下,输出复数的两种方法,这样就能方便地和Matlab比较结果了。

// 第一种输出实部、虚部、复数的方式(点坐标)
cout << complex2.at<Vec2d>(i, j)[0];
cout << complex2.at<Vec2d>(i, j)[1];
cout << complex2.at<Vec2d>(i, j);

// 第二种输出实部、虚部、复数的方式(复数格式)
cout << complex2.at<std::complex<double>>(i, j).real();
cout << complex2.at<std::complex<double>>(i, j).imag();
cout << complex2.at<std::complex<double>>(i, j);

注意at指针后面<>里面的内容,本文统一采用的是double格式数据。其实CV_64FC2的每个元素就是点坐标,点坐标的第一个值对应实部,第二个对应虚部。比较起来第二种方式更直观,但是写法稍复杂一点。另外第一种和第二种输出复数的表现形式有一点差别,如下图所示:
这里写图片描述

二、fft2的实现

OpenCV有自带的实现离散傅里叶函数:dft,但如果直接使用,往往得到的结果不太正确。此时我们可以参考官方给出的案例,具体就是,当输入的Mat数据格式是CV_64F时,需要将其转换为CV_64FC2格式。所以这里我们就要做个判断,根据输入的Mat数据格式,选择不同的处理方式。判断数据格式可以采用Mat自带的type函数,当格式为CV_8U~CV_64F时,该函数返回值为0~6,当格式为CV_8UC2~CV_64FC2时,返回值为8~14。至此,下面给出OpenCV实现fft2函数的代码。

void fft2(const Mat &src, Mat &Fourier)
{
    int mat_type = src.type();
    assert(mat_type<15); //不支持的数据格式

    if (mat_type < 7)
    {
        Mat planes[] = { Mat_<double>(src), Mat::zeros(src.size(),CV_64F) };
        merge(planes, 2, Fourier);
        dft(Fourier, Fourier);
    }
    else // 7<mat_type<15
    {
        Mat tmp;
        dft(src, tmp);
        vector<Mat> planes;
        split(tmp, planes);
        magnitude(planes[0], planes[1], planes[0]); //将复数转化为幅值
        Fourier = planes[0];
    }       
}

三、ifft2的实现

ifft2和fft2十分类似,唯一的不同就是调用dft函数时使用的参数,一定要加上DFT_SCALE,对结果进行缩放,这样才能达到与Matlab的ifft2函数相同的效果。下面是具体代码。

void ifft2(const Mat &src, Mat &Fourier)
{
    int mat_type = src.type();
    assert(mat_type<15); //不支持的数据格式

    if (mat_type < 7)
    {
        Mat planes[] = { Mat_<double>(src), Mat::zeros(src.size(),CV_64F) };
        merge(planes, 2, Fourier);
        dft(Fourier, Fourier, DFT_INVERSE + DFT_SCALE, 0);
    }
    else // 7<mat_type<15
    {
        Mat tmp;
        dft(src, tmp, DFT_INVERSE + DFT_SCALE, 0);
        vector<Mat> planes;
        split(tmp, planes);
        magnitude(planes[0], planes[1], planes[0]); //将复数转化为幅值
        Fourier = planes[0];
    }
}

PS

需要注意的是,fft2和ifft2返回的结果都是CV_64F(double)类型的,自己可以根据需要改成float型。同样地,相关的代码已经更新到了我的Github上,欢迎提意见。另外再多说几句,目前这已经是第四篇个人原创技术博客了,可是四篇的浏览量加起来才100出头,简直是在自言自语,略尴尬。自我感觉写的东西还是有点用的,特别是Matlab代码转OpenCV的时候,希望等百度能搜到我的博客后,情况会好吧。

猜你喜欢

转载自blog.csdn.net/dageda1991/article/details/78163436