c++ poencv Project2 - Document Scanner

2024-05-10 22:20

本文主要是介绍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(); }

这篇关于c++ poencv Project2 - Document Scanner的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/977717

相关文章

C++ STL-string类底层实现过程

《C++STL-string类底层实现过程》本文实现了一个简易的string类,涵盖动态数组存储、深拷贝机制、迭代器支持、容量调整、字符串修改、运算符重载等功能,模拟标准string核心特性,重点强... 目录实现框架一、默认成员函数1.默认构造函数2.构造函数3.拷贝构造函数(重点)4.赋值运算符重载函数

C++ vector越界问题的完整解决方案

《C++vector越界问题的完整解决方案》在C++开发中,std::vector作为最常用的动态数组容器,其便捷性与性能优势使其成为处理可变长度数据的首选,然而,数组越界访问始终是威胁程序稳定性的... 目录引言一、vector越界的底层原理与危害1.1 越界访问的本质原因1.2 越界访问的实际危害二、基

c++日志库log4cplus快速入门小结

《c++日志库log4cplus快速入门小结》文章浏览阅读1.1w次,点赞9次,收藏44次。本文介绍Log4cplus,一种适用于C++的线程安全日志记录API,提供灵活的日志管理和配置控制。文章涵盖... 目录简介日志等级配置文件使用关于初始化使用示例总结参考资料简介log4j 用于Java,log4c

C++归并排序代码实现示例代码

《C++归并排序代码实现示例代码》归并排序将待排序数组分成两个子数组,分别对这两个子数组进行排序,然后将排序好的子数组合并,得到排序后的数组,:本文主要介绍C++归并排序代码实现的相关资料,需要的... 目录1 算法核心思想2 代码实现3 算法时间复杂度1 算法核心思想归并排序是一种高效的排序方式,需要用

C++11范围for初始化列表auto decltype详解

《C++11范围for初始化列表autodecltype详解》C++11引入auto类型推导、decltype类型推断、统一列表初始化、范围for循环及智能指针,提升代码简洁性、类型安全与资源管理效... 目录C++11新特性1. 自动类型推导auto1.1 基本语法2. decltype3. 列表初始化3

C++11右值引用与Lambda表达式的使用

《C++11右值引用与Lambda表达式的使用》C++11引入右值引用,实现移动语义提升性能,支持资源转移与完美转发;同时引入Lambda表达式,简化匿名函数定义,通过捕获列表和参数列表灵活处理变量... 目录C++11新特性右值引用和移动语义左值 / 右值常见的左值和右值移动语义移动构造函数移动复制运算符

C++中detach的作用、使用场景及注意事项

《C++中detach的作用、使用场景及注意事项》关于C++中的detach,它主要涉及多线程编程中的线程管理,理解detach的作用、使用场景以及注意事项,对于写出高效、安全的多线程程序至关重要,下... 目录一、什么是join()?它的作用是什么?类比一下:二、join()的作用总结三、join()怎么

C++中全局变量和局部变量的区别

《C++中全局变量和局部变量的区别》本文主要介绍了C++中全局变量和局部变量的区别,全局变量和局部变量在作用域和生命周期上有显著的区别,下面就来介绍一下,感兴趣的可以了解一下... 目录一、全局变量定义生命周期存储位置代码示例输出二、局部变量定义生命周期存储位置代码示例输出三、全局变量和局部变量的区别作用域

C++中assign函数的使用

《C++中assign函数的使用》在C++标准模板库中,std::list等容器都提供了assign成员函数,它比操作符更灵活,支持多种初始化方式,下面就来介绍一下assign的用法,具有一定的参考价... 目录​1.assign的基本功能​​语法​2. 具体用法示例​​​(1) 填充n个相同值​​(2)

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被