在之前的博文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; }
未完待续。。。