1. 程式人生 > >OpenCV實踐之路——opencv玩數獨之二九宮格小方格的提取和數字的提取

OpenCV實踐之路——opencv玩數獨之二九宮格小方格的提取和數字的提取

本文由@星沉閣冰不語出品,轉載請註明作者和出處。

在之前的博文OpenCV實踐之路——opencv玩數獨之一九宮格輪廓提取與透視變換中,已經實現了九宮格最外圍矩形輪廓的提取,並利用透視變換把矩形擺正。今天接著上一篇的內容,在擺正後的矩形中檢測並提取出九九八十一個小方格,並提取出含有數字的小方格中的數字。用的方法仍然是之前提到的輪廓提取,然後對輪廓進行多邊形逼近,最後利用多邊形的面積和頂點等資訊對輪廓進行篩選。

其中最耗時間的地方仍然是閾值化的時候引數的調整。而且不具有通用型,換一幅圖片引數就得重新調整。這種方式效率比較低,但是以我目前的水平來說暫時沒有好的代替方案,只能先用這種方式。我現在就想先把完整的功能實現了之後再想著去優化。希望以後能研究出一種自適應的方法。有人知道的話希望能告知一聲。

如果說有什麼是值得注意的話,那就是對於輪廓提取,有時候引數調整進入死衚衕的時候,善於利用形態學開閉運算也許會有不錯的效果。我就是在調參上耗了大半天時間之後,加了膨脹操作才最終完全提取到八十一個小方格的。

數字外圍輪廓提取的時候,最後有一個不需要的矩形剔除不掉。被逼無奈我只能找出這個矩形的面積,專門用if語句排除這個頑固分子。真實無奈,等我完全實現之後看能不能做出優化吧。

效果如下:


下面是程式碼,完整的:

#include<opencv.hpp>
#include<iostream>

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<float>(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<vector<Point> > contours0;
	vector<Vec4i> hierarchy;
	findContours(thresh, contours0, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, Point());


	//多邊形逼近
	vector<vector<Point> > 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<Point2f> 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<Point2f> 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);

	Mat wrap_gray;
	cvtColor(out, wrap_gray, CV_BGR2GRAY);
	
	//提取數獨每一個小方格
	Mat thresh1,thresh1_clone;
	adaptiveThreshold(wrap_gray, thresh1,255, 
		ADAPTIVE_THRESH_GAUSSIAN_C,
		THRESH_BINARY_INV, 155,9);//最後兩個引數難調

	thresh1_clone = thresh1.clone();

	//多用幾次膨脹更容易提取到所有小方格輪廓
	Mat element1 = getStructuringElement(MORPH_RECT, Size(2, 2));
	//erode(thresh1, thresh1, element);
	dilate(thresh1, thresh1, element1);
	dilate(thresh1, thresh1, element1);
	dilate(thresh1, thresh1, element1);

	//輪廓提取
	vector<vector<Point> > contours1;
	vector<Vec4i> hierarchy1;
	findContours(thresh1, contours1, hierarchy1, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point());

	vector<vector<Point> > contours2;
	contours2.resize(contours1.size());

	//多邊形逼近
	for (int i = 0; i < contours1.size(); i++)
	{
		approxPolyDP(contours1[i], contours2[i], 15, true);
	}

	//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;
	//	}
	//}

	Mat out1 = out.clone();
	int j = 0;
	//畫出所有小方格的輪廓和最小外接矩形
	for (int i = 0; i < contours1.size(); i++)
	{
		if (contours2[i].size() == 4 && contourArea(contours2[i])>1300 && contourArea(contours2[i]) < 55000)
		{
			//畫出輪廓
			Scalar color(0, 255, 0);
			//drawContours(out, contours2, i, color, 4, 8);

			//最小外接矩形
			RotatedRect smallRect = minAreaRect(contours2[i]);
			Rect rect = smallRect.boundingRect();
			Rect rect2 = rect;
			
			//畫出每個小方框的矩形
			rectangle(out, rect, (0, 0, 255), 2, 8, 0);

			Mat roi = out1(rect);
			//int roi_center_x = rect.x + rect.width / 2;
			//int roi_center_y = rect.y + rect.height / 2;

			//int distance;
			//for (int i = 0; i < rect.width; i++)
			//{
			//	for (int j = 0; j < rect.height; j++)
			//	{
			//		distance = sqrt((i - roi_center_x)*(i - roi_center_x) + (j - roi_center_y)*(j - roi_center_y));
			//		if (distance > 15)
			//			roi.at<uchar>(Point(i, j)) = 0;
			//	}
			//}

			Mat roi_gray;
			cvtColor(roi, roi_gray, 6);

			Mat roi_thresh;
			adaptiveThreshold(roi_gray, roi_thresh, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY_INV, 77, 15);

			Mat element1 = getStructuringElement(MORPH_RECT, Size(3, 3));
			erode(thresh1, thresh1, element);
			erode(thresh1, thresh1, element);
			erode(thresh1, thresh1, element);
			erode(thresh1, thresh1, element);			

			//輪廓提取
			vector<vector<Point> > roi_contours;
			vector<Vec4i> roi_hierarchy;
			findContours(roi_thresh, roi_contours, roi_hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, Point());

			vector<vector<Point> > roi_contours2;
			roi_contours2.resize(roi_contours.size());

			//多邊形逼近
			for (int i = 0; i < roi_contours.size(); i++)
			{
				approxPolyDP(roi_contours[i], roi_contours2[i], 1, true);
			}

			Rect num_rect,rect_12;
			
			//畫出數字外接矩形
			for (int i = 0; i < roi_contours.size(); i++)
			{
				//&& rect.contains(Point(num_rect.x, num_rect.y))
				//rect_12 = num_rect & rect;
				num_rect = boundingRect(roi_contours[i]);
				if (num_rect.area() > 350 && num_rect.area()!=378)//沒辦法,只能這樣排除多餘的那個小矩形了
				{
					rectangle(roi, num_rect, Scalar(0, 0, 255), 1, 8, 0);
					j = j + 1;
					//cout << num_rect.area() << endl;
				}
				cout << j << endl;				
			}

		imshow("roi", roi);
		imshow("src", out);
		char c = waitKey(100);
		}
		//imshow("thresh", thresh);
		//imshow("src", out);
	}
	while (uchar(waitKey()) == 'q') { }
	return 0;
}

至今為止,已經實現了數字的提取。但是下一步如何進行還沒有頭緒。因為八十一個小方格提取的時候並不是按照完全按照一定的順序出來的,這就讓人很難辦。如何識別數字,如何解數獨不是當下要解決的問題。當務之急,是要解決如何把提取出來的數字按照原本九宮格的順序排好?又到了補充知識的時候了。

未完待續。。。

最近開通了微信公眾號,感興趣的同學可以掃碼在微信上交流。