本文由@星沉阁冰不语出品,转载请注明作者和出处。
文章链接:http://blog.csdn.net/xingchenbingbuyu/article/details/50801275
微博:http://weibo.com/xingchenbing
在之前的博文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;
}
至今为止,已经实现了数字的提取。但是下一步如何进行还没有头绪。因为八十一个小方格提取的时候并不是按照完全按照一定的顺序出来的,这就让人很难办。如何识别数字,如何解数独不是当下要解决的问题。当务之急,是要解决如何把提取出来的数字按照原本九宫格的顺序排好?又到了补充知识的时候了。
未完待续。。。
最近开通了微信公众号,感兴趣的同学可以扫码在微信上交流。