傅里叶变换将图像分解成它的正和余弦分量。换句话说,它将图像从它的空间域变换到它的频域。其思想是,任何函数都可以精确地逼近无穷个正函数和余弦函数的和。傅里叶变换是一种方法。二维图像的傅里叶变换在数学上为:
这里f是空间域的图像值,F是频域的图像值。变换的结果是复数。可以通过实像和复像显示,也可以通过幅值和相位图像显示。然而,在整个图像处理算法中,只有幅值图像是有意义的,因为它包含了我们需要的关于图像几何结构的所有信息。然而,如果你打算在这些形式中对图像做一些修改,然后你需要重新转换它,你需要保留这两个。
DFT的性能取决于图像的大小。当图像大小是数字2、3和5的倍数时,它往往是最快的。因此,为了获得最大的性能,通常一个好主意是填充边界值的图像,以获得具有这些特征的大小。getOptimalDFTSize()返回这个最佳大小,我们可以使用copyMakeBorder()函数来扩展图像的边框(拓展的像素被初始化为0):
cv::Mat src;
src = cv::imread("D:\\QtProject\\Opencv_Example\\dft\\dft.png", cv::IMREAD_GRAYSCALE);
if (src.empty()) {
cout << "Cannot load image" << endl;
return;
}
show(src);
cv::Mat padded;
int m = cv::getOptimalDFTSize( src.rows );
int n = cv::getOptimalDFTSize( src.cols );
cv::copyMakeBorder(src, padded, 0, m - src.rows, 0, n - src.cols, cv::BORDER_CONSTANT, cv::Scalar::all(0));
傅里叶变换的结果是复数。这意味着对于每个图像值,结果是两个图像值。此外,频域范围比其对应空间域大得多。因此,我们通常至少以浮点格式存储这些数据。因此,我们将我们的输入图像转换为这种类型,并扩展它与另一个通道来保存复数值:
cv::Mat planes[] = {cv::Mat_<float>(padded), cv::Mat::zeros(padded.size(), CV_32F)};
cv::Mat complexI;
cv::merge(planes, 2, complexI);
可以就地计算(输入与输出相同):
cv::dft(complexI, complexI);
一个复数有一个实部(Re)和一个复数部(虚部Im)。DFT的结果是复数。DFT的振幅为:
cv::dft(complexI, complexI);
结果表明,傅里叶系数的动态范围太大,无法在屏幕上显示。我们有一些小的和一些高的变化值,我们不能这样观察。因此高的值都是白点,小的值都是黑点。为了使用灰度值来实现可视化,我们可以将线性刻度转换为对数尺度:
cv::split(complexI, planes);
cv::magnitude(planes[0], planes[1], planes[0]);
cv::Mat magI = planes[0];
magI += cv::Scalar::all(1);
cv::log(magI, magI);
在第一步,我们扩展了图像?好了,是时候扔掉这些新引入的价值观了。出于可视化的目的,我们也可以重新排列结果的象限,以便原点(0,0)与图像中心相对应。
magI = magI(cv::Rect(0, 0, magI.cols & -2, magI.rows & -2));
int cx = magI.cols/2;
int cy = magI.rows/2;
cv::Mat q0(magI, cv::Rect(0, 0, cx, cy));
cv::Mat q1(magI, cv::Rect(cx, 0, cx, cy));
cv::Mat q2(magI, cv::Rect(0, cy, cx, cy));
cv::Mat q3(magI, cv::Rect(cx, cy, cx, cy));
cv::Mat tmp;
q0.copyTo(tmp);
q3.copyTo(q0);
tmp.copyTo(q3);
q1.copyTo(tmp);
q2.copyTo(q1);
tmp.copyTo(q2);
这也是为了可视化的目的。我们现在有了幅值,但是这仍然超出了我们图像显示的0到1范围。我们使用cv::normalize()函数将值归一化到这个范围。
cv::normalize(magI, magI, 0, 1, cv::NORM_MINMAX);
cv::imshow("Input Image",src);
cv::imshow("spectrum magnitude", magI);
cv::Mat src;
src = cv::imread("D:\\QtProject\\Opencv_Example\\dft\\dft.png", cv::IMREAD_GRAYSCALE);
if (src.empty()) {
cout << "Cannot load image" << endl;
return;
}
show(src);
cv::Mat padded;
int m = cv::getOptimalDFTSize( src.rows );
int n = cv::getOptimalDFTSize( src.cols );
cv::copyMakeBorder(src, padded, 0, m - src.rows, 0, n - src.cols, cv::BORDER_CONSTANT, cv::Scalar::all(0));
cv::Mat planes[] = {cv::Mat_<float>(padded), cv::Mat::zeros(padded.size(), CV_32F)};
cv::Mat complexI;
cv::merge(planes, 2, complexI);
cv::dft(complexI, complexI);
cv::split(complexI, planes);
cv::magnitude(planes[0], planes[1], planes[0]);
cv::Mat magI = planes[0];
magI += cv::Scalar::all(1);
cv::log(magI, magI);
magI = magI(cv::Rect(0, 0, magI.cols & -2, magI.rows & -2));
int cx = magI.cols/2;
int cy = magI.rows/2;
cv::Mat q0(magI, cv::Rect(0, 0, cx, cy));
cv::Mat q1(magI, cv::Rect(cx, 0, cx, cy));
cv::Mat q2(magI, cv::Rect(0, cy, cx, cy));
cv::Mat q3(magI, cv::Rect(cx, cy, cx, cy));
cv::Mat tmp;
q0.copyTo(tmp);
q3.copyTo(q0);
tmp.copyTo(q3);
q1.copyTo(tmp);
q2.copyTo(q1);
tmp.copyTo(q2);
cv::normalize(magI, magI, 0, 1, cv::NORM_MINMAX); into a
cv::imshow("Input Image",src);
cv::imshow("spectrum magnitude", magI);