基本原理
离散傅里叶变换(Discrete Fourier reansform, 缩写为DFT),是指傅里叶变换在时域和频域上都呈现离散的形式,将时域信号的采样变换为在离散时间傅里叶变换频域的采样。简单来说,对一张图像做傅里叶变换,就是将它分解为 sin 和 cos 两部分,也就是将图像从空间域(spatial domain)转换到频域(frequency domian)。可以使用欧拉公式进行转换,如图所示:
变换结果F(u,v)是复数,包含实部和虚部两部分:
幅度图像包含了几乎所有我们需要的几何信息,一般我们只需要使用幅度图像。如果想通过傅里叶变换修改原图像,可以先修改幅度图像,再将幅度图像与相位图像进行逆变换得到结果图像。
但傅立叶系数的动态范围太大,无法在屏幕上显示出来。为了使用灰度值进行可视化,我们可以将线性刻度的幅值图转换为对数刻度:
M1=log(1+M)
opencv中傅里叶变换的API:
void dft(InputArray src, OutputArray dst, int flags=0, int nonzeroRows=0);
由公式(1)可得:
因此,也可以使用 dft() 函数进行傅里叶逆变换。
在图像处理中的应用
傅立叶变换在图像处理中有非常多的作用。因为不仅傅立叶分析涉及图像处理的很多方面,傅立叶的改进算法,比如离散余弦变换,gabor与小波在图像处理中也有重要的分量。傅里叶变换在以下几个方面都有应用:
以下对傅里叶变换在图像旋转方向的应用进行实现,共分为4个步骤:
(1)对原图进行DFT变换,得到复数F(u,v);
(2)由实部、虚部合成幅值图,并进行M1=log(1+M)转换;
(3)重新排布幅值图像的4个象限,使原点位于图像中心,高频在角落,并将幅度转化为[ 0, 255 ]
(4)使用霍夫变换查找直线,计算旋转角度,旋转图像
实现代码如下:
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
#include <ctime>
#include "math.h"
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
//以单通道(灰度)方式读取图像
const char* filename = "../data/blue.jpg";
Mat srcImg = imread(filename, CV_LOAD_IMAGE_GRAYSCALE);
if (srcImg.empty())
return -1;
Point center(srcImg.cols / 2, srcImg.rows / 2);
clock_t start = clock();
//将图像扩展到最佳大小,以获得更快的处理速度
//设置4个方向的边框宽度
//如果 borderType==BORDER_CONSTANT, 用(0,0,0)填充
Mat padded;
int opWidth = getOptimalDFTSize(srcImg.rows);
int opHeight = getOptimalDFTSize(srcImg.cols);
copyMakeBorder(srcImg, padded, 0, opWidth - srcImg.rows, 0, opHeight - srcImg.cols, BORDER_CONSTANT, Scalar::all(0));
Mat planes[] = {
Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F) };
Mat comImg;
//合并成一个双通道图像
merge(planes, 2, comImg);
//傅里叶变换
dft(comImg, comImg);
//计算幅值
//复数形式:planes[0]=Re(DFT(I)), planes[1]=Im(DFT(I)), magnitude=sqrt(Re^2+Im^2)
split(comImg, planes);
magnitude(planes[0], planes[1], planes[0]);
//转换到log的尺度下,从而能观察到图像(否则数据过界)
//M2=log(1+M1)
Mat magMat = planes[0];
magMat += Scalar::all(1);
log(magMat, magMat);
//剪切和重分布幅度图象限
//若有奇数行或奇数列,进行裁剪
magMat = magMat(Rect(0, 0, magMat.cols & -2, magMat.rows & -2));
//重新排布傅里叶图像的4个象限,使原点位于图像中心,高频在角落
int cx = magMat.cols / 2;
int cy = magMat.rows / 2;
Mat q0(magMat, Rect(0, 0, cx, cy));
Mat q1(magMat, Rect(0, cy, cx, cy));
Mat q2(magMat, Rect(cx, cy, cx, cy));
Mat q3(magMat, Rect(cx, 0, cx, cy));
//交换象限
Mat tmp;
q0.copyTo(tmp);
q2.copyTo(q0);
tmp.copyTo(q2);
q1.copyTo(tmp);
q3.copyTo(q1);
tmp.copyTo(q3);
//将幅度标准化至 [0,1], 再转为[0,255]
normalize(magMat, magMat, 0, 1, CV_MINMAX);
Mat magImg(magMat.size(), CV_8UC1);
magMat.convertTo(magImg, CV_8UC1, 255, 0); // 像素值*255+0
//以下部分是对频谱图像进行霍夫直线变换,找到角度值,利用该角度值对原图像进行旋转,多适用于文字图像
//转为二值图像
threshold(magImg, magImg, 150, 255, CV_THRESH_BINARY);
//旋转原图
//使用霍夫变换查找直线
vector<Vec2f> lines;
float pi180 = (float)CV_PI / 180;
Mat linImg(magImg.size(), CV_8UC3);
HoughLines(magImg, lines, 1, pi180, 500, 0, 0);
int numLines = lines.size();
for (int l = 0; l < numLines; l++)
{
float rho = lines[l][0], theta = lines[l][1];
Point pt1, pt2;
double a = cos(theta), b = sin(theta);
double x0 = a * rho, y0 = b * rho;
pt1.x = cvRound(x0 + 1000 * (-b));
pt1.y = cvRound(y0 + 1000 * (a));
pt2.x = cvRound(x0 - 1000 * (-b));
pt2.y = cvRound(y0 - 1000 * (a));
line(linImg, pt1, pt2, Scalar(255, 0, 0), 1, 8, 0);
}
//imwrite("imageText_line.jpg",linImg);
if (lines.size() == 3) {
cout << "found three angels:" << endl;
cout << lines[0][1] * 180 / CV_PI << endl << lines[1][1] * 180 / CV_PI << endl << lines[2][1] * 180 / CV_PI << endl << endl;
}
//Find the proper angel from the three found angels
float angel = 0;
float piThresh = (float)CV_PI / 90;
float pi2 = CV_PI / 2;
for (int l = 0; l < numLines; l++)
{
float theta = lines[l][1];
if (abs(theta) < piThresh || abs(theta - pi2) < piThresh)
continue;
else {
angel = theta;
break;
}
}
//计算旋转角度
//如果图像不是方的,旋转后可能会达不到预期效果
angel = angel < pi2 ? angel : angel - CV_PI;
if (angel != pi2) {
float angelT = srcImg.rows * tan(angel) / srcImg.cols;
angel = atan(angelT);
}
float angelD = angel * 180 / (float)CV_PI;
cout << "the rotation angel to be applied:" << endl << angelD << endl << endl;
clock_t end = clock();
cout << "花费了" << (double)(end - start) / CLOCKS_PER_SEC << "秒" << endl;
//旋转图像
Mat rotMat = getRotationMatrix2D(center, angelD, 1.0);
Mat dstImg = Mat::ones(srcImg.size(), CV_8UC3);
warpAffine(srcImg, dstImg, rotMat, srcImg.size(), 1, 0, Scalar(0, 0, 0));
return 0;
}
实现结果如下图所示:
本文仅对傅里叶变换在图像旋转方向的应用进行了实现,今后将针对其他几个领域进行举例实现。对于代码中的相关函数的使用,在图像旋转的实现中没有进行过多研究。如有必要,今后会重新修改本篇博文进行介绍。