中文字幕精品亚洲无线码二区,国产黄a三级三级三级看三级,亚洲七七久久桃花影院,丰满少妇被猛烈进入,国产小视频在线观看网站

OpenCV基于(yu)傅里葉變(bian)換進行文本的旋轉校正



傅里葉變換(huan)可以用(yong)于將(jiang)圖像從時域轉換(huan)到頻域,對于分行的文(wen)本,其(qi)頻率譜(pu)上一定會(hui)(hui)有一定的特征,當(dang)圖像旋轉時,其(qi)頻譜(pu)也(ye)會(hui)(hui)同步旋轉,因此找出這個特征的傾(qing)角,就(jiu)可以將(jiang)圖像旋轉校正回去。


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


1、以灰度方式讀入原文(wen)件(jian)

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


2、將(jiang)圖像擴展(zhan)到合(he)適(shi)的(de)尺寸以方便(bian)快速變換

  OpenCV中的(de)DFT對圖(tu)像(xiang)尺寸有一定(ding)要求(qiu),需要用GetOptimalDFTSize方(fang)法來(lai)找到(dao)合適的(de)大小,根據這個大小建立(li)新的(de)圖(tu)像(xiang),把原圖(tu)像(xiang)拷貝過(guo)去,多出(chu)來(lai)的(de)部分直接填(tian)充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、進(jin)行DFT運算(suan)

  DFT要分別計(ji)算實部(bu)和(he)虛部(bu),這里(li)準備2個單通道(dao)的圖像,實部(bu)從原圖像中拷貝數據(ju),虛部(bu)清(qing)零(ling),然后把它們Merge為一個雙通道(dao)圖像再(zai)(zai)進行DFT計(ji)算,完成(cheng)后再(zai)(zai)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, nullnull, fourier);
Cv.DFT(fourier, fourier, DFTFlag.Forward);
Cv.Split(fourier, real, imaginary, nullnull);


4、對數(shu)據進(jin)行適(shi)當(dang)調(diao)整

  上(shang)一步(bu)中得(de)到的實部保(bao)留(liu)下來作為變換結果,并計算幅度:magnitude = sqrt(real^2 + imaginary^2)。

  考慮到幅度變化范圍很大,還(huan)要(yao)用log函數把數值范圍縮(suo)小。

  最后(hou)經過(guo)歸(gui)一化(hua),就會(hui)得到圖像的特征(zheng)譜了。

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


此時圖像是這樣的:


5、移動中心

  DFT操作的結果低(di)頻(pin)部分(fen)(fen)位于四角,高頻(pin)部分(fen)(fen)在中心,習慣(guan)上會把頻(pin)域原點(dian)調整到中心去,也就是把低(di)頻(pin)部分(fen)(fen)移動到中心。

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

最終得到圖(tu)像如下:


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


1、二值化(hua)

  把(ba)剛才得(de)到的(de)傅里葉譜放(fang)到0-255的(de)范圍,然后進(jin)行(xing)二值化,此(ci)處以(yi)150作(zuo)為分界點。

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

 得到圖像如下:


2、Houge直線檢測

  由于(yu)HoughLine2方法只接受8UC1格式(shi)的(de)圖(tu)片,因此要先進(jin)行轉(zhuan)換再調用HoughLine2方法,這里(li)的(de)threshold參數取(qu)的(de)100,能夠檢測出3條直線(xian)來。

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、找(zhao)到符合條(tiao)件的(de)那條(tiao)斜線,獲取(qu)角度

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


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


最后放完整代碼(ma):


  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. //以灰(hui)度方(fang)式讀入(ru)原文件
  15. string filename = "source.jpg";
  16. var src = IplImage.FromFile(filename, LoadMode.GrayScale);
  17. //轉換(huan)到合(he)適的大(da)小,以適應快速變(bian)換(huan)
  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. //實(shi)部(bu)、虛部(bu)(單通道)
  23. var real = new IplImage(padded.Size, BitDepth.F32, 1);
  24. var imaginary = new IplImage(padded.Size, BitDepth.F32, 1);
  25. //合(he)并(雙通道(dao))
  26. var fourier = new IplImage(padded.Size, BitDepth.F32, 2);
  27. //圖(tu)像復(fu)制到實部(bu),虛部(bu)清零(ling)
  28. Cv.ConvertScale(padded, real);
  29. Cv.Zero(imaginary);
  30. //合并、變(bian)換(huan)、再分(fen)解
  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. //把低(di)頻移動到中心
  45. ShiftDFT(real);
  46. //二(er)值化(hua),以150作(zuo)為分界點,經驗(yan)值,需要根據(ju)實際情況(kuang)調整
  47. Cv.Threshold(real, real, 150, 255, ThresholdType.Binary);
  48. //由于HoughLines2方法(fa)只接受8UC1格式的圖片,因此(ci)進行轉換
  49. var gray = new IplImage(real.Size, BitDepth.U8, 1);
  50. Cv.ConvertScale(real, gray);
  51. //找直(zhi)線,threshold參數取(qu)100,經驗值,需要(yao)根據實(shi)際情況(kuang)調整
  52. var storage = Cv.CreateMemStorage();
  53. var lines = Cv.HoughLines2(gray, storage, HoughLinesMethod.Standard, 1, Cv.PI / 180, 100);
  54. //找到符合條件的(de)那(nei)條斜線
  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. //極坐(zuo)標下的點,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. /// 將低頻(pin)部分移動到圖像(xiang)中心
  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. }



來源: 




附件列表

     

    posted on 2016-10-27 15:16  ①塊腹肌  閱讀(3612)  評論(1)    收藏  舉報