本范例主要介绍了如何提取轮廓和用一些形状描述符对轮廓进行表述,轮廓提取函数涉及到的参数很多,没有经常用到它的话,对参数的了解就不会太深刻,这里也按照本人搜索出来的一些资料进行总结,希望对大家有用。
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:计算轮廓或曲线长度。 例子