http://ronny.blog.51cto.com/8801997/1394139

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

原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://ronny.blog.51cto.com/8801997/1394139

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

一、Canny检测轮廓

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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,它的原型如下:

1
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元浮点数组构成,前两个点一组,后两个点一组,代表了在图像中直线的起始和结束点。

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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变量内,我们需要进一步将它们画在图像上:

1
2
3
4
5
6
7
8
9
10
11
12
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代表了轮廓上点的个数。下面我们通过实例来看函数的用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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里提供了一些这样的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 轮廓表示为一个矩形
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可以比较两个形状的相似性,相当有用的一个函数。

转载于:https://www.cnblogs.com/donaldlee2008/p/5230091.html

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

  1. 【OpenCV】直线、轮廓的提取与描述

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

  2. OPENCV之寻找并绘制轮廓以及提取轮廓重心坐标

    OPENCV之寻找并绘制轮廓以及提取轮廓重心坐标 1.寻找轮廓 声明:在寻找图像轮廓之前需要对图像进行阈值分割或者Canny.拉普拉斯等边缘检测算子处理. 寻找轮廓的算子: findContours( ...

  3. OpenCV成长之路:图像滤波

    滤波实际上是信号处理里的一个概念,而图像本身也可以看成是一个二维的信号.其中像素点灰度值的高低代表信号的强弱. 高频:图像中灰度变化剧烈的点. 低频:图像中平坦的,灰度变化不大的点. 根据图像的高频与 ...

  4. OpenCV成长之路(3):模仿PhotoShop中魔术棒工具

    转自:http://blog.csdn.net/cv_ronny/article/details/17438131 模仿PhotoShop中魔术棒工具 本文的主题实际上是图像的颜色空间的转换,借助一个 ...

  5. OpenCV成长之路:模仿PhotoShop中魔术棒工具

    本文的主题实际上是图像的颜色空间的转换,借助一个颜色选取程序来说明OpenCV中颜色转换函数的用法以及一些注意事项. 一.几种常见的颜色空间: RGB颜色空间:RGB采用加法混色法,因为它是描述各种& ...

  6. 【OpenCV 学习之路】(8)数独提取之一

    写在前面: 关于本次的数独识别项目,我先说明下情况: 这是本人18年6月份做的,现在把当时的笔记整理出来(口吻的描述是以当时的时间来描述). 这个数独的项目分别做了两次, 第一次做的还没做完,第二次可 ...

  7. openCV专栏(八):图像轮廓:绘制轮廓

    OPENCV基础操作 提示:本专栏所用版本仅供参考,其他版本也可 库 版本 python Python 3.9.3 opencv 4.5.5 matplotlib 3.4.3 numpy 1.19.5 ...

  8. OpenCV进阶之路:神经网络识别车牌字符

    1. 关于OpenCV进阶之路 前段时间写过一些关于OpenCV基础知识方面的系列文章,主要内容是面向OpenCV初学者,介绍OpenCV中一些常用的函数的接口和调用方法,相关的内容在OpenCV的手 ...

  9. OpenCV实战(14)——图像线条提取

    OpenCV实战(14)--图像线条提取 0. 前言 1. 检测图像轮廓 1.1 图像轮廓 1.2 使用 Canny 算子检测图像轮廓 2. 使用霍夫变换检测图像中的线条 2.1 线条的表示 2.2 ...

最新文章

  1. sql server 清空数据库表数据
  2. python php 通信,Python和php通信乱码问题解决方法
  3. WIN10 vnc 与Jeson ubuntu18.04 vncserver 远程控制的实现(亲测有效)
  4. 进程线程协程对比(通俗)
  5. jpa 托管_java – jpa非托管实体
  6. poj 3087 Shuffle'm Up 模拟(map记录)
  7. 苹果鼠标怎么充电_双十一苹果无线充电宝怎么选?充电兼容性强的品牌推荐_...
  8. VirtualBox基础使用教程
  9. Visual C++开发类似QQ游戏大厅全过程
  10. 用python编译linux内核,戴子轩/RK3399上编译linux-kernel
  11. Ubuntu——安装smplayer视频播放器
  12. iOS描述文件mobileconfig文件的签名认证
  13. 【VS开发】免费打工仔:一个完善的ActiveX Web控件教程
  14. 【观察】美达电器:以数字化重塑质量管理体系,构筑车企新“护城河”
  15. 7个趣味性超高的国产APP,总有一个能让你赞不绝口!
  16. 永信至诚成功当选CNCERT网络安全应急服务支撑单位
  17. 前端漂亮的字体 font-family
  18. 如何查看AD域账号的删除记录
  19. IO流的详解,彻底了解IO流
  20. 保罗兰德作品赏析_保罗·兰德简介

热门文章

  1. linux 中常用安装配置
  2. 修改项目生成Gemfile的模板
  3. 状态栏和navigationbar 关联上,结构体总是通过被复制的方式在代码中传递,因此请不要使用引用计数。...
  4. 神盾解密工具 之 解密 “ PHP 神盾解密工具 ”
  5. n阶行列式计算----c语言实现(完结)
  6. WebSocket简单使用(二) - 客户端
  7. MyBatis 简介、 环境搭建、数据库连接池、查询方式
  8. DDD(领域驱动设计)
  9. idea远程调试Java应用程序
  10. JS逻辑运算符——短路逻辑