OpenCV基于傅里叶变换进行文本的旋转校正
1 2 | string filename = "source.jpg" ; var src = IplImage.FromFile(filename, LoadMode.GrayScale); |
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)); |
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 ); |
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); |
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 2 | Cv.Normalize(real, real, 0, 255, NormType.MinMax); Cv.Threshold(real, real, 150, 255, ThresholdType.Binary); |
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); |
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); |
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; |
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),再存回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);
}
}
}
附件列表