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

本文描述一种利用OpenCV及傅里叶变换识别图片中文本旋转角度并自动校正的方法,由于对C#比较熟,因此本文将使用OpenCVSharp。 文章参考了http://johnhany.net/2013/11/dft-based-text-rotation-correction,对原作者表示感谢。我基于OpenCVSharp用C#进行了重写,希望能帮到同样用OpenCVSharp的同学。

================= 正文开始 =================

手里有一张图片如下,是经过旋转的,如何通过程序自动对它进行旋转校正? (旋转校正是行分割、字符识别等后续工作的基础)

傅里叶变换可以用于将图像从时域转换到频域,对于分行的文本,其频率谱上一定会有一定的特征,当图像旋转时,其频谱也会同步旋转,因此找出这个特征的倾角,就可以将图像旋转校正回去。

先来对原始图像进行一下傅里叶变换,需要这么几步:

1、以灰度方式读入原文件

string filename = "source.jpg";
var src = IplImage.FromFile(filename, LoadMode.GrayScale);

2、将图像扩展到合适的尺寸以方便快速变换

OpenCV中的DFT对图像尺寸有一定要求,需要用GetOptimalDFTSize方法来找到合适的大小,根据这个大小建立新的图像,把原图像拷贝过去,多出来的部分直接填充0。

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开。

//实部、虚部(单通道)
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函数把数值范围缩小。

最后经过归一化,就会得到图像的特征谱了。

//计算sqrt(re^2+im^2),再存回re
Cv.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),存回re
Cv.AddS(real, CvScalar.ScalarAll(1), real);
Cv.Log(real, real);

//归一化
Cv.Normalize(real, real, 0, 1, NormType.MinMax);

此时图像是这样的:

5、移动中心

DFT操作的结果低频部分位于四角,高频部分在中心,习惯上会把频域原点调整到中心去,也就是把低频部分移动到中心。

/// <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作为分界点。

Cv.Normalize(real, real, 0, 255, NormType.MinMax);
Cv.Threshold(real, real, 150, 255, ThresholdType.Binary);

得到图像如下:

2、Houge直线检测

由于HoughLine2方法只接受8UC1格式的图片,因此要先进行转换再调用HoughLine2方法,这里的threshold参数取的100,能够检测出3条直线来。

//构造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、找到符合条件的那条斜线,获取角度

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就是真正的旋转角度了。

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进行变换,就得到校正后的图像了。最后显示到界面上。

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),再存回re
            Cv.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),存回re
            Cv.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);
        }
    }
}

最后吐槽一下51cto的编译器,总是把代码的换行和缩进弄没,还要手工再处理一遍,真是受够了,难道是我打开的方式不对?

时间: 2024-09-30 14:55:22

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

基于Emgucv,C#的图片旋转方式

原文:基于Emgucv,C#的图片旋转方式 1 /// <summary> 2 /// 图片旋转 --百度 旋转仿射 3 /// </summary> 4 /// <param name="modelImage"></param> 5 /// <param name="degree"></param> 6 /// <returns></returns> 7 Image&l

基于animation.css实现动画旋转特效

分享一款基于animation.css实现动画旋转特效.这是一款基于CSS3实现的酷炫的动画旋转特效代码.效果图如下: 在线预览   源码下载 实现的代码. html代码: <div class="wrap"> <div class="mod_bg"> <div class="bg1"></div> <div class="bg2"></div> <

基于css3的3D立方体旋转特效

今天给大家分享一款基于css3的3D立方体旋转特效.这款特效适用浏览器:360.FireFox.Chrome.Safari.Opera.傲游.搜狗.世界之窗. 不支持IE8及以下浏览器.效果图如下 : 在线预览   源码下载 实现的代码. html代码: <div class="wrap"> <div class="box1 box"> 1</div> <div class="box2 box">

KINECT+opencv基于骨骼信息对视频进行动作识别

KINECT+opencv基于骨骼信息对视频进行动作识别 环境:kinect1.7+opencv2.4+vc2015 使用kinect获取并按批处理三维空间内的骨骼信息 基于视频帧差计算各关节运动向量并与本地模板匹配 目录 KINECTopencv基于骨骼信息对视频进行动作识别 目录 写在前面 对当前帧处理并匹配 kinect对帧的处理 与模板的向量余弦计算 根据动态时间规划法匹配 记录并保存模板到本地 使用opencv的FileStorage类生成xml文件 写在前面 自前一篇过去一周了.这次

一款基于css3鼠标经过圆形旋转特效

今天给大家分享一款基于css3鼠标经过圆形旋转特效.当鼠标经过的时候图片边框颜色旋转,图片显示详情.该实例适用浏览器:IE8.360.FireFox.Chrome.Safari.Opera.傲游.搜狗.世界之窗.效果图如下: 在线预览   源码下载 实现的代码. html代码: <div class="case-content"> <div class="case-item"> <div class="ih-item circ

tensorflow实现基于LSTM的文本分类方法

tensorflow实现基于LSTM的文本分类方法 作者:u010223750 引言 学习一段时间的tensor flow之后,想找个项目试试手,然后想起了之前在看Theano教程中的一个文本分类的实例,这个星期就用tensorflow实现了一下,感觉和之前使用的theano还是有很大的区别,有必要总结mark一下 模型说明 这个分类的模型其实也是很简单,主要就是一个单层的LSTM模型,当然也可以实现多层的模型,多层的模型使用Tensorflow尤其简单,下面是这个模型的图  简单解释一下这个图

基于傅里叶变换的音频重采样算法 (附完整c代码)

前面有提到音频采样算法: WebRTC 音频采样算法 附完整C++示例代码 简洁明了的插值音频重采样算法例子 (附完整C代码) 近段时间有不少朋友给我写过邮件,说了一些他们使用的情况和问题. 坦白讲,我精力有限,但一般都会抽空回复一下. 大多数情况,阅读一下代码就能解决的问题, 也是要尝试一下的. 没准,你就解决了呢? WebRtc的采样算法本身就考虑到它的自身应用场景, 所以它会有一些局限性,例如不支持任意采样率等等. 而简洁插值的这个算法, 我个人也一直在使用,因为简洁明了,简单粗暴. 我自

Vue基于vue-quill-editor富文本编辑器

1.Vue基于vue-quill-editor富文本编辑器使用心得:https://www.cnblogs.com/ZaraNet/p/10209226.html 2.使用vue-quill-editor,获得到输入内容的html,怎样才能在外部的div中有跟编辑器里: 用v-html绑定一下就行了,加上class="ql-editor"的话,样式就和富文本录入的一样了,建议自己写样式,美观一点. 原文地址:https://www.cnblogs.com/zhengzemin/p/vu

用OpenCV实现Photoshop算法(一): 图像旋转

最近学习了OpenCV,于是想用它实现Photoshop的主要功能,用于照片处理. 对于一张照片,PS的一般处理步骤包括: 1, 旋转图片,校正位置. 2,剪切,调整大小,重新构图. 3,调整色阶.曲线,使图片曝光正确.对比适中. 4,调整对比度.饱和度 5,印章去掉不想要的东西,液化调整形体线条 6,对于人像图片,美肤.美白 7, 用色彩平衡.可选颜色等调整色调,形成照片调性 8,加一些光效 9,锐化 以后的一系列博文将采用OpenCV逐一实现Photoshop的算法和功能, 并用计算机视觉人