我使用過FindContours,而且知道有能夠直接尋找聯通區域的函數。但是我使用的大多只是“最大輪廓”或者"輪廓數目“這些數據。其實輪廓還有另一個很重要的性質,那就是輪廓的相互包含特性。
,這種特性,在圖上只有三處,而且在自然圖片中是不容易重復的(當然二維碼內部還有校驗機制)。那么基於此就能夠很容易地獲取准確定位。將二維碼的這種機制進行擴展,能夠得到對實際物體的定位方法。比如如果我將筆記本的內頁設計成這樣(當然也可以是答題卡之類),那么就能夠為准確的定位、透視變換打下好的基礎。
Mat src = imread("e:/sandbox/contours.png",0);
threshold(src,src,100,255,THRESH_OTSU);
vector<vector<Point> > contours,contours2;
vector<Vec4i> hierarchy;
findContours( src, contours, hierarchy, CV_RETR_TREE, CHAIN_APPROX_NONE, Point(0, 0) );
waitKey();
}
enum
{
CV_RETR_EXTERNAL=0,
CV_RETR_LIST=1,
CV_RETR_CCOMP=2,
CV_RETR_TREE=3,
CV_RETR_FLOODFILL=4
};
enum { CHAIN_APPROX_NONE = 1,
CHAIN_APPROX_SIMPLE = 2,
CHAIN_APPROX_TC89_L1 = 3,
CHAIN_APPROX_TC89_KCOS = 4
}
Here, contours 0,1,2 are external or outermost. We can say, they are in hierarchy-0 or simply they are in same hierarchy level.
Next comes contour-2a. It can be considered as a child of contour-2 (or in opposite way, contour-2 is parent of contour-2a). So let it be in hierarchy-1. Similarly contour-3 is child of contour-2 and it comes in next hierarchy. Finally contours 4,5 are the children of contour-3a, and they come in the last hierarchy level. From the way I numbered the boxes, I would say contour-4 is the first child of contour-3a (It can be contour-5 also).
I mentioned these things to understand terms like same hierarchy level, external contour, child contour, parent contour, first child etc. Now let's get into OpenCV.
Hierarchy Representation in OpenCV (層級關系)
So each contour has its own information regarding what hierarchy it is, who is its child, who is its parent etc. OpenCV represents it as an array of four values : **[Next, Previous, First_Child, Parent]**
For eg, take contour-0 in our picture. Who is next contour in its same level ? It is contour-1. So simply put Next = 1. Similarly for Contour-1, next is contour-2. So Next = 2.
What about contour-2? There is no next contour in the same level. So simply, put Next = -1. What about contour-4? It is in same level with contour-5. So its next contour is contour-5, so Next = 5.
It is same as above. Previous contour of contour-1 is contour-0 in the same level. Similarly for contour-2, it is contour-1. And for contour-0, there is no previous, so put it as -1.
There is no need of any explanation. For contour-2, child is contour-2a. So it gets the corresponding index value of contour-2a. What about contour-3a? It has two children. But we take only first child. And it is contour-4. So First_Child = 4 for contour-3a.
It is just opposite of First_Child. Both for contour-4 and contour-5, parent contour is contour-3a. For contour-3a, it is contour-3 and so on.
- Note
- If there is no child or parent, that field is taken as -1
4. RETR_TREE
And this is the final guy, Mr.Perfect. It retrieves all the contours and creates a full family hierarchy list. It even tells, who is the grandpa, father, son, grandson and even beyond... :).
For examle, I took above image, rewrite the code for cv2.RETR_TREE, reorder the contours as per the result given by OpenCV and analyze it. Again, red letters give the contour number and green letters give the hierarchy order.
Take contour-0 : It is in hierarchy-0. Next contour in same hierarchy is contour-7. No previous contours. Child is contour-1. And no parent. So array is [7,-1,1,-1].
Take contour-2 : It is in hierarchy-1. No contour in same level. No previous one. Child is contour-2. Parent is contour-0. So array is [-1,-1,2,0].
And remaining, try yourself. Below is the full answer:
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
using namespace cv;
using namespace std;
//找到所提取輪廓的中心點
//在提取的中心小正方形的邊界上每隔周長個像素提取一個點的坐標,求所提取四個點的平均坐標(即為小正方形的大致中心)
Point Center_cal(vector<vector<Point> > contours,int i)
{
int centerx=0,centery=0,n=contours[i].size();
centerx = (contours[i][n/4].x + contours[i][n*2/4].x + contours[i][3*n/4].x + contours[i][n-1].x)/4;
centery = (contours[i][n/4].y + contours[i][n*2/4].y + contours[i][3*n/4].y + contours[i][n-1].y)/4;
Point point1=Point(centerx,centery);
return point1;
}
int main( int argc, char** argv[] )
{
Mat src = imread( "e:/sandbox/qrcode.jpg", 1 );
resize(src,src,Size(800,600));//標准大小
Mat src_gray;
Mat src_all=src.clone();
Mat threshold_output;
vector<vector<Point> > contours,contours2;
vector<Vec4i> hierarchy;
//預處理
cvtColor( src, src_gray, CV_BGR2GRAY );
blur( src_gray, src_gray, Size(3,3) ); //模糊,去除毛刺
threshold( src_gray, threshold_output, 100, 255, THRESH_OTSU );
//尋找輪廓
//第一個參數是輸入圖像 2值化的
//第二個參數是內存存儲器,FindContours找到的輪廓放到內存里面。
//第三個參數是層級,**[Next, Previous, First_Child, Parent]** 的vector
//第四個參數是類型,采用樹結構
//第五個參數是節點擬合模式,這里是全部尋找
findContours( threshold_output, contours, hierarchy, CV_RETR_TREE, CHAIN_APPROX_NONE, Point(0, 0) );
//輪廓篩選
int c=0,ic=0,area=0;
int parentIdx=-1;
for( int i = 0; i< contours.size(); i++ )
{
//hierarchy[i][2] != -1 表示不是最外面的輪廓
if (hierarchy[i][2] != -1 && ic==0)
{
parentIdx = i;
ic++;
}
else if (hierarchy[i][2] != -1)
{
ic++;
}
//最外面的清0
else if(hierarchy[i][2] == -1)
{
ic = 0;
parentIdx = -1;
}
//找到定位點信息
if ( ic >= 2)
{
contours2.push_back(contours[parentIdx]);
ic = 0;
parentIdx = -1;
}
}
//填充定位點
for(int i=0; i<contours2.size(); i++)
drawContours( src_all, contours2, i, CV_RGB(0,255,0) , -1 );
//連接定位點
Point point[3];
for(int i=0; i<contours2.size(); i++)
{
point[i] = Center_cal( contours2, i );
}
line(src_all,point[0],point[1],Scalar(0,0,255),2);
line(src_all,point[1],point[2],Scalar(0,0,255),2);
line(src_all,point[0],point[2],Scalar(0,0,255),2);
imshow( "結果", src_all );
waitKey(0);
return(0);
}

