本範例主要介紹瞭如何提取輪廓和用一些形狀描述符對輪廓進行表述,輪廓提取函數涉及到的參數很多,沒有經常用到它的話,對參數的瞭解就不會太深刻,這裏也按照本人搜索出來的一些資料進行總結,希望對大家有用。
1、代碼實現
本代碼實現了多個功能
創建了三個滑動條:
第一個滑動條表示狀態描述符,分別表示爲:
0:多邊形近似
1:外接矩形
2:外接圓
3:力矩
第二個滑動條表示輪廓檢測mode類型,分別表示爲:
0:只檢測出最外輪廓
1:檢測出所有輪廓,並保持在list中,只有一層
2:檢測出所有輪廓,並將它們組織成兩層結構,頂層是外邊界,第二層是孔邊界。
3:檢測出所有輪廓並且重新建立網狀的輪廓結構
第三個滑動條表示層次
0:表示沒有層次,輸出所指定的輪廓
1:表示第一層,輸出第一層的輪廓
2:表示第二層,輸出第二層的輪廓
3:
……
#include "stdafx.h"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace cv;
using namespace std;
#define w 500
Mat src; Mat src_gray;
int hiararchyvalue ;
int choice;
int contoursmode;
int max_mode=3;
int max_hiararchy = 4;
int max_choice=3;
RNG rng(12345);
/// 函數聲明
void thresh_callback(int, void* );
void contours_callback(int,void*);
/** @主函數 */
int main( int argc, char** argv )
{
int i, j;
/// 載入原圖像, 返回3通道圖像
// src = imread( "HappyFish.jpg", 1 );
src.zeros(cvSize(w,w),CV_8UC1);
IplImage* img = cvCreateImage( cvSize(w,w), 8, 1 );
cvZero( img );
for( i=0; i < 6; i++ )
{
int dx = (i%2)*250 - 30;
int dy = (i/2)*150;
CvScalar white = cvRealScalar(255);
CvScalar black = cvRealScalar(0);
if( i == 0 )
{
for( j = 0; j <= 10; j++ )
{
double angle = (j+5)*CV_PI/21;
cvLine(img, cvPoint(cvRound(dx+100+j*10-80*cos(angle)),
cvRound(dy+100-90*sin(angle))),
cvPoint(cvRound(dx+100+j*10-30*cos(angle)),
cvRound(dy+100-30*sin(angle))), white, 1, 8, 0);
}
}
cvEllipse( img, cvPoint(dx+150, dy+100), cvSize(100,70), 0, 0, 360, white, -1, 8, 0 );
cvEllipse( img, cvPoint(dx+115, dy+70), cvSize(30,20), 0, 0, 360, black, -1, 8, 0 );
cvEllipse( img, cvPoint(dx+185, dy+70), cvSize(30,20), 0, 0, 360, black, -1, 8, 0 );
cvEllipse( img, cvPoint(dx+115, dy+70), cvSize(15,15), 0, 0, 360, white, -1, 8, 0 );
cvEllipse( img, cvPoint(dx+185, dy+70), cvSize(15,15), 0, 0, 360, white, -1, 8, 0 );
cvEllipse( img, cvPoint(dx+115, dy+70), cvSize(5,5), 0, 0, 360, black, -1, 8, 0 );
cvEllipse( img, cvPoint(dx+185, dy+70), cvSize(5,5), 0, 0, 360, black, -1, 8, 0 );
cvEllipse( img, cvPoint(dx+150, dy+100), cvSize(10,5), 0, 0, 360, black, -1, 8, 0 );
cvEllipse( img, cvPoint(dx+150, dy+150), cvSize(40,10), 0, 0, 360, black, -1, 8, 0 );
cvEllipse( img, cvPoint(dx+27, dy+100), cvSize(20,35), 0, 0, 360, white, -1, 8, 0 );
cvEllipse( img, cvPoint(dx+273, dy+100), cvSize(20,35), 0, 0, 360, white, -1, 8, 0 );
}
Mat src1(img);
src=src1;
/// 轉化成灰度圖像並進行平滑
// cvtColor( src, src_gray, CV_BGR2GRAY );
// blur( src_gray, src_gray, Size(3,3) );
/// 創建窗口
char* source_window = "Source";
namedWindow( source_window, CV_WINDOW_AUTOSIZE );
imshow( source_window, src );
createTrackbar( " Choice:", "Source", &choice, max_choice, thresh_callback );//創建形狀描述符類型
createTrackbar("contours:","Source",&contoursmode,max_mode,contours_callback);//輪廓檢測mode類型
createTrackbar( " hiararchy:", "Source", &hiararchyvalue, max_hiararchy, contours_callback );//輪廓檢測層次
thresh_callback( 0, 0 );
contours_callback(0,0);
waitKey(0);
return(0);
}
/** @thresh_callback 函數 */
void thresh_callback(int, void* )
{
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
/// 找到輪廓
findContours( src, contours, hierarchy,CV_RETR_TREE , CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );
/// 多邊形逼近輪廓 + 獲取矩形和圓形邊界框
vector<vector<Point> > contours_poly( contours.size() );
vector<Rect> boundRect( contours.size() );
vector<Point2f>center( contours.size() );
vector<float>radius( contours.size() );
vector<Moments> mu(contours.size() );
vector<Point2f> mc( contours.size() );
for( int i = 0; i < contours.size(); i++ )
{
switch (choice)
{
case 0: approxPolyDP( Mat(contours[i]), contours_poly[i], 3, true );break;//多邊形近似
case 1: boundRect[i] = boundingRect( Mat(contours[i]) );break;//外接矩形
case 2: minEnclosingCircle( Mat(contours[i]), center[i], radius[i] );break;//外接圓
case 3: mu[i] = moments( contours[i], false );mc[i] = Point2f( mu[i].m10/mu[i].m00 , mu[i].m01/mu[i].m00 );break;//力矩
default: break;
}
}
/// 畫多邊形輪廓 + 包圍的矩形框 + 圓形框+力矩
Mat drawing = Mat::zeros( src.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) );
switch(choice)
{
case 0: drawContours( drawing, contours_poly, i, color, 1, 8, vector<Vec4i>(), 0, Point() );break;
case 1: rectangle( drawing, boundRect[i].tl(), boundRect[i].br(), color, 2, 8, 0 );break;
case 2: circle( drawing, center[i], (int)radius[i], color, 2, 8, 0 );break;
case 3: circle( drawing, mc[i], 4, color, -1, 8, 0 );
default: break;
}
}
/// 顯示在一個窗口
namedWindow( "Contours", CV_WINDOW_AUTOSIZE );
imshow( "Contours", drawing );
}
//輪廓檢測
void contours_callback(int,void*)
{
int i=0;
Mat threshold_output;
Mat drawing = Mat::zeros( src.size(), CV_8UC3 );
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
/// 找到輪廓
switch (contoursmode)
{
case 0: i=0;hiararchyvalue=0;break;//CV_RETR_EXTERNAL
case 1: i=1;hiararchyvalue=0;break;//CV_RETR_LIST
case 2:
i=2;
if (hiararchyvalue>1)
{
hiararchyvalue=0;
}
break;//CV_RETR_CCOMP
case 3: i=3;break;//CV_RETR_TREE
}
findContours( src, contours, hierarchy,i , CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );
Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
drawContours(drawing,contours,-1,color,1,8,hierarchy,hiararchyvalue+1,Point());
imshow("Contoursmode",drawing);
}
2、運行結果
圖1、原圖
圖2、多邊形近似 圖3、外接矩形
圖4、外接圓 圖5、力矩
圖6、第一層輪廓 圖7、所有輪廓
3、用到的類和函數
findContours
功能:在二值圖像中尋找輪廓
結構:
void findContours(InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset=Point())
image :輸入的 8-比特、單通道圖像. 非零元素被當成 1, 0 象素值保留爲 0 - 從而圖像被看成二值的。爲了從灰度圖像中得到這樣的二值圖像,可以使用 cvThreshold, cvAdaptiveThreshold 或 cvCanny. 本函數改變輸入圖像內容。
contours :檢測到的輪廓,每個輪廓都被保存爲一系列點集
hiararchy :包含了圖片的拓撲結構,數量和輪廓數一樣多,對於每一個輪廓都有對應的層級元素,由四個整數組成一個索引值,hierarchy[i][0] , hiearchy[i][1] 給出了同一級中下一個輪廓及前一個輪廓的索引值,hiearchy[i][2],hiearchy[i][3] 則表示第一個後代及父親的索引值,一個負數的索引值意味着輪廓列表的末尾。
mode :
輪廓檢索的模式
CV_RETR_EXTERNAL :只提取最外層的輪廓,對所有輪廓hierarchy[i][2]=hierarchy[i][3]=-1
CV_RETR_LIST 提取所有輪廓,並且放置在 list 中,只有一層
CV_RETR_CCOMP :提取所有輪廓,並且將其組織爲兩層的 hierarchy: 頂層爲連通域的外圍邊界,次層爲洞的內層邊界
CV_RETR_TREE :提取所有輪廓,並且重構嵌套輪廓的全部 hierarchy
method :
逼近方法
CV_CHAIN_APPROX_NONE :將所有點由鏈碼形式翻譯(轉化)爲點序列形式
CV_CHAIN_APPROX_SIMPLE :壓縮水平、垂直和對角分割,即函數只保留末端的象素點;
CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS :應用 Teh-Chin 鏈逼近算法
offset :每一個輪廓點的偏移量. 當輪廓是從圖像 ROI 中提取出來的時候,使用偏移量有用,因爲可以從整個圖像上下文來對輪廓做分析.
由圖我們可以比較清晰的認識輪廓檢索模式
圖8、傳遞給findcontours的測試圖(上圖),得到的輪廓(下圖):得到的輪廓只可能有兩種,外部輪廓(虛線)或孔(點線)
圖9、四種可能的mode值所得到的結果的拓撲結構
還想再瞭解Findconcours函數,可以點擊下面博客
http://blog.csdn.net/augusdi/article/details/9000893
drawContours
功能:畫輪廓或填充輪廓
結構:
void drawContours(InputOutputArray image, InputArrayOfArrays contours, int contourIdx, const Scalar& color, int thickness=1, int lineType=8, InputArray hierarchy=noArray(), int maxLevel=INT_MAX, Point offset=Point() )
image:目標圖像
contours :所有的輪廓
contourIdx :指定哪一個輪廓,如果爲負數,則表示畫所有的輪廓
color:輪廓的顏色
thickness :線的粗細程度,如果爲負數,則表示填充整個輪廓
lineType :連通性
hierarchy :如果你想畫出圖像的一部分輪廓,那麼你就需要它
maxLevel :用於畫輪廓的最大層,如果爲0,只是畫出指定的輪廓,如果爲1,畫出第一層的所有輪廓,如果爲2,畫出第一和第二層的所有輪廓,依次類推,這個參數只有在有層次關係的時候被使用
offset :每個輪廓點的偏移量
approxPolyDP:
功能:用指定精度逼近多邊形曲線
結構:
void approxPolyDP(InputArray curve, OutputArray approxCurve, double epsilon, bool closed)
curve :輸入二維的序列點
approxCurve :逼近的結果,類型和curve的一樣
epsilon :近似的精確度
closed :如果爲ture,則是閉合形狀,否則不是
boundingRect:
功能:計算點集的最外面矩形邊界
結構:
Rect boundingRect(InputArray points)
points:二維點集
minEnclosingCircle
功能:計算完全包圍已有輪廓最小圓,外接圓
結構:
void minEnclosingCircle(InputArray points, Point2f& center, float& radius)
points :輸入的二維點集
center :輸出圓的圓心
radius :輸出圓的半徑
moments
功能:計算多邊形和光柵形狀的最高達三階的所有矩
結構:
Moments moments(InputArray array, bool binaryImage=false )
array –光柵圖像 (單通道,8位或或浮點數二維數組) 或者一個二維的數組
binaryImage :如果爲ture,所有非零像素被當做1,只對圖像有效
注:
其他連通區域的形態描述符
ConvexHull:發現點集的凸外形,計算物體的凸包。例子
minAreaRect:對給定的 2D 點集,尋找最小面積的包圍矩形,創建可傾斜的邊界框。例子
fitEllipse:對給定的 2D 點集,尋找最小面積的包圍圓形。例子
PointPolygonTest:確定測試點是否在多邊形中,計算圖像中的點到輪廓的距離。例子
contourArea:計算輪廓的面積(包含的像素個數)。例子
arcLength:計算輪廓或曲線長度。例子