DFT 离散傅里叶变换
图像在经过傅里叶变换后会分解为正弦和余弦成分,换句话说,会把一副图像从空间域转换到频域,任意的函数都可以被正弦和余弦函数精确逼近,傅里叶变换所实现的就是这种逼近。
1 |
|
二维图像的傅里叶变换公式:
f 是空间域中的图像值,F 是频域中的图像值,变换的结果是复数。如果想要观察这个结果,可以观察实数部分和虚数部分的图像,也可以观察变换结果的幅度和相位图像。在整个过程中只有幅度图像是我们感兴趣的部分,因为它包含着我们需要的所有图像几何信息,但是如果想要修改变换结果,那就需要用到所有部分。
上述例子展示了如何计算和显示傅里叶变换后的幅度图像,数字图像是离散的,像素取值是离散的,因此这里傅里叶变换也是离散的,如果想从几何角度了解图像结构就会用到这个方法。以下对程序主要步骤进行详细讲解:
图像最佳尺寸扩展
DFT 的性能与输入图像的尺寸有关,如果图像的尺寸是2、3、5的倍数,速度最快。为了实现最大性能,通常对图像边界进行补充,使得能够满足最佳尺寸要求。使用函数 getOptimalDFTSize()
会返回这个最佳尺寸,然后使用 copyMakeBorder()
函数来扩展图像 (扩展部分的像素值初始化为0)
创建变量存放实部和虚部
傅里叶变换的结果是复数,这意味着每幅图形的变换结果是两幅图像(复数的实部和虚部)。另外,频域取值范围远大于对应的空域部分,因此变换结果至少用float类型的数据。所以输入图像需要转化为该类型,并且增加一个通道存放虚部。
1 | Mat planes[] = {Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F)}; |
执行函数并将实部和虚部转换为幅值
幅值计算公式如下所示:
1 | dft(complexI, complexI); // this way the result may fit in the source matrix |
dft 计算后结果覆盖了原变量,然后将其实部和虚部进行了拆分,存放在 planes 数组中。magnitude 函数使用了上述公式,计算幅值并将结果保存在 planes[0] 中。
转换为对数刻度
傅里叶系数的动态范围太大,不适宜用图像显示,而且一些较小和快速变化的值不容易观察到,这会导致高值全部变为白点,小值则是黑点。为了进行灰度值可视化,可以将线性刻度转换为对数刻度:
1 | magI += Scalar::all(1); // switch to logarithmic scale |
截取并重新组合图像
在之前进行计算时为了简化流程我们将图像进行了扩展,现在为了可视化,可以重新组合图像象限,使得原点与图像中心对齐:
1 | // crop the spectrum, if it has an odd number of rows or columns |
这里值得一提的是第一行代码 magI = magI(Rect(0, 0, magI.cols & -2, magI.rows & -2))
,这里裁剪频谱使它的行数和列数都是偶数,以便后面进行重新组合。这里将宽度和高度与 -2 进行了位与运算,通常情况下整数和整数位与运算是针对其二进制数进行位与的,-2 的二进制表示常使用补码表示,在这里其八位二进制补码是 11111110,通过与运算能确保第一位永远是 0,即行和列永远是偶数。
这里交换象限的目的是让原来的图像中四个角点拼凑到中心点,也就是将频谱的原点移到图像中心便于分析,因为这些点的位置对应于频谱的低频信息,低频信息表示图像整体亮度和颜色分布,这里有几张截图可供参考:
归一化
上面经过对数变换后,数值可能仍然不在0和1之间。这里使用 cv::normalize()
函数进行变换:
1 | normalize(magI, magI, 0, 1, NORM_MINMAX); |
读写 XML 和 YAML 文件
XML 和 YAML 文件可用于保存算法参数或数据,比如 SVM、神经网络的训练结果等,在读写过程中,两者没有区别,仅仅是文件后缀名不同而已,但是用文本编辑器查看时,会发现 YAML 文件更为简洁,这一部分主要用 OpenCV 实现上述两种文件的读写,包括常见的数据类型、Mat 类型、自定义数据类型等。针对自定义的数据类型,通过添加读写函数,实现了与常见数据类型同样的读写方法。使用到的 opencv 数据类型有 cv::FileStorage , cv::FileNode 和 cv::FileNodeIterator。