利用opencv c++实现文档扫描/文档矫正功能

        原为本人机器人视觉课程作业,要求为实现文档扫描功能,下给出基本思路及代码过程

        原要求作业如下所示

        

    步骤采用类似于ppt给出的第一种思路     

  1. 先对图像进行预处理以便提取出较好的轮廓边缘
  2. 通过霍夫变换进行直线检测
  3. 找最外层直线,并求交点
  4. 求单应性矩阵

读入图片如下,

        该图片为4608*3456尺寸大小的图片,为避免处理过于复杂,这边使用resize进行图像缩放,缩小后尺寸为576*432,缩小相关代码如下 

Mat QuickDemo::resize_demo(Mat &image) {
	Mat zoomin, zoomout;
	int h = image.rows;
	int w = image.cols;
	resize(image, zoomin, Size(w / 8, h / 8), 0, 0, INTER_LINEAR);
	//imshow("zoomin", zoomin);
	//resize(image, zoomout, Size(w*1.5, h*1.5), 0, 0, INTER_LINEAR);
	//imshow("zoomout", zoomout);
	return zoomin;
}

         再对图形进行基本预处理,预处理代码如下

  1. 灰度化
  2. 高斯滤波,这边选取核为3*3的高斯核,肉眼无法看出滤波前后差异
  3. 边缘检测,采用canny算子进行边缘检测(下为有无高斯滤波的两种情况下的检测图像,以体现滤波重要性),canny双阈值参数TH=75,TL=25。
  4. 为方便检测直线,还加入了膨胀操作,膨胀核为3*3
Mat version_lesson::pre_operate_image(Mat &image) {
	Mat gray,blur,canny, imgDil;
	cvtColor(image, gray, COLOR_BGR2GRAY);		//灰度
	//imshow("gray", gray);
	GaussianBlur(gray, blur, Size(3, 3), 3, 0);	//高斯模糊(参数四和五:sigmaX 和 sigmaY
	imshow("blur", blur);
	Canny(blur, canny, 25, 75);					//边缘检测(参数四和五:两个阈值
	//imshow("canny", canny);
	Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));	
	dilate(canny, imgDil, kernel);					//膨胀(参数三:膨胀内核
	//imshow("imgDil", imgDil);
	return canny;
}

示例处理效果如下:从左至右分别为灰度图、高斯滤波、有高斯滤波 、无高斯滤波后的边缘检测、膨胀后图像

 

        基尔霍夫直线检测(接下来代码统一在同一函数中,文后有源码)

         这一步的代码如下,HoughLinesP的输入图像为image,输出为lines,距离步长为1,角度步长为默认值CV_PI/180,累加值阈值为80,最短直线长度为100像素点(该值为不断调试出的最合适值),同一直线最大像素间隔为10

	vector<Vec4i> lines;
	HoughLinesP(image, lines, 1, CV_PI / 180.0, 80, 100, 10);
	for (int i = 0; i < lines.size(); ++i) {
		line(rgb, Point(lines[i][0], lines[i][1]), Point(lines[i][2], lines[i][3]), Scalar(0, 255, 0), 2, 8);
	}
	imshow("lines", rgb);

         找外边缘轮廓,为了更方便的寻找外边缘轮廓,csdn博主“一个热爱学习的深度渣渣”提供了一个比较好的方法(原文链接http://t.csdn.cn/uO7p6),即先判断直线是横线还是竖线,将其分类。

        在第五步的代码基础上,这边又检验了一下lines中的数据,代码及检测结果如下。

        发现检测到了13条直线,(恶补了一下返回值Vec4i的知识)每一组数据中前两个一组(x1,y1),后两个一组(x2,y2)分别表示检测到直线的起点和终点

        接下来开始判断竖线or横线,并将其分类,代码如下:

vector<Vec4i> heng_lines,shu_lines;//定义横线、竖线类别
	for (int i = 0; i < lines.size(); ++i) {
		int x = lines[i][0] - lines[i][2];//x改变量
		int y = lines[i][1] - lines[i][3];//y改变量
		x = x * x;//防止出现负数,用平方比较
		y = y * y;
		//cout << x << endl << y << endl;
		if (x <y) 
			shu_lines.push_back(lines[i]);
		else 
			heng_lines.push_back(lines[i]);
	}

       对分类的竖线、横线进行排序,并找到边框直线。竖线判断中心点x坐标即可,横线判断中心点y坐标即可,代码如下所示,思想是存到数组内后利用max_element和min_element返回其最大最小值,所在下标即对应边框直线

if (shu_lines.size() < 2 || heng_lines.size() < 2) {// 确保水平线和垂直线至少有两条
		cout << "Not enough edge lines... " << endl;
	}
	int heng_center_y[10], shu_center_x[10];
	for (int i = 0; i < heng_lines.size(); ++i) //横线判断y坐标,这边中心点不用除以2也无所谓
		heng_center_y[i] = heng_lines[i][1] + heng_lines[i][3];
	int maxp_heng = max_element(heng_center_y, heng_center_y + heng_lines.size())- heng_center_y;
	int minp_heng = min_element(heng_center_y, heng_center_y + heng_lines.size()) - heng_center_y;
	//cout << "最大下标"<<maxp_heng << endl;
	//cout << "最小下标" << minp_heng << endl;
	for (int i = 0; i < shu_lines.size(); ++i) //竖线判断x坐标,这边中心点不用除以2也无所谓
		shu_center_x[i] = shu_lines[i][0] + shu_lines[i][2];
	int maxp_shu = max_element(shu_center_x, shu_center_x + shu_lines.size()) - shu_center_x;
	int minp_shu = min_element(shu_center_x, shu_center_x + shu_lines.size()) - shu_center_x;

        找到边框直线后将其画出来,代码及效果图如下,这边竖线用蓝色,横线用绿色

	绘制最外端边框直线
	line(rgb, Point(heng_lines[minp_heng][0], heng_lines[minp_heng][1]), Point(heng_lines[minp_heng][2], heng_lines[minp_heng][3]), Scalar(0, 255, 0), 2, 8);
	line(rgb, Point(heng_lines[maxp_heng][0], heng_lines[maxp_heng][1]), Point(heng_lines[maxp_heng][2], heng_lines[maxp_heng][3]), Scalar(0, 255, 0), 2, 8);
	line(rgb, Point(shu_lines[minp_shu][0], shu_lines[minp_shu][1]), Point(shu_lines[minp_shu][2], shu_lines[minp_shu][3]), Scalar(255, 0, 0), 2, 8);
	line(rgb, Point(shu_lines[maxp_shu][0], shu_lines[maxp_shu][1]), Point(shu_lines[maxp_shu][2], shu_lines[maxp_shu][3]), Scalar(255, 0, 0), 2, 8);
	imshow("rgb", rgb);

求单应性矩阵

        求两点交点用到公式

        求两点关系用到的代码如下,这边要注意图像y坐标与笛卡尔坐标系相反

Point2f linesIntersect(double b1, double b2, double k1, double k2) {//算两两相交交点
	double x, y;//交点
	x = (b2 - b1) / (k1 - k2);
	y = (k1*b2 - k2 * b1) / (k2 - k1);
	if (x < 0)
		x = -x;
	if (y < 0)
		y = -y;
	return Point2f(x, y);
}

//算四条直线斜率
	double k_heng_min, k_heng_max, k_shu_min, k_shu_max;
	k_heng_min = (double)(heng_lines[minp_heng][3] - heng_lines[minp_heng][1]) / (double)(heng_lines[minp_heng][2] - heng_lines[minp_heng][0]); 
	k_heng_max = (double)(heng_lines[maxp_heng][3] - heng_lines[maxp_heng][1]) / (double)(heng_lines[maxp_heng][2] - heng_lines[maxp_heng][0]);
	k_shu_min = (double)(shu_lines[minp_shu][3] - shu_lines[minp_shu][1]) / (double)(shu_lines[minp_shu][2] - shu_lines[minp_shu][0]);
	k_shu_max = (double)(shu_lines[maxp_shu][3] - shu_lines[maxp_shu][1]) / (double)(shu_lines[maxp_shu][2] - shu_lines[maxp_shu][0]);
	//算四条直线y=x+b的b
	double b_heng_min, b_heng_max, b_shu_min, b_shu_max;
	b_heng_min = -k_heng_min * (double)heng_lines[minp_heng][2] + (double)heng_lines[minp_heng][3];
	b_heng_max = -k_heng_max * (double)heng_lines[maxp_heng][2] +(double)heng_lines[maxp_heng][3];
	b_shu_min = -k_shu_min * (double)shu_lines[minp_shu][2] + (double)shu_lines[minp_shu][3];
	b_shu_max = -k_shu_max * (double)shu_lines[maxp_shu][2] + (double)shu_lines[maxp_shu][3];
	//求交点
	Point2f p_zuoshang, p_zuoxia, p_youshang, p_youxia;
	p_zuoshang = linesIntersect(b_heng_min, b_shu_min, k_heng_min, k_shu_min);//左上交点为上方横线交左侧竖线
	p_zuoxia = linesIntersect(b_heng_max, b_shu_min, k_heng_max, k_shu_min);//同上类似
	p_youshang = linesIntersect(b_heng_min,b_shu_max, k_heng_min, k_shu_max );
	p_youxia = linesIntersect(b_heng_max, b_shu_max, k_heng_max, k_shu_max);
	//cout << p_zuoshang << endl << p_zuoxia << endl << p_youshang << endl << p_youxia<<endl;
	//cout << "*****" << endl;

              求得四个边框点为

        由原图像长宽为576*432,故原图像四个顶点的坐标为,由此可以得到透视前后的变化坐标,进而求取单应性变换矩阵并完成透视变换,代码如下:

vector<Point2f> ori_pts;//输入点坐标
	ori_pts.push_back(p_zuoshang);
	ori_pts.push_back(p_youshang);
	ori_pts.push_back(p_zuoxia);
	ori_pts.push_back(p_youxia);
	cout << ori_pts << endl;
	int dst_width = 432, dst_height = 576;
	vector<Point2f> dst_pts;//输出点坐标
	dst_pts.push_back(Point(0, 0));
	dst_pts.push_back(Point(dst_width - 1, 0));
	dst_pts.push_back(Point(0, dst_height - 1));
	dst_pts.push_back(Point(dst_width - 1, dst_height - 1));
	Mat H;//单应性变换矩阵
	Mat toushi;//输出图像
	H = findHomography(ori_pts, dst_pts);
	warpPerspective(yuan, toushi, H, yuan.size());
	imshow("终", toushi);

        总运行结果如下

下附部分图像操作函数完整源码

本代码仅供参考!!!请理解全文后自行敲打完成!!!

本代码仅供参考!!!请理解全文后自行敲打完成!!!

本代码仅供参考!!!请理解全文后自行敲打完成!!!

Mat version_lesson::pre_operate_image(Mat &image) {
	Mat gray,blur,canny, imgDil;
	cvtColor(image, gray, COLOR_BGR2GRAY);		//灰度
	//imshow("gray", gray);
	GaussianBlur(gray, blur, Size(3, 3), 3, 0);	//高斯模糊(参数四和五:sigmaX 和 sigmaY
	imshow("blur", blur);
	Canny(blur, canny, 25, 75);					//边缘检测(参数四和五:两个阈值
	//imshow("canny", canny);
	Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));	
	dilate(canny, imgDil, kernel);					//膨胀(参数三:膨胀内核
	//imshow("imgDil", imgDil);
	return canny;
}
Mat version_lesson::hough_P_line_detect(Mat &image,Mat &rgb,Mat &yuan) {
	vector<Vec4i> lines;
	HoughLinesP(image, lines, 1, CV_PI / 180.0, 80, 100, 10);
	for (int i = 0; i < lines.size(); ++i) {
		line(rgb, Point(lines[i][0], lines[i][1]), Point(lines[i][2], lines[i][3]), Scalar(0, 255, 0), 2, 8);
	}
	imshow("lines", rgb);
	vector<Vec4i> heng_lines,shu_lines;//定义横线、竖线类别
	for (int i = 0; i < lines.size(); ++i) {
		int x = lines[i][0] - lines[i][2];//x改变量
		int y = lines[i][1] - lines[i][3];//y改变量
		x = x * x;//防止出现负数,用平方比较
		y = y * y;
		//cout << x << endl << y << endl;
		if (x <y) 
			shu_lines.push_back(lines[i]);
		else 
			heng_lines.push_back(lines[i]);
	}
	if (shu_lines.size() < 2 || heng_lines.size() < 2) {// 确保水平线和垂直线至少有两条
		cout << "Not enough edge lines... " << endl;
	}
	int heng_center_y[10], shu_center_x[10];
	for (int i = 0; i < heng_lines.size(); ++i) //横线判断y坐标,这边中心点不用除以2也无所谓
		heng_center_y[i] = heng_lines[i][1] + heng_lines[i][3];
	int maxp_heng = max_element(heng_center_y, heng_center_y + heng_lines.size())- heng_center_y;
	int minp_heng = min_element(heng_center_y, heng_center_y + heng_lines.size()) - heng_center_y;
	//cout << "最大下标"<<maxp_heng << endl;
	//cout << "最小下标" << minp_heng << endl;
	for (int i = 0; i < shu_lines.size(); ++i) //竖线判断x坐标,这边中心点不用除以2也无所谓
		shu_center_x[i] = shu_lines[i][0] + shu_lines[i][2];
	int maxp_shu = max_element(shu_center_x, shu_center_x + shu_lines.size()) - shu_center_x;
	int minp_shu = min_element(shu_center_x, shu_center_x + shu_lines.size()) - shu_center_x;
	绘制最外端边框直线
	//line(rgb, Point(heng_lines[minp_heng][0], heng_lines[minp_heng][1]), Point(heng_lines[minp_heng][2], heng_lines[minp_heng][3]), Scalar(0, 255, 0), 2, 8);
	//line(rgb, Point(heng_lines[maxp_heng][0], heng_lines[maxp_heng][1]), Point(heng_lines[maxp_heng][2], heng_lines[maxp_heng][3]), Scalar(0, 255, 0), 2, 8);
	//line(rgb, Point(shu_lines[minp_shu][0], shu_lines[minp_shu][1]), Point(shu_lines[minp_shu][2], shu_lines[minp_shu][3]), Scalar(255, 0, 0), 2, 8);
	//line(rgb, Point(shu_lines[maxp_shu][0], shu_lines[maxp_shu][1]), Point(shu_lines[maxp_shu][2], shu_lines[maxp_shu][3]), Scalar(255, 0, 0), 2, 8);
	//imshow("rgb", rgb);
	//算四条直线斜率
	double k_heng_min, k_heng_max, k_shu_min, k_shu_max;
	k_heng_min = (double)(heng_lines[minp_heng][3] - heng_lines[minp_heng][1]) / (double)(heng_lines[minp_heng][2] - heng_lines[minp_heng][0]); 
	k_heng_max = (double)(heng_lines[maxp_heng][3] - heng_lines[maxp_heng][1]) / (double)(heng_lines[maxp_heng][2] - heng_lines[maxp_heng][0]);
	k_shu_min = (double)(shu_lines[minp_shu][3] - shu_lines[minp_shu][1]) / (double)(shu_lines[minp_shu][2] - shu_lines[minp_shu][0]);
	k_shu_max = (double)(shu_lines[maxp_shu][3] - shu_lines[maxp_shu][1]) / (double)(shu_lines[maxp_shu][2] - shu_lines[maxp_shu][0]);
	//算四条直线y=x+b的b
	double b_heng_min, b_heng_max, b_shu_min, b_shu_max;
	b_heng_min = -k_heng_min * (double)heng_lines[minp_heng][2] + (double)heng_lines[minp_heng][3];
	b_heng_max = -k_heng_max * (double)heng_lines[maxp_heng][2] +(double)heng_lines[maxp_heng][3];
	b_shu_min = -k_shu_min * (double)shu_lines[minp_shu][2] + (double)shu_lines[minp_shu][3];
	b_shu_max = -k_shu_max * (double)shu_lines[maxp_shu][2] + (double)shu_lines[maxp_shu][3];
	//求交点
	Point2f p_zuoshang, p_zuoxia, p_youshang, p_youxia;
	p_zuoshang = linesIntersect(b_heng_min, b_shu_min, k_heng_min, k_shu_min);//左上交点为上方横线交左侧竖线
	p_zuoxia = linesIntersect(b_heng_max, b_shu_min, k_heng_max, k_shu_min);//同上类似
	p_youshang = linesIntersect(b_heng_min,b_shu_max, k_heng_min, k_shu_max );
	p_youxia = linesIntersect(b_heng_max, b_shu_max, k_heng_max, k_shu_max);
	//cout << p_zuoshang << endl << p_zuoxia << endl << p_youshang << endl << p_youxia<<endl;
	//cout << "*****" << endl;
	vector<Point2f> ori_pts;//输入点坐标
	ori_pts.push_back(p_zuoshang);
	ori_pts.push_back(p_youshang);
	ori_pts.push_back(p_zuoxia);
	ori_pts.push_back(p_youxia);
	cout << ori_pts << endl;
	int dst_width = 432, dst_height = 576;
	vector<Point2f> dst_pts;//输出点坐标
	dst_pts.push_back(Point(0, 0));
	dst_pts.push_back(Point(dst_width - 1, 0));
	dst_pts.push_back(Point(0, dst_height - 1));
	dst_pts.push_back(Point(dst_width - 1, dst_height - 1));
	Mat H;//单应性变换矩阵
	Mat toushi;//输出图像
	H = findHomography(ori_pts, dst_pts);
	warpPerspective(yuan, toushi, H, yuan.size());
	imshow("终", toushi);

	return rgb;
}
#include<opencv2\opencv.hpp>
#include <version_lesson.h>
#include<quickopencv.h>
#include<iostream>
using namespace std;
using namespace cv;

int main(int argc, char **argv)
{
	version_lesson vl;
	QuickDemo qd;
	Mat src1;
	Mat src = imread("D:/Open CV/picture/version_lesson3.jpg");//version_lesson3.jpg
	src = qd.resize_demo(src);//图像过大缩小8倍
	imshow("原图", src);
	src1 = vl.pre_operate_image(src);
	src1 = vl.hough_P_line_detect(src1,src,src);
	//src1 = vl.find_contours_MAX(src1, src);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

  • 3
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: OpenCV C++官方文档OpenCV的官方教程和参考文档。在使用OpenCV进行计算机视觉和图像处理时,OpenCV C++官方文档是非常重要的学习资源。OpenCV C++官方文档覆盖了从基础操作到高级算法的广泛范围。它详细介绍了各种OpenCV函数和类的用法和参数,并提供了大量的示例代码和应用案例。OpenCV C++官方文档还包括了对5.3.0版本的介绍和对于该版本的更新内容的说明。此外,OpenCV C++官方文档还包括了对于C API、Python API等其他API的介绍和说明。如果你对OpenCV C++的使用不熟悉,我建议你先认真阅读OpenCV C++官方文档,理清楚每个函数的作用及其输入输出参数的含义。学习OpenCV不是容易的事情,仅有通过大量的实践和使用才能掌握,而OpenCV C++官方文档则是我们行走在探索计算机视觉领域的道路上的一盏明灯,立足OpenCV C++官方文档,我们才能利用OpenCV实现我们所期望的功能。 ### 回答2: OpenCV是一种计算机视觉库,它提供了大量函数和算法,可以用于图像和视频处理、特征检测、目标识别、图像分割等任务。OpenCV官方文档包括了OpenCV的核心模块、图像模块、视频模块、静态图像和视频处理模块以及计算机视觉模块等,文档详细解释每个函数和算法的用法和实际应用,对于学习和使用OpenCV非常有帮助。在官方文档中,用户可以找到大量使用OpenCV的示例程序和演示视频,方便快速掌握OpenCV的各种应用。此外,OpenCV官方文档还提供了丰富的参考资料,如编程手册、API文档、示例代码、教程等,帮助开发者快速入手OpenCV的使用。总之,OpenCV官方文档是学习和掌握OpenCV的不可缺少的工具。 ### 回答3: OpenCV(Open Source Computer Vision Library)是一个开源计算机视觉库,具有丰富的图像处理和计算机视觉算法,如图像处理、特征提取、目标检测、图像分割、摄像机标定等。官方文档涵盖了OpenCV的各个方面,从安装和配置开始,介绍了OpenCV的数据类型、图像处理函数、计算机视觉算法等内容,同时也提供了大量示例和代码,方便开发者快速了解和使用。 在官方文档中,首先介绍了OpenCV的基本概念,包括编译安装、核心数据结构、图像读取和显示等。然后深入讲解了各种图像处理函数,例如像素操作、图像变换、图像滤波、形态学操作等,并且详细介绍了各种计算机视觉算法,包括特征提取、目标检测、人脸识别等,这些算法可以在图像和视频处理中得到广泛应用。 此外,官方文档还提供了完整的参考文档,包括API文档C++参考手册、Python参考手册等,同时也提供了各种平台和语言的支持,如Windows、Linux、Mac以及C、C++、Python等语言。这样,开发者可以根据自己的需求和开发环境方便地使用OpenCV,并快速实现图像处理和计算机视觉的功能。 总之,OpenCV官方文档提供了丰富全面的图像处理和计算机视觉知识,无论是初学者还是经验丰富的开发者,都可以从中获益,并快速实现自己的项目。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值