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);// PrepropcessingimgThre = preProcessing(imgOriginal);// Get Contours - BiggestinitialPoints = getContours(imgThre);//drawPoints(initialPoints, Scalar(255, 0, 0));docPoints = reorder(initialPoints);//drawPoints(docPoints, Scalar(0, 255, 0));// warpimgWarp = getWarp(imgOriginal, docPoints, w, h);//CrapRect 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.mzph.cn/news/835274.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

uni-app(三):离线打包与插件引用(Android)

离线打包与插件引用 1.下载Android离线SDK2.使用Android Studio打开离线打包项目并更新Gradle3.解决报错4.构建5.配置AppKeya.查看证书b.申请AppKeyc.配置AppKey 6.生成本地打包App资源7.拷贝App资源到Android项目中8.修改 appid9.修改Android项目配置文件10.下载证书并配置11.…

海康威视漏洞综合利用工具-HikvisionExploitGUI

0x01 前言 在攻防演练中&#xff0c;海康威视一直是红队攻击的重点目标之一&#xff0c;红队通常需要快速打点&#xff0c;尽快发现系统中的漏洞&#xff0c;并利用它们获取权限。 0x02 工具简介 工具支持检测海康威视综合安防管理平台多种常见漏洞。提供直观友好的图像化界…

区块链中的加密算法及其作用

区块链技术以其去中心化、不可篡改、透明公开的特性&#xff0c;在全球范围内引发了广泛的关注和讨论。其中&#xff0c;加密算法作为区块链技术的核心组成部分&#xff0c;对于维护区块链网络的安全、确保数据的完整性和真实性起到了至关重要的作用。本文将详细介绍区块链中常…

LLM 可以从简单数据中学习吗?

在 10 月份的一次周会结束后&#xff0c;我提到 SFT 训练后的 Loss 曲线呈现阶梯状&#xff0c;至于为什么&#xff0c;并没有人有合理的解释&#xff0c;加上当时的重心是提升次日留存率&#xff0c;Loss 曲线呈现阶梯状与次日留存率的关系还太远&#xff0c;即使有问题&#…

torch.searchsorted

torch.searchsorted 官方文档链接&#xff1a;torch.searchsorted — PyTorch 2.3 documentation 该函数用于在已排序的序列中查找要插入的值的位置&#xff0c;以保持序列的顺序&#xff0c; torch.searchsorted(sorted_sequence, values, *, out_int32False, rightFalse, s…

Python - 金三银四心路历程 之 数据结构与算法 刷题

目录 一.引言 二.心路历程 三.刷题经历 四.刷题历程 五.总结 一.引言 <夜深人静写算法> 是 23 年 12 月底博主打算跳槽时开始做刷题准备做的专栏&#xff0c;前后准备了大约一个月&#xff0c;刷题完毕后简单准备了项目和简历后就开始加入找工作大军了&#xff0c;最…

【机器学习】逻辑化讲清PCA主成分分析

碎碎念&#xff1a;小编去年数学建模比赛的时候真的理解不了主成分分析中的“主成分”的概念&#xff01;&#xff01;但是&#xff0c;时隔两年&#xff0c;在机器学习领域我又行了&#xff0c;终于搞明白了&#xff01;且看正文&#xff01;再分享一个今天听到的播客中非常触…

Web3 Tools - Base58

Base58编码 Base58编码是一种用于表示数字的非常见的编码方法。它通常用于加密货币领域&#xff0c;例如比特币和其他加密货币的地址表示。 什么是Base58编码&#xff1f; Base58编码是一种将数字转换为人类可读形式的编码方法。与常见的Base64编码不同&#xff0c;Base58编码…

JCR一区 | Matlab实现1D-2D-GASF-CNN-GRU-MATT的多通道输入数据分类预测

JCR一区 | Matlab实现1D-2D-GASF-CNN-GRU-MATT的多通道输入数据分类预测 目录 JCR一区 | Matlab实现1D-2D-GASF-CNN-GRU-MATT的多通道输入数据分类预测分类效果基本介绍程序设计参考资料 分类效果 基本介绍 基本介绍 Matlab实现1D-2D-GASF-CNN-GRU-MATT的多通道输入数据分类预…

Ascent DMS AE电源说明书和设备连接调试教程

Ascent DMS AE电源说明书和设备连接调试教程

世上最全前端开发教程(HTMLCSS)

HTML介绍 HTML&#xff0c;全称为HyperText Markup Language&#xff0c;即超文本标记语言&#xff0c;是一种用来创建网页的标准标记语言。HTML使用一系列的标签&#xff08;Tags&#xff09;来定义网页的不同部分和它们的行为&#xff0c;比如段落、链接、图片等。 CSS介绍 …

《这就是ChatGPT》读书笔记

书名&#xff1a;这就是ChatGPT 作者&#xff1a;[美] 斯蒂芬沃尔弗拉姆&#xff08;Stephen Wolfram&#xff09; ChatGPT在做什么&#xff1f; ChatGPT可以生成类似于人类书写的文本&#xff0c;它基本任务是弄清楚如何针对它得到的任何文本产生“合理的延续”。当ChatGPT写…

数据库基础语法二

一、数据库 1、登陆数据库 2、创建数据库zoo 3、修改数据库zoo字符集为gbk 4、选择当前数据库为zoo 5、查看创建数据库zoo信息 6、删除数据库zoo mysql -uroot -p #登陆数据库 create database zoo; #创建数据库zoo alter database zoo character set gbk collate gbk_…

Android 12.0 TvSettings系统设置wifi连接密码框点击Enter键失去焦点

1.前言 在12.0的系统box产品开发中,在TvSettings中,在wifi连接的时候,在用遥控器输入wifi密码框的时候,会发现在按遥控器Enter键的时候, 发现EditText焦点失去了,导致输入法消失了,为了解决这个问题就需要拦截Enter键保证正常输入wifi密码,接下来就来实现这个功能 如图…

实用的Chrome命令 帮你打开Chrome浏览器的隐藏功能

前言 Chrome作为主力浏览器&#xff0c;支持相当丰富的第三方扩展&#xff0c;其实浏览器本身也内置了大量实用的命令。许多实用的功能并没有直接显示在Chrome的菜单上。在这篇文章中&#xff0c;我们将介绍几个实用的chrome:// commands。 通过下面整理的 Chrome 命令&#x…

什么是Unreal Engine游戏引擎?它有什么优势?

大家好&#xff0c;我是咕噜土豆&#xff0c;很高兴又和大家见面了。在游戏开发行业中&#xff0c;选择合适的游戏引擎是非常重要的。其中&#xff0c;Unreal Engine作为一款功能强大的游戏引擎&#xff0c;在业界非常受欢迎。今天我带大家简单的了解一下。 什么是Unreal Engi…

基于STM32移植lvgl(V8.2)(SPI接口的LCD)

目录 概述 1 认识LVGL 1.1 LVGL官网 1.2 LVGL库文件下载 2 认识SPI接口型LCD 2.1 PIN引脚定义 2.2 MCU IO与LCD PIN对应关系 3 实现LCD驱动 3.1 使用STM32Cube配置Project 3.2 STM32Cube生成工程 4 移植LVGL 4.1 准备移植文件 4.2 添加lvgl库文件到项目 4.2.1 src下…

【Pytorch】6.torch.nn.functional.conv2d的使用

阅读之前应该先了解基础的CNN网络的逻辑 conv2d的作用 是PyTorch中用于执行二维卷积操作的函数。它的作用是对输入数据进行二维卷积操作&#xff0c;通常用于图像处理和深度学习中的卷积神经网络&#xff08;CNN&#xff09;模型。 conv2d的使用 我们先查看一下官方文档 inpu…

深度学习之前馈神经网络

1.导入常用工具包 #在终端中输入以下命令就可以安装工具包 pip install numpy pip install pandas Pip install matplotlib注&#xff1a; numpy是科学计算基础包 pandas能方便处理结构化数据和函数 matplotlib主要用于绘制图表。 #导包的代码&#xff1a; import numpy as n…

渲染农场评测:6大热门云渲染平台全面比较

在3D行业中&#xff0c;选择一个合适的云渲染平台可能会令许多专业人士感到难以抉择。为此&#xff0c;我们精心准备了6家流行云渲染平台的详尽评测&#xff0c;旨在为您的决策过程提供实用的参考和支持。 目前&#xff0c;市面上主要的3D网络渲染平台包括六大服务商&#xff0…