OpenCV实践之路——opencv玩数独之一九宫格轮廓提取与透视变换

本文由@星沉阁冰不语出品,转载请注明作者和出处。

文章链接:http://blog.csdn.net/xingchenbingbuyu/article/details/50783585

微博:http://weibo.com/xingchenbing 


本文部分参考自如下链接:Sudoku-recognizer。前几天发现了这个网页,觉得挺好玩的,就想自己实现一下。本以为只是把代码从Python转换到C++是一件很简单的事情,经过这几天的努力发现是自己想的太简单了。到现在也没有完全实现。前面的一小半可以说是参考了上述文章,所做的只是把opencv的代码转换下语言风格,但是到了后面随着Python语言用的越来越多,很多地方已经完全看不懂了,只能自己实现。而且随着深入,自己的想法也越来越多。所以现在几乎已经跳出上文的框架,开始自己去查资料查文档按照自己的思路实现了。但是考虑到完全实现耗时过长而且不确定什么时候能调试完成,加之如果等一切搞定再来记录将会是一篇十分冗长的博文,于是就这样一边实现一边记录吧。先从最基础的开始。

还是先说要实现什么样的效果吧。给一张带有九宫格数独的照片,检测出其中的九宫格,然后取出来做透视变换。看图的画会更加一目了然。

OpenCV实践之路——opencv玩数独之一九宫格轮廓提取与透视变换_第1张图片

OpenCV实践之路——opencv玩数独之一九宫格轮廓提取与透视变换_第2张图片

OpenCV实践之路——opencv玩数独之一九宫格轮廓提取与透视变换_第3张图片


主要思路:

一、高斯滤波去掉部分噪音,拉普拉斯锐化增强轮廓以便于检测提取;自适应阈值化得到二值图像;

二、轮廓检测,多边形逼近,多边形筛选;

三、根据筛选出的四边形的四个顶点进行透视变换(顶点顺序很重要);

这里面需要注意的细节有以下几点:

一、自适应阈值化的时候最后两个参数的调整特别重要,这两个参数决定着能不能快速检测到需要的轮廓。如果找不到,就继续调整这俩值。

二、findContours()查找轮廓时,方法选取CV_CHAIN_APPROX_SIMPLE,这种方法只是存储轮廓的顶点,后面透视变换时用到的顶点就是从这里面提出来的。

三、轮廓筛选的时候可以用上述顶点数来选出四边形,然后根据面积筛选出目标四边形。

四、透视变换的时候顶点的对应顺序要正确,最好按照左上,右上,左下,右下的顺序。变换前后都是这个顺序。

五、获取透视变换矩阵的时候用到的点的格式应为Point2f,变换矩阵是一个3*3的矩阵,我得到的变换矩阵如下:


                                                                                                               OpenCV实践之路——opencv玩数独之一九宫格轮廓提取与透视变换_第4张图片


这一部分主要代码如下:

#include
#include

using namespace std;
using namespace cv;

int main()
{
	Mat src = imread("shudu.jpg");

	GaussianBlur(src, src, Size(3, 3), 0, 0);

	//拉普拉斯锐化
	Mat kernel(3, 3, CV_32F, Scalar(-1));
	kernel.at(1, 1) = 8.9;
	filter2D(src, src, src.depth(), kernel);

	Mat gray, thresh;
	cvtColor(src, gray, CV_BGR2GRAY);

	//namedWindow("thresh",0);

	//轮廓提取
	adaptiveThreshold(gray, thresh, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY_INV,77,15);

	Mat element = getStructuringElement(MORPH_RECT, Size(3, 3));
	erode(thresh, thresh, element);
	dilate(thresh, thresh, element);

	vector > contours0;
	vector hierarchy;
	findContours(thresh, contours0, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, Point());


	//多边形逼近
	vector > contours;
	contours.resize(contours0.size());

	for (int i = 0; i < contours0.size(); i++)
	{
		approxPolyDP(contours0[i], contours[i], 55, true);//15是为了得到一个矩形,小于15的数回得到更多的点
	}

	//选出最大面积的多边形
	double area = 0;
	int index=0;
	for (int i = 0; i < contours.size(); i++)
	{
		if (contourArea(contours[i])>area)
		{
			area = contourArea(contours[i]);
			index = i;
		}
	}
	
	//最外围轮廓的显示
	if (contourArea(contours[index])>50000)
	{
		Scalar color(0, 0, 255);
		drawContours(src, contours, index, color, 4, 8);
	}


	//cout << "contours[index]: " << endl << contours[index] << endl;

	//最外围轮廓顶点的显示
	//for (int i = 0; i < contours[index].size(); i++)
	//{
	//	circle(src, contours[index][i], 15, (0,0,255), 2, 8, 0);
	//}
	

	//透视变换,顶点的顺序很重要!
	vector corner;//上面提取轮廓的顶点
	corner.push_back(Point(83, 80));
	corner.push_back(Point(652, 61));
	corner.push_back(Point(13, 548));	
	corner.push_back(Point(798, 495));

	vector PerspectiveTransform;//透视变换后的顶点
	RotatedRect box = minAreaRect(cv::Mat(contours[index]));
	PerspectiveTransform.push_back(Point(0, 0));
	PerspectiveTransform.push_back(Point(box.boundingRect().width - 1, 0));
	PerspectiveTransform.push_back(Point(0, box.boundingRect().height - 1));
	PerspectiveTransform.push_back(Point(box.boundingRect().width - 1, box.boundingRect().height - 1));
	 
	//cout << "corner: " << endl << corner << endl;

	//获取变换矩阵
	Mat M = getPerspectiveTransform(corner, PerspectiveTransform);//Order of points matters!
	
	//cout << "PerspectiveTransform: " << endl << PerspectiveTransform << endl;

	Mat out;//提取出的数独方框
	cv::Size size(box.boundingRect().width, box.boundingRect().height);
	warpPerspective(src, out, M,size, 1, 0, 0);

	imshow("src",src);
	imshow("out",out);
	while (uchar(waitKey()) == 'q') { }
	return 0;
}

未完待续......


最近开通了微信公众号,感兴趣的同学可以扫码在微信上交流。





你可能感兴趣的:(Visual,Studio,C++,OpenCV,OpenCV实践之路)