利用OpenCV实现旋转文本图像矫正的原理及OpenCV代码

对图像进行旋转矫正,关键是要获取旋转角度是多少!获取了旋转角度就可以用仿射变换对图像进行矫正,图像旋转的代码可以参考我的博文http://blog.csdn.net/wenhao_ir/article/details/51469085

旋转角度怎么获取?可以对图像作傅里叶变换获取这个角度,具体怎么求,请听我慢慢道来!

文本图像的明显特征就是存在分行间隔,那么行与文字之间这个灰度值变化就不如真正的文字及文字间的变化剧烈,那么相应的这些地方的频谱值也低,即频谱的低谱部分,因为傅里叶变换就是表征图像各点的变化频率的嘛~当文本图像旋转时,基频域中的频谱也会随之改变,那么我就可以根据这一特点来计算这个角度。

具体的步骤如下:

⑴读取图像,读出的同时转化为灰度图像,代码如下:

代码中用到的图像的下载链接为:http://pan.baidu.com/s/1miMuqJQ

//OpenCV版本2.4.9
//交流QQ2487872782 

  cv::Mat srcImage = cv::imread("text2.jpg",0);
  if( !srcImage.data )
	      return 1;
  srcImage = srcImage.rowRange(0,365);
  cv::imshow("srcImage", srcImage);

⑵图像DFT尺寸变换,快速傅里叶变换是基于图像尺寸是2、3或5的倍数完成的,因此对于输入源图像,首先应将其变换成DFTSize,OpenCV中提供了函数getOptimalDFTSize()来实现尺寸转换。

图像DFT尺寸转换代码下如下:

//OpenCV版本2.4.9
//交流QQ2487872782 

  // 图像尺寸转换
  int nRows = srcImage.rows;
  int nCols = srcImage.cols;
  std::cout << "srcImage row:" << nRows << std::endl;
  std::cout << "srcImage col:" << nCols << std::endl;
  // 获取DFT尺寸
  int cRows = cv::getOptimalDFTSize(nRows);
  int cCols = cv::getOptimalDFTSize(nCols);
  std::cout << "DFTImage row:" << cRows << std::endl;
  std::cout << "DFTImage col:" << cCols << std::endl;
  // 图像拷贝,超过边界区域填充为0
  cv::Mat sizeConvMat;
  copyMakeBorder(srcImage, sizeConvMat, 0, cRows -nRows, 0,
     cCols-nCols, cv::BORDER_CONSTANT, cv::Scalar::all(0));
  cv::imshow("sizeConvMat", sizeConvMat);

运行结果如下图所示:

⑶DFT变换,源代码如下:具体的原理可以参考我写的博文http://blog.csdn.net/wenhao_ir/article/details/51142979

//OpenCV版本2.4.9
//交流QQ2487872782 

  //  通道组建立,
  cv::Mat groupMats[] = {cv::Mat_<float>(sizeConvMat),
       cv::Mat::zeros(sizeConvMat.size(), CV_32F)};//这里实际上是建立MAT数组,数组有两个成员:
						  //第一个就是sizeConvMat这个对象(只是数据类型转换成了float类型)
						  //第二个是全0的类型为32F的对象
  cv::Mat mergeMat;
  // 通道合并
  merge(groupMats,2,mergeMat);//把groupMats的第0和第1个对象合并到mergeMat,通过这个操作mergeMat是双通道的数据阵列了
  // DFT变换
  dft(mergeMat, mergeMat);
  // 分离通道
  split(mergeMat, groupMats);
  // 计算幅值
  magnitude(groupMats[0], groupMats[1], groupMats[0]);//0中存的是实部值,1中存的是虚部值
  cv::Mat magnitudeMat = groupMats[0].clone();
  // 归一化操作
  magnitudeMat += Scalar::all(1);//全部加1作对数变换,以扩大频域动态显示范围
  log(magnitudeMat, magnitudeMat);//作对数变换
  normalize(magnitudeMat, magnitudeMat, 0, 1, CV_MINMAX);
  magnitudeMat.convertTo(magnitudeMat,CV_8UC1,255,0);//注意 convertTo中大边界255在前,小边界0在后
  cv::imshow("magnitudeMat", magnitudeMat);

运行结果如下图所示

⑷频域中心移动

提问:为什么要进行频域中心的移动?答案在我的博文http://blog.csdn.net/wenhao_ir/article/details/51689960

我这里对这个问题再补充说几句吧!

傅里叶变换得到的低频部分在边缘角中,高频部分位于图像中心,对于倾斜文本图像,我们更关心的是图像中的低频部分,因此需要将其与高频部分互换中心。

代码如下:

//OpenCV版本2.4.9
//交流QQ2487872782 

 // 中心平移
  int cx = magnitudeMat.cols/2;
  int cy = magnitudeMat.rows/2;
  Mat tmp;
  // Top-Left - 为每一个象限创建ROI
  Mat q0(magnitudeMat,Rect(0,0,cx,cy));
  // Top-Right
  Mat q1(magnitudeMat,Rect(cx,0,cx,cy));
  // Bottom-Left
  Mat q2(magnitudeMat,Rect(0,cy,cx,cy));
  // Bottom-Right
  Mat q3(magnitudeMat,Rect(cx,cy,cx,cy));
  // 交换象限 (Top-Left with Bottom-Right)
  q0.copyTo(tmp);
  q3.copyTo(q0);
  tmp.copyTo(q3);
  // 交换象限 (Top-Right with Bottom-Left)
  q1.copyTo(tmp);
  q2.copyTo(q1);
  tmp.copyTo(q2);
  cv::imshow("magnitudeMat2", magnitudeMat);

运行结果如下图所示:

⑸倾斜角检测。经过频域中心移动后,由上图可以看出,只需要检测出图像中直线的倾斜角就可以对旋转文本进行校正。计算直线倾斜角有多种方法,这里采用霍夫变换线 检测方法进行直线倾斜角的计算,首先将傅里叶变换后的频谱图进行固定二值化处理,这里阈值的选择和场景有很大关系,要根据实际应用场景进行合理调整;然后根据霍夫变换检测直线的步骤来完成图像中的直线检测,具体霍夫变换的原理我后面会写博文介绍;直线检测完了后计算图像直线的角度,然后用这个角度对原图进行仿射变换矫正。

倾斜角检测的代码如下:

//OpenCV版本2.4.9
//交流QQ2487872782 

// 固定阈值二值化处理
  cv::Mat binaryMagnMat;
  threshold(magnitudeMat,binaryMagnMat,132,255,CV_THRESH_BINARY);
  cv::imshow("binaryMagnMat", binaryMagnMat);
  // 霍夫变换
  vector<Vec2f> lines;
  binaryMagnMat.convertTo(binaryMagnMat,CV_8UC1,255,0);
  HoughLines(binaryMagnMat, lines, 1, CV_PI/180, 100, 0, 0 );
  // 检测线个数
  std::cout << "lines.size:"<< lines.size() << std::endl;
  cv::Mat houghMat(binaryMagnMat.size(),CV_8UC3);
  for( size_t i = 0; i < lines.size(); i++ )
  // 绘制检测线
  {
      float rho = lines[i][0], theta = lines[i][1];
      Point pt1, pt2;
      double a = cos(theta), b = sin(theta);
      double x0 = a*rho, y0 = b*rho;
      pt1.x = cvRound(x0 + 1000*(-b));
      pt1.y = cvRound(y0 + 1000*(a));
      pt2.x = cvRound(x0 - 1000*(-b));
      pt2.y = cvRound(y0 - 1000*(a));
      line( houghMat, pt1, pt2, Scalar(0,255,0), 3, CV_AA);
  }
  cv::imshow("houghMat", houghMat);
  float theta = 0;
  // 检测线角度判断
  for( size_t i = 0; i < lines.size(); i++ )
  {
    float  thetaTemp = lines[i][1]*180/CV_PI;
    if(thetaTemp > 0 && thetaTemp < 90)
    {
        theta = thetaTemp;
        break;
    }
  }
  // 角度转换
  float angelT = nRows* tan(theta/180*CV_PI)/nCols;
  theta = atan(angelT)*180/CV_PI;
  std::cout << "theta:" << theta << std::endl;

运行结果如下图所示:

实际上,这幅图像我在美图秀秀中旋转了20度左右,可见,程序得出的角度是非常OK的!

⑹仿射变换矫正。对得到的线角度计算旋转矩阵,利用仿射变换完成旋转文本矫正。

仿射变换矫正代码如下:

// 取图像中心
cv::Point2f centerPoint = cv::Point2f(nCols/2, nRows/2);
double scale = 1;
// 计算旋转矩阵
cv::Mat warpMat = getRotationMatrix2D(centerPoint, theta, scale);
// 仿射变换
cv::Mat resultImage(srcImage.size(), srcImage.type());
cv::warpAffine(srcImage, resultImage,
  warpMat, resultImage.size());
imshow("resultImage",resultImage);

运行结果如下图所示:

可见,矫正的效果是很好的。

最后再给大家一个完整版的代码吧!

代码中用到的图像的下载链接为:http://pan.baidu.com/s/1miMuqJQ

//OpenCV版本2.4.9
//交流QQ2487872782 

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
using namespace cv;
int main( )
{
  cv::Mat srcImage = cv::imread("text2.jpg",0);
  if( !srcImage.data )
	      return 1;
  srcImage = srcImage.rowRange(0,365);
  cv::imshow("srcImage", srcImage);
  // 图像尺寸转换
  int nRows = srcImage.rows;
  int nCols = srcImage.cols;
  std::cout << "srcImage row:" << nRows << std::endl;
  std::cout << "srcImage col:" << nCols << std::endl;
  // 获取DFT尺寸
  int cRows = cv::getOptimalDFTSize(nRows);
  int cCols = cv::getOptimalDFTSize(nCols);
  std::cout << "DFTImage row:" << cRows << std::endl;
  std::cout << "DFTImage col:" << cCols << std::endl;
  // 图像拷贝,超过边界区域填充为0
  cv::Mat sizeConvMat;
  copyMakeBorder(srcImage, sizeConvMat, 0, cRows -nRows, 0,
     cCols-nCols, cv::BORDER_CONSTANT, cv::Scalar::all(0));
  cv::imshow("sizeConvMat", sizeConvMat);

  //  通道组建立,
  cv::Mat groupMats[] = {cv::Mat_<float>(sizeConvMat),
       cv::Mat::zeros(sizeConvMat.size(), CV_32F)};//这里实际上是建立MAT数组,数组有两个成员:
												   //第一个就是sizeConvMat这个对象(只是数据类型转换成了float类型)
												   //第二个是全0的类型为32F的对象
  cv::Mat mergeMat;
  // 通道合并
  merge(groupMats,2,mergeMat);//把groupMats的第0和第1个对象合并到mergeMat,通过这个操作mergeMat是双通道的数据阵列了
  // DFT变换
  dft(mergeMat, mergeMat);
  // 分离通道
  split(mergeMat, groupMats);
  // 计算幅值
  magnitude(groupMats[0], groupMats[1], groupMats[0]);//0中存的是实部值,1中存的是虚部值
  cv::Mat magnitudeMat = groupMats[0].clone();
  // 归一化操作
  magnitudeMat += Scalar::all(1);//全部加1作对数变换,以扩大频域动态显示范围
  log(magnitudeMat, magnitudeMat);//作对数变换
  normalize(magnitudeMat, magnitudeMat, 0, 1, CV_MINMAX);
  magnitudeMat.convertTo(magnitudeMat,CV_8UC1,255,0);//注意 convertTo中大边界255在前,小边界0在后
  cv::imshow("magnitudeMat", magnitudeMat);

  // 中心平移
  int cx = magnitudeMat.cols/2;
  int cy = magnitudeMat.rows/2;
  Mat tmp;
  // Top-Left - 为每一个象限创建ROI
  Mat q0(magnitudeMat,Rect(0,0,cx,cy));
  // Top-Right
  Mat q1(magnitudeMat,Rect(cx,0,cx,cy));
  // Bottom-Left
  Mat q2(magnitudeMat,Rect(0,cy,cx,cy));
  // Bottom-Right
  Mat q3(magnitudeMat,Rect(cx,cy,cx,cy));
  // 交换象限 (Top-Left with Bottom-Right)
  q0.copyTo(tmp);
  q3.copyTo(q0);
  tmp.copyTo(q3);
  // 交换象限 (Top-Right with Bottom-Left)
  q1.copyTo(tmp);
  q2.copyTo(q1);
  tmp.copyTo(q2);
  cv::imshow("magnitudeMat2", magnitudeMat);

  // 固定阈值二值化处理
  cv::Mat binaryMagnMat;
  threshold(magnitudeMat,binaryMagnMat,132,255,CV_THRESH_BINARY);
  cv::imshow("binaryMagnMat", binaryMagnMat);
  // 霍夫变换
  vector<Vec2f> lines;
  binaryMagnMat.convertTo(binaryMagnMat,CV_8UC1,255,0);
  HoughLines(binaryMagnMat, lines, 1, CV_PI/180, 100, 0, 0 );
  // 检测线个数
  std::cout << "lines.size:"<< lines.size() << std::endl;
  cv::Mat houghMat(binaryMagnMat.size(),CV_8UC3);
  for( size_t i = 0; i < lines.size(); i++ )
  // 绘制检测线
  {
      float rho = lines[i][0], theta = lines[i][1];
      Point pt1, pt2;
      double a = cos(theta), b = sin(theta);
      double x0 = a*rho, y0 = b*rho;
      pt1.x = cvRound(x0 + 1000*(-b));
      pt1.y = cvRound(y0 + 1000*(a));
      pt2.x = cvRound(x0 - 1000*(-b));
      pt2.y = cvRound(y0 - 1000*(a));
      line( houghMat, pt1, pt2, Scalar(0,255,0), 3, CV_AA);
  }
  cv::imshow("houghMat", houghMat);
  float theta = 0;
  // 检测线角度判断
  for( size_t i = 0; i < lines.size(); i++ )
  {
    float  thetaTemp = lines[i][1]*180/CV_PI;
    if(thetaTemp > 0 && thetaTemp < 90)
    {
        theta = thetaTemp;
        break;
    }
  }
  // 角度转换
  float angelT = nRows* tan(theta/180*CV_PI)/nCols;
  theta = atan(angelT)*180/CV_PI;
  std::cout << "theta:" << theta << std::endl;

// 取图像中心
cv::Point2f centerPoint = cv::Point2f(nCols/2, nRows/2);
double scale = 1;
// 计算旋转矩阵
cv::Mat warpMat = getRotationMatrix2D(centerPoint, theta, scale);
// 仿射变换
cv::Mat resultImage(srcImage.size(), srcImage.type());
cv::warpAffine(srcImage, resultImage,
  warpMat, resultImage.size());
imshow("resultImage",resultImage);

cv::waitKey(0);
return 0;
}

-------------------------------------------

欢迎大家加入图像识别技术交流群:271891601,另外,特别欢迎成都从事图像识别工作的朋友交流,我的QQ号2487872782

时间: 2024-07-29 21:27:04

利用OpenCV实现旋转文本图像矫正的原理及OpenCV代码的相关文章

C#利用GDI+绘制旋转文字等效果

C#中利用GDI+绘制旋转文本的文字,网上有很多资料,基本都使用矩阵旋转的方式实现.但基本都只提及按点旋转,若要实现在矩形范围内旋转文本,资料较少.经过琢磨,可以将矩形内旋转转化为按点旋转,不过需要经过不少的计算过程.利用下面的类可以实现该功能. [csharp] view plaincopy using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D;

C#利用GDI+绘制旋转文字等效果实例

本文实例讲述了C#利用GDI+绘制旋转文字等效果的方法,是非常实用的技巧.分享给大家供大家参考之用.具体如下: C#中利用GDI+绘制旋转文本的文字,网上有很多资料,基本都使用矩阵旋转的方式实现.但基本都只提及按点旋转,若要实现在矩形范围内旋转文本,资料较少.经过琢磨,可以将矩形内旋转转化为按点旋转,不过需要经过不少的计算过程.利用下面的类可以实现该功能. 具体实现代码如下: using System; using System.Collections.Generic; using System

利用lucene对PDF文本进行内容的解析

/* * 这段代码的功能是利用PDFBox.zip的包 * 利用lucene对PDF文本进行内容的解析 * 读取pdf文件的内容.然后重新的写入到同名的.txt文件中  * */ 结果截图: package pdfbox; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.net.MalformedU

利用 NSAttributedString 进行富文本处理

原文出自  http://blog.qiji.tech/archives/8335#RegEx_Categories [iOS] 利用 NSAttributedString 进行富文本处理 /iOS /[iOS] 利用 NSAttributedString 进行富文本处理 2016年4月4日 刘小龙 iOS 许多时候我们需要以各种灵活的形式展现文本信息,即富文本.普通的 text 属性显然无法满足要求,这时我们需要利用 Foundation 中的 NSAttributedString——属性字符

利用opencv中的级联分类器进行人脸检测-opencv学习(1)

OpenCV支持的目标检测的方法是利用样本的Haar特征进行的分类器训练,得到的级联boosted分类器(Cascade Classification).注意,新版本的C++接口除了Haar特征以外也可以使用LBP特征. 先介绍一下相关的结构,级联分类器的计算特征值的基础类FeatureEvaluator,功能包括读操作read.复制clone.获得特征类型getFeatureType,分配图片分配窗口的操作setImage.setWindow,计算有序特征calcOrd,计算绝对特征calcC

[iOS] 利用 NSAttributedString 进行富文本处理

/iOS /[iOS] 利用 NSAttributedString 进行富文本处理 2016年4月4日 刘小龙 iOS 许多时候我们需要以各种灵活的形式展现文本信息,即富文本.普通的 text 属性显然无法满足要求,这时我们需要利用 Foundation 中的 NSAttributedString--属性字符串进行设置.拥有文本显示功能(text 属性)的 UI 控件也都拥有 attributedText 属性. 常用方法 和 NSString 及 Foundation 框架其它集合一样,NSA

canvas旋转文本

canvas旋转文本 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatibl

规定文本框只能够输入整数代码实例

规定文本框只能够输入整数代码实例:有时候可能需要规定文本框内容只能够输入整数,下面给出一段能够实现此功能的代码实例,供需要的朋友参考.代码如下: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="author" content="http://www.softwhy.com/" /> <title>

【OpenCV入门教程之二】 一览众山小:OpenCV 2.4.8 or OpenCV 2.4.9组件结构全解析

之前啃了不少OpenCV的官方文档,发现如果了解了一些OpenCV整体的模块架构后,再重点学习自己感兴趣的部分的话,就会有一览众山小的感觉,于是,就决定写出这篇文章,作为启程OpenCV系列博文的第二篇. 至于OpenCV组件结构的研究方法,我们不妨管中窥豹,通过opencv安装路径下include目录里面头文件的分类存放,来一窥OpenCV这些年迅猛发展起来的庞杂组件架构. 我们进入到D:\ProgramFiles\opencv\build\include目录,可以看到有opencv和open