OpenCV4.0 C++ 实战:文件扫描器(自用 代码+注释)

10 篇文章 7 订阅

环境:OpenCV4.5.1 + VS2019

目录

1. 准备工作:

1.1 “Resources"文件

2. 主要代码

2.1 图像预处理

2.2 找出最大矩形的轮廓(文件轮廓) 的四点位置

2.3 可视化文件四点

2.4 重新将矩形四点按“Z”排序

2.5 Wrap 文件矫正

2.6 适当修剪边缘

2.7 实现

3. 总结


1. 准备工作:

1.1 “Resources"文件

“Resources"文件下载链接:

https://pan.baidu.com/s/1uzVDwl8lD2qVTY1bFlhF1A 
提取码:5n48

2. 主要代码

2.1 图像预处理

Mat preProcessing(Mat imgOriginal) {

	cvtColor(imgOriginal, imgGray, COLOR_BGR2GRAY);		//灰度
	GaussianBlur(imgGray, imgBlur, Size(3, 3), 3, 0);	//高斯模糊(参数四和五:sigmaX 和 sigmaY
	Canny(imgBlur, imgCanny, 25, 75);					//边缘检测(参数四和五:两个阈值

	Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));	//返回指定形状和尺寸的结构元素(参数一:指定形状(这里为矩形);参数二指定尺寸
	dilate(imgCanny, imgDil, kernel);					//膨胀(参数三:膨胀内核

	return imgDil;
}

2.2 找出最大矩形的轮廓(文件轮廓) 的四点位置

vector<Point> getContours(Mat imgDil) {

	vector<vector<Point>> contours;		//轮廓组
	vector<Vec4i> hierarchy;			// 向量内每个元素包含了4个int型的 向量

	findContours(imgDil, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);		//检测轮廓
	//	参数二:输入的轮廓组	参数三:画第几条轮廓(-1为负数表示全画)		参数五:线宽
	//	参数四:检测轮廓的方法(这里是只检测外轮廓))	参数五:表示一条轮廓的方法(这里是只存储水平,垂直,对角直线的起始点)
	//drawContours(img, contours, -1, Scalar(255, 0, 255), 2);							//画出轮廓

	vector<vector<Point>> conPoly(contours.size());
	vector<Point> biggest;
	int maxArea = 0;

	for (int i = 0; i < contours.size(); i++)
	{
		int area = contourArea(contours[i]);		//轮廓面积
		//cout << area << endl;

		//	通过面积大小过滤(去噪)
		if (area > 1000)
		{
			float peri = arcLength(contours[i], true);		//弧长(轮廓周长) (参数一:图像轮廓;参数二:是否闭合)
			approxPolyDP(contours[i], conPoly[i], 0.02 * peri, true);					//对图像轮廓点 进行 多边形拟合
			//参数一/二:输入/输出的点集	参数三:指定精度	参数四:是否闭合

			if (area > maxArea && conPoly[i].size() == 4) {

				maxArea = area;
				biggest = { conPoly[i][0], conPoly[i][1], conPoly[i][2], conPoly[i][3] };
				//drawContours(imgOriginal, conPoly, i, Scalar(255, 0, 255), 2);						//画出轮廓
				//	参数二:输入的轮廓组		参数三:画第i条轮廓(负数表示全画)		参数五:线宽
			}
		}
	}
	return biggest;
}

2.3 可视化文件四点

void drawPoints(vector<Point> points, Scalar color) {

	for (int i = 0; i < points.size(); i++) {
		circle(imgOriginal, points[i], 5, color, FILLED);
		putText(imgOriginal, to_string(i), { points[i].x - 5,points[i].y - 5 }, FONT_HERSHEY_PLAIN, 3, color, 3);
	}
}

2.4 重新将矩形四点按“Z”排序

vector<Point> reorder(vector<Point> points) {

	vector<Point> newPoints;
	vector<int> sumPoints, subPoints;

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

		sumPoints.push_back(points[i].x + points[i].y);
		subPoints.push_back(points[i].x - points[i].y);
	}

	newPoints.push_back(points[min_element(sumPoints.begin(), sumPoints.end()) - sumPoints.begin()]);	//0
	newPoints.push_back(points[max_element(subPoints.begin(), subPoints.end()) - subPoints.begin()]);	//1
	newPoints.push_back(points[min_element(subPoints.begin(), subPoints.end()) - subPoints.begin()]);	//2
	newPoints.push_back(points[max_element(sumPoints.begin(), sumPoints.end()) - sumPoints.begin()]);	//3
	//	(x+y)min为左上;(x+y)max为右下;(x-y)max为右上;(x-y)min为左下

	return newPoints;
}

2.5 Wrap 文件矫正

Mat getWrap(Mat img, vector<Point> points, float w, float h) {

	Mat imgWarp;

	Point2f src[4] = { points[0],points[1],points[2],points[3] };	//扭曲前的四个点坐标(顺序为Z)
	Point2f dst[4] = { {0.0f,0.0f},{w,0.0f},{0.0f,h},{w,h} };		//	  后

	Mat matrix = getPerspectiveTransform(src, dst);					//获取 透视变换矩阵
	warpPerspective(img, imgWarp, matrix, Point(w, h));				//透视变换(参数三:透视变换矩阵M;参数四:输出矩阵的尺寸)

	return imgWarp;
}

2.6 适当修剪边缘

	//	适当修剪边缘
	int cropValue = 5;
	Rect roi(cropValue, cropValue, w - (2 * cropValue), h - (2 * cropValue));
	imgCrop = imgWarp(roi);

2.7 实现

#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>

//项目二:文件扫描器
using namespace cv;
using namespace std;

		Virtual Painter		//

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

float w = 420, h = 596;		// A4的尺寸*2

Mat preProcessing(Mat imgOriginal) {

	cvtColor(imgOriginal, imgGray, COLOR_BGR2GRAY);		//灰度
	GaussianBlur(imgGray, imgBlur, Size(3, 3), 3, 0);	//高斯模糊(参数四和五:sigmaX 和 sigmaY
	Canny(imgBlur, imgCanny, 25, 75);					//边缘检测(参数四和五:两个阈值

	Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));	//返回指定形状和尺寸的结构元素(参数一:指定形状(这里为矩形);参数二指定尺寸
	dilate(imgCanny, imgDil, kernel);					//膨胀(参数三:膨胀内核

	return imgDil;
}

vector<Point> getContours(Mat imgDil) {

	vector<vector<Point>> contours;		//轮廓组
	vector<Vec4i> hierarchy;			// 向量内每个元素包含了4个int型的 向量

	findContours(imgDil, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);		//检测轮廓
	//	参数二:输入的轮廓组	参数三:画第几条轮廓(-1为负数表示全画)		参数五:线宽
	//	参数四:检测轮廓的方法(这里是只检测外轮廓))	参数五:表示一条轮廓的方法(这里是只存储水平,垂直,对角直线的起始点)
	//drawContours(img, contours, -1, Scalar(255, 0, 255), 2);							//画出轮廓

	vector<vector<Point>> conPoly(contours.size());
	vector<Point> biggest;
	int maxArea = 0;

	for (int i = 0; i < contours.size(); i++)
	{
		int area = contourArea(contours[i]);		//轮廓面积
		//cout << area << endl;

		//	通过面积大小过滤(去噪)
		if (area > 1000)
		{
			float peri = arcLength(contours[i], true);		//弧长(轮廓周长) (参数一:图像轮廓;参数二:是否闭合)
			approxPolyDP(contours[i], conPoly[i], 0.02 * peri, true);					//对图像轮廓点 进行 多边形拟合
			//参数一/二:输入/输出的点集	参数三:指定精度	参数四:是否闭合

			if (area > maxArea && conPoly[i].size() == 4) {

				maxArea = area;
				biggest = { conPoly[i][0], conPoly[i][1], conPoly[i][2], conPoly[i][3] };
				//drawContours(imgOriginal, conPoly, i, Scalar(255, 0, 255), 2);						//画出轮廓
				//	参数二:输入的轮廓组		参数三:画第i条轮廓(负数表示全画)		参数五:线宽
			}
		}
	}
	return biggest;
}

void drawPoints(vector<Point> points, Scalar color) {

	for (int i = 0; i < points.size(); i++) {
		circle(imgOriginal, points[i], 5, color, FILLED);
		putText(imgOriginal, to_string(i), { points[i].x - 5,points[i].y - 5 }, FONT_HERSHEY_PLAIN, 3, color, 3);
	}
}

vector<Point> reorder(vector<Point> points) {

	vector<Point> newPoints;
	vector<int> sumPoints, subPoints;

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

		sumPoints.push_back(points[i].x + points[i].y);
		subPoints.push_back(points[i].x - points[i].y);
	}

	newPoints.push_back(points[min_element(sumPoints.begin(), sumPoints.end()) - sumPoints.begin()]);	//0
	newPoints.push_back(points[max_element(subPoints.begin(), subPoints.end()) - subPoints.begin()]);	//1
	newPoints.push_back(points[min_element(subPoints.begin(), subPoints.end()) - subPoints.begin()]);	//2
	newPoints.push_back(points[max_element(sumPoints.begin(), sumPoints.end()) - sumPoints.begin()]);	//3
	//	(x+y)min为左上;(x+y)max为右下;(x-y)max为右上;(x-y)min为左下

	return newPoints;
}

Mat getWrap(Mat img, vector<Point> points, float w, float h) {

	Mat imgWarp;

	Point2f src[4] = { points[0],points[1],points[2],points[3] };	//扭曲前的四个点坐标(顺序为Z)
	Point2f dst[4] = { {0.0f,0.0f},{w,0.0f},{0.0f,h},{w,h} };		//	  后

	Mat matrix = getPerspectiveTransform(src, dst);					//获取 透视变换矩阵
	warpPerspective(img, imgWarp, matrix, Point(w, h));				//透视变换(参数三:透视变换矩阵M;参数四:输出矩阵的尺寸)

	return imgWarp;
}

//		Image		//

void main() {

	//string path = "C:\\Users\\ERDONGC\\Desktop\\CSDN\\微信图片_20220215203616.jpg";
	string path = "Resources/paper.jpg";
	imgOriginal = imread(path);
	//resize(imgOriginal, imgOriginal, Size(), 0.5, 0.5);

	//	Preprocessing 图像预处理
	imgThre = preProcessing(imgOriginal);

	//	Get Contours - Biggest 找出最大矩形的轮廓(文件轮廓) 的四点位置
	initialPoints = getContours(imgThre);
	//drawPoints(initialPoints, Scalar(0, 0, 255));	//可视化文件四点

	//	重新将矩形四点按Z排序
	docPoints = reorder(initialPoints);
	//drawPoints(docPoints, Scalar(0, 255, 0));		//可视化重新排序后的文件四点

	//	Wrap 文件矫正
	imgWarp = getWrap(imgOriginal, docPoints, w, h);

	//	适当修剪边缘
	int cropValue = 5;
	Rect roi(cropValue, cropValue, w - (2 * cropValue), h - (2 * cropValue));
	imgCrop = imgWarp(roi);

	imshow("Image", imgOriginal);
	//imshow("Image Dilation", imgThre);
	//imshow("Image Warp", imgWarp);
	imshow("Image Crop", imgCrop);
	waitKey(0);	//写0为无穷大
}

3. 总结

实现功能:

可自动检测图片中的文件部分,并将检测到的文件部分进行矫正和适当修剪后单独显示出来

未完善地方:

暂只能对图片进行扫描,无法通过摄像头进行实时扫描。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值