码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2016-06-24 15:25:11      阅读:242      评论:0      收藏:0      [点我收藏+]

标签:

对图像进行旋转矫正,关键是要获取旋转角度是多少!获取了旋转角度就可以用仿射变换对图像进行矫正,图像旋转的代码可以参考我的博文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

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

标签:

原文地址:http://blog.csdn.net/wenhao_ir/article/details/51733627

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!