目录

一、什么是文档扫描文档扫描步骤
二、使用的函数、变量介绍
变量介绍
函数介绍
三、实操
1 图像预处理
2 获取图像轮廓
3 提取并标记文档边缘
4 重新排序边缘
5 裁剪修饰边缘


一、什么是文档扫描

文档扫描即对采用不同视角所拍摄到的文本图像,以正视的形式将文本呈现。如下图:

javacv 机器视觉训练 机器视觉 opencv_#include

所希望得到的是图片中的A4文档信息,经文档扫描后得到如下:

javacv 机器视觉训练 机器视觉 opencv_javacv 机器视觉训练_02

文档扫描步骤

1 将导入的图片经过转灰度、模糊降噪、canny边缘检测、膨胀等图像预处理
2 定义,轮廓变量,获取预处理后的图像轮廓
3 提取轮廓中的文本框部分,通过找最大外矩形轮廓函数
4 使用透视矩阵,使图像位于正视图,修剪边缘

二、使用的函数、变量介绍

变量介绍

vector 说明:

vector是向量类型,可以容纳许多类型的数据,因此也被称为容器(可以理解为动态数组,是封装好了的类)

进行vector操作前应添加头文件#include 具体可以查看

vector< Point >

vector容器里面放二维坐标点

vector<vector< Point >>

vector容器里面放了一个vector容器,子容器里放二维坐标点

vector< Vec4i > vector< int >

vector容器中放4维int向量

Point2f 二维坐标点

Rect 像素width * height from 位置(x*y)

javacv 机器视觉训练 机器视觉 opencv_opencv_03

RotateRect [angle,center[0,0],size[width*height]]

javacv 机器视觉训练 机器视觉 opencv_取值_04

函数介绍

findContours(image, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
对findContours函数的具体内容可以查看findContours函数参数详解。
简单说明如下:
第一个image :为输入图像,可为灰度图,二值图常用,一般为经过Canny等边缘检测
第二个countors 定义为“vector<vector> 型向量,并且是一个双重向量,第一重向量对应轮廓元素,例如countours[i] 为第i个轮廓元素。且轮廓元素也为向量,保存的数据类型为Point类型。例如countours[i][j]为第i个轮廓的第J个特征点。
第三个 hierarchy 定义为vector < Vec4i >类型,即向量内每一个元素包括4个int型变量。vector分别表示第 i个轮廓的后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号。
第四个为int型的mode 定义轮廓检索模式
取值一:CV_RETR_EXTERNAL只检测最外围轮廓,包含在外围轮廓内的内围轮廓被忽略
取值二:CV_RETR_LIST 检测所有的轮廓,包括内围、外围轮廓,但是检测到的轮廓不建立等级关 系,彼此之间独立,没有等级关系,这就意味着这个检索模式下不存在父轮廓或内嵌轮廓, 所以hierarchy向量内所有元素的第3、第4个分量都会被置为-1。
取值三:CV_RETR_CCOMP 检测所有的轮廓,但所有轮廓只建立两个等级关系,外围为顶层,若外围 内的内围轮廓还包含了其他的轮廓信息,则内围内的所有轮廓均归属于顶层
取值四:CV_RETR_TREE, 检测所有轮廓,所有轮廓建立一个等级树结构。外层轮廓包含内层轮廓,内层轮廓还可以继续包含内嵌轮廓。
第五个参数:int型的method,定义轮廓的近似方法:
取值一:CV_CHAIN_APPROX_NONE 保存物体边界上所有连续的轮廓点到contours向量内
取值二:CV_CHAIN_APPROX_SIMPLE 仅保存轮廓的拐点信息,把所有轮廓拐点处的点保存入contours 向量内,拐点与拐点之间直线段上的信息点不予保留。
取值三和四: CV_CHAIN_APPROX_TC89_L1CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似算法
第六个参数: Point偏移量 所有点相对移动一段距离,本项目未使用


approxPolyDP(contours[i], conPoly[i], 0.02 * peri, true);
void approxPolyDP(InputArray curve, OutputArray approxCurve, double epsilon, bool closed);//找出轮廓的多边形拟合曲线
第一个参数 InputArray curve:输入的点集
第二个参数OutputArray approxCurve:输出的点集,当前点集是能最小包容指定点集的。画出来即是一个多边形。
第三个参数double epsilon:指定的精度,也即是原始曲线与近似曲线之间的最大距离,根据周长来计算。
第四个参数bool closed:若为true,则说明近似曲线是闭合的;反之,若为false,则断开。


double arcLength( InputArray curve, bool closed );计算轮廓周长
InputArray类型的curve,输入的向量,二维点(轮廓顶点),可以为std::vector或Mat类型。
bool类型的closed,用于指示曲线是否封闭的标识符,一般设置为true

三、实操

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<vector<Point>> contours;
	vector<Vec4i> hierarchy;

	findContours(image, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

3 提取并标记文档边缘

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;
		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) {
				biggest = { conPoly[i][0],conPoly[i][1] ,conPoly[i][2] ,conPoly[i][3] };
				maxArea = area;
			}
		}
	}
	return biggest;
}

4 重新排序边缘

vector<Point> reorder(vector<Point> points)
{
	vector<Point> newPoints;
	vector<int> sumPoints, subPoints;
	for (int i = 0; i < 4; i++)
	{
		sumPoints.push_back(points[i].x + points[i].y);
		subPoints.push_back(points[i].x - points[i].y);
	}
	//cout << sumPoints.begin() << endl;
	newPoints.push_back(points[min_element(sumPoints.begin(), sumPoints.end())-sumPoints.begin()]); // 0
	//cout << newPoints[0].x << endl;
	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
	cout << sizeof(newPoints) << endl;
	return newPoints;
}

其中push_back为在容器后添加一个元素,min_elment和max_element分别为查找最大最小值。

5 绘制裁剪修饰边缘

得到透视投影后的图像

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} }; //变化后的四周
	Mat matrix = getPerspectiveTransform(src, dst); //得到变化矩阵
	warpPerspective(img, imgWarp, matrix, Point(w, h));   //我,h为变化后的像素大小
	return imgWarp;
}

得到图像结果如下:

javacv 机器视觉训练 机器视觉 opencv_opencv_05

可知四周仍有瑕疵,故对其修剪得到如下:

int cropVal = 5;
	Rect roi(cropVal, cropVal, w-(2 * cropVal), h-(2 * cropVal));
	imgCrop = imgWarp(roi);

最终得到运行结果如下:

javacv 机器视觉训练 机器视觉 opencv_opencv_06

最后代码如下:

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

using namespace cv;
using namespace std;

/// Project 2 – Document Scanner //

Mat imgOriginal, imgGray, imgBlur, imgCanny, imgThre, imgDil, imgErode, 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 image) {

	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;

	findContours(image, 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]);
		//cout << area << endl;

		string objectType;

		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) {

				//drawContours(imgOriginal, conPoly, i, Scalar(255, 0, 255), 5);
				biggest = { conPoly[i][0],conPoly[i][1] ,conPoly[i][2] ,conPoly[i][3] };
				maxArea = area;
			}
			//drawContours(imgOriginal, conPoly, i, Scalar(255, 0, 255), 2);
			//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 < 4; i++)
	{
		sumPoints.push_back(points[i].x + points[i].y);
		subPoints.push_back(points[i].x - points[i].y);
	}
	//cout << sumPoints.begin() << endl;
	newPoints.push_back(points[min_element(sumPoints.begin(), sumPoints.end())-sumPoints.begin()]); // 0
	//cout << newPoints[0].x << endl;
	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
	cout << sizeof(newPoints) << endl;
	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} };

	Mat matrix = getPerspectiveTransform(src, dst);
	warpPerspective(img, imgWarp, matrix, Point(w, h));

	return imgWarp;
}

void main() {

	string path = "resources/paper.jpg";
	imgOriginal = imread(path);
	resize(imgOriginal, imgOriginal, Size(), 0.5, 0.5);

	// Preprpcessing – Step 1
	imgThre = preProcessing(imgOriginal);

	// Get Contours – Biggest – Step 2
	initialPoints = getContours(imgThre);
	//drawPoints(initialPoints, Scalar(0, 0, 255));
	docPoints = reorder(initialPoints);
	//drawPoints(docPoints, Scalar(0, 255, 0));

	// Warp – Step 3
	imgWarp = getWarp(imgOriginal, docPoints, w, h);

	//Crop – Step 4
	int cropVal = 5;
	Rect roi(cropVal, cropVal, w-(2 * cropVal), h-(2 * cropVal));
	imgCrop = imgWarp(roi);

	imshow("Image", imgOriginal);
	//imshow("Image Dilation", imgThre);
	imshow("Image Warp", imgWarp);
	imshow("Image Crop", imgCrop);
	waitKey(0);

}