您的位置:首页 > 运维架构

基于OpenCV和傅里叶变换的文本图片校正

2018-01-26 17:11 615 查看
之前没有接触过基于傅立叶变换的图像校正方法,偶然发现博主JohnHany的OpenCV实现基于傅立叶变换的旋转文本校正这篇文章比较详细,在一番研读和实际操作后体会颇多,现总结如下。
1.  傅立叶变换与图像处理

在信号处理中,傅立叶变换用于将时域信号转换到频域上。图像也可以看作一种平面空间上的二维信号,所以对图像也可以施加傅立叶变换,对图像施加傅立叶变换的结果,就是将其从空间域变换到频域中。

从实际意义上说,对图像进行二维傅立叶变换得到频谱图,就是图像梯度的分布图,如果频谱图中暗的点数更多,那么实际图像是比较柔和的;反之,如果频谱图中亮的点数多,那么实际图像一定是尖锐的,也就是边界分明且两边像素差异较大的。

2.  思路

利用这个特性,就可以产生一些很具实际意义的应用了,比如常见的横排版的文本图像,由于有文字的行整体上是较暗的,行间的空白是较亮的,这就自然形成了明暗交替的特征。通过上述傅立叶变换,即可在频谱图上产生垂直于行的亮线,再通过hough方法检测这条直线,并算得其与水平线的夹角,接着对原图施加合适角度的旋转变换即可校正原图了。当然,为了弥补由于旋转造成的图像部分空缺,还要对图像进行延扩处理。

3.  主要涉及OpenCV函数

(1)  void merge(const Mat* mv,size_t count, OutputArray dst)

用于矩阵融合,在本应用中,将要处理的图像作为傅立叶变换输入的实部,一个全零的矩阵作为虚部,函数merge()用来  融合这两个矩阵。

(2)  getOptimalDFTSize(intvecsize)

OpenCV中使用的快速傅立叶算法要求图像尺寸是2、3、5的倍数时候处理速度最快,这个函数可以根据实际待处理图像的尺寸找到合适的傅立叶变换输入尺寸,得到尺寸后用copyMakeBorder()填充多余部分。事实上,纯色填充导致后面的傅立叶谱图出现水平、垂直亮线,然而这并不影响我们判断文字行的方向。

(3)  dft(InputArray src,OutputArray dst, int flags=0, int nonzeroRows=0)

OpenCV傅立叶变换函数,其中的标志位flag表示一些转换参数,本次应用选用默认值0即可。

(4)  HoughLines(InputArrayimage, OutputArray lines, 

double rho, double theta, int threshold, 

doublesrn=0, double stn=0, 

double min_theta=0, double max_theta=CV_PI )

OpenCV中的hough直线检测函数,用于检测图像中的直线,实际上hough检测采用了一种“投票式”的检测直线方式,如有若干点(这个指标由参数threshold控制)在一条直线上,则认为图中存在这条直线。

(5)  getRotationMatrix2D(Point2fcenter, double angle, double scale)

获得旋转矩阵函数,提供给下述的仿射变换使用。

(6)  warpAffine(InputArraysrc, OutputArray dst,

 InputArray M, Size dsize,

 int flags=INTER_LINEAR,

int borderMode=BORDER_CONSTANT,

 const Scalar& borderValue=Scalar())

仿射变换,本应用使用了旋转仿射变换,用于对图像进行旋转操作。

4.  代码和解析

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

using namespace cv;
using namespace std;

#define GRAY_THRESH 150
#define HOUGH_VOTE 80 //霍夫检测的投票数,即需要多少点才能确定一条直线,根据lines窗口反馈调整

int main(int argc, char** argv)
{
//以灰度方式读入图像,并检查是否成功读取后显示图像
const char* filename = "C:\\Users\\Steve\\Desktop\\Picture correction - 副本\\text.jpg";
Mat srcImg = imread(filename, CV_LOAD_IMAGE_GRAYSCALE);
if (srcImg.empty())
return -1;
imshow("source", srcImg);

//获取图像中心点坐标
Point center(srcImg.cols / 2, srcImg.rows / 2);

//图像延扩
//设置四边尺寸,用getOptimalDFTSize()返回最优离散傅立叶变换(DFT)尺寸
//以BORDER_CONSTANT方法延扩图像,用白色填充空白部分
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));

//DFT
//DFT要分别计算实部和虚部,把要处理的图像作为输入的实部、一个全零的图像作为输入的虚部
Mat planes[] = { Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F) };
Mat comImg;
merge(planes, 2, comImg); //实虚部合并
dft(comImg, comImg);

//获得DFT图像
//一般都会用幅度图像来表示图像傅里叶的变换结果(傅里叶谱)
//幅度的计算公式:magnitude = sqrt(Re(DFT) ^ 2 + Im(DFT) ^ 2)。
//由于幅度的变化范围很大,而一般图像亮度范围只有[0, 255],容易造成一大片漆黑,只有几个点很亮。所以要用log函数把数值的范围缩小
split(comImg, planes);
magnitude(planes[0], planes[1], planes[0]);
Mat magMat = planes[0];
magMat += Scalar::all(1);
log(magMat, magMat);

//dft()直接获得的结果中,低频部分位于四角,高频部分位于中间
//习惯上会把图像做四等份,互相对调,使低频部分位于图像中心,也就是让频域原点位于中心
magMat = magMat(Rect(0, 0, magMat.cols & -2, magMat.rows & -2));
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);

//虽然用log()缩小了数据范围,但仍然不能保证数值都落在[0, 255]之内
//所以要先用normalize()规范化到[0, 1]内,再用convertTo()把小数映射到[0, 255]内的整数。结果保存在一幅单通道图像内
normalize(magMat, magMat, 0, 1, CV_MINMAX);
Mat magImg(magMat.size(), CV_8UC1);
magMat.convertTo(magImg, CV_8UC1, 255, 0);
imshow("magnitude", magImg);
//imwrite("imageText_mag.jpg",magImg);

//Hough变换要求输入图像是二值的,所以要用threshold()把图像二值化
threshold(magImg, magImg, GRAY_THRESH, 255, CV_THRESH_BINARY);
imshow("mag_binary", magImg);

//Hough直线检测
vector<Vec2f> lines; //float型二维向量集合“lines”
float pi180 = (float)CV_PI / 180; //pi180表示角度制1度
Mat linImg(magImg.size(), CV_8UC3); //创建一副新图像,用来显示霍夫检测结果
HoughLines(magImg, lines, 1, pi180, HOUGH_VOTE, 0, 0); //霍夫直线检测HoughLines(dst, lines, 分辨率, 单位, 投票数, 参数1, 参数2)

//Hough直线检测结果展示
int numLines = lines.size();
for (int l = 0; l < numLines; l++)
{
float rho = lines[l][0], theta = lines[l][1]; //获得第一个向量的rho、theta值
Point pt1, pt2;	//两个点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), 3, 8, 0);
}
imshow("lines", linImg);

//找出除了0度,90度之外的第三条线角度
float angel = 0;
float piThresh = (float)CV_PI / 90;	//2度
float pi2 = CV_PI / 2;	//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;
}
}

//计算旋转角度
//图片必须是方形才能计算正确
if (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;

//转正图片
Mat rotMat = getRotationMatrix2D(center, angelD, 1.0);
Mat dstImg = Mat::zeros(srcImg.size(), CV_8UC3);
warpAffine(srcImg, dstImg, rotMat, srcImg.size(), 1, 0, Scalar(255, 25
95e2
5, 255));
imshow("result", dstImg);

waitKey(0);
return 0;
}

5.  结果展示


























6.  思考

事实上,这个方法适用于所有在某一方向上具备明暗变化特征的图像的方向纠正,根据需要调整旋转角度即可。

参考资料: http://johnhany.net/2013/11/dft-based-text-rotation-correction/ http://blog.csdn.net/abcjennifer/article/details/7622228
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: