讀入彩色3通道圖像,轉換成灰度圖像,再轉換成二值圖像,完後檢測輪廓。html
// cvtcolor.cpp : 定義控制檯應用程序的入口點。 // #include "stdafx.h" #include <iostream> #include <opencv2/highgui/highgui.hpp> #include <opencv2/core/core.hpp> #include <opencv2/imgproc/imgproc.hpp> #pragma comment(lib, "opencv_highgui2410d.lib") #pragma comment(lib, "opencv_core2410d.lib") #pragma comment(lib, "opencv_imgproc2410d.lib") using namespace cv; using namespace std; int main() { string image_name = "swan.jpg"; Mat src = imread(image_name); imshow("src",src); Mat gray(src.size(),CV_8U); cvtColor(src,gray,CV_BGR2GRAY);//轉換成灰度圖 imshow("gray",gray); threshold(gray,gray,128,255,THRESH_BINARY);//轉換成2值圖像 imshow("binary",gray); / std::vector<std::vector<cv::Point>> contours; cv::findContours(gray, contours, // a vector of contours CV_RETR_EXTERNAL, // retrieve the external contours CV_CHAIN_APPROX_NONE); // retrieve all pixels of each contours // Print contours' length std::cout << "Contours: " << contours.size() << std::endl; std::vector<std::vector<cv::Point>>::const_iterator itContours= contours.begin(); for ( ; itContours!=contours.end(); ++itContours) { std::cout << "Size: " << itContours->size() << std::endl; } // draw black contours on white image cv::Mat result(gray.size(),CV_8U,cv::Scalar(255)); cv::drawContours(result,contours, -1, // draw all contours cv::Scalar(0), // in black 2); // with a thickness of 2 cv::namedWindow("Contours"); cv::imshow("Contours",result); // draw contours on the original image cv::Mat original= cv::imread(image_name); cv::drawContours(original,contours, -1, // draw all contours cv::Scalar(255,255,255), // in white -1); // with a thickness of 2 cv::namedWindow("Contours on Animals"); cv::imshow("Contours on Animals",original); // Let's now draw black contours on white image result.setTo(cv::Scalar(255)); cv::drawContours(result,contours, -1, // draw all contours cv::Scalar(0), // in black -1); // with a thickness of 1 //image= cv::imread("test.png",0); waitKey(0); return 0; }
實現效果:ios
添加代碼只顯示不大不小的輪廓:c++
//除去太長或者過短的輪廓 int cmin = 10; int cmax = 500000; vector<std::vector<cv::Point>>::iterator itc = contours.begin(); while(itc != contours.end()) { if(itc->size() < cmin || itc->size() > cmax) itc = contours.erase(itc); else ++itc; }
其餘相關的一些說明:算法
http://blog.sina.com.cn/s/blog_8fc98fe501017ypb.html數組
先看提取輪廓的代碼:app
- Mat
image = imread("D:/picture/images/binaryGroup.bmp",0); - if(!image.data)
-
return -1; - imshow("源圖像",image);
-
- //獲取輪廓
- std::vector>
contours; - //獲取輪廓:
- findContours(image,
//圖像 -
contours, //輪廓點 -
//包含圖像拓撲結構的信息(可選參數,這裏沒有選) -
CV_RETR_EXTERNAL, //獲取輪廓的方法(這裏獲取外圍輪廓) -
CV_CHAIN_APPROX_NONE); //輪廓近似的方法(這裏不近似,獲取所有輪廓) - //打印輪廓信息
- std::cout<<"共有外圍輪廓:"<<contours.size()<<"條"<<std::endl;
- std::vector>::const_iterator
itContours = contours.begin(); - for(;itContours
!= contours.end();++itContours) - {
-
std::cout<<"每一個輪廓的長度: "<<itContours->size()<<std::endl; - }
注意到輪廓的存儲格式爲std::vector>,他說明整個輪廓是若干條輪廓按必定順序組成的,而每一個輪廓中的點也是有順序的。函數
畫出輪廓就比較簡單了:ui
- //畫出輪廓
- Mat
result(image.size(),CV_8U,Scalar(255)); - //畫出輪廓,參數爲:畫板,輪廓,輪廓指示(這裏畫出全部輪廓),顏色,線粗
- drawContours(result,contours,-1,Scalar(0),2);
- imshow("提取外圍輪廓",result);
還要注意提取輪廓的方法還有不少種,好比CV_RETR_LIST表明全部輪廓spa
- findContours(image,
//圖像 -
contours, //輪廓點 -
//包含圖像拓撲結構的信息(可選參數,這裏沒有選) -
CV_RETR_LIST, //獲取輪廓的方法(這裏獲取全部輪廓) -
CV_CHAIN_APPROX_NONE); //輪廓近似的方法(這裏不近似,獲取所有輪廓 - //畫出輪廓
- drawContours(result,contours,-1,Scalar(0),2);
- imshow("提取全部輪廓",result);
一般,這樣提取的輪廓包含一些咱們不但願的輪廓(好比一些小洞),或者假如咱們知道咱們感興趣的物體輪廓的大概範圍時,咱們就能夠用下面的辦法縮小目標範圍:.net
- //除去太長或者過短的輪廓
- int
cmin = 100; - int
cmax = 1000; - std::vector>::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<</span>int>(radius),Scalar(0),2);
-
- //多邊形估計
- std::vector
poly; - //參數爲:輸入圖像的2維點集,輸出結果,估計精度,是否閉合
- approxPolyDP(Mat(contours[2]),poly,5,true);
- std::cout<<"多邊形大小:"<<poly.size()<<std::endl;
- //畫出結果
- std::vector::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
hull; - convexHull(Mat(contours[3]),hull);
- std::vector::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兩個輪廓的類似程度等等。這裏就不一一介紹了。
findContours函數,這個函數的原型爲:
void findContours(InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierar-
chy, int mode, int method, Point offset=Point())
參數說明
輸入圖像image必須爲一個2值單通道圖像
contours參數爲檢測的輪廓數組,每個輪廓用一個point類型的vector表示
hiararchy參數和輪廓個數相同,每一個輪廓contours[ i ]對應4個hierarchy元素hierarchy[ i ][ 0 ] ~hierarchy[ i ][ 3 ],分別表示後一個輪廓、前一個輪廓、父輪廓、內嵌輪廓的索引編號,若是沒有對應項,該值設置爲負數。
mode表示輪廓的檢索模式
CV_RETR_EXTERNAL表示只檢測外輪廓
CV_RETR_LIST檢測的輪廓不創建等級關係
CV_RETR_CCOMP創建兩個等級的輪廓,上面的一層爲外邊界,裏面的一層爲內孔的邊界信息。若是內孔內還有一個連通物體,這個物體的邊界也在頂層。
CV_RETR_TREE創建一個等級樹結構的輪廓。具體參考contours.c這個demo
method爲輪廓的近似辦法
CV_CHAIN_APPROX_NONE存儲全部的輪廓點,相鄰的兩個點的像素位置差不超過1,即max(abs(x1-x2),abs(y2-y1))==1
CV_CHAIN_APPROX_SIMPLE壓縮水平方向,垂直方向,對角線方向的元素,只保留該方向的終點座標,例如一個矩形輪廓只需4個點來保存輪廓信息
CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似算法
offset表示表明輪廓點的偏移量,能夠設置爲任意值。對ROI圖像中找出的輪廓,並要在整個圖像中進行分析時,這個參數仍是頗有用的。
findContours後會對輸入的2值圖像改變,因此若是不想改變該2值圖像,需建立新mat來存放,findContours後的輪廓信息contours可能過於複雜不平滑,能夠用approxPolyDP函數對該多邊形曲線作適當近似
contourArea函數能夠獲得當前輪廓包含區域的大小,方便輪廓的篩選
findContours常常與drawContours配合使用,用來將輪廓繪製出來。其中第一個參數image表示目標圖像,第二個參數contours表示輸入的輪廓組,每一組輪廓由點vector構成,第三個參數contourIdx指明畫第幾個輪廓,若是該參數爲負值,則畫所有輪廓,第四個參數color爲輪廓的顏色,第五個參數thickness爲輪廓的線寬,若是爲負值或CV_FILLED表示填充輪廓內部,第六個參數lineType爲線型,第七個參數爲輪廓結構信息,第八個參數爲maxLevel
獲得了複雜輪廓每每不適合特徵的檢測,這裏再介紹一個點集凸包絡的提取函數convexHull,輸入參數就能夠是contours組中的一個輪廓,返回外凸包絡的點集
還能夠獲得輪廓的外包絡矩形,使用函數boundingRect,若是想獲得旋轉的外包絡矩形,使用函數minAreaRect,返回值爲RotatedRect;也能夠獲得輪廓的外包絡圓,對應的函數爲minEnclosingCircle;想獲得輪廓的外包絡橢圓,對應的函數爲fitEllipse,返回值也是RotatedRect,能夠用ellipse函數畫出對應的橢圓
若是想根據多邊形的輪廓信息獲得多邊形的多階矩,可使用類moments,這個類能夠獲得多邊形和光柵形狀的3階之內的全部矩,類內有變量m00,m10,m01,m20,m11,m02,m30,m21,m12,m03,好比多邊形的質心爲 x = m10 / m00,y = m01 / m00。
若是想得到一點與多邊形封閉輪廓的信息,能夠調用pointPolygonTest函數,這個函數返回值爲該點距離輪廓最近邊界的距離,爲正值爲在輪廓內部,負值爲在輪廓外部,0表示在邊界上。
static int getContoursByCplus(char* Imgname, double minarea, double whRatio) { cv::Mat src, dst, canny_output; /// Load source image and convert it to gray src = imread(Imgname, 0); if (!src.data) { std::cout << "read data error!" << std::endl; return -1; } blur(src, src, Size(3, 3)); //the pram. for findContours, vector<vector<Point> > contours; vector<Vec4i> hierarchy; /// Detect edges using canny Canny(src, canny_output, 80, 255, 3); /// Find contours findContours(canny_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0)); //CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE double maxarea = 0; int maxAreaIdx = 0; for (int i = 0; i<contours.size(); i++) { double tmparea = fabs(contourArea(contours[i])); if (tmparea>maxarea) { maxarea = tmparea; maxAreaIdx = i; continue; } if (tmparea < minarea) { //刪除面積小於設定值的輪廓 contours.erase(contours.begin() + i); std::wcout << "delete a small area" << std::endl; continue; } //計算輪廓的直徑寬高 Rect aRect =boundingRect(contours[i]); if ((aRect.width / aRect.height)<whRatio) { //刪除寬高比例小於設定值的輪廓 contours.erase(contours.begin() + i); std::wcout << "delete a unnomalRatio area" << std::endl; continue; } } /// Draw contours,彩色輪廓 dst= Mat::zeros(canny_output.size(), CV_8UC3); for (int i = 0; i< contours.size(); i++) { //隨機顏色 Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)); drawContours(dst, contours, i, color, 2, 8, hierarchy, 0, Point()); } // Create Window char* source_window = "countors"; namedWindow(source_window, CV_WINDOW_NORMAL); imshow(source_window, dst); cv:; waitKey(0); return 0; }
cvDrawContours(gray_image,c,cvScalarAll(0),cvScalarAll(0),0,CV_FILLED);
用參數CV_FILLED就能夠了 ,這樣能夠填充輪廓,進而獲得模版有點相似圖像分割了。
還有一種方法就是:
http://blog.csdn.net/augusdi/article/details/9011935
本文同步分享在 博客「shiter」(CSDN)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。