文档章节

OpenCV图像的轮廓的匹配

1071954237
 1071954237
发布于 2017/03/31 17:17
字数 3731
阅读 318
收藏 3
点赞 0
评论 0

一个跟轮廓相关的最常用到的功能是匹配两个轮廓.如果有两个轮廓,如何比较它们;或者如何比较一个轮廓和另一个抽象模板.

比较两个轮廓最简洁的方式是比较他们的轮廓矩.这里先简短介绍一个矩的含义.简单的说,矩是通过对轮廓上所有点进行积分运算(或者认为是求和运算)而得到的一个粗略特征.通常,我们如下定义一个轮廓的(p,q)矩:

在公式中p对应x纬度上的矩,q对应y维度上的矩,q对应y维度上的矩,阶数表示对应的部分的指数.该计算是对轮廓边界上所有像素(数目为n)进行求和.如果p和q全为0,那么m00实际上对轮廓边界上点的数目.

下面的函数用于计算这些轮廓矩

void cvContoursMoments(CvSeq* contour,CvMoments* moments)

第一个参数是我们要处理的轮廓,第二个参数是指向一个结构,该结构用于保存生成的结果.CvMonments结构定义如下

[cpp] view plain copy

  1. /* Spatial and central moments */    
  2. typedef struct CvMoments    
  3. {    
  4.     double  m00, m10, m01, m20, m11, m02, m30, m21, m12, m03; /* spatial moments */    
  5.     double  mu20, mu11, mu02, mu30, mu21, mu12, mu03; /* central moments */    
  6.     double  inv_sqrt_m00; /* m00 != 0 ? 1/sqrt(m00) : 0 */    
  7. }    
  8. CvMoments;    

在cvContourMoments()函数中,只用到m00,m01,...,m03几个参数;以mu开头的参数在其他函数中使用.

 

在使用CvMoment结构的时候,我们可以使用以下的函数来方便地一个特定的矩:

[cpp] view plain copy

  1. CVAPI(double)  cvGetSpatialMoment( CvMoments* moments, int x_order, int y_order );    

调用cvContoursMonments()函数会计算所有3阶的矩(m21和m12会被计算,但是m22不会被计算).

 

再论矩

刚刚描述的矩计算给出了一些轮廓的简单属性,可以用来比较两个轮廓.但是在很多实际使用中,刚才的计算方法得到的矩并不是做比较时的最好的参数.具体说来,经常会用到归一化的矩(因此,不同大小但是形状相同的物体会有相同的值).同样,刚才的小节中的简单的矩依赖于所选坐标系,这意味这物体旋转后就无法正确匹配.

OpenCV提供了计算Hu不变矩[Hu62]以及其他归一化矩的函数.CvMoments结构可以用cvmoments或者cvContourMoments计算.并且,cvContourMoments现在只是cvMoments
的一个别名.

一个有用的小技巧是用cvDrawContour()描绘一幅轮廓的图像后,调用一个矩的函数处理该图像.使用无论轮廓填充与否,你都能用同一个函数处理.

以下是4个相关函数的定义:

 

[cpp] view plain copy

  1. /* Calculates all spatial and central moments up to the 3rd order */    
  2. CVAPI(void) cvMoments( const CvArr* arr, CvMoments* moments, int binary CV_DEFAULT(0));    

[cpp] view plain copy

  1. CVAPI(double)  cvGetCentralMoment( CvMoments* moments, int x_order, int y_order );    

[cpp] view plain copy

  1. CVAPI(double)  cvGetNormalizedCentralMoment(CvMoments* moments,int x_order, int y_order);    

[cpp] view plain copy

  1. /* Calculates 7 Hu's invariants from precalculated spatial and central moments */    
  2. CVAPI(void) cvGetHuMoments( CvMoments*  moments, CvHuMoments*  hu_moments );    

 

第一个函数除了使用的是图像(而不是轮廓)作为参数,其他方面和cvContoursMoments()函数相同,另外还增加了一个参数.增加的参数isBinary如果为CV_TRUE,cvMoments将把图像当作二值图像处理,所有的非0像素都当作1.当函数被调用的时候,所有的矩被计算(包含中心矩,请看下一段).除了x和y的值被归一化到以0为均值,中心距本质上跟刚才描述的矩一样.

 

归一化矩和中心矩也基本相同,除了每个矩都要除以m00的某个幂:

最后来介绍Hu矩,Hu矩是归一化中心距的线性组合.之所以这样做是为了能够获取代表图像某个特性的矩函数,这些矩函数对于某些变化如缩放,旋转和镜像映射(除了h1)具有不变性.Hu矩是从中心矩中计算得到,其计算公式如下所示:

参考图8-9和表8-1,我们可以直观地看到每个图像对应的7个Hu矩.通过观察可以发现,当阶数变高时,Hu矩一般会变小.对于这一点不必感到奇怪,因为根据定义,高阶Hu矩由多个归一化矩的高阶幂计算得到,而归一化矩都是小于1的,所以指数越大,计算所得的值越小.


需要特别注意的是"I",它对于180度旋转和镜面反射都是对称的,它的h3到h7矩都是0;而"O"具有同样的对称特性,所有的Hu矩都是非0的.

使用Hu矩进行匹配

[cpp] view plain copy

  1. /* Compares two contours by matching their moments */    
  2. CVAPI(double)  cvMatchShapes( const void* object1, const void* object2,    
  3.                               int method, double parameter CV_DEFAULT(0));   

很自然,使用Hu矩我们想要比较两个物体并且判明他们是否相似.当然,可能有很多"相似"的定义.为了使比较过程变得简单,OpenCV的函数cvMatShapes()允许我们简单地提供两个物体,然后计算他们的矩并根据我们提供的标准进行比较.

 

这些物体可以是灰度图图像或者轮廓.如果你提供了图像,cvMatchShape()会在对比的进程之间为你计算矩.cvMatchShapes()使用的方法是表8-2中列出的三种中的一种.

 关于对比度量标准(metric)是如何被计算的,表8-2中的三个常量每个都用了不同的方法.这个度量标准最终决定了cvMatchShapes()的返回值.最后一个参数变量现在不能用,因此我们可以把它设成默认值0.

等级匹配

我们经常想要匹配两个轮廓,然后用一个相似度量来计算轮廓所有匹配部分.使用概况参数的方法(比如矩)是相当快的,但是他们能够表达的信息却不是很多.

为了找到一个更精确的相似度量度,首先考虑一下轮廓树的结构应该会有帮助.请注意,此外的轮廓树是用来表述一个特定形状(不是多个特定形状)内各部分的等级关系.

类似于cvFindContours()着怎样的函数放回多个轮廓,轮廓树(contout tree)并不会把这些等级关系搞混,事实上,他正是对于某个特定轮廓形状的登记描述.

理解了轮廓树的创建会比较容易理解轮廓树.从一个轮廓创建一个轮廓树是从底端(叶节点)到顶端(根节点)的.首相搜索三角形突出或凹陷的形状的周边(轮廓上的每一个点都不是完全和它的相邻点共线的).每个这样的三角形被一条线段代替,这条线段通过连接非相邻点的两点得到;因此实际上三角形或者被削平(例如,图8-10的三角形D)或者被填满(三角形C).每个这样的替代把轮廓的顶点减少1,并且给轮廓创建一个新节点.如果这样一个三角形的两侧有原始的边,那么它就是得到的轮廓树的叶子;如果一侧是已存在三角形,那么他就是那个三角形的父节点.这个过程的迭代最终把物体的外形剪成一个四边形,这个四边形也被剖开;得到的两个三角形是根节点的两个子节点.

结果的二分树(图8-11)最终将原始轮廓的形状信息编码.每个节点被它对应的三角形信息(比如三角形的大小,它的生成是被切出来还是被填进去的,这样的信息)所注释

这些树一旦被建立,就可以很有效的对比两个轮廓.这个过程开始定义两个树节点的对应关系,然后比较对应节点的特性.对吼的结果就是两个树的相似度.

事实上,我们基本不需要理解这个过程.OpenCV提供了一个函数从普通的CvContour对象自动生成轮廓树并转换返回;还提供一个函数用来对比两个树.不幸的是,建立的轮廓树并不太鲁棒(例如,轮廓上很小的改变可能会彻底改变结果的树).同事,最初的三角形(树的根节点)是随意选取的.因此,为了得到较好的描述实现使用函数cvApproxPoly()之后将轮廓排列(运用循环移动)成最初的三角形不怎么受到旋转影响的状态.

CvContourTree* cvCreateContourTree(const CvSeq* contour,CvMemStorage* storage,double  threshold);

CvSeq * cvContourFromContourTree(const CvContourTree* tree,CvMemStorage* storage, CvTermCriteria  criteris);

double cvMatchContourTrees(const CvContourTree* tree1,const CvContourTree* tree2,int method,double threshold);

这个代码提到了CvTremCriteria(),该函数细节将在第9章给出.现在可以用下面的默认值使用cvTermCriteria()简单建立一个结构体.

CvTermCriteria termcrit = cvTermCriteria(CV_TERMCRIT_ITER | CV_TeRMCRT_EPS,5,1);

轮廓的凸包和凸缺陷

另一个理解物体形状或轮廓的有用的方法是计算一个物体的凸包(convex hull)然后计算其凸缺陷(convexity defects)[Homma85].很多复杂物体的特性能很好的被这种缺陷表现出来.

图8-12用人手举例说明了凸缺陷这一概念.手周围深色的线描画出了凸包,A到H被标出的区域是凸包的各个"缺陷".正如所看到的,这些凸度缺陷提供了手以及手状态的特征表现的方法.

 

[cpp] view plain copy

  1. enum    
  2. {    
  3.     CV_CLOCKWISE         =1,    
  4.     CV_COUNTER_CLOCKWISE =2    
  5. };    

[cpp] view plain copy

  1. /* Calculates exact convex hull of 2d point set */    
  2. CVAPI(CvSeq*) cvConvexHull2( const CvArr* input,    
  3.                              void* hull_storage CV_DEFAULT(NULL),    
  4.                              int orientation CV_DEFAULT(CV_CLOCKWISE),    
  5.                              int return_points CV_DEFAULT(0));    

[cpp] view plain copy

  1. /* Checks whether the contour is convex or not (returns 1 if convex, 0 if not) */    
  2. CVAPI(int)  cvCheckContourConvexity( const CvArr* contour );    

[cpp] view plain copy

  1. /* Finds convexity defects for the contour */    
  2. CVAPI(CvSeq*)  cvConvexityDefects( const CvArr* contour, const CvArr* convexhull,    
  3.                                    CvMemStorage* storage CV_DEFAULT(NULL));    

 

 

OpenCV有三个关于凸包和凸缺陷的重要函数.第一个函数简单计算已知轮廓的凸包,第二个函数用来检查一个已知轮廓是否是凸的.第三个函数在已知轮廓是凸包的情况下计算凸缺陷.

函数cvConvexHull2()的第一个参数是点的数组,这个数组是一个n行2列的矩阵(n×2),或者是一个轮廓.如果是点矩阵,点应该是32位整型(CV_32SC1)或者是浮点型(CV_32F1).下一个参数是指向内存存储的一个指针,为结果分配内存空间.下一参数是CV_CLOCkWISE或者是CV_COUNTERCLOCkWISE中的一个.这参数决定了程序返回点的排列方向.最后一个参数returnPoints,可以是0或1.如果设置为1,点会被存储在返回数组中.如果设置为0,只有索引被存储在返回数组中.索引是传递给cvConvexHull2()的原始数组索引.

读着可能要问:"如果参数hull_storage是内存存储,为什么它的类型是void* 而不是CvMemSotrage* ?",这是因为很多时候作为凸包放回的点的形式,数组可能比序列更加有用.可虑到这一点,参数hull_storage的另一个可能性是传递一个指向矩阵的指针CvMat*. 这种情况下,矩阵应该是一维的且和输入点的个数相同.当cvConvexHull2()被调用的时候,它会修改矩阵头来指明当前的列数.

有时候,已知一个轮廓但并不知道它是否是凸的.这种情况下,我们可以调用函数cvCheckContourConvexity().这个测试简单快速,但是如果传递的轮廓自身有交叉的时候不会得到正确的结果.

第三个函数cvConvexityDefects(),计算凸缺陷返回一个缺陷的序列.为了完成这个任务,cvConvexityDefects()要求输入轮廓,凸包和内存空间,从这个内存空间来获得存放结果序列的内存.前两个参数是CvArr*,和传递给cvConvexHull2()的参数input的形式相同.

[cpp] view plain copy

  1. typedef struct CvConvexityDefect    
  2. {    
  3.     CvPoint* start; /* point of the contour where the defect begins */    
  4.     CvPoint* end; /* point of the contour where the defect ends */    
  5.     CvPoint* depth_point; /* the farthest from the convex hull point within the defect */    
  6.     float depth; /* distance between the farthest point and the convex hull */    
  7. } CvConvexityDefect;    

函数cvConvexityDefects()返回一个CvConvexityDefect结构体的序列,其中包括一些简单的参数用来描述凸缺陷.start和end是凸包上的缺陷的起始点和终止点.depth_point是缺陷中的距离凸包的边(跟该缺陷有关的凸包便)最远的点.最后一个参数depth是最远点和包的边(edge)的距离.
 

 

成对几何直方图

Freeman链码编码是对一个多边形的的序列如何"移动"的描述,每个这样的移动有固定长度和特定的方向.但是,我们并没有更多说明为什么需要用到这种描述.

Freeman链码编码的用处很多,但最常见的一种值得深入了解一下,因为它支持了成对几何直方图(PGH)的基本思想.

PGH实际上是链码编码直方图(CCH)的一个扩展或延伸.CCH是一种直方图,用来统计一个轮廓的Freeman链码编码每一种走法的数字.这种直方图有一些良好的性质.最显著的是,将物体旋转45度,那么新的直方图是老直方图的循环平移(图8-13).这就提供了一个不被此类旋转影响的形状识别方法.

PGH的构成如下图所示(图8-14).多边形的每一个边被选择成为"基准边".之后考虑其他的边相对于这些基础边的关系,并且计算三个值:dmin,dmax和θ.dmin是两条边的最小距离,dmax是最大距离,θ是两边的夹角.PGH是一个二维直方图,其两个维度分别是角度和距离.对于每一对边,有两个bin,一个bin为(dmin,θ),另一个bin为(dmax,θ).对于这样的每一组边,这两个bin都被增长,中间值d(dmin和dmax之间的值)同样也被增长.

PGh的使用和FCC相似.一个重要不同是,PGH的描述能力更强,因此在尝试解决复杂问题的时候很有用,比如说大量形状需要被辨识,并且/或者有很多背景噪声的时候.用来计算PGh的函数是

void cvCalcPGH(const CvSeq* contour,CvHistogram* hist)

在这里轮廓可以包含整数值的点的坐标;当然直方图必须是二维的.

本文转载自:http://blog.csdn.net/augusdi/article/details/9000829

共有 人打赏支持
1071954237
粉丝 2
博文 76
码字总数 36187
作品 0
程序员
OpenCV中几何形状识别与测量

经常看到有学习OpenCV不久的人提问,如何识别一些简单的几何形状与它们的颜色,其实通过OpenCV的轮廓发现与几何分析相关的函数,只需不到100行的代码就可以很好的实现这些简单几何形状识别与...

gloomyfish ⋅ 04/16 ⋅ 0

关于 opencv 训练LBP联级分类器的一点总结

最近需要做一个联级分类器来定位图像中的目标(用车牌定位来做例子),于是选用opencv的LBP算法。关于介绍,这篇博文写的还可以 点击打开链接 实现的时候查找其他博客也遇到了很多问题,我用...

evinxu ⋅ 04/13 ⋅ 0

基于OpenCV和Python的文件操作——捕获摄像头的帧,在窗口显示图像,在窗口显示摄像头帧和视频文件的读/写

0 写在前面 这篇博客主要参考资料为《OpenCV 3计算机视觉Python语言实现》(Learning OpenCV 3 Computer Vison with Python)。 因为之前用Faster R-CNN做过一个红绿灯检测的小实践,但是Git...

learning_tortosie ⋅ 04/12 ⋅ 0

(三)OpenCV中的图像处理之图像变换及模板匹配

注释:本文翻译自OpenCV3.0.0 document->OpenCV-Python Tutorials,包括对原文档种错误代码的纠正 3.10 OpenCV中的图像变换 第一节:傅里叶变换(Fourier Transform) 1.目标 使用OpenCV查找图...

u014403318 ⋅ 05/30 ⋅ 0

OPenCV 的安装,环境配置(Windows平台)

1、在官网下载opencv,链接:http://opencv.org/,下载至某盘(推荐D),下载后点击,如下: 随后弹出一个提示框,可不用管它,等一段时间,会解压出一个OpenCV文件夹,其中有如下几个文件: ...

weixin_40647819 ⋅ 04/14 ⋅ 0

Ros图像与Opencv图像的相互转换(C++)

转自:https://blog.csdn.net/qq_27050183/article/details/51141998 Ros图像与Opencv图像的相互转换(C++)(译文*来自wiki)(ROS为indigo版本) 摘要:此教程通过将ROS图像转换为OpenCV图...

qq_39907831 ⋅ 05/29 ⋅ 0

Python各类图像库的图片读写方式总结

转载来源:http://www.cnblogs.com/skyfsm/p/8276501.html Python各类图像库的图片读写方式总结 最近在研究深度学习视觉相关的东西,经常需要写python代码搭建深度学习模型。比如写CNN模型相...

chenxueying1993 ⋅ 04/24 ⋅ 0

为Visual Studio配置OpenCV

配置的环境是: Windows 8, Visual Studio 2012 openCV_2.47 下面开始详细地讲解整个配置的过程: 步骤1: 首先需要下载openCV,这是Sourceforge的链接:http://sourceforge.net/projects/ope...

Comma_H ⋅ 05/14 ⋅ 0

1.5OpenCV官方示例学习

OpenCV官方示例   OpenCV作为一个在全球使用人数众多的计算机视觉库,官方已经准备了大量的示例程序,供大家学习。官方提供的示例代码具体位于…opencvsourcessamples目录下,如下图所示:...

webzhuce ⋅ 05/27 ⋅ 0

图片人脸检测(OpenCV版)

图片人脸检测 人脸检测使用到的技术是OpenCV,上一节已经介绍了OpenCV的环境安装,点击查看. 功能展示 识别一种图上的所有人的脸,并且标出人脸的位置,画出人眼以及嘴的位置,展示效果图如下...

vipstone ⋅ 05/21 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Day 17 vim简介与一般模式介绍

vim简介 vi和Vim的最大区别就是编辑一个文件时vi不会显示颜色,而Vim会显示颜色。显示颜色更便于用户编辑,凄然功能没有太大的区别 使用 yum install -y vim-enhanced 安装 vim的三种常用模式...

杉下 ⋅ 56分钟前 ⋅ 0

【每天一个JQuery特效】根据可见状态确定是否显示或隐藏元素(3)

效果图示: 主要代码: <!DOCTYPE html><html><head><meta charset="UTF-8"><title>根据可见状态确定 是否显示或隐藏元素</title><script src="js/jquery-3.3.1.min.js" ty......

Rhymo-Wu ⋅ 今天 ⋅ 0

OSChina 周四乱弹 —— 初中我身体就已经垮了,不知道为什么

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @加油东溪少年 :下完这场雨 后弦 《下完这场雨》- 后弦 手机党少年们想听歌,请使劲儿戳(这里) @马丁的代码 :买了日本 日本果然赢了 翻了...

小小编辑 ⋅ 今天 ⋅ 12

浅谈springboot Web模式下的线程安全问题

我们在@RestController下,一般都是@AutoWired一些Service,由于这些Service都是单例,所以并不存在线程安全问题。 由于Controller本身是单例模式 (非线程安全的), 这意味着每个request过来,...

算法之名 ⋅ 今天 ⋅ 0

知乎Java数据结构

作者:匿名用户 链接:https://www.zhihu.com/question/35947829/answer/66113038 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 感觉知乎上嘲讽题主简...

颖伙虫 ⋅ 今天 ⋅ 0

Confluence 6 恢复一个站点有关使用站点导出为备份的说明

推荐使用生产备份策略。我们推荐你针对你的生产环境中使用的 Confluence 参考 Production Backup Strategy 页面中的内容进行备份和恢复(这个需要你备份你的数据库和 home 目录)。XML 导出备...

honeymose ⋅ 今天 ⋅ 0

JavaScript零基础入门——(九)JavaScript的函数

JavaScript零基础入门——(九)JavaScript的函数 欢迎回到我们的JavaScript零基础入门,上一节课我们了解了有关JS中数组的相关知识点,不知道大家有没有自己去敲一敲,消化一下?这一节课,...

JandenMa ⋅ 今天 ⋅ 0

火狐浏览器各版本下载及插件httprequest

各版本下载地址:http://ftp.mozilla.org/pub/mozilla.org//firefox/releases/ httprequest插件截至57版本可用

xiaoge2016 ⋅ 今天 ⋅ 0

Docker系列教程28-实战:使用Docker Compose运行ELK

原文:http://www.itmuch.com/docker/28-docker-compose-in-action-elk/,转载请说明出处。 ElasticSearch【存储】 Logtash【日志聚合器】 Kibana【界面】 答案: version: '2'services: ...

周立_ITMuch ⋅ 今天 ⋅ 0

使用快嘉sdkg极速搭建接口模拟系统

在具体项目研发过程中,一旦前后端双方约定好接口,前端和app同事就会希望后台同事可以尽快提供可供对接的接口方便调试,而对后台同事来说定好接口还仅是个开始、设计流程,实现业务逻辑,编...

fastjrun ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部