c/c++语言开发共享C++ OpenCV实现文档矫正功能

需求将一个斜着拍摄的文档矫正成正的,如图所示:思路1.读取原始图像,若图像太大可以先进行缩放处理,并获取原始图像的宽和高2.对图像进行预处理得到边缘,依次进行灰度处理、高斯模糊、边缘检测、膨胀、腐蚀。

需求

将一个斜着拍摄的文档矫正成正的,如图所示:

C++ OpenCV实现文档矫正功能

C++ OpenCV实现文档矫正功能

思路

1.读取原始图像,若图像太大可以先进行缩放处理,并获取原始图像的宽和高

2.对图像进行预处理得到边缘,依次进行灰度处理、高斯模糊、边缘检测、膨胀、腐蚀。

3.找到最大的轮廓,并提取角点

  • 进行降噪处理:检测轮廓面积,只保留大于阈值面积的轮廓
  • 计算每个轮廓的周长,使用dp算法计算出轮廓点的个数,规则为周长*0.02
  • 找到图像中面积最大的,且角点为4的轮廓

4.将找到的四个角点排列成一个固定的顺序,排列后的顺序为:左上角-右上角-左下角-右下角

  • 将每个点的xy坐标值相加(x+y),左上角的点的坐标和应该是最小的,右下角的点的坐标和应该是最大的
  • 将每个点的xy坐标值相减(x-y),左下角的点的坐标差应该是最小的,右上角的点的坐标差应该是最大的
  • 重新排列四个角点

5.进行透视变换

  • 根据变换前及变换后的四个角点,创建变换矩阵
  • 根据变换矩阵对图像进行透视变换

6.若透视变换后有一些毛边,按需要进行裁剪,裁剪后重新调整比例

  • 创建一个矩形用来裁剪,并设定四周裁剪5像素
  • 裁剪后重新调整图像宽高

7.显示变换后图像

代码

代码中均有详细注释,请仔细阅读

#include <iostream>  #include<opencv2/opencv.hpp>  #include <opencv2/highgui.hpp>  #include <opencv2/imgproc.hpp>    using namespace cv;  using namespace std;    // 一些定义  mat image_origin,     // 原始图像  	image_gray,       // 灰度处理后的图像  	image_blur,       // 高斯模糊处理后的图像  	image_canny,      // 边缘检测后的图像  	image_dilate,     // 膨胀后的图像  	image_erode,      // 腐蚀后的图像  	image_preprocess, // 预处理后的图像  	image_trans,      // 透视变换后的图像  	image_crop;	      // 裁剪后的图像    vector<point> origin_points,  // 重新排列前的角点  			  reorder_points; // 重新排列后的角点  			    			    int origin_width = 0, origin_height = 0;    /*   * 函数功能:预处理,依次进行灰度处理、高斯模糊、边缘检测、膨胀、腐蚀。   * 输入:图像,是否显示(0-不显示 1-显示每一步处理后的图像 2-只显示最终图像)   * */  mat preprocess(const mat& image, int display)  {  	// 灰度处理  	cvtcolor(image, image_gray, color_bgr2gray);    	// 高斯模糊  	gaussianblur(image_gray, image_blur, size(3, 3), 3, 0);    	// 边缘检测(边缘检测前对图像进行一次高斯模糊)  	canny(image_blur, image_canny, 50, 150);    	// 膨胀和腐蚀(有时进行边缘检测的时候,没有被完全填充,或者无法正确检测,可以用膨胀和腐蚀)  	// 创建一个用于膨胀和腐蚀的内核,后面的数字越大膨胀的越多(数字要用奇数)  	mat kernel = getstructuringelement(morph_rect, size(3, 3));  	// 膨胀  	dilate(image_canny, image_dilate, kernel);  	// 腐蚀  	//erode(image_dilate, image_erode, kernel);    	// 显示预处理效果  	if(display == 1)  	{  		imshow("灰度处理后的图像", image_gray);  		imshow("高斯模糊后的图像", image_blur);  		imshow("边缘检测后的图像", image_canny);  		imshow("膨胀后的图像", image_dilate);  //		imshow("腐蚀后的图像", image_erode);  	}  	else if(display == 2)  	{  		imshow("预处理后的图像", image_dilate);  	}    	return image_dilate;  }    /*   * 函数功能:找到面积最大的轮廓   * 输入:源图像   * 输出:最大轮廓的四个角点数组   * */  vector<point> getmaxcontour(const mat& img_input)  {  	/*  	 * contours是一个双重向量,向量内每个元素保存了一组由连续的point点构成的点的集合的向量,每一组point点集就是一个轮廓。有多少轮廓,向量contours就有多少元素。  	 * 相当于创建了这样一个向量{{point(),point()},{},{}}  	 * */  	vector<vector<point>> contours;  	/*  	 * hierarchy向量内每个元素保存了一个包含4个int整型的数组。向量hiararchy内的元素和轮廓向量contours内的元素是一一对应的,向量的容量相同。  	 * hierarchy向量内每一个元素的4个int型变量——hierarchy[i][0] ~ hierarchy[i][3],分别表示第i个轮廓的后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号。  	 * 如果当前轮廓没有对应的后一个轮廓、前一个轮廓、父轮廓或内嵌轮廓的话,则hierarchy[i][0] ~ hierarchy[i][3]的相应位被设置为默认值-1。  	 * */  	vector<vec4i> hierarchy;    	/*  	 * findcontours找到轮廓  	 * 第一个参数:单通道图像矩阵,可以是灰度图,但更常用的是二值图像,一般是经过canny、拉普拉斯等边缘检测算子处理过的二值图像;  	 * 第二个参数:contours (前文介绍过)  	 * 第三个参数:hierarchy(前文介绍过)  	 * 第四个参数:轮廓的检索模式  	 * 		取值一:cv_retr_external 只检测最外围轮廓,包含在外围轮廓内的内围轮廓被忽略  	 * 		取值二:cv_retr_list     检测所有的轮廓,包括内围、外围轮廓,但是检测到的轮廓不建立等级关系,彼此之间独立,没有等级关系,这就意味着这个检索模式下不存在父轮廓或内嵌轮廓,所以hierarchy向量内所有元素的第3、第4个分量都会被置为-1,具体下文会讲到  	 * 		取值三:cv_retr_ccomp    检测所有的轮廓,但所有轮廓只建立两个等级关系,外围为顶层,若外围内的内围轮廓还包含了其他的轮廓信息,则内围内的所有轮廓均归属于顶层  	 * 		取值四:cv_retr_tree     检测所有轮廓,所有轮廓建立一个等级树结构。外层轮廓包含内层轮廓,内层轮廓还可以继续包含内嵌轮廓。  	 * 第五个参数:轮廓的近似方法  	 * 		取值一:cv_chain_approx_none   保存物体边界上所有连续的轮廓点到contours向量内  	 * 		取值二:cv_chain_approx_simple 仅保存轮廓的拐点信息,把所有轮廓拐点处的点保存入contours向量内,拐点与拐点之间直线段上的信息点不予保留  	 * 		取值三和四:cv_chain_approx_tc89_l1,cv_chain_approx_tc89_kcos使用teh-chinl chain 近似算法  	 * 第六个参数:point偏移量,所有的轮廓信息相对于原始图像对应点的偏移量,相当于在每一个检测出的轮廓点上加上该偏移量,且point可以是负值。不填为默认不偏移point()  	 * */  	findcontours(img_input, contours, hierarchy, retr_external, chain_approx_simple);  	/*  	 * drawcontours绘出轮廓  	 * 第一个参数:指明在哪幅图像上绘制轮廓。image为三通道才能显示轮廓  	 * 第二个参数:contours  	 * 第三个参数:指定绘制哪条轮廓,如果是-1,则绘制其中的所有轮廓  	 * 第四个参数:轮廓线颜色  	 * 第五个参数:轮廓线的宽度,如果是-1(filled),则为填充  	 * */  //	// 不全输出,在下文只输出角点  //	drawcontours(image, contours, -1, scalar(255, 0, 255), 2);    	// 定义轮廓,大小与contours相同,但内层向量中只有角点(例如三角形就是3,四边形就是4,圆形可能七八个)  	vector<vector<point>> corners_contours(contours.size());    	// 定义边界框,大小与contours相同  	vector<rect> bounding_box(contours.size());    	vector<point> biggest_contours;  	double max_area = 0;    	for (int i = 0; i < contours.size(); i++)  	{  		// 检测轮廓面积  		double contour_area = contourarea(contours[i]);  //		cout << area << endl;    		// 假设图像中有噪声,需要将其过滤,只保留面积大于1000的轮廓  		if (contour_area > 1000)  		{  			// 计算每个轮廓的周长  			double contour_perimeter = arclength(contours[i], true);    			// 使用dp算法计算出轮廓点的个数,规则为周长*0.02  			approxpolydp(contours[i], corners_contours[i], 0.02 * contour_perimeter, true);    			// 找到图像中面积最大的,且角点为4的轮廓  			if (contour_area > max_area && corners_contours[i].size() == 4 ) {    				//drawcontours(image_origin, conpoly, i, scalar(255, 0, 255), 5);  				biggest_contours = { corners_contours[i][0],corners_contours[i][1] ,corners_contours[i][2] ,corners_contours[i][3] };  				max_area = contour_area;  			}    //			// 只绘制角点之间的边框线,debug用,取消注释可以看到检测出的所有边界框  //			drawcontours(image_origin, corners_contours, i, scalar(255, 0, 255), 2);  //			rectangle(image_origin, bounding_box[i].tl(), bounding_box[i].br(), scalar(0, 255, 0), 5);  		}  	}    	// 返回最大的轮廓  	return biggest_contours;  }    /*   * 函数功能:绘制一些点   * 输入:点集,颜色   * */  void drawpoints(vector<point> points, const scalar& color)  {  	for (int i = 0; i < points.size(); i++)  	{  		circle(image_origin, points[i], 10, color, filled);  		puttext(image_origin, to_string(i), points[i], font_hershey_plain, 4, color, 4);  	}  }    /*   * 函数功能:重新排列四个角点的顺序   * 最终顺序为: 0  1   * 			  2  3   * 			  数组中为左上角-右上角-左下角-右下角   * */  vector<point> reorderpoints(vector<point> points)  {  	vector<point> newpoints;  	vector<int>  sumpoints, subpoints;    	// opencv中左上顶点为(0,0),右为x轴正向,下为y轴正向。  	for (int i = 0; i < 4; i++)  	{  		// 将每个点的xy坐标值相加(x+y),左上角的点的坐标和应该是最小的,右下角的点的坐标和应该是最大的  		sumpoints.push_back(points[i].x + points[i].y);  		// 将每个点的xy坐标值相减(x-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 和的最大值    	return newpoints;  }    /*   * 函数功能:   * 输入:源图像,四个角点的集合(角点的顺序为,左上角-右上角-左下角-右下角),输出的宽,输出的高   * 输出:透视变换后的图像   * */  mat perspectivetrans(const mat& img, vector<point> points, float width, float height )  {  	// 前面经过重新排列,四个角点的顺序为:左上角-右上角-左下角-右下角  	point2f src[4] = { points[0],points[1],points[2],points[3] };  	// 变换后的四个角点  	point2f dst[4] = { {0.0f,0.0f},{width,0.0f},{0.0f,height},{width,height} };    	// 创建变换矩阵  	mat matrix = getperspectivetransform(src, dst);  	// 透视变换  	warpperspective(img, image_trans, matrix, point(width, height));    	return image_trans;  }    int main()  {  	// 1.读取原始图像  	string path = "res/image_origin.jpg";  	image_origin = imread(path);    //	// 若图像太大可以先进行缩放处理  //	resize(image_origin, image_origin, size(), 0.5, 0.5);    	// 获取原始图像的宽和高  	origin_width  = image_origin.size().width;  	origin_height = image_origin.size().height;    	// 2.对图像进行预处理得到边缘,依次进行灰度处理、高斯模糊、边缘检测、膨胀、腐蚀。  	image_preprocess = preprocess(image_origin, 0);    	// 3.找到最大的轮廓,并提取角点  	origin_points = getmaxcontour(image_preprocess);  //	drawpoints(origin_points, scalar(0, 0, 255)); // 红色  	// 此时发现,角点的顺序不固定,为了后面进行透视变换时与代码中变换后点集的顺序相同,需要将其排列成一个固定的顺序,排列后的顺序为:左上角-右上角-左下角-右下角  	reorder_points = reorderpoints(origin_points);  //	drawpoints(reorder_points, scalar(0, 255, 0)); //绿色    	// 4.透视变换  	image_trans = perspectivetrans(image_origin, reorder_points, origin_width, origin_height);    	// 透视变换后有一些毛边,若需要可以进行裁剪  	// 四周裁剪5像素  	int cropval= 5;  	// 创建一个矩形用来裁剪  	rect roi(cropval, cropval, origin_width - (2 * cropval), origin_height - (2 * cropval));  	image_crop = image_trans(roi);  	// 裁剪后重新调整比例  	resize(image_crop, image_crop, size(origin_width, origin_height));    	// 5.显示并输出变换后图像  	imshow("源图像", image_origin);  	imshow("最终图像", image_crop);        	imwrite("res/image_output.jpg", image_crop);    	waitkey(0);  }    

效果

C++&nbsp;OpenCV实现文档矫正功能

到此这篇关于c++ opencv实现文档矫正功能的文章就介绍到这了,更多相关opencv文档矫正内容请搜索<计算机技术网(www.ctvol.com)!!>以前的文章或继续浏览下面的相关文章希望大家以后多多支持<计算机技术网(www.ctvol.com)!!>!

需要了解更多c/c++开发分享C++ OpenCV实现文档矫正功能,都可以关注C/C++技术分享栏目—计算机技术网(www.ctvol.com)!

本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。

ctvol管理联系方式QQ:251552304

本文章地址:https://www.ctvol.com/c-cdevelopment/1082324.html

(0)
上一篇 2022年4月24日
下一篇 2022年4月24日

精彩推荐