OpenCV實踐之路——opencv玩數獨之一九宮格輪廓提取與透視變換
本文由@星沉閣冰不語出品,轉載請註明作者和出處。
本文部分參考自如下連結:Sudoku-recognizer。前幾天發現了這個網頁,覺得挺好玩的,就想自己實現一下。本以為只是把程式碼從Python轉換到C++是一件很簡單的事情,經過這幾天的努力發現是自己想的太簡單了。到現在也沒有完全實現。前面的一小半可以說是參考了上述文章,所做的只是把opencv的程式碼轉換下語言風格,但是到了後面隨著Python語言用的越來越多,很多地方已經完全看不懂了,只能自己實現。而且隨著深入,自己的想法也越來越多。所以現在幾乎已經跳出上文的框架,開始自己去查資料查文件按照自己的思路實現了。但是考慮到完全實現耗時過長而且不確定什麼時候能除錯完成,加之如果等一切搞定再來記錄將會是一篇十分冗長的博文,於是就這樣一邊實現一邊記錄吧。先從最基礎的開始。
還是先說要實現什麼樣的效果吧。給一張帶有九宮格數獨的照片,檢測出其中的九宮格,然後取出來做透視變換。看圖的畫會更加一目瞭然。
主要思路:
一、高斯濾波去掉部分噪音,拉普拉斯銳化增強輪廓以便於檢測提取;自適應閾值化得到二值影象;
二、輪廓檢測,多邊形逼近,多邊形篩選;
三、根據篩選出的四邊形的四個頂點進行透視變換(頂點順序很重要);
這裡面需要注意的細節有以下幾點:
一、自適應閾值化的時候最後兩個引數的調整特別重要,這兩個引數決定著能不能快速檢測到需要的輪廓。如果找不到,就繼續調整這倆值。
二、findContours()查詢輪廓時,方法選取CV_CHAIN_APPROX_SIMPLE,這種方法只是儲存輪廓的頂點,後面透視變換時用到的頂點就是從這裡面提出來的。
三、輪廓篩選的時候可以用上述頂點數來選出四邊形,然後根據面積篩選出目標四邊形。
四、透視變換的時候頂點的對應順序要正確,最好按照左上,右上,左下,右下的順序。變換前後都是這個順序。
五、獲取透視變換矩陣的時候用到的點的格式應為Point2f,變換矩陣是一個3*3的矩陣,我得到的變換矩陣如下:
這一部分主要程式碼如下:
#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); <span style="white-space:pre"> </span>imshow("src",src);
<span style="white-space:pre"> </span>imshow("out",out);
while (uchar(waitKey()) == 'q') { }
return 0;
}
未完待續......
最近開通了微信公眾號,感興趣的同學可以掃碼在微信上交流。