Mat image = imread("D:/picture/images/binaryGroup.bmp",0); if(!image.data) return -1; imshow("源映像",image); //擷取輪廓 std::vector<std::vector<Point>> contours; //擷取輪廓: findContours(image, //映像 contours, //輪廓點 //包含映像拓撲結構的資訊(選擇性參數,這裡沒有選) CV_RETR_EXTERNAL, //擷取輪廓的方法(這裡擷取外圍輪廓) CV_CHAIN_APPROX_NONE); //輪廓近似的方法(這裡不近似,擷取全部輪廓) //列印輪廓資訊 std::cout<<"共有外圍輪廓:"<<contours.size()<<"條"<<std::endl; std::vector<std::vector<Point>>::const_iterator itContours = contours.begin(); for(;itContours != contours.end();++itContours) { std::cout<<"每個輪廓的長度: "<<itContours->size()<<std::endl; }
注意到輪廓的儲存格式為std::vector<std::vector<Point>>,他說明整個輪廓是若干條輪廓按一定順序組成的,而每個輪廓中的點也是有順序的。
畫出輪廓就比較簡單了:
//畫出輪廓 Mat result(image.size(),CV_8U,Scalar(255)); //畫出輪廓,參數為:畫板,輪廓,輪廓指示(這裡畫出所有輪廓),顏色,線粗 drawContours(result,contours,-1,Scalar(0),2); imshow("提取外圍輪廓",result);
還要注意提取輪廓的方法還有很多種,比如CV_RETR_LIST代表所有輪廓
findContours(image, //映像 contours, //輪廓點 //包含映像拓撲結構的資訊(選擇性參數,這裡沒有選) CV_RETR_LIST, //擷取輪廓的方法(這裡擷取所有輪廓) CV_CHAIN_APPROX_NONE); //輪廓近似的方法(這裡不近似,擷取全部輪廓 //畫出輪廓 drawContours(result,contours,-1,Scalar(0),2); imshow("提取所有輪廓",result);
通常,這樣提取的輪廓包含一些我們不希望的輪廓(比如一些小洞),或者假如我們知道我們感興趣的物體輪廓的大概範圍時,我們就可以用下面的辦法縮小目標範圍:
//除去太長或者太短的輪廓int cmin = 100;int cmax = 1000;std::vector<std::vector<Point>>::const_iterator itc = contours.begin();while(itc != contours.end()){if(itc->size() < cmin || itc->size() > cmax)itc = contours.erase(itc);else++itc;}//把結果畫在源映像上:Mat original = imread("D:/picture/images/group.jpg");if(!original.data)return -1;drawContours(original,contours,-1,Scalar(255,255,255),2);imshow("動物的輪廓",original);//將輪廓重繪於白板上result.setTo(Scalar(255));drawContours(result,contours,-1,Scalar(0),1);
怎麼提取輪廓的特徵呢?OpenCV提供了很多函數,我們展示其中的幾個:
//輪廓的形狀描述子 //外接矩形 Rect r0 = boundingRect(Mat(contours[0])); rectangle(result,r0,Scalar(0),2); //最小外接圓 float radius; Point2f center; minEnclosingCircle(Mat(contours[1]),center,radius); circle(result,Point(center),static_cast<int>(radius),Scalar(0),2); //多邊形估計 std::vector<Point> poly; //參數為:輸入映像的2維點集,輸出結果,估計精度,是否閉合 approxPolyDP(Mat(contours[2]),poly,5,true); std::cout<<"多邊形大小:"<<poly.size()<<std::endl; //畫出結果 std::vector<Point>::const_iterator itp = poly.begin(); while(itp != poly.end()-1) { line(result,*itp,*(itp+1),Scalar(0),2); ++itp; } //將第一個點和最後一點連起來 line(result,*(poly.begin()),*(poly.end()-1),Scalar(128),2); //計算凸包 std::vector<Point> hull; convexHull(Mat(contours[3]),hull); std::vector<cv::Point>::const_iterator it= hull.begin(); while(it != (hull.end()-1)) { line(result,*it,*(it+1),Scalar(0),2); ++it; } line(result,*(hull.begin()),*(hull.end()-1),Scalar(0),2); //計算矩資訊 itc = contours.begin(); while(itc != contours.end()) { //計算所有的距 Moments mom = moments(Mat(*itc++)); //計算並畫出質心 circle(result,Point(mom.m10/mom.m00,mom.m01/mom.m00),2,Scalar(2),2); } imshow("形狀描述子",result);
我們再次看到,輪廓的確是有順序的。值得注意的是矩資訊:OpenCV提供了一個結構體Moments,它的元素就是計算好的矩資訊,裡面存放了常用的距。
其實,OpenCV還提供了許多其他的形狀描述子,比如函數cv::minAreaRect計算了最小外界傾斜的矩形。函數cv::contourArea估計輪廓地區的面積(裡面的像素數)。函數cv::pointPolygonTest計算一個點是否在輪廓內,cv::matchShapes測量了2兩個輪廓的相似程度等等。