OpenCV基於傅里葉變換進行文本的旋轉校正



wKiom1Wx7WaTkCKUAAP8FR_1gxY697.jpg


傅里葉變換可以用於將圖像從時域轉換到頻域,對於分行的文本,其頻率譜上一定會有一定的特征,當圖像旋轉時,其頻譜也會同步旋轉,因此找出這個特征的傾角,就可以將圖像旋轉校正回去。


先來對原始圖像進行一下傅里葉變換,需要這么幾步:


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),再存回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);


此時圖像是這樣的:

wKioL1Wx8Hrw4_DKAASfCPJ9KK4456.jpg


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);
}

最終得到圖像如下:

wKioL1Wx8Jah8u2hAASfEauPYhA310.jpg


可以明顯的看到過中心有一條傾斜的直線,可以用霍夫變換把它檢測出來,然后計算角度。 需要以下幾步:


1、二值化

  把剛才得到的傅里葉譜放到0-255的范圍,然后進行二值化,此處以150作為分界點。

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

 得到圖像如下:

wKioL1Wx8NPRQYgUAACELmMFysQ406.jpg


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();
}


最終結果如下,效果還不錯:

wKiom1Wx8QOjPEd8AAL5za5_XCA781.jpg


最后放完整代碼:


   
   
   
           
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Text;
  5. using OpenCvSharp;
  6. using OpenCvSharp.Extensions;
  7. using OpenCvSharp.Utilities;
  8. namespace OpenCvTest
  9. {
  10. class Program
  11. {
  12. static void Main(string[] args)
  13. {
  14. //以灰度方式讀入原文件
  15. string filename = "source.jpg";
  16. var src = IplImage.FromFile(filename, LoadMode.GrayScale);
  17. //轉換到合適的大小,以適應快速變換
  18. int width = Cv.GetOptimalDFTSize(src.Width);
  19. int height = Cv.GetOptimalDFTSize(src.Height);
  20. var padded = new IplImage(width, height, BitDepth.U8, 1);
  21. Cv.CopyMakeBorder(src, padded, new CvPoint(0, 0), BorderType.Constant, CvScalar.ScalarAll(0));
  22. //實部、虛部(單通道)
  23. var real = new IplImage(padded.Size, BitDepth.F32, 1);
  24. var imaginary = new IplImage(padded.Size, BitDepth.F32, 1);
  25. //合並(雙通道)
  26. var fourier = new IplImage(padded.Size, BitDepth.F32, 2);
  27. //圖像復制到實部,虛部清零
  28. Cv.ConvertScale(padded, real);
  29. Cv.Zero(imaginary);
  30. //合並、變換、再分解
  31. Cv.Merge(real, imaginary, null, null, fourier);
  32. Cv.DFT(fourier, fourier, DFTFlag.Forward);
  33. Cv.Split(fourier, real, imaginary, null, null);
  34. //計算sqrt(re^2+im^2),再存回re
  35. Cv.Pow(real, real, 2.0);
  36. Cv.Pow(imaginary, imaginary, 2.0);
  37. Cv.Add(real, imaginary, real);
  38. Cv.Pow(real, real, 0.5);
  39. //計算log(1+re),存回re
  40. Cv.AddS(real, CvScalar.ScalarAll(1), real);
  41. Cv.Log(real, real);
  42. //歸一化,落入0-255范圍
  43. Cv.Normalize(real, real, 0, 255, NormType.MinMax);
  44. //把低頻移動到中心
  45. ShiftDFT(real);
  46. //二值化,以150作為分界點,經驗值,需要根據實際情況調整
  47. Cv.Threshold(real, real, 150, 255, ThresholdType.Binary);
  48. //由於HoughLines2方法只接受8UC1格式的圖片,因此進行轉換
  49. var gray = new IplImage(real.Size, BitDepth.U8, 1);
  50. Cv.ConvertScale(real, gray);
  51. //找直線,threshold參數取100,經驗值,需要根據實際情況調整
  52. var storage = Cv.CreateMemStorage();
  53. var lines = Cv.HoughLines2(gray, storage, HoughLinesMethod.Standard, 1, Cv.PI / 180, 100);
  54. //找到符合條件的那條斜線
  55. float angel = 0f;
  56. float piThresh = (float)Cv.PI / 90;
  57. float pi2 = (float)Cv.PI / 2;
  58. for (int i = 0; i < lines.Total; ++i)
  59. {
  60. //極坐標下的點,X是極徑,Y是夾角,我們只關心夾角
  61. var p = lines.GetSeqElem<CvPoint2D32f>(i);
  62. float theta = p.Value.Y;
  63. if (Math.Abs(theta) >= piThresh && Math.Abs(theta - pi2) >= piThresh)
  64. {
  65. angel = theta;
  66. break;
  67. }
  68. }
  69. angel = angel < pi2 ? angel : (angel - (float)Cv.PI);
  70. Cv.ReleaseMemStorage(storage);
  71. //轉換角度
  72. if (angel != pi2)
  73. {
  74. float angelT = (float)(src.Height * Math.Tan(angel) / src.Width);
  75. angel = (float)Math.Atan(angelT);
  76. }
  77. float angelD = angel * 180 / (float)Cv.PI;
  78. Console.WriteLine("angtlD = {0}", angelD);
  79. //旋轉
  80. var center = new CvPoint2D32f(src.Width / 2.0, src.Height / 2.0);
  81. var rotMat = Cv.GetRotationMatrix2D(center, angelD, 1.0);
  82. var dst = new IplImage(src.Size, BitDepth.U8, 1);
  83. Cv.WarpAffine(src, dst, rotMat, Interpolation.Cubic | Interpolation.FillOutliers, CvScalar.ScalarAll(255));
  84. //顯示
  85. using (var window = new CvWindow("Image"))
  86. {
  87. window.Image = src;
  88. using (var win2 = new CvWindow("Dest"))
  89. {
  90. win2.Image = dst;
  91. Cv.WaitKey();
  92. }
  93. }
  94. }
  95. /// <summary>
  96. /// 將低頻部分移動到圖像中心
  97. /// </summary>
  98. /// <param name="image"></param>
  99. /// <remarks>
  100. /// 0 | 3 2 | 1
  101. /// ------- ===> -------
  102. /// 1 | 2 3 | 0
  103. /// </remarks>
  104. private static void ShiftDFT(IplImage image)
  105. {
  106. int row = image.Height;
  107. int col = image.Width;
  108. int cy = row / 2;
  109. int cx = col / 2;
  110. var q0 = image.Clone(new CvRect(0, 0, cx, cy));//左上
  111. var q1 = image.Clone(new CvRect(0, cy, cx, cy));//左下
  112. var q2 = image.Clone(new CvRect(cx, cy, cx, cy));//右下
  113. var q3 = image.Clone(new CvRect(cx, 0, cx, cy));//右上
  114. Cv.SetImageROI(image, new CvRect(0, 0, cx, cy));
  115. q2.Copy(image);
  116. Cv.ResetImageROI(image);
  117. Cv.SetImageROI(image, new CvRect(0, cy, cx, cy));
  118. q3.Copy(image);
  119. Cv.ResetImageROI(image);
  120. Cv.SetImageROI(image, new CvRect(cx, cy, cx, cy));
  121. q0.Copy(image);
  122. Cv.ResetImageROI(image);
  123. Cv.SetImageROI(image, new CvRect(cx, 0, cx, cy));
  124. q1.Copy(image);
  125. Cv.ResetImageROI(image);
  126. }
  127. }
  128. }






附件列表

     


    免責聲明!

    本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



     
    粵ICP備18138465號   © 2018-2025 CODEPRJ.COM