opencv轮廓提取原理与代码的实现

嵌入式设计应用

128人已加入

描述

  在检测物体的轮廓时,我们通常会使用到opencv中的findcontour和drawcontour,比较常用而且效果不错。那么findcontour是基于什么原理来实现轮廓的提取呢?

  轮廓的提取与描述

  在目标识别中我们首先要把感兴趣的目标提取出来,而一般常见的步骤都是通过颜色或纹理提取出目标的前景图(一幅黑白图像,目标以白色显示在图像中),接下来我们要对前景图进行分析进一步地把目标提取出来,而这里常常用到的就是提取目标的轮廓。

  OpenCV里提取目标轮廓的函数是findContours,它的输入图像是一幅二值图像,输出的是每一个连通区域的轮廓点的集合:vector《vector《Point》》。外层vector的size代表了图像中轮廓的个数,里面vector的size代表了轮廓上点的个数。下面我们通过实例来看函数的用法。

  view plain copy print?

  int main()

  {

  using namespace cv;

  Mat image=imread(“。。/shape.png”);

  cvtColor(image,image,CV_BGR2GRAY);

  vector《vector《Point》》 contours;

  // find

  findContours(image,contours,CV_RETR_EXTERNAL,CV_CHAIN_APPROX_NONE);

  // draw

  Mat result(image.size(),CV_8U,Scalar(0));

  drawContours(result,contours,-1,Scalar(255),2);

  namedWindow(“contours”);

  imshow(“contours”,result);

  waitKey();

  return 0;

  }

  int main()

  {

  using namespace cv;

  Mat image=imread(“。。/shape.png”);

  cvtColor(image,image,CV_BGR2GRAY);

  vector《vector《Point》》 contours;

  // find

  findContours(image,contours,CV_RETR_EXTERNAL,CV_CHAIN_APPROX_NONE);

  // draw

  Mat result(image.size(),CV_8U,Scalar(0));

  drawContours(result,contours,-1,Scalar(255),2);

  namedWindow(“contours”);

  imshow(“contours”,result);

  waitKey();

  return 0;

  }

  

  上面程序中包含了2个函数,第一个是查找轮廓函数,它的第三个参数说明查找轮廓的类型,这里我们使用的是外轮廓,还可以查找所有轮廓,即

  包括一些孔洞的部分,像图像人物胳膊与腰间形成的轮廓。第4个参数说明了轮廓表示的方法,程序中的参数说明轮廓包括了所有点,也可以用其

  他参数让有点直线的地方,只保存直线起始与终点的位置点,具体参数用法可以参考手册里函数的介绍。

  第二个函数drawContours是一个画轮廓的函数,它的第3个参数程序里设置-1表示所有的轮廓都画,你也可以指定要画的轮廓的序号。

  提取到轮廓后,其实我们更关心的是如果把这些轮廓转换为可以利用的特征,也就是涉及到轮廓的描述问题,这时就有多种方法可以选择,比如矢

  量化为多边形、矩形、椭圆等。OpenCV里提供了一些这样的函数。

  [cpp] view plain copy print?

  // 轮廓表示为一个矩形

  Rect r = boundingRect(Mat(contours[0]));

  rectangle(result, r, Scalar(255), 2);

  // 轮廓表示为一个圆

  float radius;

  Point2f center;

  minEnclosingCircle(Mat(contours[1]), center, radius);

  circle(result, Point(center), static_cast《int》(radius), Scalar(255), 2);

  // 轮廓表示为一个多边形

  vector《Point》 poly;

  approxPolyDP(Mat(contours[2]), poly, 5, true);

  vector《Point》::const_iterator itp = poly.begin();

  while (itp != (poly.end() - 1))

  {

  line(result, *itp, *(itp + 1), Scalar(255), 2);

  ++itp;

  }

  line(result, *itp, *(poly.begin()), Scalar(255), 2);

  // 轮廓表示为凸多边形

  vector《Point》 hull;

  convexHull(Mat(contours[3]), hull);

  vector《Point》::const_iterator ith = hull.begin();

  while (ith != (hull.end() - 1))

  {

  line(result, *ith, *(ith + 1), Scalar(255), 2);

  ++ith;

  }

  line(result, *ith, *(hull.begin()), Scalar(255), 2);

  // 轮廓表示为一个矩形

  Rect r = boundingRect(Mat(contours[0]));

  rectangle(result, r, Scalar(255), 2);

  // 轮廓表示为一个圆

  float radius;

  Point2f center;

  minEnclosingCircle(Mat(contours[1]), center, radius);

  circle(result, Point(center), static_cast《int》(radius), Scalar(255), 2);

  // 轮廓表示为一个多边形

  vector《Point》 poly;

  approxPolyDP(Mat(contours[2]), poly, 5, true);

  vector《Point》::const_iterator itp = poly.begin();

  while (itp != (poly.end() - 1))

  {

  line(result, *itp, *(itp + 1), Scalar(255), 2);

  ++itp;

  }

  line(result, *itp, *(poly.begin()), Scalar(255), 2);

  // 轮廓表示为凸多边形

  vector《Point》 hull;

  convexHull(Mat(contours[3]), hull);

  vector《Point》::const_iterator ith = hull.begin();

  while (ith != (hull.end() - 1))

  {

  line(result, *ith, *(ith + 1), Scalar(255), 2);

  ++ith;

  }

  line(result, *ith, *(hull.begin()), Scalar(255), 2);

  程序中我们依次画了矩形、圆、多边形和凸多边形。最终效果如下:

  

  对连通区域的分析到此远远没有结束,我们可以进一步计算每一个连通区域的其他属性,比如:重心、中心矩等特征,这些内容以后有机会展开来写。

  以下几个函数可以尝试:minAreaRect:计算一个最小面积的外接矩形,contourArea可以计算轮廓内连通区域的面积;pointPolygenTest可以

  用来判断一个点是否在一个多边形内。mathShapes可以比较两个形状的相似性,相当有用的一个函数。

  一、大概思想

  他主要介绍了两种算法,用来对数字二值图像进行拓扑分析。第一种算法是在确定二值图像边界的围绕关系,即确定外边界、孔边界以及他们的层次关系,由于这些边界和原图的区域具有一一对应关系(外边界对应像素值为1的连通区域,孔边界对应像素值为0的区域),因此我们就可以用边界来表示原图。第二种算法,是第一种算法的修改版本,本质一样,但是它只找最外面的边界。

  也许你会问,这个算法怎么来确定外边界,孔边界以及他们的层级关系?他采用编码的思想,给不同的边界赋予不同的整数值,从而我们就可以确定它是什么边界以及层次关系。输入的二值图像即为0和1的图像,用f(i,j)表示图像的像素值。每次行扫描,遇到以下两种情况终止:

  (1)f(i,j-1)=0,f(i,j)=1;//f(i,j)是外边界的起始点

  (2)f(i,j)》=1,f(i,j+1)=0;//f(i,j)是孔边界的起始点

  然后从起始点开始,标记边界上的像素。在这里分配一个唯一的标示符给新发现的边界,叫做NBD。初始时NBD=1,每次发现一个新边界加1。在这个过程中,遇到f(p,q)=1,f(p,q+1)=0时,将f(p,q)置为-NBD。什么意思呢?就是右边边界的终止点。假如一个外边界里有孔边界时,怎么推导呢?限于篇幅,你可以看论文的附录1。

  二、实例运用

  下面举个例子,对一幅图像先进行边沿提取,这里使用canny,然后再用findcontour提取轮廓,最后用drawcontour画出轮廓。

  #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;

  Mat src; Mat src_gray;int thresh = 100;int max_thresh = 255;RNG rng(12345);

  /// Function headervoid thresh_callback(int, void* );

  /** @function main */int main(){ /// Load source image and convert it to gray src = imread(“lena.jpg”, 1 );

  /// Convert image to gray and blur it cvtColor( src, src_gray, CV_BGR2GRAY ); blur( src_gray, src_gray, Size(3,3) );

  /// Create Window char* source_window = “Source”; namedWindow( source_window, 0); imshow( source_window, src );

  createTrackbar( “ Canny thresh:”, “Source”, &thresh, max_thresh, thresh_callback ); thresh_callback( 0, 0 );

  waitKey(0); return(0);}

  /** @function thresh_callback */void thresh_callback(int, void* ){ Mat canny_output; vector《vector《Point》 》 contours; vector《Vec4i》 hierarchy;

  /// Detect edges using canny Canny( src_gray, canny_output, thresh, thresh*2, 3 ); /// Find contours findContours( canny_output, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );

  /// Draw contours Mat drawing = 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( drawing, contours, i, color, CV_FILLED, 8, hierarchy, 0, Point() ); }

  /// Show in a window namedWindow( “Contours”, 0 ); imshow( “Contours”, drawing );

  来自CODE的代码片contour.cpp

  结果图如下:

  三、代码注释

  findContours( canny_output, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );

  来自CODE的代码片findcontour.cpp

  canny_output:使用canny算子提取边缘的结果图,这里的图像必须是二值图像;

  contours:提取得到的轮廓图,每个轮廓作为一个点向量来存储;

  hierarchy:关于输出图像的拓扑信息,例如第i个轮廓,hierarchy[i][0]和hiearchy[i][1]表示该轮廓的前一个和后一个轮廓,同一层关系;hiearchy[i][2]和hiearchy[i][3]表示父轮廓和子轮廓,不同层的关系。

  CV_RETR_EXTERNAL:轮廓检索模式,这里采用外轮廓的模式;

  CV_CHAIN_APPROX_SIMPLE:轮廓的近似方法,这里采用压缩方式,只用端点来表示;

  Point(0,0):偏移量,这里无偏移。

  drawContours( drawing, contours, i, color,CV_FILLED, 8, hierarchy, 0, Point() );

  来自CODE的代码片drawContours.cpp

  drawing:目标图像;

  contours:所有输入的轮廓;

  i:指定那个轮廓被画;

  color:轮廓的颜色

  CV_FILLED:线的宽度,这里采用填充模式;

  8:线的连接度;

  hierarchy:只有当你只需要画一些轮廓时,这个参数才需要:

  0:所画轮廓的最大级别,为0时表示只画指定的轮廓;

  Point():偏移量。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分