【OpenCV】基于Qt的“破产版”全能扫描王

功能介绍

  • 图片打开和保存
  • 图片矫正(证件扫描、文字纠正…)
  • 图片锐化增强
  • 图片清空
  • 阈值设置

项目实现

基本思路(证件扫描)

  • 抠图:提取轮廓
  • 矫正:透视变换
  • 锐化增强:二值化

算法设计(证件扫描)

第一步:提取边缘
  • 读取图像,转化为灰度图
  • 降噪,二值化 高斯滤波 GaussianBlur()
  • 适当膨胀,提高检测效率
  • 边缘检测 Canny(),打印出二值图验证
第二步:轮廓查找与筛选
  • 轮廓检测 findContours()
  • 霍夫直线检测 HoughLines()
  • 绘制检测到的直线并验证 line()
  • 排除距离过近、不相交的直线
  • 排除距离过近的两直线交点
第三步:透视变换
  • 由第二步筛选出的四个顶点得出一组坐标
  • 确定输出图像长宽(或自适应),验证
  • 计算透视变换矩阵 GetPerspectiveTransform()
  • 透视变换函数 warpPerspective()
第四步:锐化增强
  • 必要的二值化 adaptiveThreshold()
  • 输出图像

UI设计

1

核心代码

  • 证件扫描
Mat scanning()
{Mat src = imread(path);Mat source = src.clone();Mat bkup = src.clone();Mat img = src.clone();//二值化threshold(img, img, GRAY_THRESH, 255, CV_THRESH_BINARY);   //高斯滤波GaussianBlur(img, img, Size(5, 5), 0, 0); //获取自定义核Mat element = getStructuringElement(MORPH_RECT, Size(3, 3)); //适当膨胀dilate(img, img, element);//边缘提取Canny(img, img, 30, 120, 3);vector<vector<Point> > contours;vector<vector<Point> > f_contours;vector<Point> approx2;//轮廓检测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;}}//找顶点Mat f_img = img.clone();vector<Vec4i> lines;vector<Point2f> corners;//验证轮廓drawContours(f_img, contours, 0, Scalar(255)); lines.clear();corners.clear();//这里的阈值提供给用户修改//直线检测HoughLinesP(f_img, lines, 1, PI / 180, HOUGH_VOTE, 30, 10);//1.过滤不符条件的直线//2.计算直线交点//3.过滤不符条件的点DstSize(corners); //计算输出尺寸Mat dst = Mat::zeros(dst_hight, dst_width, CV_8UC3);vector<Point2f> f_points; //四边形顶点坐标组f_points.push_back(Point2f(0, 0));f_points.push_back(Point2f(dst.cols, 0));f_points.push_back(Point2f(dst.cols, dst.rows));f_points.push_back(Point2f(0, dst.rows));Mat temp = getPerspectiveTransform(corners, f_points); //计算透视变换矩阵 warpPerspective(source, dst, temp, dst.size()); //透视变换//这里也可以提供给用户修改//自动增强Mat local, gray;cvtColor(dst, gray, CV_RGB2GRAY);int blockSize = 25;int constValue = 10;//自适应二值化adaptiveThreshold(gray, local, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, blockSize, constValue); return local;
}
  • 文字纠正
Mat rotate(Mat srcImage)
{//转换为灰度图Mat grayImage;cvtColor(srcImage, grayImage, CV_RGB2GRAY);//获取图片原尺寸const int nRows = grayImage.rows;const int nCols = grayImage.cols;//图片尺寸转换,获取傅里叶变换尺寸//返回DFT最优尺寸大小的函数int mRows = getOptimalDFTSize(nRows);int mCols = getOptimalDFTSize(nCols);Mat newImage;//边界扩充函数copyMakeBorder(grayImage, newImage, 0, mRows - nRows, 0, mCols - nCols, BORDER_CONSTANT, Scalar::all(0));//图像DFT变换//通道组建立,使用Mat_容器,一个存实部,一个存虚部Mat groupImage[] = { Mat_<float>(newImage), Mat::zeros(newImage.size(), CV_32F) };Mat mergeImage;//合并通道merge(groupImage, 2, mergeImage);//离散傅里叶变换即DFTdft(mergeImage, mergeImage);//分离通道 split(mergeImage, groupImage);//调整数据//计算傅里叶变化各频率的幅值magnitude(groupImage[0], groupImage[1], groupImage[0]);Mat magImage = groupImage[0];//归一化操作,幅值加1magImage  = Scalar::all(1);//取对数log(magImage, magImage);//重新分配象限,使(0,0)移动到图像中心,即把低频部分移动到中心  //傅里叶变换之前要对源图像乘以(-1)^(x y),进行中心化  int cx = magImage.cols / 2;int cy = magImage.rows / 2;Mat temp;//左上象限Mat LT(magImage, Rect(0, 0, cx, cy));//右上象限Mat RT(magImage, Rect(cx, 0, cx, cy));//左下象限Mat LB(magImage, Rect(0, cy, cx, cy));//右下象限Mat RB(magImage, Rect(cx, cy, cx, cy));//交换象限,左上换右下LT.copyTo(temp);RB.copyTo(LT);temp.copyTo(RB);//交换象限,右上换左下 RT.copyTo(temp);LB.copyTo(RT);temp.copyTo(LB);//归一化//在0-1之间是统计概率分布,为了后续操作方便normalize(magImage, magImage, 0, 1, CV_MINMAX);//像素强度变换,输出单通道灰度图Mat magImg;magImage.convertTo(magImg, CV_8UC1, 255, 0);//imshow("magnitude", magImg);//检测直线//二值化threshold(magImg, magImg, GRAY_THRESH, 255, CV_THRESH_BINARY);//构造8UC1格式图像vector<Vec2f> lines;Mat houghImg(magImg.size(), CV_8UC3);//Houge直线检测HoughLines(magImg, lines, 1, CV_PI / 180, HOUGH_VOTE, 0, 0);// cout << "检测直线条数:  " << lines.size() << endl;//绘制检测线for (int l = 0; l < lines.size(); l  ){float rho = lines[l][0], theta = lines[l][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(houghImg, pt1, pt2, Scalar(255, 0, 0), 3, 8, 0);}// imshow("hough", houghImg);//获取角度float angel = 0;float m = PI / 90;float n = PI / 2;for (int l = 0; l < lines.size(); l  ){//遍历检测直线的角度float theta = lines[l][1];if (abs(theta) > m && abs(n - theta) > m){//取有效角度angel = theta;break;}}//确保角度在0到90度内angel = angel < PI / 2 ? angel : angel - PI;//角度换算if (angel != PI / 2){//作图一目了然float angelT = srcImage.rows * tan(angel) / srcImage.cols;angel = atan(angelT);}float angel_rad = angel * 180 / PI;// cout << "旋转角度: " << angel_rad << endl;//取图像中心Point2f centerPoint = Point2f(nCols / 2, nRows / 2);double scale = 1;//计算旋转中心Mat rotateMat = getRotationMatrix2D(centerPoint, angel_rad, scale);//仿射变换Mat resultImage(grayImage.size(), srcImage.type());warpAffine(srcImage, resultImage, rotateMat, srcImage.size(), 1, 0, Scalar(255, 255, 255));return resultImage;
}

项目截图

  • 证件扫描
    1
  • 文字纠正
    2
  • 效果对比
    1
  • 效果对比(娱乐向)
    2

项目总结

  • 本项目基本实现了证件扫描和文字纠正两大基本功能,其中类似“全能扫描王”的扫描功能被我单独做了个版本,所以上述截图UI有些不一样,特此说明;

  • 在这种基于透视变化的算法中,可以看见一定弊端:直线检测的阈值、轮廓检测的标准、顶点筛选的严密性等,对最终的结果影响很大,所以找到合理的、或者自适应的参数是最关键一步;故市面上的扫描软件一定有更复杂的思路或算法,还需要继续学习!

  • 刚开始写的时候对OCR等词汇的理解不当,所以在函数命名和UI设计上出现了失误,特此指出;OCR(Optical Character Recognition,光学字符识别)意为文字识别,与本项目的功能不同;

  • 希望本文能帮助到那些刚入门图像处理的同学,咱们一起加油!

  • 关于证件扫描算法的疑惑可以参考 这位大神的干货文章

  • 完整源码链接(仅供参考)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/1380340.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

eNSP:ibgp的破水平切割练习

实验要求&#xff1a; 拓扑展示&#xff1a; 命令操作&#xff1a; R1&#xff1a; <Huawei>sys [Huawei]sys r1 [r1]int g 0/0/1 [r1-GigabitEthernet0/0/1]ip add 12.1.1.1 24 [r1-GigabitEthernet0/0/1]int lo0 [r1-LoopBack0]ip add 1.1.1.1 24 [r1-LoopBack0]osp…

css clip-path 画五角星

简介 使用裁剪方式创建元素的可显示区域&#xff0c;实现区域裁剪。 浏览器兼容性 常用的三种方法 1. clip-path: circle(50px at 50px 50px) 以 50px 50px 的坐标为圆心裁剪一个半径 50px 的圆&#xff1b; 2. clip-path: ellipse(30px 40px at 50px 50px) 以 50px 50px…

canvas绘图详解-06-绘制一个五角星-常用绘图原理

先讲如何画一个正规的五角星 在五角星的内外画两个圆&#xff0c;五角星有五个角&#xff0c;360/572度 所以得出这两个角的度数 然后算出这两个点坐标 角度转弧度 角度/180*Math.PI 所以外顶点坐标 x: Math.cos( (1872*i)/180*Math.PI) * R y: Math.sin((1872*i)/180*Math.P…

钓鱼网站原理实验

实验环境:一台Windows 2003&#xff08;WA&#xff09;作为客户机&#xff0c;一台Linux&#xff08;LB&#xff09;作为服务器。 1.LB设置网络适配器 2.LB配置eth0&#xff08;vim /etc/sysconfig/network-scripts/ifcfg-eth0&#xff09; 3.LB将eth0的内容copy一份到eth1 …

电子邮件如何追溯到他们的源IP地址

你要做的第一件事当你听到检查发送电子邮件通知,对吧? 这是最快的方法找出谁是电子邮件,以及可能的内容。 但是你知道每个邮件都有更多的信息比出现在大多数的电子邮件客户端? 有主机的信息发送者包括在邮件头信息可以用来跟踪电子邮件回源。 下面是如何跟踪,邮件回它是从哪…

红队攻击:轻松玩转邮件钓鱼

首发在合天智汇&#xff1a;https://mp.weixin.qq.com/s/sRXnwIdy3eQ0CJS58_bI1g 邮件钓鱼老生常谈&#xff0c;攻击手段也并不新颖。但对于网络安全工程师来说,“懂原理”和“会使用”是两个概念。会用&#xff0c;用得好不好&#xff0c;又是不同的层次。红队攻击要讲究攻击的…

seeker+ngrok 钓鱼获取目标位置

免责说明&#xff1a;本文章只是参考&#xff0c;一切后果自行承担&#xff0c;网络环境靠大家&#xff0c;遵守网络安全法是每个人应尽的责任 文章目录 一、工具介绍seekerngrok 二、使用步骤 一、工具介绍 seeker seeker工具是一款社会工程学的利器&#xff0c;可以通过see…

使用十六进制IP地址的网络钓鱼攻击活动

网络犯罪分子正在不断优化其网络攻击工具&#xff0c;策略和技术&#xff0c;以逃避垃圾邮件检测系统。 由于一些系统会直接提取邮件中内嵌的链接进行检测&#xff0c;而一种此类URL混淆技术采用了URL主机名部分中使用的编码十六进制IP地址格式来逃避检测。 由于IP地址可以用多…

1. 如何爬取自己的CSDN博客文章列表(获取列表)(博客列表)(手动+python代码方式)

文章目录 写在最前步骤打开chrome浏览器&#xff0c;登录网页按pagedown一直往下刷呀刷呀刷&#xff0c;直到把自己所有的博文刷出来然后我们按F12&#xff0c;点击选取元素按钮然后随便点一篇博文&#xff0c;产生如下所示代码然后往上翻&#xff0c;找到头&#xff0c;复制然…

Maven在IDEA2021版本中全局配置(一次配置处处生效)

前言 我们在开发中&#xff0c;Maven是必不可少的&#xff0c;但是每次都需要设置一遍Maven的仓库和settings.xml。真的是心累&#xff0c;今天教大家全局配置一下。再也不要每次项目都配了&#xff0c;Maven还经常出问题。 解决方案 友情提示&#xff1a;小编的IDEA版本为2…

Tableau 聚合计算 - 分组求和(sum、fixed、include的使用)

一、聚合计算例子 有以下数据&#xff1a; // 计算1 SUM(IF [shuxue]123 then [yingyu] END)// 计算2 IF [shuxue]123 then {fixed [shuxue]: SUM([yingyu])} END// 计算3 IF [shuxue]123 then {SUM([yingyu])} END// 计算4 {fixed [shuxue]: SUM([yingyu])}// 计算5 {include…

Kubernetes pod调度约束[亲和性 污点] 生命阶段 排障手段

调度约束 Kubernetes 是通过 List-Watch 的机制进行每个组件的协作&#xff0c;保持数据同步的&#xff0c;每个组件之间的设计实现了解耦。 用户是通过 kubectl 根据配置文件&#xff0c;向 APIServer 发送命令&#xff0c;在 Node 节点上面建立 Pod 和 Container。 APIServer…

[Pandas] 分组比例计算求和

美图欣赏2022/08/01 问题:在最近的需求开发中&#xff0c;有这么个分组比例计算求和问题&#xff0c;根据字段CPN进行分组&#xff0c;计算每一笔PO Line Actual CT2R * line 数量比重&#xff0c;取名为Weighted(QTY)CT2R&#xff0c;再根据相同的CPN对每行Weighted(QTY)CT2R值…

SQL多字段分组求和

表结构 表数据 按店铺名称、发货时间分组并求总数 select g.[店铺名称],g.[发货时间],sum(g.[数量]) as 总数 from t_goods g GROUP BY g.[店铺名称],g.[发货时间] ORDER BY g.[店铺名称]

oracle 分析函数之分组求和、连续求和

最近在《sql cookbook》书上发现了名叫 分析函数 的东西&#xff0c;之前学 oracle 时没有印象&#xff0c;现在感觉其分析函数的功能相当强大、神奇&#xff0c; 就特意去找到了 chm 文档研究了一下&#xff0c;想要的朋友在本文末尾自行下载。 本文的例子都来源于 chm 文档…

简单卡通水

参考 https://roystan.net/articles/toon-water/ 源码 https://github.com/IronWarrior/ToonWaterShader 两张噪声图 挂在相机上&#xff0c;开启深度模式&#xff0c;使shader可以拿到深度图 using UnityEngine;public class ChangeCameraDepth : MonoBehaviour {public De…

编写简单的动画

UIImageView提供了实现简单动画的功能。比如要实现这样的功能&#xff1a; 跳动的火焰。基本思路是&#xff0c;一个数组&#xff0c;包含火焰跳动所需的多张图片。然后设置播放时间&#xff0c;并开始播放。UIImageView已经提供了这方面的机制&#xff0c;只需把图片数组交给它…

干货!学会这些动漫绘画素材,小白也能学会动漫绘画,简单易学!

百头练习 只画头部,着重对五官进行精细刻画。 女性头像练习 1 定出五官的位置,注意不管是什么角度,人的眼睛、嘴巴都是和鼻子呈90的。 2 画出五官。 3 注意线条有实有虚,把五官细化描绘出来,然后大致勾勒出头发的走向。 4 画出头发丝和眼珠。 5 添加光影,使头发…

制作简单的动画

1、使用jQuery&#xff08;annimate&#xff09;制作 a、首先要做简单的页面布局&#xff08;两个div加上10幅图&#xff09; <div id"img2"><input type "button" value"上一页" id"a" ></input><input type…