C语言实现opencv提取直线、轮廓及ROI实例详解

 更新时间:2018年01月05日 14:29:08   作者:sac761  
这篇文章主要介绍了C语言实现opencv提取直线、轮廓及ROI实例详解,具有一定借鉴价值,需要的朋友可以参考下

一、Canny检测轮廓

在上一篇文章中有提到sobel边缘检测,并重写了soble的C++代码让其与matlab中算法效果一致,而soble边缘检测是基于单一阈值的,我们不能兼顾到低阈值的丰富边缘和高阈值时的边缘缺失这两个问题。而canny算子则很好的弥补了这一不足,从目前看来,canny边缘检测在做图像轮廓提取方面是最优秀的边缘检测算法。

canny边缘检测采用双阈值值法,高阈值用来检测图像中重要的、显著的线条、轮廓等,而低阈值用来保证不丢失细节部分,低阈值检测出来的边缘更丰富,但是很多边缘并不是我们关心的。最后采用一种查找算法,将低阈值中与高阈值的边缘有重叠的线条保留,其他的线条都删除。

本篇文章中不对canny的算法原理作进一步说明,稍后会在图像处理算法相关的文章中详细介绍。

下面我们用OpenCV中的Canny函数来检测图像边缘

int main() 
{ 
  Mat I=imread("../cat.png"); 
  cvtColor(I,I,CV_BGR2GRAY); 
                         
  Mat contours; 
  Canny(I,contours,125,350); 
  threshold(contours,contours,128,255,THRESH_BINARY); 
  namedWindow("Canny"); 
  imshow("Canny",contours); 
  waitKey(); 
  return 0; 
} 

显示效果如下:

二、直线检测

用到的是霍夫变换检测直线的算法

直线在图像中出现的频率非常之高,而直线作为图像的特征对于基本内容的图像分析有着很重要的作用,本文通过OpenCV中的hough变换来检测图像中的线条。

我们先看最基本的Hough变换函数HoughLines,它的原型如下:

void HoughLines(InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn=0, double stn=0 );

它的输入是一个二值的轮廓图像,往往是边缘检测得到的结果图像;它的输出是一个包含多个Vec2f点的数组,数组中的每个元素是一个二元浮点数据对<rou,theta>,rou代表直线离坐标原点的距离,theta代表角度。第3和第4个参数代表步长,因为Hough变换实际上是一个穷举的算法,rho表示距离的步长,theta代表角度的步长。第5个参数是一个阈值设置直接的最低投票个数,知道Hough原理的,这个参数应该很容易理解。

从这个函数的输出结果我们可以看出,得到的直线并没有指定在图像中的开始点与结束点,需要我们自己去计算,如果我们想把直接显示在图像中就会比较麻烦,而且会有很多角度接近的直线,其实它们是重复的,为了解决上面这些问题,OpenCV又提供了一个函数HoughLinesP()。它的输出是一个Vector of Vec4i。Vector每一个元素代表一条直线,是由一个4元浮点数组构成,前两个点一组,后两个点一组,代表了在图像中直线的起始和结束点。

void HoughLinesP(InputArray image, OutputArray lines, double rho, double theta,int threshold, double minLineLength=0, double maxLineGap=0 );

解释一下最后两个参数,minLineLength指定了检测直线中的最小宽度,如果低于最小宽度则舍弃掉,maxLineGap指定通过同一点的直线,如果距离小于maxLineGap就会进行合并。

下面是一个用HoughLinesP检测直线的例子:

int main() 
{ 
  Mat image=imread("../car.png"); 
  Mat I; 
  cvtColor(image,I,CV_BGR2GRAY); 
               
  Mat contours; 
  Canny(I,contours,125,350); 
  threshold(contours,contours,128,255,THRESH_BINARY); 
  vector<Vec4i> lines; 
  // 检测直线,最小投票为90,线条不短于50,间隙不小于10 
  HoughLinesP(contours,lines,1,CV_PI/180,80,50,10); 
  drawDetectLines(image,lines,Scalar(0,255,0)); 
  namedWindow("Lines"); 
  imshow("Lines",image); 
  waitKey(); 
  return 0; 
} 

上面程序将检测到的线条保存在lines变量内,我们需要进一步将它们画在图像上:

void drawDetectLines(Mat& image,const vector<Vec4i>& lines,Scalar & color) 
{ 
  // 将检测到的直线在图上画出来 
  vector<Vec4i>::const_iterator it=lines.begin(); 
  while(it!=lines.end()) 
  { 
    Point pt1((*it)[0],(*it)[1]); 
    Point pt2((*it)[2],(*it)[3]); 
    line(image,pt1,pt2,color,2); // 线条宽度设置为2 
    ++it; 
  } 
} 

实际上Hough变换可以检测很多固定的形状,比如:圆、正方形等。它们的原理基本相同,都是构造一个投票矩阵。OpenCV里提供了检测圆的函数HoughCircles,它的输出是一个Vector of Vec3i,Vector的每个元素包含了3个浮点数,前2个是圆的中心坐标,最后一个是半径。

三、轮廓的提取与描述

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

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

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里提供了一些这样的函数。

// 轮廓表示为一个矩形 
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可以比较两个形状的相似性,相当有用的一个函数。

总结

以上就是本文关于C语言实现opencv提取直线、轮廓及ROI实例详解的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!

相关文章

  • 算法之排列算法与组合算法详解

    算法之排列算法与组合算法详解

    这篇文章主要介绍了算法之排列算法与组合算法详解,本文以字典序法、递归法为例讲解了排列算法、全组合算法等,需要的朋友可以参考下
    2014-08-08
  • 怎么用C++提取任意一张图片的特征(从内存读取数据)

    怎么用C++提取任意一张图片的特征(从内存读取数据)

    这篇文章主要介绍了用C++提取任意一张图片的特征(从内存读取数据)的相关资料,需要的朋友可以参考下
    2017-05-05
  • C++简明分析inline函数的使用

    C++简明分析inline函数的使用

    inline是C++关键字,在函数声明或定义中,函数返回类型前加上关键字inline,即可以把函数指定为内联函数。这样可以解决一些频繁调用的函数大量消耗栈空间(栈内存)的问题
    2022-07-07
  • C++ 中将一维数组转成多维的三种方式示例详解

    C++ 中将一维数组转成多维的三种方式示例详解

    这篇文章主要介绍了C++ 中将一维数组转成多维的三种方式,每种方式结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2023-12-12
  • C++中简单读写文本文件的实现方法

    C++中简单读写文本文件的实现方法

    本篇文章是对C++中简单读写文本文件的方法进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • C语言中scanf与scnaf_s函数详解

    C语言中scanf与scnaf_s函数详解

    大家好,本篇文章主要讲的是C语言中scanf与scnaf_s函数详解,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下
    2022-01-01
  • C++实现LeetCode(129.求根到叶节点数字之和)

    C++实现LeetCode(129.求根到叶节点数字之和)

    这篇文章主要介绍了C++实现LeetCode(129.求根到叶节点数字之和),本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-07-07
  • 深入解析C++设计模式编程中解释器模式的运用

    深入解析C++设计模式编程中解释器模式的运用

    这篇文章主要介绍了C++设计模式编程中解释器模式的运用,解释器模式给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子,需要的朋友可以参考下
    2016-03-03
  • opencv学习笔记C++绘制灰度直方图

    opencv学习笔记C++绘制灰度直方图

    这篇文章主要为大家介绍了opencv学习笔记C++绘制灰度直方图的实现代码,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-05-05
  • C++浅析程序中内存的分布

    C++浅析程序中内存的分布

    这篇文章主要介绍了C++内存分布及用法,从内存的基础概念到内存分配进行了讲解,内存是我们开发中最重要的一部分,往往逻辑上的错误就会造成内存泄漏,导致程序无法运行,下面我们就来了解文章对该内容的详细介绍
    2022-08-08

最新评论