相信很多人手机里都装了个“扫描全能王”APP,平时可以用它来可以扫描一些证件、文本,确实很好用,第一次用的时候确实感觉功能很强大啊算法很牛逼啊。但是仔细一想,其实这些实现起来也是很简单的,我想了下,实现的步骤应该就只有下面三个:

将证件轮廓找到

提取证件矩形轮廓四点进行透视变换

二值化

知道原理之后,我马上利用强大的opencv开发一个类似“全能扫描王”扫描工具。

整理一下我们要制作的这个扫描工具有哪些功能:

图像的信息区域的提取与矫正

图像的二值化

锐化和增强

第二第三点都非常简单,那么制作这个工具的难点完全落在了第一点“ 图像的信息区域的提取与矫正”上了。在编码实现的过程中,确实有很多坑需要踩一踩。

我们先展示一下效果,我们有这么一个用手机拍摄的图片

经过扫描工具一番处理后变成这样子。也就是说,我们将原图中的那个文件抠了了出来,并且完成矫正。

实现过程查阅了大量资料,也看了网上很多类似的博客,前辈们实现过相类似的透视变换的代码,但是他们的代码实现的都不理想,很多图片根本没法检测。不过还是可以从前人的经验中获取到很多好想法的,所以先列出一些有借鉴的博客:

正式实现

第一步,二值化+高斯滤波+膨胀+canny边缘提取

一开始我是没有采取形态学处理的,仅仅是二值化+高斯滤波+canny边缘提取的策略,但是实际运行下效果并不好,原因在于有一些图片的信息区域轮廓没法闭合,这就导致了信息区域轮廓没法提取。但是加入适当的膨胀后,效果就好多了。

Mat src = imread("1.png");

imshow("src img", src);

Mat source = src.clone();

Mat bkup = src.clone();

Mat img = src.clone();

cvtColor(img, img, CV_RGB2GRAY); //二值化

imshow("gray", img);

//equalizeHist(img, img);

//imshow("equal", img);

GaussianBlur(img, img, Size(5, 5), 0, 0); //高斯滤波

//获取自定义核

Mat element = getStructuringElement(MORPH_RECT, Size(3, 3)); //第一个参数MORPH_RECT表示矩形的卷积核,当然还可以选择椭圆形的、交叉型的

//膨胀操作

dilate(img, img, element); //实现过程中发现,适当的膨胀很重要

imshow("dilate", img);

Canny(img, img, 30, 120, 3); //边缘提取

imshow("get contour", img);

}

轮廓提取效果如下:

第二步,轮廓查找并筛选

一般情况下,我们提取到的轮廓不会像上图那样的干净,而是带有很多干扰项轮廓,如果我们不能很好的剔除这些轮廓,我们根本没法找出我们想要的信息区域。我筛选轮廓的方法很简单,就是找出一张图片中面积最大的那个轮廓作为我们的信息区域轮廓,这招真是屡试不爽,因为根据我们日常经验,我们对一张证件或者文件性扫描拍摄,证件区域占整张图片的面积肯定是最大的。

vector > contours;

vector > f_contours;

std::vector<:point> approx2;

//注意第5个参数为CV_RETR_EXTERNAL,只检索外框

findContours(img, f_contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); //找轮廓

//求出面积最大的轮廓

int max_area = 0;

int index;

for (int i = 0; i < f_contours.size(); i++)

{

double tmparea = fabs(contourArea(f_contours[i]));

if (tmparea > max_area)

{

index = i;

max_area = tmparea;

}

}

contours.push_back(f_contours[index]);

筛选出我们所需的轮廓

第三步,找出这个四边形轮廓的四个顶点

因为我们需要轮廓的四个顶点坐标来实现透视变换,现在的问题来了:我们怎么利用这个四边形轮廓的点集来找出初四边形的四个顶点?

这真是一个大难题!

一开始我的想法是这样子的:直接从四边形点集中筛选出四个定点(比如x坐标最大的那个坐标肯定是四边形右上角坐标或者右下角坐标,x坐标最小的那个坐标肯定是左上角或者下角的那个坐标,如此类推),但是这种想法实现起来是很有问题的而因为它很受限于四边形的姿态,所以一个思路一直没法进行下去。如果大家有仅依赖四边形点集就能找出四边形的四个顶点坐标的方法,请告诉我,我们一同探讨。

所以我切换了另外一个思路:基于直线交点的思路。我们首先使用霍夫变换找出四边形的边,然后求两两直线的交点不就是四边形的定点吗?的确是这样子的,但是实际操作起来也是问题多多啊。

最大的问题就是,我们怎么保证我们使用霍夫变换找到的直线刚好就是形成四边形的四条直线?

所以我们就必须不断地去改变霍夫变换的参数,不断迭代,来求出一个可以形成四边形的直线情况。

那什么情况的直线我们不能接受?

两两直线过于接近我们排除

两两直线没有交点我们排除

检测出来的直线数目不是4条我们排除

如果找到了满足条件的四条直线,我们就可以去计算他们的交点了。算法如下:

cv::Point2f computeIntersect(cv::Vec4i a, cv::Vec4i b)

{

int x1 = a[0], y1 = a[1], x2 = a[2], y2 = a[3];

int x3 = b[0], y3 = b[1], x4 = b[2], y4 = b[3];

if (float d = ((float)(x1 - x2) * (y3 - y4)) - ((y1 - y2) * (x3 - x4)))

{

cv::Point2f pt;

pt.x = ((x1*y2 - y1*x2) * (x3 - x4) - (x1 - x2) * (x3*y4 - y3*x4)) / d;

pt.y = ((x1*y2 - y1*x2) * (y3 - y4) - (y1 - y2) * (x3*y4 - y3*x4)) / d;

return pt;

}

else

return cv::Point2f(-1, -1);

}

计算出四个交点后,我们不能完全信任他们就是我们要找的四个顶点,所以继续筛选:

如果两两定点的距离过近,我们排除

bool IsGoodPoints = true;

//保证点与点的距离足够大以排除错误点

for (int i = 0; i < corners.size(); i++)

{

for (int j = i + 1; j < corners.size(); j++)

{

int distance = sqrt((corners[i].x - corners[j].x)*(corners[i].x - corners[j].x) + (corners[i].y - corners[j].y)*(corners[i].y - corners[j].y));

if (distance < 5)

{

IsGoodPoints = false;

}

}

}

if (!IsGoodPoints) continue;

如果这四个点构成不了四边形我们排除

cv::approxPolyDP(cv::Mat(corners), approx, cv::arcLength(cv::Mat(corners), true) * 0.02, true);

if (lines.size() == 4 && corners.size() == 4 && approx.size() == 4)

{

flag = 1;

break;

}

如果都通过以上筛选条件的,我们就可以认为他们就是我们找的那四个顶点,这时我们就可以停止迭代,进行顶点排序,即确定这四个顶点哪个是左上角点,哪个又是右下点。

算法如下:

bool x_sort(const Point2f & m1, const Point2f & m2)

{

return m1.x < m2.x;

}

//确定四个点的中心线

void sortCorners(std::vector<:point2f>& corners,

cv::Point2f center)

{

std::vector<:point2f> top, bot;

vector backup = corners;

sort(corners, x_sort); //注意先按x的大小给4个点排序

for (int i = 0; i < corners.size(); i++)

{

if (corners[i].y < center.y && top.size() < 2) //这里的小于2是为了避免三个顶点都在top的情况

top.push_back(corners[i]);

else

bot.push_back(corners[i]);

}

corners.clear();

if (top.size() == 2 && bot.size() == 2)

{

//cout << "log" << endl;

cv::Point2f tl = top[0].x > top[1].x ? top[1] : top[0];

cv::Point2f tr = top[0].x > top[1].x ? top[0] : top[1];

cv::Point2f bl = bot[0].x > bot[1].x ? bot[1] : bot[0];

cv::Point2f br = bot[0].x > bot[1].x ? bot[0] : bot[1];

corners.push_back(tl);

corners.push_back(tr);

corners.push_back(br);

corners.push_back(bl);

}

else

{

corners = backup;

}

}

第四步,四点法透射变换

我们拿到原图信息区域四边形的四个顶点,现在我们还需要变换后图像的四个顶点才可以实现投射变换。

求变换后四个顶点坐标前我们还需要做的一件事就是,确定变换后的图像尺寸。第一种方法就是人工指定,比如我直接规定好变换后的图片大小是bbb*aaa。第二种方法就是,通过计算确定信息区域的尺寸,也就是说,信息区域有多大,我们变换后的图像就有多大。

既然我们知道了四边形的四个顶点了,那么我们可以直接求两点的距离来确定四边形的长宽。变换后的图像高度宽度可以这么确定:

int g_dst_hight; //最终图像的高度

int g_dst_width; //最终图像的宽度

void CalcDstSize(const vector<:point2f>& corners)

{

int h1 = sqrt((corners[0].x - corners[3].x)*(corners[0].x - corners[3].x) + (corners[0].y - corners[3].y)*(corners[0].y - corners[3].y));

int h2 = sqrt((corners[1].x - corners[2].x)*(corners[1].x - corners[2].x) + (corners[1].y - corners[2].y)*(corners[1].y - corners[2].y));

g_dst_hight = MAX(h1, h2);

int w1 = sqrt((corners[0].x - corners[1].x)*(corners[0].x - corners[1].x) + (corners[0].y - corners[1].y)*(corners[0].y - corners[1].y));

int w2 = sqrt((corners[2].x - corners[3].x)*(corners[2].x - corners[3].x) + (corners[2].y - corners[3].y)*(corners[2].y - corners[3].y));

g_dst_width = MAX(w1, w2);

}

透射变换:

cv::Mat quad = cv::Mat::zeros(g_dst_hight, g_dst_width, CV_8UC3);

std::vector<:point2f> quad_pts;

quad_pts.push_back(cv::Point2f(0, 0));

quad_pts.push_back(cv::Point2f(quad.cols, 0));

quad_pts.push_back(cv::Point2f(quad.cols, quad.rows));

quad_pts.push_back(cv::Point2f(0, quad.rows));

cv::Mat transmtx = cv::getPerspectiveTransform(corners, quad_pts);

cv::warpPerspective(source, quad, transmtx, quad.size());

所有关键步骤都已经说明完毕,运行一下代码,看看效果。

再拍一些其他图片,看看处理效果

试一下带有干扰背景的图像,效果还是不错的

额外效果:二值化

有些时候还需要将一些文本或者证件弄成扫描模样,那我们就加入二值化实现该效果。

Mat local,gray;

cvtColor(quad, gray, CV_RGB2GRAY);

int blockSize = 25;

int constValue = 10;

adaptiveThreshold(gray, local, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, blockSize, constValue);

imshow("二值化", local);

二值化效果挺好的

完整代码以及测试图像可以在github上获取。欢迎大家一起探讨~

增强 扫描王 源码_制作一个类“全能扫描王”的简易扫描软件(opencv)相关推荐

  1. 增强 扫描王 源码_OpenCV探索之路(二十二):制作一个类“全能扫描王”的简易扫描软件...

    相信很多人手机里都装了个"扫描全能王"APP,平时可以用它来可以扫描一些证件.文本,确实很好用,第一次用的时候确实感觉功能很强大啊算法很牛逼啊.但是仔细一想,其实这些实现起来也是很 ...

  2. 制作一个类“全能扫描王”的简易扫描软件(opencv)

    相信很多人手机里都装了个"扫描全能王"APP,平时可以用它来可以扫描一些证件.文本,确实很好用,第一次用的时候确实感觉功能很强大啊算法很牛逼啊.但是仔细一想,其实这些实现起来也是很 ...

  3. OpenCV制作一个类“全能扫描王”的简易扫描软件

    转自:https://www.cnblogs.com/skyfsm/p/7324346.html 相信很多人手机里都装了个"扫描全能王"APP,平时可以用它来可以扫描一些证件.文本 ...

  4. jquery项目源码_第一个jQuery程序

    1.配置jQuery环境 1.1获取jQuery最新版本 进入jQuery的官方网址 http://jquery.com ,下载最新的jQuery库. 1.2 jQuery库类型说明 目前jQuery ...

  5. 哔哩哔哩网站前端源码_分享一个仿制哔哩哔哩镜子网站源码

    我老婆非常喜欢看哔哩哔哩,前些天她兴奋地和我说哔哩哔哩网站有个隐藏的彩蛋,传送门http://www.ilidilid.com/,我看了下,相当于把镜子中的网站样子弄出来了. 于是,我寻思着,把自己的 ...

  6. java写一个音乐播放器源码_求一个JAVA音乐播放器的源代码

    展开全部 import javax.media.ControllerEvent; import javax.media.ControllerListener; import javax.media.E ...

  7. 安卓角色扮演游戏源码_角色扮演类安卓手游排行榜 热门手游推荐

    角色扮演RPG手机游戏大全汇集了所有的角色扮演RPG游戏下载资源,不断更新最新最热门的角色扮演RPG游戏信息,包括2020最好玩的角色扮演RPG手游前十名排行,玩家们可以在这个专题找到自己喜欢的游戏, ...

  8. 图片拼图微信小程序源码_支持多模板制作和流量主

    介绍: 该款小程序支持多种流量主: 另外支持多种图形模板制作切割: 另外也支持长图合成等功能: 安装简单,新手容易上手,具体就不多说了大家自行研究吧!!!! 图片拼图微信小程序源码_支持多模板制作和流 ...

  9. 团长头像制作小程序源码_支持流量主

    最近各类团长挺火的 然后就诞生了这款团长头像制作器 支持流量主模式 支持自定义文字,和拥有各种团长模板等等 源码下载:团zhang头像制作小程序源码_支持流量主-小程序文档类资源-CSDN下载

  10. mysql管理器源码_一个HelloWorld版的MySQL数据库管理器的设计与实现(源码)

    2011年,实习期间写了一个简单的数据库管理器. 今天,特意整理了下,分享给大家. 有兴趣的同学,可以下载源码,瞧瞧. 源码只有4个类:LoginGUI,DatabaseGUI,Record,MySQ ...

最新文章

  1. 帝国cms底部代码哪里改?要修改版权和统计代码
  2. Android USB Gadget复合设备驱动(打印机)测试方法
  3. css 缩放_CSS 中 transform、animation、transition、translate的区别
  4. python--数据类型bytes
  5. html小球跳跃技术原理,HTML5在文本上跳跃的小球
  6. linux 更换窗口管理器,linux Gnome .KDE.xfce4窗口管理器切换
  7. java 程序在Eclipse 或者 Linux 运行报 Unsupported major.minor version 51.0解决办法
  8. javascript内存泄露
  9. asp. net sqlsever旅游管理系统动态网站设计制作作业成品
  10. 计算机一级和答案,全国计算机一级操作题及答案
  11. Silverlight 2 中简单的2.5D控件
  12. sql获取服务器系统时间,sql server 获取系统时间的方法
  13. SPSS——方差分析
  14. cad一直正在加载_CAD总是打开要加载好久,卡到不行。
  15. python-pptx 操作PPTx幻灯片文件删除并替换图片
  16. 一键将苹果实况照片livp转jpg的简单方法
  17. Navicat Premium for Mac破解教程
  18. 特征选择----relief及reliefF算法
  19. 稀疏一元多项式(C++)
  20. 英文诗歌 学习 月是故乡明

热门文章

  1. 八爪鱼批量爬取html中的数据,批量采集网页数据 - 八爪鱼采集器
  2. 最新使用Python进行开发网站教程项目实战(完整)
  3. win10怎么把c盘锁住_win10怎样锁住c盘 win10删除c盘无用文件
  4. 华为安装gsm框架_华为手机怎么安装 GMS 框架和谷歌应用?
  5. 收藏 | 江苏省各地教师公务员等实际工资爆料
  6. java mail authen,javaxmail发送邮件:用Authenticator的子类进行身份验证及策略模式(Strategy)...
  7. 中望3d快捷键命令大全_cad快捷键大全下载
  8. 基于微信校园二手书交易小程序系统 毕业设计毕设参考
  9. word2016添加题注|图注文献标号的交叉引用及引用的更新|添加不同类型的页码|文献自动编号|文献编号的自动引用|删除空白页
  10. python num函数,python函数