转载:https://www.cnblogs.com/skyfsm/p/6902524.html

那什么是图像的矫正呢?举个例子就好明白了。

我的好朋友小明给我拍了这几张照片,因为他的拍照技术不咋地,照片都拍得歪歪扭扭的,比如下面这些照片:

人民币

发票

文本

这些图片让人看得真不舒服!看个图片还要歪脖子看,实在是太烦人了!我叫小明帮我扫描一下一本教科书,小明把每一页书都拍成上面的文本那样了。好气啊那该怎么办呢?一页一页用PS来处理?1000页的矫正啊,当然交给计算机去做!

真的,对于图像矫正的问题,在图像处理领域还真得多,比如人民币的矫正、文本的矫正、车牌的矫正、身份证矫正等等。这些都是因为拍摄者总不可能100%正确地拍摄好图片,这就要求我们通过后期的图像处理技术将图片还原好,才能进一步做后面的处理,比如数字分割啊数字识别啊,不然歪歪扭扭的文字数字,想识别出来估计就很难了。

上面几个图,我们在日常生活中遇到的可不少,因为拍摄时拍的不好,导致拍出来的图片歪歪扭扭的,很不自然,那么我们能不能把这些图片尽可能地矫正过来呢?

OpenCV告诉我们,没问题!工具我给你,算法你自己设计!

比如图一,我要想将人民币矫正,并且把人民币整个抠出来保存,该怎么做?那就涉及到了图像的矫正和感兴趣区域提取两大技术了。

总的来说,要进行进行图像矫正,至少有以下几项知识储备:

  • 轮廓提取技术
  • 霍夫变换知识
  • ROI感兴趣区域知识

下面以人民币矫正、发票矫正、文本矫正为例,一步步剖析如何实现图像矫正。

首先分析如何矫正人民币。

比如我们要矫正这张人民币,思路应该是怎么样?

首先分析这张图的特点。

在这张图里,人民币有一定的倾斜角度,但是角度不大;人民币的背景是黑色的,而且人民币的边缘应该比较明显。

没错,我们就抓住人民币的的边缘比较明显来做文章!我们是不是可以先把人民币的轮廓找出来(找出来的轮廓当然就是一个大大的矩形),然后用矩形去包围它,得到他的旋转角度,然后根据得到的角度进行旋转,那样不就可以实现矫正了吗!

再详细地总结处理步骤:

  1. 图片灰度化
  2. 阈值二值化
  3. 检测轮廓
  4. 寻找轮廓的包围矩阵,并且获取角度
  5. 根据角度进行旋转矫正
  6. 对旋转后的图像进行轮廓提取
  7. 对轮廓内的图像区域抠出来,成为一张独立图像

我把该矫正算法命名为基于轮廓提取的矫正算法,因为其关键技术就是通过轮廓来获取旋转角度。

#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;//第一个参数:输入图片名称;第二个参数:输出图片名称
void GetContoursPic(const char* pSrcFileName, const char* pDstFileName)
{Mat srcImg = imread(pSrcFileName);imshow("原始图", srcImg);Mat gray, binImg;//灰度化cvtColor(srcImg, gray, COLOR_RGB2GRAY);imshow("灰度图", gray);//二值化threshold(gray, binImg, 100, 200, CV_THRESH_BINARY);imshow("二值化", binImg);vector<vector<Point> > contours;vector<Rect> boundRect(contours.size());//注意第5个参数为CV_RETR_EXTERNAL,只检索外框  findContours(binImg, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); //找轮廓cout << contours.size() << endl;for (int i = 0; i < contours.size(); i++){//需要获取的坐标  CvPoint2D32f rectpoint[4];CvBox2D rect =minAreaRect(Mat(contours[i]));cvBoxPoints(rect, rectpoint); //获取4个顶点坐标  //与水平线的角度  float angle = rect.angle;cout << angle << endl;int line1 = sqrt((rectpoint[1].y - rectpoint[0].y)*(rectpoint[1].y - rectpoint[0].y) + (rectpoint[1].x - rectpoint[0].x)*(rectpoint[1].x - rectpoint[0].x));int line2 = sqrt((rectpoint[3].y - rectpoint[0].y)*(rectpoint[3].y - rectpoint[0].y) + (rectpoint[3].x - rectpoint[0].x)*(rectpoint[3].x - rectpoint[0].x));//rectangle(binImg, rectpoint[0], rectpoint[3], Scalar(255), 2);//面积太小的直接passif (line1 * line2 < 600){continue;}//为了让正方形横着放,所以旋转角度是不一样的。竖放的,给他加90度,翻过来  if (line1 > line2) {angle = 90 + angle;}//新建一个感兴趣的区域图,大小跟原图一样大  Mat RoiSrcImg(srcImg.rows, srcImg.cols, CV_8UC3); //注意这里必须选CV_8UC3RoiSrcImg.setTo(0); //颜色都设置为黑色  //imshow("新建的ROI", RoiSrcImg);//对得到的轮廓填充一下  drawContours(binImg, contours, -1, Scalar(255),CV_FILLED);//抠图到RoiSrcImgsrcImg.copyTo(RoiSrcImg, binImg);//再显示一下看看,除了感兴趣的区域,其他部分都是黑色的了  namedWindow("RoiSrcImg", 1);imshow("RoiSrcImg", RoiSrcImg);//创建一个旋转后的图像  Mat RatationedImg(RoiSrcImg.rows, RoiSrcImg.cols, CV_8UC1);RatationedImg.setTo(0);//对RoiSrcImg进行旋转  Point2f center = rect.center;  //中心点  Mat M2 = getRotationMatrix2D(center, angle, 1);//计算旋转加缩放的变换矩阵 warpAffine(RoiSrcImg, RatationedImg, M2, RoiSrcImg.size(),1, 0, Scalar(0));//仿射变换 imshow("旋转之后", RatationedImg);imwrite("r.jpg", RatationedImg); //将矫正后的图片保存下来}#if 1//对ROI区域进行抠图//对旋转后的图片进行轮廓提取  vector<vector<Point> > contours2;Mat raw = imread("r.jpg");Mat SecondFindImg;//SecondFindImg.setTo(0);cvtColor(raw, SecondFindImg, COLOR_BGR2GRAY);  //灰度化  threshold(SecondFindImg, SecondFindImg, 80, 200, CV_THRESH_BINARY);findContours(SecondFindImg, contours2, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);//cout << "sec contour:" << contours2.size() << endl;for (int j = 0; j < contours2.size(); j++){//这时候其实就是一个长方形了,所以获取rect  Rect rect = boundingRect(Mat(contours2[j]));//面积太小的轮廓直接pass,通过设置过滤面积大小,可以保证只拿到外框if (rect.area() < 600){continue;}Mat dstImg = raw(rect);imshow("dst", dstImg);imwrite(pDstFileName, dstImg);}
#endif}void main()
{GetContoursPic("6.jpg", "FinalImage.jpg");waitKey();
}

效果依次如下:
原始图

二值化图

掩膜mask是这样的

旋转矫正之后

将人民币区域抠出来

该算法的效果还是很不错的!那赶紧试试其他图片,我把倾斜的发票图像拿去试试。

原始图

倾斜矫正之后

最后把目标区域抠出来,成为单独的照片。

上面的算法可以很好的处理人民币和发票两种情况的倾斜矫正,那文本矫正可以吗?我赶紧试了一下,结果是失败的。

原图

算法矫正后,还是原样,矫正失败。

认真分析一下,还是很容易看出文本矫正失败的原因的。

原因就在于,人民币图像和发票图像他们有明显的的边界轮廓,而文本图像没有。文本图像的背景是白色的,所以我们没有办法像人民币发票那类有明显边界的矩形物体那样,提取出轮廓并旋转矫正。

经过深入分析可以看出,虽然文本类图像没有明显的边缘轮廓,但是他们有一个很重要的特征,那就是每一行文字都是呈现一条直线形状,而且这些直线都是平行的!

对于这种情况,我想到了另一种方法:基于直线探测的矫正算法

首先介绍一下我的算法思路:

  1. 用霍夫线变换探测出图像中的所有直线
  2. 计算出每条直线的倾斜角,求他们的平均值
  3. 根据倾斜角旋转矫正
  4. 最后根据文本尺寸裁剪图片

然后给出OpenCV的实现算法:


#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;#define ERROR 1234//度数转换
double DegreeTrans(double theta)
{double res = theta / CV_PI * 180;return res;
}//逆时针旋转图像degree角度(原尺寸)
void rotateImage(Mat src, Mat& img_rotate, double degree)
{//旋转中心为图像中心    Point2f center;center.x = float(src.cols / 2.0);center.y = float(src.rows / 2.0);int length = 0;length = sqrt(src.cols*src.cols + src.rows*src.rows);//计算二维旋转的仿射变换矩阵  Mat M = getRotationMatrix2D(center, degree, 1);warpAffine(src, img_rotate, M, Size(length, length), 1, 0, Scalar(255,255,255));//仿射变换,背景色填充为白色
}//通过霍夫变换计算角度
double CalcDegree(const Mat &srcImage, Mat &dst)
{Mat midImage, dstImage;Canny(srcImage, midImage, 50, 200, 3);cvtColor(midImage, dstImage, CV_GRAY2BGR);//通过霍夫变换检测直线vector<Vec2f> lines;HoughLines(midImage, lines, 1, CV_PI / 180, 300, 0, 0);//第5个参数就是阈值,阈值越大,检测精度越高//cout << lines.size() << endl;//由于图像不同,阈值不好设定,因为阈值设定过高导致无法检测直线,阈值过低直线太多,速度很慢//所以根据阈值由大到小设置了三个阈值,如果经过大量试验后,可以固定一个适合的阈值。if (!lines.size()){HoughLines(midImage, lines, 1, CV_PI / 180, 200, 0, 0);}//cout << lines.size() << endl;if (!lines.size()){HoughLines(midImage, lines, 1, CV_PI / 180, 150, 0, 0);}//cout << lines.size() << endl;if (!lines.size()){cout << "没有检测到直线!" << endl;return ERROR;}float sum = 0;//依次画出每条线段for (size_t i = 0; i < lines.size(); i++){float rho = lines[i][0];float theta = lines[i][1];Point pt1, pt2;//cout << theta << endl;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));//只选角度最小的作为旋转角度sum += theta;line(dstImage, pt1, pt2, Scalar(55, 100, 195), 1, LINE_AA); //Scalar函数用于调节线段颜色imshow("直线探测效果图", dstImage);}float average = sum / lines.size(); //对所有角度求平均,这样做旋转效果会更好cout << "average theta:" << average << endl;double angle = DegreeTrans(average) - 90;rotateImage(dstImage, dst, angle);//imshow("直线探测效果图2", dstImage);return angle;
}void ImageRecify(const char* pInFileName, const char* pOutFileName)
{double degree;Mat src = imread(pInFileName);imshow("原始图", src);Mat dst;//倾斜角度矫正degree = CalcDegree(src,dst);if (degree == ERROR){cout << "矫正失败!" << endl;return;}rotateImage(src, dst, degree);cout << "angle:" << degree << endl;imshow("旋转调整后", dst);Mat resulyImage = dst(Rect(0, 0, dst.cols, 500)); //根据先验知识,估计好文本的长宽,再裁剪下来imshow("裁剪之后", resulyImage);imwrite("recified.jpg", resulyImage);
}int main()
{ImageRecify("correct2.jpg", "FinalImage.jpg");waitKey();return 0;
}

看看效果。这是原始图

直线探测的效果。

矫正之后的效果。

我们发现矫正之后的图像有较多留白,影响观看,所以需要进一步裁剪,保留文字区域。

赶紧再试多一张。

原始图

直线探测

矫正效果

进一步裁剪

可以看出,基于直线探测的矫正算法在文本处理上效果真的很不错!

最后总结一下两个算法的应用场景:

  • 基于轮廓提取的矫正算法更适用于车牌、身份证、人民币、书本、发票一类矩形形状而且边界明显的物体矫正。

  • 基于直线探测的矫正算法更适用于文本类的矫正。

OpenCV探索之路(十六):图像矫正技术深入探讨相关推荐

  1. 第十六章 网络通信协议探讨

                               第十六章    网络通信协议探讨      LINUX的源代码中属于网络的就有近38万行:我必须先花时间彻底解决网络编程问题,再论其它:所以.其它 ...

  2. 未雨绸缪,迎接运维新时代—— Tech Neo第十六期技术沙龙

    运维发展历程与工业革命异曲同工,工业的三次革命分别是机械化.电气化与信息化,运维则是原始手工.脚本与自动化工具.那么工业4.0悄然来临的今天,智能化又将会给运维带来哪些影响?坦白讲,AIOps是新概念 ...

  3. OpenCV精进之路(十四):图像矫正技术深入探讨

    刚进入实验室导师就交给我一个任务,就是让我设计算法给图像进行矫正.哎呀,我不太会图像这块啊,不过还是接下来了,硬着头皮开干吧! 那什么是图像的矫正呢?举个例子就好明白了. 我的好朋友小明给我拍了这几张 ...

  4. 图像矫正技术深入探讨

    转载:https://www.cnblogs.com/skyfsm/category/1000207.html 刚进入实验室导师就交给我一个任务,就是让我设计算法给图像进行矫正.哎呀,我不太会图像这块 ...

  5. opencv(十六)-图像形态学

    索引目录 1.形态学概述 2.膨胀与腐蚀 2.1 膨胀 2.2 腐蚀 3.开运算和闭运算 3.1 开运算 3.2 闭运算 4.形态学梯度 5.击中与击不中变换 6.顶帽与黑帽变换 6.1 顶帽 6.2 ...

  6. opencv学习十六:圆检测

    圆检测 原理 圆周上任意三点所确定的圆,经Hough变换后在三维参数空间应对应一点.遍历圆周上所有点,任意三个点所确定的候选圆进行投票.遍历结束后,得票数最高点(理论上圆周上任意三点确定的圆在Houg ...

  7. OpenCV(十六)边缘检测2 -- Laplace(拉普拉斯)二阶微分算子

    目录 一.基础理论 1.原理 2.过程 3.Laplacian函数 代码 效果 参考资料 一.基础理论 1.原理 Laplace算子作为边缘检测之一,和Sobel算子一样也是工程数学中常用的一种积分变 ...

  8. OpenCV探索之路(六):图像变换——霍夫变换

    前言 我们如何在图像中快速识别出其中的圆和直线?一个非常有效的方法就是霍夫变换,它是图像中识别各种几何形状的基本算法之一. 霍夫线变换 霍夫线变换是一种在图像中寻找直线的方法.OpenCV中支持三种霍 ...

  9. OpenCV 【十六】RNG随机数发生器putText绘制文字

    1 目的 使用 随机数发生器类 (RNG) 并得到均匀分布的随机数. 通过使用函数 putText 显示文字. 第一步是实例化一个 Random Number Generator(随机数发生器对象) ...

  10. 达拉草201771010105《面向对象程序设计(java)》第十六周学习总结

    达拉草201771010105<面向对象程序设计(java)>第十六周学习总结 第一部分:理论知识 1.程序与进程的概念: (1)程序是一段静态的代码,它是应用程序执行的蓝 本. (2)进 ...

最新文章

  1. 临沂机器人火锅_临沂知名智能无人火锅前景
  2. 惊了!7 行代码优雅地实现 Excel 文件生成下载功能
  3. Asp.Net登陆记住用户功能实现
  4. win7系统怎么更改语言及字体
  5. microsoft excel正在等待其他某个应用程序_如何删除 Mac 储存空间的其他选项?
  6. mysql中常见的几种索引
  7. AndroidStudio_安卓原生开发_精美自定义多选控件_多选Spinner_MultiSpinner_拿来即用---Android原生开发工作笔记144
  8. 写出好的 commit message
  9. asp.net2.0自定义控件---鼠标移到按钮上更改背景颜色,移出后恢复
  10. TCP 和 UDP 区别
  11. Dukto 文件传输软件(推荐)
  12. 27个最佳Beaver Builder主题和模板(2020)
  13. 微信小程序常用组件库收藏备用
  14. 创新研发高通量芯片技术,JASMINER实现区块链芯片大突破
  15. 口红送什么色号,这是一个难题,爬取口红数据,希望对你有所帮助
  16. Springfox3一个类多个接口使用解决方案
  17. 文本处理工具简单介绍
  18. 曹操比袁绍“宽容”并非胸襟开阔
  19. 如何实现台达ES系列PLC的组态监控?
  20. 滴普科技完成B+轮融资,中航产融战略投资,聚焦湖仓一体数据智能基础软件...

热门文章

  1. js时间对象相关函数
  2. git学习笔记-(11-git存储)
  3. Linux中文件的7大类型
  4. 云优后台提交显示parsererror_Web测试必备技能——F12定位bug属于前端还是后台
  5. python defaultdict tree_python – 从非空的defaultdict中挑选一个随机元素
  6. 海思35系列型号排行_11月手机性能排行榜:小米10至尊纪念版排名第三
  7. 存在链接注入漏洞_【安全提示】CNVD发布上周关注度较高的产品安全漏洞(20200817-20200823)...
  8. java.util.concurrent.ExecutionException: org.apache.catalina.LifecycleException
  9. ubuntu16.04卸载火狐,Amazon
  10. 【ZJOI 2018】 历史(lct)