OpenCV入门教程之八 直线、轮廓的提取与描述

基于内容的图像分析的重点是提取出图像中具有代表性的特征,而线条、轮廓、块往往是最能体现特征的几个元素,这篇文章就针对于这几个重要的图像特征,研究它们在OpenCV中的用法,以及做一些简单的基础应用。

一、Canny检测轮廓

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

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

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

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

 1 int main()
 2 {
 3     Mat I=imread("../cat.png");
 4     cvtColor(I,I,CV_BGR2GRAY);
 5
 6     Mat contours;
 7     Canny(I,contours,125,350);
 8     threshold(contours,contours,128,255,THRESH_BINARY);
 9     namedWindow("Canny");
10     imshow("Canny",contours);
11     waitKey();
12     return 0;
13 }

显示效果如下:

二、直线检测

直线在图像中出现的频率非常之高,而直线作为图像的特征对于基本内容的图像分析有着很重要的作用,本文通过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检测直线的例子:

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

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

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

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

三、轮廓的提取与描述

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

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

 1 int main()
 2 {
 3 using namespace cv;
 4
 5 Mat image=imread("../shape.png");
 6 cvtColor(image,image,CV_BGR2GRAY);
 7 vector<vector<Point>> contours;
 8 // find
 9 findContours(image,contours,CV_RETR_EXTERNAL,CV_CHAIN_APPROX_NONE);
10 // draw
11 Mat result(image.size(),CV_8U,Scalar(0));
12 drawContours(result,contours,-1,Scalar(255),2);
13
14 namedWindow("contours");
15 imshow("contours",result);
16 waitKey();
17 return 0;
18 }

上面程序中包含了2个函数,第一个是查找轮廓函数,它的第三个参数说明查找轮廓的类型,这里我们使用的是外轮廓,还可以查找所有轮廓,即包括一些孔洞的部分,像图像人物胳膊与腰间形成的轮廓。第4个参数说明了轮廓表示的方法,程序中的参数说明轮廓包括了所有点,也可以用其他参数让有点直线的地方,只保存直线起始与终点的位置点,具体参数用法可以参考手册里函数的介绍。

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

提取到轮廓后,其实我们更关心的是如果把这些轮廓转换为可以利用的特征,也就是涉及到轮廓的描述问题,这时就有多种方法可以选择,比如矢量化为多边形、矩形、椭圆等。OpenCV里提供了一些这样的函数。

 1 // 轮廓表示为一个矩形
 2 Rect r = boundingRect(Mat(contours[0]));
 3 rectangle(result, r, Scalar(255), 2);
 4 // 轮廓表示为一个圆
 5 float radius;
 6 Point2f center;
 7 minEnclosingCircle(Mat(contours[1]), center, radius);
 8 circle(result, Point(center), static_cast<int>(radius), Scalar(255), 2);
 9 // 轮廓表示为一个多边形
10 vector<Point> poly;
11 approxPolyDP(Mat(contours[2]), poly, 5, true);
12 vector<Point>::const_iterator itp = poly.begin();
13 while (itp != (poly.end() - 1))
14 {
15 line(result, *itp, *(itp + 1), Scalar(255), 2);
16 ++itp;
17 }
18 line(result, *itp, *(poly.begin()), Scalar(255), 2);
19 // 轮廓表示为凸多边形
20 vector<Point> hull;
21 convexHull(Mat(contours[3]), hull);
22 vector<Point>::const_iterator ith = hull.begin();
23 while (ith != (hull.end() - 1))
24 {
25 line(result, *ith, *(ith + 1), Scalar(255), 2);
26 ++ith;
27 }
28 line(result, *ith, *(hull.begin()), Scalar(255), 2);

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

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

以下几个函数可以尝试:minAreaRect:计算一个最小面积的外接矩形,contourArea可以计算轮廓内连通区域的面积;pointPolygenTest可以用来判断一个点是否在一个多边形内。mathShapes可以比较两个形状的相似性,相当有用的一个函数。

时间: 2024-11-22 18:15:08

OpenCV入门教程之八 直线、轮廓的提取与描述的相关文章

系列文章 -- OpenCV入门教程

<OpenCV3编程入门>内容简介&勘误&配套源代码下载 [OpenCV入门教程之十八]OpenCV仿射变换 & SURF特征点描述合辑 [OpenCV入门教程之十七]OpenCV重映射 & SURF特征点检测合辑 [OpenCV入门教程之十六]OpenCV角点检测之Harris角点检测 [OpenCV入门教程之十五]水漫金山:OpenCV漫水填充算法(Floodfill) [OpenCV入门教程之十四]OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑 [Ope

OpenCV成长之路:直线、轮廓的提取与描述

http://ronny.blog.51cto.com/8801997/1394139 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://ronny.blog.51cto.com/8801997/1394139 基于内容的图像分析的重点是提取出图像中具有代表性的特征,而线条.轮廓.块往往是最能体现特征的几个元素,这篇文章就针对于这几个重要的图像特征,研究它们在OpenCV中的用法,以及做一些简单的基础应用. 一.Canny检测轮

【OpenCV入门教程之十八】OpenCV仿射变换 &amp; SURF特征点描述合辑

本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/33320997 作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442 知乎:http://www.zhihu.com/people/mao-xing-yun 邮箱: [email protected] 写作当前博文时配套使用的OpenCV版本: 2.4.9 本篇文章中,我们一起探讨了OpenCV中

【OpenCV入门教程之六】 创建Trackbar &amp; 图像对比度、亮度值调整(转)

本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/21479533 作者:毛星云(浅墨)    邮箱: [email protected] 写作当前博文时配套使用的OpenCV版本: 2.4.8 这篇文章中我们一起学习了如何在OpenCV中用createTrackbar函数创建和使用轨迹条,以及图像对比度.亮度值的动态调整. 文章首先详细讲解了OpenCV2.0中的新版创建轨迹条的函数c

opencv ,亮度调整【【OpenCV入门教程之六】 创建Trackbar &amp; 图像对比度、亮度值调整

http://blog.csdn.net/poem_qianmo/article/details/21479533 [OpenCV入门教程之六] 创建Trackbar & 图像对比度.亮度值调整 标签: opencvvs2010c++图像处理 2014-03-18 21:43 43189人阅读 评论(99) 收藏 举报  分类: [OpenCV](18)  目录(?)[+] 本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qian

【OpenCV入门教程之五】 分离颜色通道&amp;多通道图像混合

上篇文章中我们讲到了使用addWeighted函数进行图像混合操作,以及将ROI和addWeighted函数结合起来使用,对指定区域进行图像混合操作. 而为了更好的观察一些图像材料的特征,有时需要对RGB三个颜色通道的分量进行分别显示和调整.通过OpenCV的split和merge方法可以很方便的达到目的. 一.分离颜色通道 就让我们来详细介绍一下这两个互为冤家的函数.首先是进行通道分离的split函数. <1>split函数详解 将一个多通道数组分离成几个单通道数组.ps:这里的array按

【OpenCV入门教程之三】 图像的载入,显示和输出 一站式完全解析

了解过之前老版本OpenCV的童鞋们都应该清楚,对于OpenCV1.0时代的基于 C 语言接口而建的图像存储格式IplImage*,如果在退出前忘记release掉的话,就会造成内存泄露.而且用起来超级麻烦,我们往往在debug的时候,很大一部分时间在纠结手动释放内存的问题.虽然对于小型的程序来说手动管理内存不是问题,但一旦我们写的代码变得越来越庞大,我们便会开始越来越多地纠缠于内存管理的问题,而不是着力解决你的开发目标. 这,就有些舍本逐末的感觉了. 而自从OpenCV踏入2.0时代,用Mat

【OpenCV入门教程之十七】OpenCV重映射 &amp; SURF特征点检测合辑

本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/30974513 作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442 知乎:http://www.zhihu.com/people/mao-xing-yun 邮箱: [email protected] 写作当前博文时配套使用的OpenCV版本: 2.4.9 本篇文章中,我们一起探讨了OpenCV中

【OpenCV入门教程之四】 ROI区域图像叠加&amp;初级图像混合 全剖析(转)

本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/20911629 作者:毛星云(浅墨)    邮箱: [email protected] 写作当前博文时配套使用的OpenCV版本: 2.4.8 在这篇文章里,我们一起学习了在OpenCV中如何定义感兴趣区域ROI,如何使用addWeighted函数进行图像混合操作,以及将ROI和addWeighted函数结合起来使用,对指定区域进行图像