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

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

        原要求作业如下所示

利用opencv c++实现文档扫描/文档矫正功能_第1张图片

利用opencv c++实现文档扫描/文档矫正功能_第2张图片

        

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

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

读入图片如下,

利用opencv c++实现文档扫描/文档矫正功能_第3张图片

        该图片为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;
}

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

利用opencv c++实现文档扫描/文档矫正功能_第4张图片 利用opencv c++实现文档扫描/文档矫正功能_第5张图片利用opencv c++实现文档扫描/文档矫正功能_第6张图片利用opencv c++实现文档扫描/文档矫正功能_第7张图片利用opencv c++实现文档扫描/文档矫正功能_第8张图片

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

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

	vector 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);

利用opencv c++实现文档扫描/文档矫正功能_第9张图片

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

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

利用opencv c++实现文档扫描/文档矫正功能_第10张图片利用opencv c++实现文档扫描/文档矫正功能_第11张图片

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

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

vector 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 

       对分类的竖线、横线进行排序,并找到边框直线。竖线判断中心点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 << "最大下标"<

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

	绘制最外端边框直线
	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);

利用opencv c++实现文档扫描/文档矫正功能_第12张图片

求单应性矩阵

        求两点交点用到公式

        求两点关系用到的代码如下,这边要注意图像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<

              求得四个边框点为

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

vector 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 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);

        总运行结果如下

利用opencv c++实现文档扫描/文档矫正功能_第13张图片

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

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

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

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

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 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 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  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 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
#include 
#include
#include
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;
}

你可能感兴趣的:(c++,opencv)