OpenCV图像的轮廓的匹配

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

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


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

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

void cvContoursMoments(CvSeq* contour,CvMoments* moments)

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

/* Spatial and central moments */  typedef struct CvMoments  {      double  m00, m10, m01, m20, m11, m02, m30, m21, m12, m03; /* spatial moments */      double  mu20, mu11, mu02, mu30, mu21, mu12, mu03; /* central moments */      double  inv_sqrt_m00; /* m00 != 0 ? 1/sqrt(m00) : 0 */  }  CvMoments;  
在cvContourMoments()函数中,只用到m00,m01,...,m03几个参数;以mu开头的参数在其他函数中使用.

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

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个相关函数的定义:

/* Calculates all spatial and central moments up to the 3rd order */  CVAPI(void) cvMoments( const CvArr* arr, CvMoments* moments, int binary CV_DEFAULT(0));  
CVAPI(double)  cvGetCentralMoment( CvMoments* moments, int x_order, int y_order );  
CVAPI(double)  cvGetNormalizedCentralMoment(CvMoments* moments,int x_order, int y_order);  
/* Calculates 7 Hu's invariants from precalculated spatial and central moments */  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矩进行匹配

/* Compares two contours by matching their moments */  CVAPI(double)  cvMatchShapes( const void* object1, const void* object2,                                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被标出的区域是凸包的各个"缺陷".正如所看到的,这些凸度缺陷提供了手以及手状态的特征表现的方法.

enum  {      CV_CLOCKWISE         =1,      CV_COUNTER_CLOCKWISE =2  };  
/* Calculates exact convex hull of 2d point set */  CVAPI(CvSeq*) cvConvexHull2( const CvArr* input,                               void* hull_storage CV_DEFAULT(NULL),                               int orientation CV_DEFAULT(CV_CLOCKWISE),                               int return_points CV_DEFAULT(0));  
/* Checks whether the contour is convex or not (returns 1 if convex, 0 if not) */  CVAPI(int)  cvCheckContourConvexity( const CvArr* contour );  
/* Finds convexity defects for the contour */  CVAPI(CvSeq*)  cvConvexityDefects( const CvArr* contour, const CvArr* convexhull,                                     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的形式相同.

typedef struct CvConvexityDefect  {      CvPoint* start; /* point of the contour where the defect begins */      CvPoint* end; /* point of the contour where the defect ends */      CvPoint* depth_point; /* the farthest from the convex hull point within the defect */      float depth; /* distance between the farthest point and the convex hull */  } 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)

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

           

再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/427527.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java Servlet 过滤器与 springmvc 拦截器的区别?

前言:在工作中,遇到需要记录日志的情况,不知道该选择过滤器还是拦截器,故总结了一下。 servlet 过滤器 定义 java过滤器能够对目标资源的请求和响应进行截取。过滤器的工作方式分为四种 应用场景 可以通过 doFilter 方法的 reques…

surface pro 6 黑苹果_微软Surface新款超薄触控笔抢鲜评测

微软Surface新款超薄触控笔抢鲜评测中文名:微软 Surface 超薄触控笔英文名:Surface Slim Pen日文名:Surface スリム ペン颜色:典雅黑(只有这一个颜色)中文官方链接:https://www.microsoftstore.…

java轻松实现无锁队列

1、什么是无锁(Lock-Free)编程 当谈及 Lock-Free 编程时,我们常将其概念与 Mutex(互斥) 或 Lock(锁) 联系在一起,描述要在编程中尽量少使用这些锁结构,降低线程间互相阻塞的机会,以提高应用程序的性能。类同的概念还有 "Lock…

numpy数组按某一维度相加_Python数据分析之NumPy(高级篇)

​一些更高级的ndarray处理where和一些其他的逻辑运算np.where(cond,x,y):满足条件(cond)输出x,不满足输出yx_arr np.array([1.1, 1.2, 1.3, 1.4, 1.5])y_arr np.array([2.1, 2.2, 2.3, 2.4, 2.5])cond np.array([True, False, True, True, False])pr…

md5与des算法有何不同_到底AI芯片和传统芯片有何区别?

前两天成立仅两年国内专做人工智能FPGA加速算法的初创公司深鉴科技被国际巨头赛灵思收购了,在业界引起不小的震动。目前国内做AI芯片的公司可谓不少了,AI芯片已然成为了当下芯片行业最热领域。但是大部分人对AI芯片的架构应该都不是太了解。那么AI 芯片和…

Active Directory PowerShell模块收集AD信息

0x00 前言简介 Microsoft为Windows Server 2008 R2(以及更高版本)提供了多个Active Directory PowerShell cmdlet,这大大简化了以前需要将涉及到的ADSI冗长代码行放在一起的任务。 在Windows客户端上,需要安装远程服务器管理工具&…

anaconda对应python版本_Python基础——如何查看python版本、如何查看多个python版本

前言初学者来说,安装python过程是存在一定难度的。在安装过程中,可能安装了多个python版本,可能安装了anaconda导致有自带的python,同时本身电脑也安装了官方下载的python也茫然不知。导致可能有以下情况发生:1.pip in…

MATLAB统计与回归

11.1 前言統計的技巧與資料分析常常形影不離。一般統計使用加法、累加法、平均值,中間值等等,由於處理的對象是矩陣資料,故其基本統計之技巧已經廣為應用,其觀念也會在正常之運作中出現。統計學中比較特殊應用者為機率、亂數、常態…

yii2通过url访问类中的方法_每日学点---nginx变量使用方法详解(3)

也有一些内建变量是支持改写的,其中一个例子是 $args. 这个变量在读取时返回当前请求的 URL 参数串(即请求 URL 中问号后面的部分,如果有的话 ),而在赋值时可以直接修改参数串。我们来看一个例子:location /test { set $orig_args…

python语言format用法_详解Python中的format格式化函数的使用方法

format函数实现字符串格式化的功能 基本语法为: 通过 : 和 {} 来控制字符串的操作 一、对字符串进行操作 1. 不设置指定位置,按默认顺序插入 ①当参数个数等于{}个数的时候 str_1 "小明{}小美,可是小美{}小明".format("喜欢", &quo…

ps安装了可以打开但开始里面找不到_PS2018打开了钢笔压力但却没有压感的解决方法...

1.首先应确定是否安装数位板的驱动,如果驱动出现问题也可以试着重装一下。2.(这里以Photoshop CC 2018为例)接下来检查这个“始终对‘大小’使用‘压力’”按钮是否打开,如果是关闭的,试着打开。3.随后F5进入“画笔”选…

t检验的p值对照表_论文数据分析实战 | 如何对汇总数据进行t检验

在SPSS统计分析交流群中有学员在阅读论文的过程中看到下面的这张表格:这张表中记录了第16届世界男子篮球锦标赛中国队与前8名球队进攻指标比较的结果,其中这份表格并没有给出详细的P值,而只是告诉我们P值小于多少。在这种只有汇总数据&#x…

for命令不跳过空白行_Java程序员必备:查看日志常用的linux命令

iwenhou趁周末,复习一下鸟哥的linux私房菜,看了文件内容查阅部分,做个笔记,哈哈,希望对你有帮助哦。catcat : 由第一行开始显示文件所有内容参数说明cat [-AbEnTv] 参数: -A : 相当于-vET 的整合参数&#…

Java并发编程笔记之Semaphore信号量源码分析

JUC 中 Semaphore 的使用与原理分析,Semaphore 也是 Java 中的一个同步器,与 CountDownLatch 和 CycleBarrier 不同在于它内部的计数器是递增的,那么,Semaphore 的内部实现是怎样的呢? Semaphore 信号量也是Java 中一个…

设计模式--命令模式

实验16:命令模式 本次实验属于模仿型实验,通过本次实验学生将掌握以下内容: 1、理解命令模式的动机,掌握该模式的结构; 2、能够利用命令模式解决实际问题。 [实验任务]:多次撤销和重复的命令模式 某系…

wxpython界面切换_Python图形界面开发—wxPython库的布局管理及页面切换

前言 wxPython是基于Python的跨平台GUI扩展库,对wxWidgets( C 编写)封装实现。GUI程序的开发中界面布局是很重要的一个部分,合理的页面布局能够给予用户良好使用体验。虽然在GUI的控件和窗口布局上可以使用坐标,但更多…

javah找不到类文件

这样即可,在src目录下寻找类,类要写全,即包名.类名 转载于:https://www.cnblogs.com/Java-Starter/p/9283830.html

python卸载opencv_20.Windows python,opencv的安装与卸载

本机Windows7系统,之前安装了Python3.5的版本,后来因为要装opencv,而opencv只支持Python2.7的版本,所以需要将Python3.5进行卸载。在控制面板-卸载程序,将Python卸载后删除其注册表等残留文件 下载Python2.7&#xff0…

LinuX 硬盘分区细节详谈 【 整理至 LinuxSir BY FreeXploiT 】

系统引导过程及硬盘分区结构论述作者: zhy2111314来自: LinuxSir.Org ouc.edu.cn摘要: 本文是理论性文档,主要讲述系统引导过程以及硬盘的物理结构;正文一、系统引导过程简介系统引导过程主要由以下几个步骤组成(以硬盘…

破解WEP密钥过程全解(上)

WLAN技术出现之后,“安全”就成为始终伴随在“无线”这个词身边的影子,针对无线网络技术中涉及的安全认证加密协议的攻击与破解就层出不穷。现在,因特网上可能有数以百计,甚至以千计的文章介绍关于怎么攻击与破解WEP,但…