OpenCV基于傅里叶变换进行文本的旋转校正

傅里叶变换可以用于将图像从时域转换到频域,对于分行的文本,其频率谱上一定会有一定的特征,当图像旋转时,其频谱也会同步旋转,因此找出这个特征的倾角,就可以将图像旋转校正回去。
先来对原始图像进行一下傅里叶变换,需要这么几步:
1、以灰度方式读入原文件
|
1
2
|
string filename = "source.jpg";var src = IplImage.FromFile(filename, LoadMode.GrayScale); |
2、将图像扩展到合适的尺寸以方便快速变换
OpenCV中的DFT对图像尺寸有一定要求,需要用GetOptimalDFTSize方法来找到合适的大小,根据这个大小建立新的图像,把原图像拷贝过去,多出来的部分直接填充0。
|
1
2
3
4
|
int width = Cv.GetOptimalDFTSize(src.Width);int height = Cv.GetOptimalDFTSize(src.Height);var padded = new IplImage(width, height, BitDepth.U8, 1);//扩展后的图像,单通道Cv.CopyMakeBorder(src, padded, new CvPoint(0, 0), BorderType.Constant, CvScalar.ScalarAll(0)); |
3、进行DFT运算
DFT要分别计算实部和虚部,这里准备2个单通道的图像,实部从原图像中拷贝数据,虚部清零,然后把它们Merge为一个双通道图像再进行DFT计算,完成后再Split开。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
//实部、虚部(单通道)var real = new IplImage(padded.Size, BitDepth.F32, 1);var imaginary = new IplImage(padded.Size, BitDepth.F32, 1);//合成(双通道)var fourier = new IplImage(padded.Size, BitDepth.F32, 2);//图像复制到实部,虚部清零Cv.ConvertScale(padded, real);Cv.Zero(imaginary);//合并、变换、再分解Cv.Merge(real, imaginary, null, null, fourier);Cv.DFT(fourier, fourier, DFTFlag.Forward);Cv.Split(fourier, real, imaginary, null, null); |
4、对数据进行适当调整
上一步中得到的实部保留下来作为变换结果,并计算幅度:magnitude = sqrt(real^2 + imaginary^2)。
考虑到幅度变化范围很大,还要用log函数把数值范围缩小。
最后经过归一化,就会得到图像的特征谱了。
|
1
2
3
4
5
6
7
8
9
10
11
12
|
//计算sqrt(re^2+im^2),再存回reCv.Pow(real, real, 2.0);Cv.Pow(imaginary, imaginary, 2.0);Cv.Add(real, imaginary, real);Cv.Pow(real, real, 0.5);//计算log(1+re),存回reCv.AddS(real, CvScalar.ScalarAll(1), real);Cv.Log(real, real);//归一化Cv.Normalize(real, real, 0, 1, NormType.MinMax); |
此时图像是这样的:

5、移动中心
DFT操作的结果低频部分位于四角,高频部分在中心,习惯上会把频域原点调整到中心去,也就是把低频部分移动到中心。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
/// <summary>/// 将低频部分移动到图像中心/// </summary>/// <param name="image"></param>/// <remarks>/// 0 | 3 2 | 1/// ------- ===> -------/// 1 | 2 3 | 0/// </remarks>private static void ShiftDFT(IplImage image){ int row = image.Height; int col = image.Width; int cy = row / 2; int cx = col / 2; var q0 = image.Clone(new CvRect(0, 0, cx, cy)); //左上 var q1 = image.Clone(new CvRect(0, cy, cx, cy)); //左下 var q2 = image.Clone(new CvRect(cx, cy, cx, cy)); //右下 var q3 = image.Clone(new CvRect(cx, 0, cx, cy)); //右上 Cv.SetImageROI(image, new CvRect(0, 0, cx, cy)); q2.Copy(image); Cv.ResetImageROI(image); Cv.SetImageROI(image, new CvRect(0, cy, cx, cy)); q3.Copy(image); Cv.ResetImageROI(image); Cv.SetImageROI(image, new CvRect(cx, cy, cx, cy)); q0.Copy(image); Cv.ResetImageROI(image); Cv.SetImageROI(image, new CvRect(cx, 0, cx, cy)); q1.Copy(image); Cv.ResetImageROI(image);} |
最终得到图像如下:

可以明显的看到过中心有一条倾斜的直线,可以用霍夫变换把它检测出来,然后计算角度。 需要以下几步:
1、二值化
把刚才得到的傅里叶谱放到0-255的范围,然后进行二值化,此处以150作为分界点。
|
1
2
|
Cv.Normalize(real, real, 0, 255, NormType.MinMax);Cv.Threshold(real, real, 150, 255, ThresholdType.Binary); |
得到图像如下:

2、Houge直线检测
由于HoughLine2方法只接受8UC1格式的图片,因此要先进行转换再调用HoughLine2方法,这里的threshold参数取的100,能够检测出3条直线来。
|
1
2
3
4
5
6
7
|
//构造8UC1格式图像var gray = new IplImage(real.Size, BitDepth.U8, 1);Cv.ConvertScale(real, gray);//找直线var storage = Cv.CreateMemStorage();var lines = Cv.HoughLines2(gray, storage, HoughLinesMethod.Standard, 1, Cv.PI / 180, 100); |
3、找到符合条件的那条斜线,获取角度
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
float angel = 0f;float piThresh = (float)Cv.PI / 90;float pi2 = (float)Cv.PI / 2;for (int i = 0; i < lines.Total; ++i){ //极坐标下的点,X是极径,Y是夹角,我们只关心夹角 var p = lines.GetSeqElem<CvPoint2D32f>(i); float theta = p.Value.Y; if (Math.Abs(theta) >= piThresh && Math.Abs(theta - pi2) >= piThresh) { angel = theta; break; }}angel = angel < pi2 ? angel : (angel - (float)Cv.PI); |
4、角度转换
由于DFT的特点,只有输入图像是正方形时,检测到的角度才是真正文本的旋转角度,但原图像明显不是,因此还要根据长宽比进行变换,最后得到的angelD就是真正的旋转角度了。
|
1
2
3
4
5
6
|
if (angel != pi2){ float angelT = (float)(src.Height * Math.Tan(angel) / src.Width); angel = (float)Math.Atan(angelT);}float angelD = angel * 180 / (float)Cv.PI; |
5、旋转校正
这一步比较简单了,构建一个仿射变换矩阵,然后调用WarpAffine进行变换,就得到校正后的图像了。最后显示到界面上。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
var center = new CvPoint2D32f(src.Width / 2.0, src.Height / 2.0);//图像中心var rotMat = Cv.GetRotationMatrix2D(center, angelD, 1.0);//构造仿射变换矩阵var dst = new IplImage(src.Size, BitDepth.U8, 1);//执行变换,产生的空白部分用255填充,即纯白Cv.WarpAffine(src, dst, rotMat, Interpolation.Cubic | Interpolation.FillOutliers, CvScalar.ScalarAll(255));//展示using (var win = new CvWindow("Rotation")){ win.Image = dst; Cv.WaitKey();} |
最终结果如下,效果还不错:

最后放完整代码:
using System;using System.Collections.Generic;using System.IO;using System.Text;using OpenCvSharp;using OpenCvSharp.Extensions;using OpenCvSharp.Utilities;namespace OpenCvTest{class Program{static void Main(string[] args){//以灰度方式读入原文件string filename = "source.jpg";var src = IplImage.FromFile(filename, LoadMode.GrayScale);//转换到合适的大小,以适应快速变换int width = Cv.GetOptimalDFTSize(src.Width);int height = Cv.GetOptimalDFTSize(src.Height);var padded = new IplImage(width, height, BitDepth.U8, 1);Cv.CopyMakeBorder(src, padded, new CvPoint(0, 0), BorderType.Constant, CvScalar.ScalarAll(0));//实部、虚部(单通道)var real = new IplImage(padded.Size, BitDepth.F32, 1);var imaginary = new IplImage(padded.Size, BitDepth.F32, 1);//合并(双通道)var fourier = new IplImage(padded.Size, BitDepth.F32, 2);//图像复制到实部,虚部清零Cv.ConvertScale(padded, real);Cv.Zero(imaginary);//合并、变换、再分解Cv.Merge(real, imaginary, null, null, fourier);Cv.DFT(fourier, fourier, DFTFlag.Forward);Cv.Split(fourier, real, imaginary, null, null);//计算sqrt(re^2+im^2),再存回reCv.Pow(real, real, 2.0);Cv.Pow(imaginary, imaginary, 2.0);Cv.Add(real, imaginary, real);Cv.Pow(real, real, 0.5);//计算log(1+re),存回reCv.AddS(real, CvScalar.ScalarAll(1), real);Cv.Log(real, real);//归一化,落入0-255范围Cv.Normalize(real, real, 0, 255, NormType.MinMax);//把低频移动到中心ShiftDFT(real);//二值化,以150作为分界点,经验值,需要根据实际情况调整Cv.Threshold(real, real, 150, 255, ThresholdType.Binary);//由于HoughLines2方法只接受8UC1格式的图片,因此进行转换var gray = new IplImage(real.Size, BitDepth.U8, 1);Cv.ConvertScale(real, gray);//找直线,threshold参数取100,经验值,需要根据实际情况调整var storage = Cv.CreateMemStorage();var lines = Cv.HoughLines2(gray, storage, HoughLinesMethod.Standard, 1, Cv.PI / 180, 100);//找到符合条件的那条斜线float angel = 0f;float piThresh = (float)Cv.PI / 90;float pi2 = (float)Cv.PI / 2;for (int i = 0; i < lines.Total; ++i){//极坐标下的点,X是极径,Y是夹角,我们只关心夹角var p = lines.GetSeqElem<CvPoint2D32f>(i);float theta = p.Value.Y;if (Math.Abs(theta) >= piThresh && Math.Abs(theta - pi2) >= piThresh){angel = theta;break;}}angel = angel < pi2 ? angel : (angel - (float)Cv.PI);Cv.ReleaseMemStorage(storage);//转换角度if (angel != pi2){float angelT = (float)(src.Height * Math.Tan(angel) / src.Width);angel = (float)Math.Atan(angelT);}float angelD = angel * 180 / (float)Cv.PI;Console.WriteLine("angtlD = {0}", angelD);//旋转var center = new CvPoint2D32f(src.Width / 2.0, src.Height / 2.0);var rotMat = Cv.GetRotationMatrix2D(center, angelD, 1.0);var dst = new IplImage(src.Size, BitDepth.U8, 1);Cv.WarpAffine(src, dst, rotMat, Interpolation.Cubic | Interpolation.FillOutliers, CvScalar.ScalarAll(255));//显示using (var window = new CvWindow("Image")){window.Image = src;using (var win2 = new CvWindow("Dest")){win2.Image = dst;Cv.WaitKey();}}}/// <summary>/// 将低频部分移动到图像中心/// </summary>/// <param name="image"></param>/// <remarks>/// 0 | 3 2 | 1/// ------- ===> -------/// 1 | 2 3 | 0/// </remarks>private static void ShiftDFT(IplImage image){int row = image.Height;int col = image.Width;int cy = row / 2;int cx = col / 2;var q0 = image.Clone(new CvRect(0, 0, cx, cy));//左上var q1 = image.Clone(new CvRect(0, cy, cx, cy));//左下var q2 = image.Clone(new CvRect(cx, cy, cx, cy));//右下var q3 = image.Clone(new CvRect(cx, 0, cx, cy));//右上Cv.SetImageROI(image, new CvRect(0, 0, cx, cy));q2.Copy(image);Cv.ResetImageROI(image);Cv.SetImageROI(image, new CvRect(0, cy, cx, cy));q3.Copy(image);Cv.ResetImageROI(image);Cv.SetImageROI(image, new CvRect(cx, cy, cx, cy));q0.Copy(image);Cv.ResetImageROI(image);Cv.SetImageROI(image, new CvRect(cx, 0, cx, cy));q1.Copy(image);Cv.ResetImageROI(image);}}}
附件列表
OpenCV基于傅里叶变换进行文本的旋转校正的更多相关文章
- OpenCV.Net基于傅里叶变换进行文本的旋转校正
本文描述一种利用OpenCV及傅里叶变换识别图片中文本旋转角度并自动校正的方法,由于对C#比较熟,因此本文将使用OpenCVSharp. 文章参考了http://johnhany.net/2013/1 ...
- OpenCV实现基于傅里叶变换的旋转文本校正
代码 先给出代码,再详细解释一下过程: #include <opencv2/core/core.hpp> #include <opencv2/imgproc/imgproc.hpp& ...
- 基于css3的3D立方体旋转特效
今天给大家分享一款基于css3的3D立方体旋转特效.这款特效适用浏览器:360.FireFox.Chrome.Safari.Opera.傲游.搜狗.世界之窗. 不支持IE8及以下浏览器.效果图如下 : ...
- tensorflow实现基于LSTM的文本分类方法
tensorflow实现基于LSTM的文本分类方法 作者:u010223750 引言 学习一段时间的tensor flow之后,想找个项目试试手,然后想起了之前在看Theano教程中的一个文本分类的实 ...
- 一文详解如何用 TensorFlow 实现基于 LSTM 的文本分类(附源码)
雷锋网按:本文作者陆池,原文载于作者个人博客,雷锋网已获授权. 引言 学习一段时间的tensor flow之后,想找个项目试试手,然后想起了之前在看Theano教程中的一个文本分类的实例,这个星期就用 ...
- 基于animation.css实现动画旋转特效
分享一款基于animation.css实现动画旋转特效.这是一款基于CSS3实现的酷炫的动画旋转特效代码.效果图如下: 在线预览 源码下载 实现的代码. html代码: <div class ...
- 基于 Spark 的文本情感分析
转载自:https://www.ibm.com/developerworks/cn/cognitive/library/cc-1606-spark-seniment-analysis/index.ht ...
- (4.2)基于LingPipe的文本基本极性分析【demo】
酒店评论情感分析系统(四)—— 基于LingPipe的文本基本极性分析[demo] (Positive (favorable) vs. Negative (unfavorable)) 这篇文章为Lin ...
- 一款基于css3鼠标经过圆形旋转特效
今天给大家分享一款基于css3鼠标经过圆形旋转特效.当鼠标经过的时候图片边框颜色旋转,图片显示详情.该实例适用浏览器:IE8.360.FireFox.Chrome.Safari.Opera.傲游.搜狗 ...
随机推荐
- Sprint3(12.18)总结
Sprint3第三阶段 1.类名:软件工程-第三阶段 2.时间:至12.18 3.选题内容:web版-餐厅到店点餐系统 4.团队博客地址: http://www.cnblogs.com/queenju ...
- 接微软技术(c#,.net,vb.net, asp.net, sql server, bi, dw etc)项目
最近闲赋在家,接微软技术的项目,主要有c#,.net,vb.net, asp.net, sql server, bi, dw etc,欢迎推荐.不好意思,借首页发一下.
- mouseover、mouseout,mouseenter、mouseleave区别
心情压抑的一天,我想好好的. mouseover与mouseenter 不论鼠标指针穿过被选元素或其子元素,都会触发 mouseover 事件. 只有在鼠标指针穿过被选元素时,才会触发 mouseen ...
- 推荐有料哥:HR社保公积金状况大揭底
昨天下午有料哥和几位三茅运营成员一起和大家进行三个小时的面对面,如潮的问题让几位三茅团队成员都措手不及了,有料哥也融入了大家紧张好奇问题的好奇,无奈水平有限不能很好回复,请大家谅解,有问题还可以在有料 ...
- 用eclipse搭建SSH(struts+spring+hibernate)框架
声明: 本文是个人对ssh框架的学习.理解而编辑出来的,可能有不足之处,请大家谅解,但希望能帮助到大家,一起探讨,一起学习! Struts + Spring + Hibernate三者各自的特点都是什 ...
- hadoop显示ConnectionrRefused
产生原因重启了服务器 (1)在安装目录/root/cloud/hadoop-2.2.0/ 重新hdfs namenode -format (2) 目录/root/cloud/hadoop-2.2.0/ ...
- 使用CSS设置行间距,字间距.
字间距1.text-indent设置抬头距离css缩进即对,对应div设置css样式text-indent : 20px; 缩进了20px 2.letter-spacing来设置字与字间距_字符间距离 ...
- CentOS 访问Windows7共享文件夹
在终端中输入命令mount -t cifs -o username="xxx",password="****" //192.168.1.1/share_fold ...
- html给div加超链接的方法
1.通过window.open函数 <div onclick="window.open('www.baidu.com')">在新窗口跳转至百度</div> ...
- 60行以内写mvc
标题党.几天前看到一个30行写mvc的文章,东施效颦,也动手写了个60行的,功能上略微扩充一些,记录下来,后面有时间可以继续优化. mvc其实是一个观察者模式.view来监听model,所以当mode ...