本文描述一種利用OpenCV及傅里葉變換識別圖片中文本旋轉角度并自動校正的方法,由于對C#比較熟,因此本文將使用OpenCVSharp。 文章參考了http://johnhany.net/2013/11/dft-based-text-rotation-correction,對原作者表示感謝。我基于OpenCVSharp用C#進行了重寫,希望能幫到同樣用OpenCVSharp的同學。
創(chuàng)新互聯(lián)擁有一支富有激情的企業(yè)網(wǎng)站制作團隊,在互聯(lián)網(wǎng)網(wǎng)站建設行業(yè)深耕十年,專業(yè)且經(jīng)驗豐富。十年網(wǎng)站優(yōu)化營銷經(jīng)驗,我們已為千余家中小企業(yè)提供了成都網(wǎng)站建設、成都網(wǎng)站設計解決方案,按需網(wǎng)站建設,設計滿意,售后服務無憂。所有客戶皆提供一年免費網(wǎng)站維護!
================= 正文開始 =================
手里有一張圖片如下,是經(jīng)過旋轉的,如何通過程序自動對它進行旋轉校正? (旋轉校正是行分割、字符識別等后續(xù)工作的基礎)

傅里葉變換可以用于將圖像從時域轉換到頻域,對于分行的文本,其頻率譜上一定會有一定的特征,當圖像旋轉時,其頻譜也會同步旋轉,因此找出這個特征的傾角,就可以將圖像旋轉校正回去。
先來對原始圖像進行一下傅里葉變換,需要這么幾步:
1、以灰度方式讀入原文件
string filename = "source.jpg"; var src = IplImage.FromFile(filename, LoadMode.GrayScale);
2、將圖像擴展到合適的尺寸以方便快速變換
OpenCV中的DFT對圖像尺寸有一定要求,需要用GetOptimalDFTSize方法來找到合適的大小,根據(jù)這個大小建立新的圖像,把原圖像拷貝過去,多出來的部分直接填充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個單通道的圖像,實部從原圖像中拷貝數(shù)據(jù),虛部清零,然后把它們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、對數(shù)據(jù)進行適當調整
上一步中得到的實部保留下來作為變換結果,并計算幅度:magnitude = sqrt(real^2 + imaginary^2)。
考慮到幅度變化范圍很大,還要用log函數(shù)把數(shù)值范圍縮小。
最后經(jīng)過歸一化,就會得到圖像的特征譜了。
//計算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="p_w_picpath"></param>
/// <remarks>
/// 0 | 3 2 | 1
/// ------- ===> -------
/// 1 | 2 3 | 0
/// </remarks>
private static void ShiftDFT(IplImage p_w_picpath)
{
int row = p_w_picpath.Height;
int col = p_w_picpath.Width;
int cy = row / 2;
int cx = col / 2;
var q0 = p_w_picpath.Clone(new CvRect(0, 0, cx, cy)); //左上
var q1 = p_w_picpath.Clone(new CvRect(0, cy, cx, cy)); //左下
var q2 = p_w_picpath.Clone(new CvRect(cx, cy, cx, cy)); //右下
var q3 = p_w_picpath.Clone(new CvRect(cx, 0, cx, cy)); //右上
Cv.SetImageROI(p_w_picpath, new CvRect(0, 0, cx, cy));
q2.Copy(p_w_picpath);
Cv.ResetImageROI(p_w_picpath);
Cv.SetImageROI(p_w_picpath, new CvRect(0, cy, cx, cy));
q3.Copy(p_w_picpath);
Cv.ResetImageROI(p_w_picpath);
Cv.SetImageROI(p_w_picpath, new CvRect(cx, cy, cx, cy));
q0.Copy(p_w_picpath);
Cv.ResetImageROI(p_w_picpath);
Cv.SetImageROI(p_w_picpath, new CvRect(cx, 0, cx, cy));
q1.Copy(p_w_picpath);
Cv.ResetImageROI(p_w_picpath);
}最終得到圖像如下:

可以明顯的看到過中心有一條傾斜的直線,可以用霍夫變換把它檢測出來,然后計算角度。 需要以下幾步:
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參數(shù)取的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的特點,只有輸入圖像是正方形時,檢測到的角度才是真正文本的旋轉角度,但原圖像明顯不是,因此還要根據(jù)長寬比進行變換,最后得到的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);
//執(zhí)行變換,產生的空白部分用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作為分界點,經(jīng)驗值,需要根據(jù)實際情況調整
Cv.Threshold(real, real, 150, 255, ThresholdType.Binary);
//由于HoughLines2方法只接受8UC1格式的圖片,因此進行轉換
var gray = new IplImage(real.Size, BitDepth.U8, 1);
Cv.ConvertScale(real, gray);
//找直線,threshold參數(shù)取100,經(jīng)驗值,需要根據(jù)實際情況調整
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="p_w_picpath"></param>
/// <remarks>
/// 0 | 3 2 | 1
/// ------- ===> -------
/// 1 | 2 3 | 0
/// </remarks>
private static void ShiftDFT(IplImage p_w_picpath)
{
int row = p_w_picpath.Height;
int col = p_w_picpath.Width;
int cy = row / 2;
int cx = col / 2;
var q0 = p_w_picpath.Clone(new CvRect(0, 0, cx, cy));//左上
var q1 = p_w_picpath.Clone(new CvRect(0, cy, cx, cy));//左下
var q2 = p_w_picpath.Clone(new CvRect(cx, cy, cx, cy));//右下
var q3 = p_w_picpath.Clone(new CvRect(cx, 0, cx, cy));//右上
Cv.SetImageROI(p_w_picpath, new CvRect(0, 0, cx, cy));
q2.Copy(p_w_picpath);
Cv.ResetImageROI(p_w_picpath);
Cv.SetImageROI(p_w_picpath, new CvRect(0, cy, cx, cy));
q3.Copy(p_w_picpath);
Cv.ResetImageROI(p_w_picpath);
Cv.SetImageROI(p_w_picpath, new CvRect(cx, cy, cx, cy));
q0.Copy(p_w_picpath);
Cv.ResetImageROI(p_w_picpath);
Cv.SetImageROI(p_w_picpath, new CvRect(cx, 0, cx, cy));
q1.Copy(p_w_picpath);
Cv.ResetImageROI(p_w_picpath);
}
}
}最后吐槽一下51cto的編譯器,總是把代碼的換行和縮進弄沒,還要手工再處理一遍,真是受夠了,難道是我打開的方式不對?
PS:最近增加了源碼,因為加了opencv的dll,比較大,下載鏈接
http://down.51cto.com/data/2329576
分享文章:OpenCV基于傅里葉變換進行文本的旋轉校正
轉載注明:http://www.chinadenli.net/article8/geeeip.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供外貿網(wǎng)站建設、用戶體驗、企業(yè)網(wǎng)站制作、建站公司、商城網(wǎng)站、網(wǎng)站制作
聲明:本網(wǎng)站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經(jīng)允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)