c++ poencv Project2 - Document Scanner

惯例先上结果图:

本文提供一种文本提取思路:

1、首先图像预处理:灰度转换、高斯模糊、边缘提取,膨胀。

Mat preProcessing(Mat img) 
{
	cvtColor(img, imgGray, COLOR_BGR2GRAY);
	GaussianBlur(imgGray, imgBlur, Size(3, 3), 3, 0);
	Canny(imgBlur, imgCanny, 25, 75);

	Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
	dilate(imgCanny, imgDil, kernel);
	//erode(imgDil, imgErode, kernel);
	return imgDil;
}

2、预处理之后,获得轮廓特征、从而找到最大矩形,获取最大矩形的坐标。

vector<Point> getContours(Mat Dil) {
	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;
	//contours定义为“vector<vector<Point>> contours”,是一个双重向量(向量内每个元素保存了一组由连续的Point构成的点的集合的向量),每一组点集就是一个轮廓,有多少轮廓,contours就有多少元素;
	/*  hierarchy包含4个值的数组:[Next, Previous, First Child, Parent]
		Next:与当前轮廓处于同一层级的下一条轮廓
		举例来说,前面图中跟0处于同一层级的下一条轮廓是1,所以Next = 1;同理,对轮廓1来说,Next = 2;那么对于轮廓2呢?没有与它同一层级的下一条轮廓了,此时Next = -1。
		Previous:与当前轮廓处于同一层级的上一条轮廓
		跟前面一样,对于轮廓1来说,Previous = 0;对于轮廓2,Previous = 1;对于轮廓2a,没有上一条轮廓了,所以Previous = -1。
		First Child:当前轮廓的第一条子轮廓
		比如对于轮廓2,第一条子轮廓就是轮廓2a,所以First Child = 2a;对轮廓3,First Child = 3a。
		Parent:当前轮廓的父轮廓
		比如2a的父轮廓是2,Parent = 2;轮廓2没有父轮廓,所以Parent = -1。*/
		//RETR_EXTERNAL
		//这种方式只寻找最高层级的轮廓,也就是只寻找最外层轮廓:
		//CV_CHAIN_APPROX_SIMPLE:仅保存轮廓的拐点信息,把所有轮廓拐点处的点保存入contours向量内,拐点与拐点之间直线段上的信息点不予保留;
	findContours(Dil, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
	//drawContours(img, contours, -1, Scalar(255, 0, 255),2);

	vector<vector<Point>>conPoly(contours.size());
	vector<Rect>boundRect(contours.size());

	vector<Point> biggest;
	int maxArea = 0;

	//排除干扰
	for (int i = 0; i < contours.size(); i++) {
		//计算轮廓面积 
		int area = contourArea(contours[i]);
		string objectType;
		//cout << area <<"  ";
		if (area > 1000 ) {
			//arcLength(contours[i], true);计算轮廓周长  
			//InputArray类型的curve,输入的向量,二维点(轮廓顶点),可以为std::vector或Mat类型。
			//bool类型的closed,用于指示曲线是否封闭的标识符,一般设置为true。
			float peri = arcLength(contours[i], true);
			 对图像轮廓点进行多边形拟合
			approxPolyDP(contours[i], conPoly[i], 0.02 * peri, true);
			//cout << area << endl;
			if (area > maxArea && conPoly[i].size()==4 ) {

				//绘制轮廓
				//drawContours(imgOriginal, conPoly, i, Scalar(255, 0, 255), 2);
				biggest = {conPoly[i][0],conPoly[i][1], conPoly[i][2], conPoly[i][3]};
				maxArea = area;
				//cout << maxArea << endl;
			}
			
			//绘制矩形框
			//rectangle(imgOriginal, boundRect[i].tl(), boundRect[i].br(), Scalar(0, 255, 0), 5);

		}
	}
	return biggest;
}

获取坐标之后,要进行仿射提取出文本,不过坐标提取出来的是0312(矩形从左到右从上到下标记),要变成0123。之后才能仿射,参考另一篇文章:轮廓提取、矩形标记时,点的位置需要重标-CSDN博客

全部代码实现:对于绘制函数可以视情况显示。

#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/objdetect.hpp>
#include <iostream>
using namespace std;
using namespace cv;

   Document Scanner     ///

Mat imgOriginal, imgGray, imgCanny, imgDil, imgThre, imgBlur, imgWarp, imgCrop;
vector<Point>initialPoints, docPoints;

float w = 420, h = 596;

Mat preProcessing(Mat img) 
{
	cvtColor(img, imgGray, COLOR_BGR2GRAY);
	GaussianBlur(imgGray, imgBlur, Size(3, 3), 3, 0);
	Canny(imgBlur, imgCanny, 25, 75);

	Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
	dilate(imgCanny, imgDil, kernel);
	//erode(imgDil, imgErode, kernel);
	return imgDil;
}

vector<Point> getContours(Mat Dil) {
	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;
	//contours定义为“vector<vector<Point>> contours”,是一个双重向量(向量内每个元素保存了一组由连续的Point构成的点的集合的向量),每一组点集就是一个轮廓,有多少轮廓,contours就有多少元素;
	/*  hierarchy包含4个值的数组:[Next, Previous, First Child, Parent]
		Next:与当前轮廓处于同一层级的下一条轮廓
		举例来说,前面图中跟0处于同一层级的下一条轮廓是1,所以Next = 1;同理,对轮廓1来说,Next = 2;那么对于轮廓2呢?没有与它同一层级的下一条轮廓了,此时Next = -1。
		Previous:与当前轮廓处于同一层级的上一条轮廓
		跟前面一样,对于轮廓1来说,Previous = 0;对于轮廓2,Previous = 1;对于轮廓2a,没有上一条轮廓了,所以Previous = -1。
		First Child:当前轮廓的第一条子轮廓
		比如对于轮廓2,第一条子轮廓就是轮廓2a,所以First Child = 2a;对轮廓3,First Child = 3a。
		Parent:当前轮廓的父轮廓
		比如2a的父轮廓是2,Parent = 2;轮廓2没有父轮廓,所以Parent = -1。*/
		//RETR_EXTERNAL
		//这种方式只寻找最高层级的轮廓,也就是只寻找最外层轮廓:
		//CV_CHAIN_APPROX_SIMPLE:仅保存轮廓的拐点信息,把所有轮廓拐点处的点保存入contours向量内,拐点与拐点之间直线段上的信息点不予保留;
	findContours(Dil, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
	//drawContours(img, contours, -1, Scalar(255, 0, 255),2);

	vector<vector<Point>>conPoly(contours.size());
	vector<Rect>boundRect(contours.size());

	vector<Point> biggest;
	int maxArea = 0;

	//排除干扰
	for (int i = 0; i < contours.size(); i++) {
		//计算轮廓面积 
		int area = contourArea(contours[i]);
		string objectType;
		//cout << area <<"  ";
		if (area > 1000 ) {
			//arcLength(contours[i], true);计算轮廓周长  
			//InputArray类型的curve,输入的向量,二维点(轮廓顶点),可以为std::vector或Mat类型。
			//bool类型的closed,用于指示曲线是否封闭的标识符,一般设置为true。
			float peri = arcLength(contours[i], true);
			 对图像轮廓点进行多边形拟合
			approxPolyDP(contours[i], conPoly[i], 0.02 * peri, true);
			//cout << area << endl;
			if (area > maxArea && conPoly[i].size()==4 ) {

				//绘制轮廓
				//drawContours(imgOriginal, conPoly, i, Scalar(255, 0, 255), 2);
				biggest = {conPoly[i][0],conPoly[i][1], conPoly[i][2], conPoly[i][3]};
				maxArea = area;
				//cout << maxArea << endl;
			}
			
			//绘制矩形框
			//rectangle(imgOriginal, boundRect[i].tl(), boundRect[i].br(), Scalar(0, 255, 0), 5);

		}
	}
	return biggest;
}

void drawPoints(vector<Point>points, Scalar color)
{
	
	for (int i = 0; i < points.size(); i++)
	{
		circle(imgOriginal, points[i], 10, color, FILLED);
		putText(imgOriginal, to_string(i), points[i], FONT_HERSHEY_PLAIN, 4, color,4);

	}

}

vector<Point> reorder(vector<Point> points)
{
	vector<Point> newPoints;
	vector<int>  sumPoints, subPoints;
	for (int i = 0; i < points.size(); i++) {
		cout << points[i].x << ", " << points[i].y << endl;
		sumPoints.push_back(points[i].x + points[i].y);
		cout << sumPoints[i] << endl;
	}
	for (int i = 0; i < points.size(); i++) {
		subPoints.push_back(points[i].x - points[i].y);
		cout << subPoints[i] << endl;
	}

	///  冒泡实现  /
	///*for (int j = 0; j < sumPoints.size(); j++) {
	//	for (int i = 1; i < sumPoints.size(); i++) {
	//		if (sumPoints[j] > sumPoints[i]) {
	//			newPoints = points[i];
	//			points[i] = points[j];
	//			points[j] = newPoints;
	//		}
	//	}

	//}
	//if (points[1].x - points[0].x < points[2].x - points[0].x) {
	//	Point p;
	//	p = points[1];
	//	points[1] = points[2];
	//	points[2] = p;
	//}*/
	
	
	newPoints.push_back(points[min_element(sumPoints.begin(),sumPoints.end()) - sumPoints.begin()]);
	newPoints.push_back(points[max_element(subPoints.begin(), subPoints.end()) - subPoints.begin()]);
	newPoints.push_back(points[min_element(subPoints.begin(), subPoints.end()) - subPoints.begin()]);
	newPoints.push_back(points[max_element(sumPoints.begin(), sumPoints.end()) - sumPoints.begin()]);

	return newPoints;
}

Mat getWarp(Mat img, vector<Point> points, float w, float h) {
	Point2f src[4] = { points[0], points[1], points[2], points[3]};
	Point2f dst[4] = { {0.0f,0.0f},{w,0.0f},{0.0f,h},{w,h} };

	// 透视变换,将图片投影到一个新的视平面,也称投影映射
	// src 输入图像四个点坐标 //dst 输出图像四个点坐标
	Mat matrix = getPerspectiveTransform(src, dst);
	//透视变换,img:原图像 imgWarp:输出图像 matrix:变换矩阵,Point(w,h):宽高 
	warpPerspective(img, imgWarp, matrix, Point(w, h));

	return imgWarp;
}

void main() {
	
	    string path = "Learn-OpenCV-cpp-in-4-Hours-main\\Resources\\paper.jpg";
	    imgOriginal = imread(path);
		
		resize(imgOriginal, imgOriginal, Size(), 0.5, 0.5);

		// Prepropcessing
		imgThre = preProcessing(imgOriginal);
		// Get Contours - Biggest
		initialPoints = getContours(imgThre);
		//drawPoints(initialPoints, Scalar(255, 0, 0));
		docPoints = reorder(initialPoints);
		//drawPoints(docPoints, Scalar(0, 255, 0));

		// warp
		imgWarp = getWarp(imgOriginal, docPoints, w, h);
		//Crap
		Rect roi(5, 5, w - (2 * 5), h - (2 * 5));
		imgCrop = imgWarp(roi);
		
		namedWindow("Image",WINDOW_FREERATIO);
		namedWindow("imgdilation", WINDOW_FREERATIO);
		imshow("Image", imgOriginal);
		imshow("imgdilation", imgThre);
		//imshow("imgWarp", imgWarp);
		imshow("imgCrop", imgCrop);

		waitKey(0);
		destroyAllWindows(); 
	
	

}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于鸢尾花数据集的四种聚类算法(kmeans,层次聚类,DBSCAN,FCM)和学习向量量化对比

基于鸢尾花数据集的四种聚类算法&#xff08;kmeans&#xff0c;层次聚类&#xff0c;DBSCAN,FCM&#xff09;和学习向量量化对比 注&#xff1a;下面的代码可能需要做一点参数调整&#xff0c;才得到所有我的运行结果。 kmeans算法&#xff1a; import matplotlib.pyplot a…

从面试官视角出发,聊聊产品经理的面试攻略

一、请进行自我介绍 这题基本是面试的开胃菜了&#xff0c;估计面试多的&#xff0c;自己答案都能倒背如流啦。 其实自我介绍还是蛮重要的&#xff0c;对我来说主要有 3 个作用&#xff1a;面试准备、能力预估、思维评估。 面试准备&#xff1a;面试官每天都要面 3 ~6 人&am…

嵌入式C语言高级教程:实现基于STM32的智能水质监测系统

智能水质监测系统可以实时监控水体的质量&#xff0c;对于环境保护和水资源管理具有重要意义。本教程将指导您如何在STM32微控制器上实现一个基本的智能水质监测系统。 一、开发环境准备 硬件要求 微控制器&#xff1a;STM32F303K8&#xff0c;因其高精度模拟特性而被选用。…

嵌入式C语言高级教程:实现基于STM32的智能照明系统

智能照明系统不仅可以自动调节光源的亮度和色温&#xff0c;还可以通过感应用户的行为模式来优化能源消耗。本教程将指导您如何在STM32微控制器上实现一个基本的智能照明系统。 一、开发环境准备 硬件要求 微控制器&#xff1a;STM32F103RET6&#xff0c;具有足够的处理能力…

苹果再失资深设计师,Jony Ive 团队基本离开;OpenAI 或于下周发布 AI 搜索丨 RTE 开发者日报 Vol.201

开发者朋友们大家好&#xff1a; 这里是 「RTE 开发者日报」 &#xff0c;每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE&#xff08;Real Time Engagement&#xff09; 领域内「有话题的 新闻 」、「有态度的 观点 」、「有意思的 数据 」、「有思考的 文…

测试环境搭建整套大数据系统(十六:超级大文件处理遇到的问题)

一&#xff1a;yarn出现损坏的nodemanger 报错现象 日志&#xff1a;1/1 local-dirs usable space is below configured utilization percentage/no more usable space [ /opt/hadoop-3.2.4/data/nm-local-dir : used space above threshold of 90.0% ] ; 1/1 log-dirs usabl…

【SRC实战】合成类小游戏外挂漏洞

挖个洞先 https://mp.weixin.qq.com/s/ZnaRn222xJU0MQxWoRaiJg “以下漏洞均为实验靶场&#xff0c;如有雷同&#xff0c;纯属巧合” 合成类小游戏三个特点&#xff1a; 1、一关比一关难&#xff0c;可以参考“羊了个羊” 2、无限关卡无限奖励&#xff0c;可以参考“消灭星星…

【Qt 学习笔记】Qt常用控件 | 多元素控件 | List Widget的说明及介绍

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt常用控件 | 多元素控件 | List Widget的说明及介绍 文章编号&#x…

【Java代码审计】代码审计的方法及常用工具

【Java代码审计】代码审计的方法及常用工具 代码审计的常用思路代码审计辅助工具代码编辑器测试工具反编译工具Java 代码静态扫描工具 代码审计的常用思路 1、接口排查&#xff08;“正向追踪”&#xff09;&#xff1a;先找出从外部接口接收的参数&#xff0c;并跟踪其传递过…

ICode国际青少年编程竞赛- Python-3级训练场-综合练习3

ICode国际青少年编程竞赛- Python-3级训练场-综合练习3 1、 for i in range(10):if i < 2 or i > 7: Flyer[i].step(1) Dev.step(Dev.y - Item[0].y)2、 for i in range(8):if i < 3 or i > 4:Spaceship.turnRight()else:Spaceship.turnLeft()Spaceship.step(i …

VBA_NZ系列工具NZ06:VBA创建PDF文件说明

我的教程一共九套及VBA汉英手册一部&#xff0c;分为初级、中级、高级三大部分。是对VBA的系统讲解&#xff0c;从简单的入门&#xff0c;到数据库&#xff0c;到字典&#xff0c;到高级的网抓及类的应用。大家在学习的过程中可能会存在困惑&#xff0c;这么多知识点该如何组织…

InLine Chat功能优化对标Github Copilot,CodeGeeX带来更高效、更直观的编程体验!

VSCode中的CodeGeeX 插件上线InLine Chat功能后&#xff0c;收到不少用户的反馈&#xff0c;大家对行内交互编程这一功能非常感兴趣。近期我们针对这个功能再次进行了深度优化&#xff0c;今天详细介绍已经在VSCode插件v2.8.0版本上线的 CodeGeeX InLine Chat功能&#xff0c;以…

IPO压力应变桥信号处理系列隔离放大器 差分信号隔离转换0-10mV/0-20mV/0-±10mV/0-±20mV转4-20mA/0-5V/0-10V

概述&#xff1a; IPO压力应变桥信号处理系列隔离放大器是一种将差分输入信号隔离放大、转换成按比例输出的直流信号混合集成厚模电路。产品广泛应用在电力、远程监控、仪器仪表、医疗设备、工业自控等行业。该模块内部嵌入了一个高效微功率的电源&#xff0c;向输入端和输出端…

海外多语言盲盒系统开发:加快盲盒企业出海

近几年&#xff0c;全球都进入到了潮玩文化发展期&#xff0c;在这种时代背景下&#xff0c;盲盒迅速发展&#xff0c;与消费者建立了深厚的情感连接&#xff0c;市场规模逐渐扩大。目前&#xff0c;我国盲盒企业纷纷布局海外市场&#xff0c;纵观海外庞大的发展空间&#xff0…

MathType7.6最新免费汉化版安装包下载地址

MathType是一款由Design Science公司开发的数学公式编辑器&#xff0c;被广泛用于编辑论文、书籍、报刊、数学试卷、演示文件等&#xff0c;是编辑数学资料的得力工具。以下是对MathType软件的详细介绍&#xff1a; 安装免费版MathType和mathtype7.4产品密钥 MTWE691-011524-9…

基于docker安装flink

文章目录 环境准备Flinkdocker-compose方式二进制部署 KafkaMysql Flink 执行 SQL命令进入SQL客户端CLI执行SQL查询表格模式变更日志模式Tableau模式窗口计算 窗口计算滚动窗口demo滑动窗口 踩坑 环境准备 Flink docker-compose方式 version: "3" services:jobman…

乡村振兴与城乡融合发展:加强城乡间经济、文化、社会等方面的交流与合作,推动城乡一体化发展,实现美丽乡村共荣

目录 一、引言 二、乡村振兴与城乡融合发展的意义 三、城乡交流合作的现状与挑战 &#xff08;一&#xff09;现状 &#xff08;二&#xff09;挑战 四、加强城乡交流合作的策略与路径 &#xff08;一&#xff09;完善城乡交流合作机制 &#xff08;二&#xff09;推动…

electron-vite工具打包后通过内置配置文件动态修改接口地址实现方法

系列文章目录 electronvitevue3 快速入门教程 文章目录 系列文章目录前言一、实现过程二、代码演示1.resources/env.json2.App.vue3.main/index.js4.request.js5.安装后修改 前言 使用electron-vite 工具开发项目打包完后每次要改接口地址都要重新打包&#xff0c;对于多环境…

后端的一些科普文章

后端开发一般有4个方面 后端开发流程 1阶段 域名认证 是每一个计算机在网络上有一个ip地址&#xff0c;可以通过这个地址来访问102.305.122.5&#xff08;举例&#xff09;&#xff0c; 但是这个公网ip地址&#xff0c;比较难记忆&#xff0c;所以大家使用域名来更好的记忆…

越秀城投·星汇城 | 看得再多,都不如实景现房更安心

对于大多数家庭而言&#xff0c;买房是人生大事。经历了前几年房企暴雷、楼盘停工烂尾的风波&#xff0c;“现房”成为买房人心中最安心的代名词。无需再等待&#xff0c;所见即所得。 越秀城投星汇城位于平度南部新城核芯片区&#xff0c;不仅享受区域发展的利好&#xff0c;…
最新文章