图像处理 基于补丁的分割

2019-07-09 22:15:51 electech6 阅读数 4950

本文作者 张伟,公众号:计算机视觉life,编辑成员

0、引言

医学图像处理的对象是各种不同成像机理的医学影像,临床广泛使用的医学成像种类主要有X-射线成像 (X-CT)、核磁共振成像(MRI)、核医学成像(NMI)和超声波成像(UI)四类。在目前的影像医疗诊断中,主要是通过观察一组二维切片图象去发现病变体,这往往需要借助医生的经验来判定。利用计算机图象处理技术对二维切片图象进行分析和处理,实现对人体器官、软组织和病变体的分割提取、三维重建和三维显示,可以辅助医生对病变体及其它感兴趣的区域进行定性甚至 定量的分析,从而大大提高医疗诊断的准确性和可靠性;在医疗教学、手术规划、手术仿真及各种医学研究中也能起重要的辅助作用[1,2]。目前,医学图像处理主要集中表现在病变检测、图像分割、图像配准及图像融合四个方面。

用深度学习方法进行数据分析呈现快速增长趋势,称为2013年的10项突破性技术之一。深度学习是人工神经网络的改进,由更多层组成,允许更高层次包含更多抽象信息来进行数据预测。迄今为止,它已成为计算机视觉领域中领先的机器学习工具,深度神经网络学习自动从原始数据(图像)获得的中级和高级抽象特征。最近的结果表明,从CNN中提取的信息在自然图像中的对目标识别和定位方面非常有效。世界各地的医学图像处理机构已经迅速进入该领域,并将CNN和其它深度学习方法应用于各种医学图像分析。

在医学成像中,疾病的准确诊断和评估取决于医学图像的采集和图像解释。近年来,图像采集已经得到了显着改善,设备以更快的速率和更高的分辨率采集数据。然而,图像解释过程,最近才开始受益于计算机技术。对医学图像的解释大多数都是由医生进行的,然而医学图像解释受到医生主观性、医生巨大差异认知和疲劳的限制。

用于图像处理的典型CNN架构由一系列卷积网络组成,其中包含有一系列数据缩减即池化层。与人脑中的低级视觉处理一样,卷积网络检测提取图像特征,例如可能表示直边的线或圆(例如器官检测)或圆圈(结肠息肉检测),然后是更高阶的特征,例如局部和全局形状和纹理特征提取[3]。CNN的输出通常是一个或多个概率或种类标签。

CNN是高度可并行化的算法。与单核的CPU处理相比,今天使用的图形处理单元(GPU)计算机芯片实现了大幅加速(大约40倍)。在医学图像处理中,GPU首先被引入用于分割和重建,然后用于机器学习。由于CNN的新变种的发展以及针对现代GPU优化的高效并行网络框架的出现,深度神经网络吸引了商业兴趣。从头开始训练深度CNN是一项挑战[4]。首先,CNN需要大量标记的训练数据,这一要求在专家注释昂贵且疾病稀缺的医学领域中可能难以满足。其次,训练深度CNN需要大量的计算和内存资源,否则训练过程将是非常耗时。第三,深度CNN训练过程中由于过度拟合和收敛问题而复杂化,这通常需要对网络的框架结构或学习参数进行重复调整,以确保所有层都以相当的速度学习[5]。鉴于这些困难,一些新的学习方案,称为“迁移学习”和“微调”,被证明可以解决上述问题从而越来越受欢迎。

1、病变检测

计算机辅助检测(CAD)是医学图像分析的有待完善的领域,并且非常适合引入深度学习。在CAD 的标准方法中,一般通过监督方法或经典图像处理技术(如过滤和数学形态学)检测候选病变位置。病变位置检测是分阶段的,并且通常由大量手工制作的特征描述。将分类器用于特征向量映射到候选者来检测实际病变的概率。采用深度学习的直接方式是训练CNN操作一组以图像为中心的图像数据候选病变。Setio等在3D胸部CT扫描中检测肺结节,并在九个不同方向上提取以这些候选者为中心的2D贴片[6],使用不同CNN的组合来对每个候选者进行分类,CAD系统结构如图1所示。根据检测结果显示,与先前公布的用于相同任务的经典CAD系统相比略有改进。罗斯等人应用CNN改进三种现有的CAD系统,用于检测CT成像中的结肠息肉,硬化性脊柱变形和淋巴结肿大[7]。他们还在三个正交方向上使用先前开发的候选检测器和2D贴片,以及多达100个随机旋转的视图。随机旋转的“2.5D”视图是从原始3D数据分解图像的方法。采用CNN对这些2.5D视图图像检测然后汇总,来提高检测的准确率。对于使用CNN的三个CAD系统,病变检测的准确率度提高了13-34%,而使用非深度学习分类器(例如支持向量机)几乎不可能实现这种程度的提升。早在1996年,Sahiner等人就已将CNN应用于医学图像处理。从乳房X线照片中提取肿块或正常组织的ROI。 CNN由输入层,两个隐藏层和输出层组成,并用于反向传播。在“GPU时代”以前,训练时间被描述为“计算密集型”,但没有给出任何时间。1993年,CNN应用于肺结节检测;1995年CNN用于检测乳腺摄影中的微钙化。

img

图1.CAD系统概述。(a)从立方体的九个对称平面中提取的二维斑块的示例。候选者位于贴片的中心,边界框为50 50 mm和64 64 px。(b)通过合并专门为固体,亚固体和大结节设计的探测器的输出来检测候选人。误报减少阶段是作为多个ConvNets的组合实现的。每个ConvNets流处理从特定视图中提取的2-D补丁。(c)融合每个ConvNet流输出的不同方法。 灰色和橙色框表示来自第一个完全连接的层和结节分类输出的连接神经元。 使用完全连接的层与softmax或固定组合器(产品规则)组合神经元。(a)使用体积对象的九个视图提取二维补丁。(b)拟议系统的示意图。(c)融合方法。

img

图2.结肠息肉的检测:不同息肉大小的FROC曲线,使用792测试CT结肠成像患者的随机视图ConvNet观察。

2、图像分割

医学图像分割就是一个根据区域间的相似或不同把图像分割成若干区域的过程。目前,主要以各种细胞、组织与器官的图像作为处理的对象。传统的图像分割技术有基于区域的分割方法和基于边界的分割方法,前者依赖于图像的空间局部特征,如灰度、纹理及其它象素统计特性的均匀性等,后者主要是利用梯度信息确定目标的边界。结合特定的理论工具,图象分割技术有了更进一步的发展。比如基于三维可视化系统结合FastMarching算法和Watershed 变换的医学图象分割方法,能得到快速、准确的分割结果[8]。

img

图3Watershed分割方法原理

近年来,随着其它新兴学科的发展,产生了一些全新的图像分割技术。如基于统计学的方法、基于模糊理论的方法、基于神经网络的方法、基于小波分析的方法、基于模型的snake 模型(动态轮廓模型)、组合优化模型等方法。虽然不断有新的分割方法被提出,但结果都不是很理想。目前研究的热点是一种基于知识的分割方法,即通过某种手段将一些先验的知识导入分割过程中,从而约束计算机的分割过程,使得分割结果控制在我们所能认识的范围内而不至于太离谱。比如在肝内部肿块与正常肝灰度值差别很大时,不至于将肿块与正常肝看成 2 个独立的组织。

医学图像分割方法的研究具有如下显著特点:现有任何一种单独的图像分割 算法都难以对一般图像取得比较满意的结果,要更加注重多种分割算法的有效结合;由于人体解剖结构的复杂性和功能的系统性,虽然已有研究通过医学图像的自动分割区分出所需的器官、组织或找到病变区的方法,但目前现成的软件包一般无法完成全自动的分割,尚需要解剖学方面的人工干预[9]。在目前无法完全由计算机来完成图像分割任务的情况下,人机交互式分割方法逐渐成为研究重点;新的分割方法的研究主要以自动、精确、快速、自适应和鲁棒性等几个方向作为研究目标,经典分割技术与现代分割技术的综合利用(集成技术)是今后医学图像分割技术的发展方向[10,11]。

利用2891次心脏超声检查的数据集,Ghesu等结合深度学习和边缘空间学习进行医学图像检测和分割[12]。“大参数空间的有效探索”和在深度网络中实施稀疏性的方法相结合,提高了计算效率,并且与同一组发布的参考方法相比,平均分割误差减少了13.5%,八位患者的检测结果如图4所示。Brosch等人利用MRI图像上研究多发性硬化脑病变分割的问题。开发了一种3D深度卷积编码器网络,它结合了卷积和反卷积[13],图5.增加网络深度对病变的分割性能的影响。卷积网络学习了更高级别的特征,并且反卷积网络预进行像素级别分割。将网络应用于两个公开的数据集和一个临床试验数据集,与5种公开方法进行了比较,展现了最好的方法。Pereira等人的研究中对MRI上的脑肿瘤分割进行了研究,使用更深层的架构,数据归一化和数据增强技巧[14]。将不同的CNN架构用于肿瘤,该方法分别对疑似肿瘤的图像增强和核心区域进行分割。在2013年的公共挑战数据集上获得了最高成绩。

img img img
img img img

图4示例图像显示了不同患者的检测结果从测试集。检测到的边界框以绿色显示,标准的框以黄色显示。原点位于每个框中心的线段定义相应的坐标系

img

图5.增加网络深度对病变的分割性能的影响。真阳性,假阴性和假阳性体素分别以绿色,黄色和红色突出显示。由于感受野的大小增加,具有和不具有捷径的7层CEN能够比3层CEN更好地分割大的病变。

2018年德国医疗康复机构提出一种具有代表性的基于全卷积的前列腺图像分割方法。用CNN在前列腺的MRI图像上进行端到端训练,并可以一次完成整个分割。提出了一种新的目标函数,在训练期间根据Dice系数进行优化[15]。通过这种方式,可以处理前景和背景之间存在不平衡的情况,并且增加了随机应用的数据非线性变换和直方图匹配。实验评估中表明,该方法在公开数据集上取得了优秀的结果,但大大降低了处理时间。

img

图6 网络架构的示意图

img

图7 PROMISE 2012数据集分割结果。

3、图像配准

图象配准是图象融合的前提,是公认难度较大的图象处理技术,也是决定医学图象融合技术发展的关键技术。在临床诊断中,单一模态的图像往往不能提供医生所需要的足够信息,常需将多种模式或同一模式的多次成像通过配准融合来实现感兴趣区的信息互补。在一幅图像上同时表达来自多种成像源的信息,医生就能做出更加准确的诊断或制定出更加合适的治疗方法[16]。医学图像配准包括图像的定位和转换,即通过寻找一种空间变换使两幅图像对应点达到空间位置和解剖结构上的完全一致。图6简单说明了二维图像配准的概念。图(a)和图(b)是对应于同一人脑同一位置的两幅 MRI 图像,其中图(a)是质子密度加权成像,图(b)是纵向弛豫加权成像。这两幅图像有明显的不同,第一是方位上的差异,即图(a)相对于图(b)沿水平和垂直方向分别进行了平移;第二是两幅图像所表达的内容是不一致的,图(a)表达不同组织质子含量的差别,而图(b)则突出不同组织纵向弛豫的差别。图©给出了两幅图像之间像素点的对应映射关系,即(a)中的每一个点fx都被映射到(b)中唯一的一个点rx。如果这种映射是一一对应的,即一幅图像空间中的每一个点在另外一幅图像空间中都有对应点,或者至少在医疗诊断上感兴趣的那些点能够准确或近似准确的对应起来,我们就称之为配准[17,18]。图(d)给出了图(a)相对于图(b)的配准图像。从图(d)中可以看出,图(d)与(b)之间的的像素点的空间位置已经近似一致了。1993 年 Petra 等综述了二维图像的配准方法,并根据配准基准的特性,将图像配准的方法分为基于外部特征的图象配准(有框架) 和基于图象内部特征的图象配准(无框架) 两种方法。 后者由于其无创性和可回溯性, 已成为配准算法的研究中心。

img

​ (a) (b) (c) (d)

图8 医学图像配准原理

2019年华中科技大学对基于PCANet的结构非刚性多模医学图像配准展开研究。提出了一种基于PCANet的结构表示方法用于多模态医学图像配准[19]。与人工设计的特征提取方法相比,PCANet可以通过多级线性和非线性变换自动从大量医学图像中学习内在特征。所提出的方法可以通过利用PCANet的各个层中提取的多级图像特征来为多模态图像提供有效的结构表示。对Atlas,BrainWeb和RIRE数据集的大量实验表明,与MIND,ESSD,WLD和NMI方法相比,所提出的方法可以提供更低的TRE值和更令人满意的结果。

img

图9 第一行分别是x和y方向变形的真实结果,第二行是PSR与x和y方向的真实情况的差异;第三行是MIND方法的变形和真实值之间的差异

img

图10 PSR,MIND,ESSD,WLD和NMI方法的CT-MR图像配准。(a)参考PD图像;(b)浮动CT图像;(c)PSR方法;(d)MIND方法;(e)ESSD方法;(f)WLD方法;(g)NMI方法

近年来,医学图像配准技术有了新的进展,在配准方法上应用了信息学的理论和方法,例如应用最大化的互信息量作为配准准则进行图像的配准,基于互信息的弹性形变模型也逐渐成为研究热点[20]。在配准对象方面从二维图像发展到三维多模医学图像的配准。一些新算法,如基于小波变换的算法、统计学参数绘图算法、遗传算法等,在医学图像上的应用也在不断扩展。向快速和准确方面改进算法,使用最优化策略改进图像配准以及对非刚性图像配准的研究是今后医学图像配准技 术的发展方向[21,22]。

4、图像融合

图像融合的主要目的是通过对多幅图像间的冗余数据的处理来提高图像的可读性,对多幅图像间的互补信息的处理来提高图像的清晰度。多模态医学图像的融合把有价值的生理功能信息与精确的解剖结构结合在一起,可以为临床提供更加全面和准确的资料[23]。融合图像的创建分为图像数据的融合与融合图像的显示两部分来完成。目前,图像数据融合主要有以像素为基础的方法和以图像特征为基础的方法。前者是对图像进行逐点处理,把两幅图像对应像素点的灰度值进行加权求和、灰度取大或者灰度取小等操作,算法实现比较简单,不过实现效果和效率都相对较差,融合后图像会出现一定程度的模糊。后者要对图像进行特征提取、目标分割等处理,用到的算法原理复杂,但是实现效果却比较理想。融合图像的显示常用的有伪彩色显示法、断层显示法和三维显示法等。伪彩色显示一般以某个图像为基准,用灰度色阶显示,另一幅图像叠加在基准图像上,用彩色色阶显示。断层显示法常用于某些特定图像,可以将融合后的三维数据以横断面、冠状面和矢状面断层图像同步地显示,便于观察者进行诊断。三维显示法是将融合后数据以三维图像的形式显示,使观察者可更直观地观察病灶的空间解剖位置,这在外科手术设计和放疗计划制定中有重要意义。

img

图11 医学图像融合阶段的总结。 两阶段过程包括图像配准,然后是图像融合。

在图像融合技术研究中,不断有新的方法出现,其中小波变换、 基于有限元分析的非线性配准以及人工智能技术在图像融合中的应用将是今后图像融合研究的热点与方向。随着三维重建显示技术的发展,三维图像融合技术的研究也越来越受到重视,三维图像的融合和信息表达,也将是图像融合研究的一个重点。

在计算机辅助图像处理的基础上,开发出综合利用图像处理方法, 结合人体常数和部分疾病的影像特征来帮助或模拟医生分析、诊断的图像分析系统成为一种必然趋势。目前已有一些采用人机交互定点、自动测量分析的图像分析软件,能定点或定项地完成一些测量和辅助诊断的工作,但远远没有达到智能分析和专家系统的水平;全自动识别标志点并测量分析以及医学图像信息与文本信息的融合, 是计算机辅助诊断技术今后的发展方向。

img

图12 多模态医学图像融合的例子。使用特定图像融合技术的模态1与模态2的组合可以使医学诊断和评估改进

5、预测与挑战

1)数据维度问题-2D与3D:在迄今为止的大多数工作中,是在2D图像中进行处理分析。人们常常质疑向3D过渡是否是迈向性能提高的重要一步。数据增强过程中存在若干变体,包括2.5D。例如,在Roth等人的研究中,以结肠息肉或淋巴结候选体中的体素为中心截取轴向图像,存在冠状和矢状图像。

2)学习方法 - 无监督与监督:当我们查看网络文献时,很明显大多数工作都集中在受监督的CNN上,以实现分类。这种网络对于许多应用是重要的,包括检测,分割和标记。尽管如此,一些工作仍集中于无监督方案,这些方案主要表现为图像编码。诸如玻尔兹曼机器(RBM)之类的无监督表示学习方法可能胜过滤波器,因为它们直接从训练数据中学习特征描述。RBM通过生成学习目标进行培训;这使网络成为可能从未标记的数据中学习,但不一定产生最适合分类的特征。Van Tulder等人进行了一项调查,结合卷积分类和RBM的生成和判别学习目标的优点,该机器学习了对描述训练数据和分类都很好的过滤器。结果表明,学习目标的组合完全胜过生成性学习。

3)迁移学习和微调:在医学成像领域中获取与ImageNet一样全面注释的数据集仍然是一个挑战。当没有足够的数据时,有几种方法可以继续:1)迁移学习:从自然图像数据集或不同医学领域预训练的CNN模型(监督)用于新的医疗任务。在一个方案中,预先训练CNN应用于输入图像,然后从网络层提取输出。提取的输出被认为是特征并且用于训练单独的模式分类器。2)微调:当手头的任务确实存在中等大小的数据集时,较好的方案是使用预先训练的CNN作为网络的初始化,然后进行进一步的监督训练,其中几个(或全部)网络层,使用任务的新数据。

4)数据隐私受社会和技术问题的影响,需要从社会学和技术学的角度共同解决。在卫生部门讨论隐私时,会想到HIPAA(1996年健康保险流通与责任法案)。它为患者提供有关保护个人身份信息的法律权利,并为医疗保健提供者承担保护和限制其使用或披露的义务。在医疗保健数据不断增加的同时,研究人员面临如何加密患者信息以防止其被使用或披露的问题。同时带来,限制访问数据可能遗漏非常重要的信息。

6、结论

近几年来,与传统的机器学习算法相比,深度学习在日常生活自动化方面占据了中心位置,并取得了相当大的进步。基于优秀的性能,大多数研究人员认为在未来15年内,基于深度学习的应用程序将接管人类和大多数日常活动。但是,与其它现实世界的问题相比,医疗保健领域的深度学习尤其是医学图像的发展速度非常慢。到目前为止深度学习应用提供了积极的反馈,然而,由于医疗保健数据的敏感性和挑战,我们应该寻找更复杂的深度学习方法,以便有效地处理复杂的医疗数据。随着医疗技术和计算机科学的蓬勃发展,对医学图象处理提出的要求也越来越高。有效地提高医学图象处理技术的水平,与多学科理论的交叉融合,医务人员和理论技术人员之间的交流就显得越来越重要。医学图象处理技术作为提升现代医疗诊断水平的有力依据, 使实施风险低、创伤性小的手术方案成为可能,必将在医学信息研究领域发挥更大的作用。

参考文献

[1]林晓, 邱晓嘉. 图像分析技术在医学上的应用 [J] . 包头医学院学报, 2005, 21 (3) : 311~ 314

[2]周贤善. 医学图像处理技术综述[J]. 福建电脑, 2009(1):34-34.

[3]Mcinerney T , Terzopoulos D . Deformable models in medical image analysis: a survey[J]. Medical Image Analysis, 1996, 1(2):91.

[4]Litjens G , Kooi T , Bejnordi B E , et al. A survey on deep learning in medical image analysis[J]. Medical Image Analysis, 2017, 42:60-88.

[5]Deserno T M , Heinz H , Maier-Hein K H , et al. Viewpoints on Medical Image Processing: From Science to Application[J]. Current Medical Imaging Reviews, 2013, 9(2):79-88.

[6]A. Setio et al., “Pulmonary nodule detection in CT images using multiview convolutional networks,” IEEE Trans. Med. Imag., vol. 35, no. 5,pp. 1160–1169, May 2016.

[7]H. Roth et al., “Improving computer-aided detection using convolutional neural networks and random view aggregation,” IEEE Trans.Med. Imag., vol. 35, no. 5, pp. 1170–1181, May 2016

[8]林瑶, 田捷. 医学图像分割方法综述[J]. 模式识别与人工智能, 2002, 15(2).

[9]Ghesu F C , Georgescu B , Mansi T , et al. An Artificial Agent for Anatomical Landmark Detection in Medical Images[C]// International Conference on Medical Image Computing & Computer-assisted Intervention. Springer, Cham, 2016.

[10]Pham D L , Xu C , Prince J L . Current methods in medical image segmentation.[J]. Annual Review of Biomedical Engineering, 2000, 2(2):315-337.

[11]Lehmann T M , Gonner C , Spitzer K . Survey: interpolation methods in medical image processing[J]. IEEE Transactions on Medical Imaging, 1999, 18(11):1049-1075.

[12]Cootes T F , Taylor C J . Statistical Models of Appearance for Medical Image Analysis and Computer Vision[J]. Proceedings of SPIE - The International Society for Optical Engineering, 2001, 4322(1).

[13] T. Brosch et al., “Deep 3D convolutional encoder networks with shortcuts for multiscale feature integration applied to multiple sclerosis lesion segmentation,” IEEE Trans. Med. Imag., vol. 35, no. 5,pp. 1229–1239, May 2016.

[14]Ghesu F C , Krubasik E , Georgescu B , et al. Marginal Space Deep Learning: Efficient Architecture for Volumetric Image Parsing[J]. IEEE Transactions on Medical Imaging, 2016, 35(5):1217-1228.

[15]Milletari F , Navab N , Ahmadi S A . V-Net: Fully Convolutional Neural Networks for Volumetric Medical Image Segmentation[J]. 2016.

[16] .周永新, 罗述谦. 一种人机交互式快速脑图象配准系统[J] . 北京生物医学工程, 2002; 21 (1) :11~14

[17]杨虎, 马斌荣, 任海萍. 基于互信息的人脑图象配准研究[J] . 中国医学物理学杂志, 2001; 18 (2) :69~73

[18]汪家旺,愈同福,姜晓彤,等.肺部孤立性结节定量研究[J].中国医学影 像技术,2003,19(9):1218~1219

[19]Ishihara S , Ishihara K , Nagamachi M , et al. An analysis of Kansei structure on shoes using self-organizing neural networks[J]. International Journal of Industrial Ergonomics, 1997, 19(2):93-104.

[20]Maintz J B , Viergever M A . A Survey of Medical Image Registration[J]. Computer & Digital Engineering, 2009, 33(1):140-144.

[21]Hill D L G , Batchelor P G , Holden M , et al. Medical image registration[J]. Physics in Medicine & Biology, 2008, 31(4):1-45.

[22]Razzak M I , Naz S , Zaib A . Deep Learning for Medical Image Processing: Overview, Challenges and Future[J]. 2017.

[23]林晓, 邱晓嘉. 图像分析技术在医学上的应用 [J] . 包头医学院学报, 2005, 21 (3) : 311~ 314

推荐阅读

如何从零开始系统化学习视觉SLAM?
从零开始一起学习SLAM | 为什么要学SLAM?
从零开始一起学习SLAM | 学习SLAM到底需要学什么?
从零开始一起学习SLAM | SLAM有什么用?
从零开始一起学习SLAM | C++新特性要不要学?
从零开始一起学习SLAM | 为什么要用齐次坐标?
从零开始一起学习SLAM | 三维空间刚体的旋转
从零开始一起学习SLAM | 为啥需要李群与李代数?
从零开始一起学习SLAM | 相机成像模型
从零开始一起学习SLAM | 不推公式,如何真正理解对极约束?
从零开始一起学习SLAM | 神奇的单应矩阵
从零开始一起学习SLAM | 你好,点云
从零开始一起学习SLAM | 给点云加个滤网
从零开始一起学习SLAM | 点云平滑法线估计
从零开始一起学习SLAM | 点云到网格的进化
从零开始一起学习SLAM | 理解图优化,一步步带你看懂g2o代码
从零开始一起学习SLAM | 掌握g2o顶点编程套路
从零开始一起学习SLAM | 掌握g2o边的代码套路
零基础小白,如何入门计算机视觉?
SLAM领域牛人、牛实验室、牛研究成果梳理
我用MATLAB撸了一个2D LiDAR SLAM
可视化理解四元数,愿你不再掉头发
最近一年语义SLAM有哪些代表性工作?
视觉SLAM技术综述
汇总 | VIO、激光SLAM相关论文分类集锦
研究SLAM,对编程的要求有多高?
2018年SLAM、三维视觉方向求职经验分享
2018年SLAM、三维视觉方向求职经验分享
深度学习遇到SLAM | 如何评价基于深度学习的DeepVO,VINet,VidLoc?
视觉SLAM关键方法总结
SLAM方向公众号、知乎、博客上有哪些大V可以关注?
SLAM实验室
SLAM方向国内有哪些优秀公司?
SLAM面试常见问题
SLAM相关领域数据集调研
从零开始一起学习SALM-ICP原理及应用
解放双手——相机与IMU外参的在线标定
目标检测
图像分割综述

2020-01-14 19:59:20 john_bh 阅读数 525

转载请注明作者和出处: http://blog.csdn.net/john_bh/

论文链接:Multi-Task Template Matching for Object Detection, Segmentation and
Pose Estimation Using Depth Images

作者及团队:Kiru Park & Timothy Patten & Johann Prankl & Markus Vincze
会议及时间: ICRA 2019
code:

Abstract

在有限数量的样本下,模板匹配已显示出可以准确估计新对象的姿态。然而,被遮挡物体的姿势估计仍然具有挑战性。此外,许多机器人应用程序领域遇到的无纹理对象的深度图像比彩色图像更适合。在本文中,我们提出了一种新颖的框架,即多任务模板匹配(MTTM),该框架可以从深度图像中找到目标对象的最接近模板,同时预测分割蒙版以及模板与场景中检测到的对象之间的姿势变换使用对象区域的相同特征图。所提出的特征比较网络通过比较模板的特征图和场景的裁剪特征来计算分割蒙版和姿势预测。该网络的分割结果通过排除不属于该对象的点,提高了姿态估计的鲁棒性。实验结果表明,尽管仅使用深度图像,MTTM仍优于基线方法对被遮挡对象进行分割和姿态估计。

1.Introduction

估计已知对象的6D姿态对于机器人来说是一项重要任务,因为它使机器人能够操纵现实世界中的对象。与许多机器人和计算机视觉任务一样,深度卷积神经网络(CNN)改善了使用彩色和深度图像进行姿势估计的性能[24],[1],[25]。但是,在机器人应用中,快速学习新对象至关重要。例如,当机器人在工业中或在家中工作时,该机器人每天都会遇到新物体,而这些新物体无法被训练集或CAD模型覆盖。因此,直接识别物体的方法需要重新训练以覆盖新物体,这需要额外的训练时间,并且需要许多新物体的样本图像。为解决此问题,模板匹配已广泛用于快速整合新物体。机器人采摘任务[5],[16]中的对象。手工制作的局部和全局描述符已被广泛用于将模板的特征与测试场景的特征进行匹配[10],[2],[7]。最近,基于CNN的本地和全局描述符使用合成渲染的图像和少量的真实图像进行训练[24],[1]。像手工制作的描述符一样,场景中对象的描述符与特征空间中具有最小距离的最近的模板匹配。因此,对于新对象,不需要对CNN进行进一步的培训,并且不需要高性能的GPU进行培训。

即使最近的研究表明在没有深度信息的情况下使用彩色图像进行物体姿态估计的效果很好[20],[13],但在许多机器人操纵任务中,物体的几何信息比纹理更重要[21],[4]。换句话说,通过使用具有相似几何形状但是不同纹理的不同对象的模板,例如,可以适当地操纵日常生活中的许多物品。盒子,杯子,碗和盘子。此外,工业领域中的物体通常没有纹理。因此,仅使用深度信息来检索具有相似几何形状和方向的最近邻(NN)模板是值得的。此外,深度图像对于不同的光照条件和环境更鲁棒,并且易于模拟,这允许使用合成渲染的图像进行训练。

在本文中,我们提出了一种新颖的框架,即多任务模板匹配(MTTM),该框架使用深度图像模板集对对象进行6D姿态估计和分割,而无需对新对象进行CNN训练。 MTTM的输出是具有最接近姿态的NN模板,逐像素分割蒙版以及从NN模板的姿态到测试图像中对象姿态的姿态变换。总而言之,我们的论文提出了以下贡献:

  • 一种新颖的基于深度的框架MTTM,它与NN模板匹配,并使用共享特征图预测对象的分割蒙版和姿态,而无需对新对象进行额外的训练。
  • 无需对象与场景的任何对齐即可生成分割蒙版,这通过排除不属于对象一部分的点而增强了姿态估计性能的鲁棒性。
  • 实验结果表明MTTM优于使用颜色的基线方法。

在本文的其余部分安排如下。 在第二节中,我们提供了相关工作的简要概述.MTTM的详细信息在第三节中进行了解释。 在第四节中,实验结果表明了MTTM的性能和有效性。 最后,我们在第五节中对本文进行了总结,并作了进一步的工作。
在这里插入图片描述

2. Related Work

在本节中,总结了与使用模板进行姿势估计有关的先前研究以及包括使用合成数据在内的多任务学习的最新成果。

A.Template-Based Object Detection and Pose Estimation

本文的重点是使用模板数据库来估计对象的姿态,而无需对学习的描述符进行微调。为此目的,手工制作的特征已被广泛使用,并且在无纹理物体的物体检测和姿态估计中显示出了出色的性能[10],[2]。最近,已经开发了基于CNN的描述符来导出对象的几何特征的隐式表示。 Kehl等。 [14]使用RGB-D图像和卷积自动编码器训练局部描述符。对象的每个局部对应关系使用从中收集局部补丁的相对位置对对象的中心进行投票。

另一方面,裁剪的对象块用于学习可区分特征空间[24],[1],[26]中不同对象和姿势的流形。三重态损失会指导特征在相同类别和相似姿势下具有更近的距离。这些方法的局限性在于它们使用裁剪的补丁程序作为网络的输入。因此,它们不会从整个图像输入中检测出物体,也不会评估检测结果以获得最佳识别结果或消除误报。据我们所知,没有工作可以在不对齐场景中已知3D模型的情况下使用最接近的模板来估计分割蒙版,这在使用迭代最近点(ICP)算法的姿势优化步骤中可能很有用。使用深度信息进行姿态估计的最佳方法是基于点对特征(PPF)的方法[7],[11],[15]。但是,PPF需要完整的CAD模型来提取特征,这限制了它在没有目标对象CAD模型的机器人应用程序中的使用。

B.Multi-Task Learning

最近,已经表明,从深度神经网络中提取的特征图具有解决不同任务的巨大潜力,例如:对象检测和实例分割[8]或对象分割和姿态估计[25]。这些最新研究表明,当针对不同任务共享中间特征图时,它的计算效率很高;此外,它还对网络模型进行了正则化,以避免过分适合特定任务,从而提高了每个任务的性能。最近的研究表明,这种优势适用于姿势估计任务。 Sock等。 [21]和项祥等。 [25]使用共享的特征图来回归对象的姿势,同时使用相同的特征图检测或分割对象。 Bui等。 [4]还使用共享特征进行流形学习以及姿势回归。但是,这些方法直接预测对象类别或姿势,这需要对新对象进行额外的训练步骤。

C.Synthetically Rendered Images for Training

通常,训练CNN需要大量训练数据。由于难以手动注释对象的姿势,因此合成渲染的图像广泛用于训练带有少量真实图像的CNN [24],[1],[26],[25],[22]。在各种光照条件和其他环境因素的影响下,深度图像比彩色图像更健壮。因此,渲染的深度图像通常需要简单地增加背景,例如分形噪声,从而在识别任务上提供合理的性能[4]。但是,如果在训练过程中不涉及真实图像,那就更好了。布雷吉尔等。 [3]引入了用于模拟逼真的深度图像的传感器仿真。这种传感器模拟产生了真实图像中存在的更逼真的噪声。受到这项工作的启发,本文中的合成图像通过传感器模拟进行渲染,以最大程度地减少合成深度图像与实际深度图像之间的差距。

3. Method

本节介绍了用于渲染合成深度图像的方法以及MTTM的详细信息,包括网络体系结构,用于多种任务的训练方法以及推导和评估姿势假设的过程。

A.Rendering of Noisy Depth Images

与以前使用一小部分真实图像的工作相反,MTTM仅使用合成图像进行训练,因此MTTM可以应用于任何其他没有足够训练图像的领域。如[3]中所述,通过模拟典型的立体相机来渲染嘈杂的深度图像。渲染的图像已经具有在每个对象的边界处包含无效值的像素。这使我们能够训练MTTM网络,而无需真实图像或任何其他传感器噪声的增加。在将目标对象随机放置在平面上之后,可以从任意相机姿势绘制训练图像。

B.Network Architecture

在这里插入图片描述
网络在测试场景中提取给定兴趣区域(ROI)的特征描述符,以检索最接近的模板,同时预测目标对象的分割蒙版,并构成场景中从最接近的模板到对象的变换。如图2所示,Resnet-50 [9]体系结构用作骨干网络,并通过Imagenet数据集[6]上训练的权重进行初始化。由于原始网络需要彩色图像作为输入,因此具有一个通道的深度图像必须转换为三通道图像。我们应用一种典型的技术,使用表面法线的x,y和z分量作为图像像素的每个通道。 Resnet-50的第三个残差块的输出用作给定输入图像的特征图。附加的具有256个滤镜的3x3卷积层可用于减小特征图的尺寸。因此,图像的共享特征图的尺寸为256通道,分辨率比输入图像小八倍。像[8]中使用的方法一样,使用特征图的双线性插值来裁剪ROI的特征图。与[8]的原始ROI-Align方法不同,应用了一种简化方法,该方法可在特征图中裁剪提议的ROI区域,并将其调整为所需的输出大小(14x14),该方法的工作没有[[ 8]。 ROI-Align使网络可以将任意大小的图像作为输入。

固定大小的每个ROI特征图都用于多项任务:提取描述符以进行流形学习,蒙版预测和使用ROI特征图对进行姿态回归。描述符由滤波器大小分别为256、256和128的完全连接的层计算。最后一层具有线性激活,而其他所有层均使用elu激活。因此,每个ROI的特征向量的维数为128,f 2R128 。该特征向量是独立计算的,因此可以预先计算模板的特征向量并将其存储在数据库中,以便在测试时进行有效的树搜索。

由场景和模板中的ROI特征图组成的一对在进行特征图的通道级联之前,分别进入具有256个滤镜的3x3卷积层。因此,级联特征图的输出尺寸为14x14x512。如图2所示,此合并的特征图分别用于特征比较网络中的蒙版预测和姿势回归。对于蒙版预测,具有256个滤波器的3x3卷积层和具有S型激活的单通道输出的1x1卷积层是用于表示每个像素的掩模预测。对于姿态回归,将对具有256、256和4个输出通道的全连接层应用最后一层的双曲正切(tanhtanh)激活,这将给出四元数对中的姿态差qR4q \in R^{4}

C.Learning Networks for Multiple Tasks

相似姿势之间的特征向量距离应小于到不同姿势或不同对象的距离。三重态损耗为此提供了可靠的性能[24],[1],[26]。与使用裁切后的图像补丁的先前工作相比,整个场景的特征图将被计算以裁剪每个ROI的特征图。对于每个包含多个对象的训练场景a1,a2,...aia_1,a_2,... a_i,将正负模板分配给每个对象。从同一类别的前五个最接近的姿势模板中选择一个正模板tp,it_{p, i}否定模板tn,it_{n,i}是从同一对象的不同类别或不同姿势中随机选择的。否定模板的一半来自同一类别,另一半来自不同类别。

即使对象的姿势不同,对称对象也可以具有紧密的形状。 Balntas等。 [1]引入了一个函数,该函数使用每个姿势q1,q2q_1 , q_2的渲染深度图像来测量对象的姿势相似性:
在这里插入图片描述
其中s(q1)ps(q_1)_p表示将对象置于姿态qq之后在像素p处渲染图像的深度值。 如果值小于ϕl\phi l,则ϕ\phi值设置为零;如果值大于ϕu\phi u,则ϕ\phi值设置为1。 两个视图的姿势距离定义为:
在这里插入图片描述
姿态qq是四元数中物体的旋转姿态。 当姿势距离δ(q1,q2)\delta(q_1,q_2)大于positive模板的姿势距离时,可以分配negative模板。

流形学习的损失函数定义为
在这里插入图片描述
其中fafpfnf_a,f_p和f_n代表锚图像,正负模板的ROI特征向量。 mm是[26]引入的动态裕度,它迫使要素与不同类别之间的距离更大
在这里插入图片描述
其中cac_acnc_n表示锚点ROI和负模板中对象的类别。 当负模板不是同一类时,边距mm相对较大。 为了引导特征在相似姿势[4]中具有更近的距离,还对正对施加了附加损失
在这里插入图片描述
仅针对正对计算分割蒙版和姿势变换。 由于假定已经给出了模板的地面真实分割,因此模板的ROI特征图将通过其分割蒙版进行过滤,以最大程度地减少背景的影响。 因此,对于所有通道,将不属于该对象的特征值设置为零。 当为模板预先计算特征描述符而测试场景的ROI特征图保留所有值时,也将应用此过滤的ROI特征图。用于蒙版预测的损失函数LmaskL_{mask}是整个过程中二进制交叉熵损失的平均值。 调整后的ROI功能图的像素。 姿态回归损失LposeregL_{pose-reg}是四元数中地面真值变换与预测变换之间的欧几里得距离[18]。 最后,所有任务的总损失为
在这里插入图片描述

D. Object Detection and Pose Hypotheses Generation

对于输入场景,对中心像素进行均匀采样并生成具有固定空间大小的ROI建议。因此,采样点pp的ROI建议的宽度和高度wwwp=ssizef/dpw_p = s_{size} f / dp,其中dd表示每个采样像素的深度值,ff为焦距,并且ssizes_{size}设置为覆盖3D空间中目标对象的最大大小。这些框可在特征图中保留对象的长宽比和空间比例。对于每个ROI,计算每个ROI的特征向量,以在欧氏空间中使用有效的Kd-Tree搜索在数据库中找到NN模板,并且还计算特征距离。

从该第一匹配步骤中,选择距离其最近的模板更近的ROI来估计分割掩码。然后,使用模板的预先计算的ROI特征图对选定的ROI进行第一次蒙版预测。将来自特征比较网络的每个分割蒙版调整为原始大小。为了去除冗余的掩码预测,通过非最大抑制算法将重叠的掩码合并。对于每次迭代,将种子蒙版设置为具有最小特征距离的蒙版,并合并重叠超过50%的蒙版。其余的掩码用于指定新的ROI提案的新中心。

在第二个匹配步骤中,使用预测的分割蒙版过滤掉特征图中的背景,以再次匹配精炼的NN模板。然后,估计最终的分割蒙版和姿势。结果,使用五个最接近的模板TiT_i,预测的分割掩码M和旋转矩阵MiRk={(Tk1,Mk,Qk1),(Tk2,Mk,Qk2)...(Tk5,Mk,Qk5)}M_i,R_k = \{(T_k^1,M_k,Q_k^1),(T_k^2,M_k,Q_k^2)...(T_k^5,M_k,Q_k^5)\},为每个区域RkR_k生成了五个姿势假设。使用第一个假设的结果为每个区域计算一次分割掩码,以提高计算效率。

E. Post-Processing

后处理是改善姿势并从假设池中消除错误检测的重要步骤。如果给出了目标对象的CAD模型,则后处理将变得更加容易,因为每个假设的精确表示都是可能的。使用深度图像和一组没有CAD模型的模板来拒绝和获得最佳结果具有挑战性,这超出了本文的范围。取而代之的是,CAD模型仅用于评估生成的假设和完善姿态预测。

在第一个剔除步骤中,如果对ICP进行了三次精减迭代(具有下采样点),则预测的分割蒙版与第一个姿势假设的渲染区域之间的重叠度低于30%时,将删除区域。除去错误区域后,将接受对象区域中的假设进行精化,并通过以精巧的姿势渲染对象来进行评估。与以前使用深度图像进行验证的工作[10],[15]一样,计算得出的深度图像和场景之间的差异可得出内部点数NiN_i,遮挡点数NoccN_{occ}和关于渲染模型点的数量NmNm的异常值NoutN_{out}的数量。与[15]中使用的深度适应度评分相反,去除了遮挡惩罚项以检测出没有遮挡的被遮挡物体,而是添加了异常值PO=1Nout/NmP_O =1- N_{out} / N_m的惩罚项。因此,通过SD=PONi/NmNoccS_D = P_ON_i /(N_m-N_{occ})得出深度适应度SDS_D。评估的其余部分,例如重叠边界点的比率SBS_B和匹配的表面法线的比率SNS_N,在[15]的相同公式中使用。最终分数是这些分数的简单乘积Sfinal=SDSBSNS_{final} = S_DS_BS_N,用于过滤掉错误的检测并选择最佳预测。

4. Experiments

本节提供了实现细节和实验结果。实验是使用场景中具有严重遮挡对象的数据集(遮挡数据集[2])执行的。该数据集由LineMOD数据集[10]中的一系列帧组成,并带有八个对象的附加姿势注释。

A.Implementation Details

为了训练MTTM的网络,使用原始数据集中的15个对象渲染具有多个对象的混乱场景和模板图像。用于网络训练的混乱场景总数约为20,000。对于每个训练场景,包含少于50%的被遮挡的对象都用于训练。每个ROI的中心点都会随机移动,以确保未对准中心点的稳定性。由于表面法线对于从相机到物体的距离是不变的,因此使用[10]的相同方法对合成模板的视点进行采样,只是比例不同。在常规二十面体的两个细分之后,从301个视点对对象进行采样,并从4545-45度到45度进行额外的面内旋转,步长为15度。结果,每个对象的模板包含2107个样本。参数设置为ϕl=0.1mϕu=0.5mγ=10\phi_{l} = 0.1m,\phi_{u} = 0.5m,\gamma = 10ssize=0.3ms_{size} = 0.3m。在训练25个epochs后,所有实验都使用相同的网络,并且权重相同。

在计算表面法线之前,使用渲染和真实深度图像的周围像素值来修补缺少值的像素。通过计算深度梯度可以有效地计算输入图像的表面法线[17]。初始检测步骤中使用的中心点每20个像素均匀采样一次。在假设生成步骤中,删除在相同区域中已经具有相似假设的假设,并选择下一个NN模板以避免多余的假设。在后处理步骤中,将深度内在阈值设置为10mm,除了较小的物体(猿猴和鸭子),需要较小的深度内阈值为5mm。所有实验均在Intel i7-6700K CPU和NVIDIA GTX1080 GPU上进行。

B.Evaluation of Segmentation

由于MTTM的优势在于无需将对象对准场景即可预测分割蒙版,因此可以评估分割性能。为了注释数据集上的分割蒙版,使用地面真实姿势放置对象并计算测试图像和渲染图像之间的差异,以确定哪个像素属于每个对象。如果像素的深度差小于0.02m,则将其标记为对象的一部分。在此评估中使用的分割结果是在第III-D节中的第二个匹配步骤之后每个对象的细分片段。因此,此评估不涉及模板或CAD模型的对齐。
在这里插入图片描述
该性能与其他分割方法进行了比较:使用对象的关注点从场景中正确分割目标对象[19]和基于边缘的分割方法[23]。提供测试场景中对象的中心作为[19]方法的注意点。由[19]的作者实现的公共代码1和[23]代码2使用默认参数进行评估。对于[23]的方法,我们得出带有和不带有模糊的结果,并为每个对象在它们之间取得最佳性能。
在这里插入图片描述
如表I所示,即使MTTM使用颜色和深度值,其分割结果也优于其他方法。这表明MTTM使用NN模板的功能来预测目标对象的相应分割蒙版,而不是使用任意对象的一般边界。图4和图1中的结果清楚地显示了MTTM的优势。

C.Evaluation of Object Detection and Pose Estimation

在该评估中,假定目标对象在场景中可见。在第III-E节的第一个剔除步骤之后,最多选择50个具有较大内点的区域来计算最终分数SfinalS_{f inal}。得分最高的15个假设可以通过ICP的最多30次迭代来完善。然后,重新计算最终分数,以决定最佳预测。仅在每个识别步骤中都包含一组对象的模板。因此,每个区域都不会与其他对象类别竞争。为了分析姿势回归网络的效果,在不进行姿势回归的情况下执行了相同的实验,该实验直接使用模板的姿势进行初始对齐。度量是AD{DI}AD\{D|I\}误差,已用于测量数据集的姿态误差[10]。此误差测量的是估计姿态与地面真实姿态之间的平均顶点距离。对于对称对象,boxboxglueglue,将使用顶点最近点之间的平均距离。当误差小于每个对象的最大直径(kdk_{d})的10%时,将预测的姿势视为正确的估计。由于没有以前的工作仅使用深度图像来匹配模板以进行检测和姿态估计,因此我们将我们的方法与基于模板的方法进行了比较,该方法已被广泛用作原始数据集的基线[10]。
在这里插入图片描述
如表II所示,尽管基线方法同时使用了颜色和深度信息,但MTTM在八个对象中的六个上表现更好。第二列报告没有姿势预测的结果,与使用姿势预测相比,该结果显示出较差的性能。很明显,除了对称对象,boxboxglueglue,姿势回归网络预测的姿势比NN模板的原始姿势更相似。由于对于对称对象存在多个变换,因此姿势回归网络有时会尝试预测从模板到姿势的变换,而该姿势在可能的候选对象中不是最接近的,这会导致估计的准确性降低。该结果表明,对于非对称对象,使用姿势回归网络是值得的。由于MTTM仅预测对象的旋转并从对象的可见部分估计中心点,因此大多数故障情况是由错误的平移估计引起的。因此,当对象被部分遮挡时,初始对齐会发生偏移。

D.Real Templates and New Objects

使用真实模板和新颖对象的定性结果如图4所示。左侧图像是使用目标数据集中包含的对象的真实模板的结果。真实模板是从[10]的原始数据集中的目标对象序列得出的,该序列具有每个对象的序列,每个对象具有1,000多个图像,而没有严重遮挡。因此,这些图像可通过使用由
与细分实验中使用的方法相同。
在这里插入图片描述
图4右侧的图像显示了T-Less数据集中[12]的三个新对象的识别结果。模板可以轻松地替换为给定的训练图像,并且网络保持不变。通常,分割遮罩对于训练数据中包含的对象类别的模板不匹配具有鲁棒性,而对于新颖的对象则不具有鲁棒性,这会导致去除背景点后的检索性能变差。补充视频中提供了更多定性结果。

7.Conclusion

在实际环境中学习新对象需要直接从有限数量的示例场景而不是CAD模型或大量的训练数据中得出特征。此外,检测没有纹理的物体对于处理具有相似几何形状但不同纹理的物体很重要。 MTTM通过使用具有多任务体系结构的模板的学到的功能来解决此难题。因此,将目标对象的样本图像添加到模板数据库是新对象所需的唯一步骤。

MTTM的局限性在于,如果没有CAD模型,则无法正确评估生成的姿势假设。由于MTTM不使用颜色信息并且不产生任何本地对应关系,因此如果没有ICP并计算每个假设的适用性,则很难拒绝错误的检测。这还会导致更长的计算时间来进行评估,该过程最多需要20秒,而每个图像的识别需要1-2秒的时间才能找到NN模板并生成假设。作为进一步的工作,应研究适当的评估方法,以在没有CAD模型的情况下选择最佳假设,并减少拒绝错误检测的时间。

2018-11-27 19:07:06 lovebyz 阅读数 2629

在这里插入图片描述
SSS据说是迄今为止效果最好的图像分割算法,效果超过Mask-RCNN,我们来研究一下下。
项目测试结果在最后
Github项目:https://github.com/yaksoy/SemanticSoftSegmentation

算法主要处理步骤:

  • 特征降维,从128维特征降到3维。
  • 超像素处理,用于计算Superpixels。
  • 计算仿射变换和 Laplacian,包括:Matting Affinity,semantic affinity 和 non-local
    color affinity 以及 affinityMatrixToLaplacian.
  • 计算 Laplacian 的特征分解,得到 100 个特征向量(eigenvectors).
  • 初始化优化,主要计算初始化 soft segments,对特征进行语义初始化;关于深度网络提取的特征进行 Group segments.
  • 最终优化Final optimization,sparsification.

简介

我们提出了“光谱消光”:一种自然图像消光的新方法,它可以从适当定义的拉普拉斯矩阵的最小特征向量自动计算一组基本模糊消光分量。因此,我们的方法扩展了光谱分割技术,其目标是提取硬片段,提取软垫片组件。然后可以将这些组件用作构建块,以便以无人监督的方式或基于少量用户输入容易地构造具有语义意义的前景遮罩(Mask)。
在这里插入图片描述
在这里插入图片描述
选择和合成是图像编辑过程的核心。例如,局部调整通常从选择开始,并且组合来自不同图像的元素是产生新内容的有效方式。但是,创建准确的选择是一项繁琐的工作,尤其是涉及模糊边界和透明度时,磁性套索和魔杖等工具可以帮助用户,但他们只利用低级别的线索,并严重依赖用户的技能和图像内容的解释来产生良好的效果。此外,他们只生成二元选择,需要进一步细化以考虑软边界,如毛茸茸的狗的轮廓。还有用于帮助用户完成此任务的消光工具,但它们只会增加整个编辑过程的乏味。如果图像满足若干标准,则通过提供中间图像表示,可以加快编辑过程的速度。首先,这种分割应该提供图像的不同部分,同时还准确地表示它们之间的软转换。为了允许目标编辑,每个片段应限于图像中语义上有意义的区域的范围,例如,它不应跨越两个对象之间的边界延伸。最后,分割应该完全自动完成,不添加交互点或需要艺术家的专业知识。用于语义分割,图像消光或软颜色分割的先前方法不能满足这些特性中的至少一个。在本文中,我们引入了语义软分割,将输入图像全自动分解为一组覆盖场景对象的层,由软转换分隔。我们从谱分解角度处理语义软分割问题。我们将来自输入图像的纹理和颜色信息与我们使用经过场景分析训练的卷积神经网络生成的高级语义线索相结合。我们设计了一个图形结构,在相应的拉普拉斯矩阵的特征向量中揭示语义对象以及它们之间的软转换。我们引入了一个空间变化的层稀疏模型,它可以从特征向量生成高质量的图层,可用于图像编辑。我们证明我们的算法成功地将图像分解为少量的图层,紧凑而准确地表示场景对象,如图1所示。我们后来表明我们的算法可以成功处理对其他技术具有挑战性的图像,并提供编辑示例从我们的图层表示中受益的本地颜色调整或背景替换等操作。

软分割

软分割将图像分解成两个或更多个片段,其中每个像素可以部分地属于多于一个片段。层内容根据相应方法的具体目标而改变。例如,软色分割方法使用全局优化提取均匀颜色的软层[Singaraju和Vidal 2011;泰等人。 2007; Tan等人。 2016]或每像素颜色分解[Aksoy et al。 2016,2017b]。虽然软色段被示出对于诸如图像重新着色的若干图像编辑应用是有用的,但是它们的内容通常不尊重对象边界,不允许有针对性的编辑。为了生成空间连通的软段,Singaraju和Vidal [2011]从一组用户定义的区域开始,多次解决两层软分割问题,生成多个层。莱文等人。另一方面,[2008b]提出频谱消光,通过频谱分解自动估计一组空间连接的软段。 Singaraju和Vidal [2011]以及Levin等人。 [2008b]围绕消光拉普拉斯算子构造他们的算法[Levin et al。 2008a],它为图像中的局部软转换提供了强大的表示。我们还利用了消光拉普拉斯和谱分解,遵循光谱消光的想法。然而,与以前的工作不同,我们构建了一个图表,该图表将来自深度网络的高级信息与本地纹理信息融合,以便生成对应于图像中具有语义意义的区域的软片段。
自然图像消光。自然图像消光是用户定义的前景区域的每像素不透明度的估计。自然消光算法的典型输入是trimap,它定义了不透明前景,透明背景和未知空间区域。虽然这个问题有不同的方法,所有这些方法都利用了定义的前景和背景区域的颜色特征,但我们最密切相关的方法被归类为基于亲和力的方法。基于亲和力的方法,如封闭式消光[Levin et al。 2008a],KNN matting [Chen et al。 2013]和信息流消息[Aksoy et al。 2017a],定义像素间亲和度以构建反映图像中不透明度过渡的图形。与自然图像消光方法相反,我们依赖于自动生成的语义特征来定义我们的软段而不是trimap,并生成多个软段而不是前景分割。虽然它们看似相似,但自然消光和软分割具有根本差异。以trimap作为输入的自然消光成为前景和背景颜色建模的问题,可能是通过选择颜色样本或传播颜色信息。同时,软分段侧重于检测最适合目标应用的软转换,在我们的例子中是对应于语义边界的软转换。
有针对性的编辑传播。一些图像编辑方法依赖于用户定义的图像稀疏编辑并将它们传播到整个图像。 ScribbleBoost [Li et al。 2008]提出了一个管道,他们对用户涂鸦指定的对象进行分类,以允许编辑图像中的特定对象类,以及DeepProp [Endo et al。 2016]利用深度网络传播依赖于类的颜色编辑。艾纳德等人。 [2014]构造一个图,并与我们的方法平行,分析相应的拉普拉斯矩阵的特征分解,以创建相干的重新着色结果。 An和Pellacini [2008]和Chen等人。 [2012]还定义了像素间的亲和度,并利用拉普拉斯矩阵的性质来求解用户定义的编辑的合理传播。虽然我们的结果也可以用于目标编辑,而不是使用先验定义的编辑,我们直接将图像分解为软片段,让艺术家将它们用作各种场景中的中间图像表示并使用外部图像编辑工具。
语义分割。 随着深度神经网络的引入,语义分割得到了显着改善。 虽然关于语义分割的详细报告超出了我们的范围,但语义分割的最新技术包括Zhao等人的场景解析工作。 [2017],He等人的实例分割方法。 [2017]和Fathi等人。 [2017],Bertasius等人的工作。 [2016]通过颜色边界线索增强语义分割。 我们还使用深度网络来表示语义特征,但是我们的软分割方法是类不可知的,即我们对关于语义边界的图像的准确分割感兴趣,但是我们不打算对所选择的分类或检测进行分类。 一组课程。 其他人也利用类不可知的语义信息来提高视频去模糊的性能[Ren et al。 2017]或电影世代[Oh et al。2017年]。

方法

我们寻求自动生成输入图像的软分割,即分解为表示场景中对象的层,包括透明度和存在时的软转换。 每层的每个像素用不透明度值α∈[0,1]增强,其中α= 0表示完全透明,α= 1表示完全不透明,并且中间值表示部分不透明度。 正如该领域的其他研究,如[Aksoy等。2017b; Singaraju和Vidal 2011],我们使用附加图像形成模型:
在这里插入图片描述
即,我们将输入RGB像素表示为每个层i中的像素之和,其由相应的α值加权。 我们还约束α值在每个像素处总和为1,表示完全不透明的输入图像。
我们的方法使用与谱匹配相同的形式来制定软分割任务作为特征向量估计问题[Levin et al。2008B。 该方法的核心部分是拉普拉斯矩阵L的创建,其表示图像中的每对像素属于同一片段的可能性。 虽然光谱遮罩仅使用低级局部颜色分布来构建此矩阵,但我们描述了如何使用非局部线索和高级语义信息来增强此方法。 原始方法还描述了如何使用稀疏化从L的特征向量创建层。 我们展示了这种原创技术的轻松版本如何实际产生更好的结果。 图2显示了我们方法的概述。

背景

光谱消光算法

我们的方法建立在Levin等人的工作基础之上[2008A; 2008B]。 他们首先介绍了使用局部颜色分布来定义矩阵L的消光拉普拉斯算子,该矩阵L捕获局部贴片中每对像素之间的亲和度,通常为5×5像素。使用该矩阵,它们最小化受用户提供的约束的二次函数αTLα,其中α表示由层的所有α值构成的矢量。该公式表明,与L的小特征值相关联的特征向量在创建高质量遮罩中起着重要作用。受此观察的启发,他们随后在光谱消光方面的工作使用L的特征向量来构建软分割[Levin et al。 2008B。每个软段是K个特征向量的线性组合,其对应于最小化消光稀疏度的L的最小特征值,即,最小化部分不透明度的发生。通过最小化有利于α= 0和α= 1的能量函数来创建分段:
在这里插入图片描述
其中αip是第i个段的第p个像素的α值,E是包含具有最小特征值的L的K个特征向量的矩阵,yi是定义软段的特征向量上的线性权重,并且γ<1是参数 先控制稀疏度的强度。
当图像包含具有不同颜色的单个识别良好的对象时,光谱消光产生令人满意的结果,但它与更复杂的对象和场景斗争。 仅基于仅考虑小补丁的低级统计的消光拉普拉斯算子,其识别对象的能力有限。 在我们的工作中,我们扩展了这种方法,以融合相同拉普拉斯公式中的语义特征,捕获更高级别的概念,如场景对象,并获得更广泛的图像数据视图。
在这里插入图片描述
亲和力和拉普拉斯矩阵。 莱文等人。 [2008a]将他们的方法表示为最小二乘优化问题,直接导致拉普拉斯矩阵。 另一种方法是表达像素对之间的亲和力[Aksoy et al。2017A。 具有正亲和力的对更可能具有相似的值,零亲和力对是独立的,并且具有负亲和力的对可能具有不同的值。 在这项工作中,我们将使用亲和方法并使用众所周知的公式构建相应的归一化拉普拉斯矩阵:
在这里插入图片描述

其中W是包含所有像素对之间的亲和度的方阵,D是对应的度矩阵,即具有元素W 1, 1的对角矩阵是1个行向量。 正如Levin等人所指出的,由于存在负面的亲和力,L可能并不总是真正的拉普拉斯图,但仍然具有相似的性质,例如是半正半球。

非局部颜色亲和力

我们定义了一个额外的低级别关联项,表示基于颜色的长程交互。 一种天真的方法是在消光拉普拉斯算子的定义中使用更大的补丁。 但是,这个选项很快变得不切实际,因为它使拉普拉斯矩阵更密集。 另一种选择是从非局部邻域中采样像素以插入连接,同时保留矩阵中的一些稀疏性。 KNN消光[陈等人。 2013]和信息流消息[Aksoy等。 2017a]已经显示出与这种采样的中程相互作用的良好结果。 然而,这种策略在稀疏性和鲁棒性之间进行权衡:较少的样本可能会错过重要的图像特征,而更多的样本会使计算更容易处理。
在这里插入图片描述

添加稀疏颜色连接可提供更清晰的前景表示。
我们提出了一种基于图像分割的引导式采样。 我们使用SLIC生成2500个超像素[Achanta et al。 2012]并且估计每个超像素与半径内的所有超像素之间的亲和度,该半径对应于图像尺寸的20%。 这种方法的优点在于,每个特征都足够大以成为超像素,稀疏性仍然很高,因为我们每个超像素使用一个样本,并且它通过使用大半径连接可能断开的区域,例如, 当通过物体上的洞看到背景时。 形式上,我们定义两个超像素s和t的质心之间的颜色亲和度wC s,t,其间距小于图像尺寸的20%,如下所示:

其中cs和ct是位于[0,1]的s和t的超像素的平均颜色,erf是高斯误差函数,ac和bc是控制亲和力降低的速度和阈值变为零的参数。 erf取[-1,1]中的值,这里使用的主要是它的S形形状。 我们在所有结果中使用ac = 50和bc = 0.05。 这种亲和力基本上确保具有非常相似颜色的区域在具有挑战性的场景结构中保持连接,并且其效果在图3中示出。

高级语义亲和力

虽然非局部颜色亲和力为分割过程增加了长程交互,但它仍然是一个低级别的特征。我们的实验表明,在没有附加信息的情况下,分割仍然经常合并属于不同对象的相似颜色的图像区域。为了创建局限于语义相似区域的片段,我们添加了一个语义关联项,即一个鼓励对属于同一场景对象的像素进行分组并阻止来自不同对象的像素的术语。我们在对象识别领域的先前工作的基础上,在每个像素上计算与底层对象相关的特征向量。我们通过神经网络计算特征向量,如第3.5节所述。生成特征向量,使得对于属于同一对象fp和fq的两个像素p和q是相似的,即∥fp-fq∥≡0,并且对于不同语义区域中的第三像素r,fr远离,即∥fp - fq∥«∥fp - fr∥。

。。。
与颜色亲和力不同,语义亲和力仅涉及附近的超像素以支持创建连接的对象。 这种非局部颜色亲和力与局部语义亲和度的选择允许创建可以覆盖相同语义相干区域的空间断开区域的层。 这通常适用于通常出现在背景中的绿色和天空等元素,这使得它们可能由于遮挡而分裂为多个断开连接的组件。 由于包含局部语义亲和性,L的特征向量揭示了对象边界,如图4和图5所示。

创建图层

我们通过使用本节前面描述的亲和力来创建层,以形成拉普拉斯矩阵L.我们从该矩阵中提取特征向量,并使用两步稀疏化过程从这些特征向量创建层。
形成拉普拉斯矩阵。 我们通过将亲和度矩阵加在一起并使用公式3形成拉普拉斯矩阵L:
在这里插入图片描述
其中WL是包含消光亲和力的矩阵,WC是包含非局部颜色亲和力的矩阵(第3.2节),WS是具有语义亲和力的矩阵(第3.3节),以及控制每个术语影响的σS和σC参数,两者都设置为 是0.01。

受约束的稀疏化。我们提取对应于L的100个最小特征值的特征向量。我们使用Levin等人的优化过程形成一组中间层。 [2008b]关于Eq。 2,γ= 0.8。与在特征向量上使用k均值聚类来初始化优化的光谱消光不同,我们对由其特征向量f表示的像素使用k均值聚类。这个初始猜测与场景语义更加一致,并产生更好的软分割。我们用这种方法生成了40层,实际上,它们中的一些都是零,留下15到25个非平凡层。我们通过在由它们的平均特征向量表示的这些非平凡层上运行k = 5的k均值算法来进一步减少层数。这种方法比尝试将100个特征向量直接稀疏化为5层更好,因为这种急剧减少会使问题过度约束并且不能产生足够好的结果,特别是在哑光稀疏度方面。分组前后的初始估计软段如图7所示。我们将段数设置为5而不失一般化;虽然这个数字可以由用户根据场景结构设置,但我们观察到它对于大多数图像来说是合理的数字。因为这5个层被约束在有限数量的特征向量的子空间内,所以实现的稀疏性是次优的,在层中留下许多半透明区域,这在普通场景中是不可能的。接下来,我们介绍了稀疏程序的宽松版本来解决此问题。

实施细节

我们使用MATLAB中可用的稀疏特征分解和直接求解器来实现我们算法的约束稀疏化阶段的概念验证实现。 对于640×480图像,此步骤大约需要3分钟。 松弛的稀疏化步骤使用MATLAB的预处理共轭梯度优化实现。 每次迭代通常在50到80次迭代中收敛,并且该过程大约需要30秒。 我们算法的运行时间随着像素数的增加而线性增长。

实验分析

在语义分割,自然图像消光和软分割的交叉处的语义软分割对于数值评估具有挑战性。语义分割数据集提供的二进制标记并不总是像素精确,这使得它们不适合用于对语义软分割进行基准测试。自然图像消光方法通常在专用基准上进行评估[Rhemann等人。 2009]和数据集[Xu et al.2017]。这些基准旨在评估利用辅助输入的方法,称为trimap,定义预期的前景和背景,以及不确定的区域。此外,我们工作的语义方面超出了这些基准的范围。另一方面,软色分割是一个缺乏基本事实的可靠定义的问题。虽然Aksoy等人。 [2017b]提出了几个用于评估的盲目指标,它们专门用于软分色,也忽略了语义方面。因此,我们采用与相关方法的定性比较,并讨论各种方法之间的特征差异。

光谱消光和语义分割

在图9和图10中,我们将结果与光谱消光结果一起显示[Levin et al。 2008b]作为我们最相关的软分割方法,以及两种最先进的语义分割方法:赵等人的场景解析方法。 [2017](PSPNet)和He等人的实例分割方法。 [2017](Mask R-CNN)。补充材料中提供了更多这些比较。光谱消光每个图像生成大约20个软片段,并通过组合软片段以最大化对象分数来提供多个可选的前景遮罩。这些遮罩不是明确的结果,而是作为选项提供给用户,并且显示所有20个分段将使得比较更难以评估。相反,我们应用我们的软段分组方法,该方法将语义特征用于光谱消光的结果。所呈现的示例表明,语义分割方法虽然成功地识别和定位图像中的对象,但是在对象的边缘周围具有低精度。虽然它们的准确性对于语义分割的任务是令人满意的,但是对象边缘的错误对于图像编辑或合成应用是有问题的。在光谱的另一端,光谱消光能够成功捕获物体周围的大部分软转换。然而,由于缺乏语义信息,它们的片段通常同时覆盖多个对象,并且对于任何给定对象,alpha值通常不稀疏。相比之下,我们的方法捕获对象的整体或子对象而不对不相关的对象进行分组,并在边缘处实现高精度,包括适当时的软转换。
在这里插入图片描述

应该注意的是,我们的方法在多个段中表示相同的对象并不罕见,例如图9(2)中的马车或图9(4)中的背景栏。这主要是由于预设的层数,五,有时超过图像中有意义的区域的数量。尽管被语义特征检测到,但是在最终片段中可能遗漏一些小物体,例如图10(5)中的背景中的人。这是因为,特别是当物体的颜色与周围环境相似时,物体在特征向量中看起来没有明确定义,并且它们最终被合并为近似段。我们的语义特征不是实例感知的,即同一类的两个不同对象的特征是相似的。这导致多个对象在同一层中表示,例如图9(1)中的奶牛,图9(5)中的人或图10(3)中的长颈鹿。但是,使用实例感知功能,我们的方法将能够为不同的对象实例生成单独的软段。
在这里插入图片描述

从输入图像(a)和我们的特征向量(b),我们的方法生成(c)中所示的遮罩。 我们展示了具有不同未知区域宽度的三维图,由PSPNet使用语义段生成[Zhao et al。 2017](d)或Mask R-CNN未能可靠地提供前景和背景区域,这会影响使用信息流消息产生的消光效果[Aksoy et al。 2017a]负面。 在底部示例中,通过选择单个类(左)或与该对象对应的所有类来生成PSPNet三维图。 我们还使用由结果(f)生成的trimap提供matting结果,该trimap在给定精确trimap的情况下演示了matting算法的性能。 来自[Lin et al。2014。
对于软分割和图像消光方法而言,灰度图像尤其具有挑战性,缺少这些方法通常依赖的颜色提示。 另一方面,语义分割方法的性能在处理灰度图像时不会显着降低。 图10(5)表明我们的方法可以成功地利用语义信息进行灰度图像的软分割。
所呈现的示例表明,语义分割方法虽然成功地识别和定位图像中的对象,但是在对象的边缘周围具有低精度。 虽然它们的准确性对于语义分割的任务是令人满意的,但是对象边缘的错误对于图像编辑或合成应用是有问题的。 在光谱的另一端,光谱消光能够成功捕获物体周围的大部分软转换。 然而,由于缺乏语义信息,它们的片段通常同时覆盖多个对象,并且对于任何给定对象,alpha值通常不稀疏。 相比之下,我们的方法捕获对象的整体或子对象而不对不相关的对象进行分组,并在边缘处实现高精度,包括适当时的软转换。

应该注意的是,我们的方法在多个段中表示相同的对象并不罕见,例如图9(2)中的马车或图9(4)中的背景栏。这主要是由于预设的层数有时超过图像中有意义的区域的数量。尽管被语义特征检测到,但是在最终片段中可能遗漏一些小物体,例如图10(5)中的背景中的人。特别是因为,**当物体的颜色与周围环境相似时,物体在特征向量中看起来没有明确定义,并且它们最终被合并为近似段。我们的语义特征不是实例感知的,即同一类的两个不同对象的特征是相似的。这导致多个对象在同一层中表示,**例如图9(1)中的奶牛,图9(5)中的人或图10(3)中的长颈鹿。但是,使用实例感知功能,我们的方法将能够为不同的对象实例生成单独的软段。
在这里插入图片描述
在底部示例中,通过选择单个类(左)或与该对象对应的所有类来生成PSPNet三维图。 我们还使用由结果(f)生成的trimap提供matting结果,该trimap在给定精确trimap的情况下演示了matting算法的性能。 来自[Lin et al。2014。
对于软分割和图像消光方法而言,灰度图像尤其具有挑战性,缺少这些方法通常依赖的颜色提示。 另一方面,语义分割方法的性能在处理灰度图像时不会显着降低。 图10(5)表明我们的方法可以成功地利用语义信息进行灰度图像的软分割。

自然图像消光

原则上,语义软段可以通过级联语义分割和自然图像匹配来生成。定义前景,背景和软过渡区域的trimap可以从语义硬片段生成,以馈送到自然消光方法。秦等人[2017]对类特定问题使用类似的方法。我们在图11中展示了这种情景的两个例子,通过使用Mask R-CNN和PSPNet结果生成三维图并使用最先进的消光方法估计遮罩来证明这种方法的缺点,信息流消息[Aksoy 2017A]由自然图像消光方法做出的强烈假设是所提供的三元图是正确的,即,定义的前景和背景区域被用作硬约束以指导方法来对层颜色进行建模。然而,估计的语义边界中的不准确性通常无法提供可靠的三维图,即使具有大的未知区域宽度。如图中突出显示的,这导致消光结果中的严重伪影。我们展示了使用我们的演示结果生成的准确trimap,自然消光方法成功。
我们的软段和前景对象的相应遮罩。 请注意,通常为自然消光提供的三维图不会用于产生这些结果,[Xu et al.2017]。
在这里插入图片描述

软色分割

软色分割,最初由Tai等人提出的概念 [2007],将输入图像分解为均匀颜色的软层,并且已被证明对图像编辑和重新着色应用有用。 作为语义软段和软色段之间的概念比较,图13显示了基于非混合的软颜色分段的段[Aksoy.2017]。为了更方便的定性比较,我们使用闭合颜色估计方法估算了软段的层颜色[Levin et al。2008]
在这里插入图片描述
可以立即看到柔和色段的内容超出对象边界,而我们的结果显示同一段中具有语义意义的对象,无论其颜色内容如何。 由于这些表示彼此正交,因此可以在编排中使用它们来生成目标重新着色结果。
在这里插入图片描述
图14.我们展示了软分割结果以及使用每层操作或简单组合生成的图像编辑结果,以演示在目标图像编辑任务中使用我们的分割。

使用语义软段进行图像编辑

我们在图14中展示了用于目标图像编辑和合成的软段的几个用例。图14(1,3,4,7)显示了合成结果,其中我们使用闭合层颜色估计估算了段的层颜色[ 莱文等人2008A]。注意所选前景层和新背景之间的自然柔和过渡。 软段也可用于目标图像编辑,其中它们用于定义特定调整图层的蒙版,例如在(2)中向列车添加运动模糊,在(5,6)中分别对人和背景进行颜色分级和 (8)中的热气球,天空,地形和人的风格分开。 虽然这些编辑可以通过用户绘制的蒙版或自然消光算法完成,但我们的表示提供了方便的中间图像表示,使艺术家可以毫不费力地进行目标编辑。

限制和未来的工作

虽然我们能够生成精确的图像软分割,但在我们的原型实现中,我们的求解器并未针对速度进行优化。 因此,我们640×480图像的运行时间在3到4分钟之间 我们的方法的效率可以通过多种方式进行优化,例如多尺度求解器,但线性求解器和特征分解的有效实现超出了本文的范围。
在约束稀疏化步骤中,我们生成大约15-25个分段,然后使用特征向量将其分组为5.通过经验观察设置层数,并且在一些情况下,可以将对象分成若干层。 虽然这不会影响我们方法的适用性,因为在编辑中组合这些层是微不足道的,可以设计出更复杂的分组方法,例如通过识别和分类。
我们的方法不会为同一类对象的不同实例生成单独的图层。 这是由于我们的特征向量,它不提供实例感知语义信息。 然而,我们的软分割公式对语义特征是不可知的。 因此,更高级的特征生成器可以在与更合适的分段分组策略相结合时生成实例级软分段结果。
我们已经从自然消光数据集中显示了几个结果。 但是,应该指出的是,我们的目的并不是解决一般的自然消光问题。 自然消光是一个成熟的领域,有许多特定的挑战,例如在非常相似颜色的前景和背景区域周围生成精确的遮罩,最先进的方法取决于两个区域的颜色分布,以提高这些区域周围的性能。 如图15所示,当对象颜色非常相似时,我们的方法可能在初始约束稀疏化步骤中失败,或者由于围绕大过渡区域的不可靠的语义特征向量,软段的分组可能失败。
在这里插入图片描述
图15.显示了两种故障情况。 顶部示例:如果大区域覆盖具有非常相似颜色的不同对象(a)我们的特征向量(b)和分组前的分段(c)无法识别图像中的单独对象并导致不准确的分割(d)。 下面的示例:当我们的特征向量无法表示对象时,即使初始层能够生成准确的软转换(c),软段(d)的分组也可能失败。 来自[Rhemann等人的图片。2009年]。

结论

我们已经提出了一种方法,通过将来自神经网络的高级信息与完全自动的低级图像特征融合,生成对应于图像中的语义上有意义的区域的软片段。 我们已经表明,通过仔细定义图像中不同区域之间的亲和力,可以通过对构造的拉普拉斯矩阵的谱分析来揭示具有语义边界的软段。 所提出的用于软段的松弛稀疏化方法可以生成精确的软转换,同时还提供稀疏的层组。 我们已经证明,虽然语义分割光谱软分割方法无法提供足够精确的图像编辑任务层,但我们的软片段提供了方便的中间图像表示使得几个目标图像编辑任务变得微不足道,否则需要熟练的艺术家手工劳动。

运行过程,可以看出其运行原理:

Semantic Soft Segmentation
     Computing affinities
     Computing eigenvectors
     Initial optimization
          Computing k-means initialization using semantic features...
          Starting optimization...
               Iteration 10 of 40
               Iteration 20 of 40
               Iteration 30 of 40
               Iteration 40 of 40
     Final optimization
               Iteration 5 of 20
               Iteration 10 of 20
               Iteration 15 of 20
               Iteration 20 of 20
pcg 在解的 迭代 17 处收敛,并且相对残差为 6.9e-07。
     Done.
Spectral Matting
     Computing affinities
     Computing eigenvectors
     Optimization
          Computing k-means initialization...
          Starting optimization...
               Iteration 10 of 20
               Iteration 20 of 20
     Done.
>> 

官方结果图:
在这里插入图片描述

下面是我的个人测试结果:
在这里插入图片描述
看出差距了吧。。哈哈
 
再来点个人图(高清):
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
每次运行得到的结果都有点区别,而且算法只能计算宽大于高的图片,否则报未知错误。测试4G内存笔记本跑大于1M图片卡死,测试500k图片运行时间10分钟左右吧。测试效果。。emmm。。。

2017-01-05 09:33:43 DaveBobo 阅读数 3258

cv篇包含以下几个部分:

图像处理

结构分析

运动分析与对象跟踪

模式识别

相机标定和三维重建

  1、图像处理

图像处理主要针对二维像素数组,但是我们称这些数组为“图像”,但是它们不一定非得是IplImage 结构,也可以是CvMat或者CvMatND结构。

梯度、边缘和角点

Sobel

使用扩展 Sobel 算子计算一阶、二阶、三阶或混合图像差分

void cvSobel( const CvArr* src, CvArr* dst, int xorder, int yorder, int aperture_>

src

输入图像.

dst

输出图像.

xorder

x 方向上的差分阶数

yorder

y 方向上的差分阶数

aperture_size

扩展 Sobel 核的大小,必须是 1, 3, 5 或 7。 除了尺寸为 1, 其它情况下, aperture_size ×aperture_size 可分离内核将用来计算差分。对 aperture_>

对 x-方向 或矩阵转置后对 y-方向。

函数 cvSobel 通过对图像用相应的内核进行卷积操作来计算图像差分:

由于Sobel 算子结合了 Gaussian 平滑和微分,所以,其结果或多或少对噪声有一定的鲁棒性。通常情况,函数调用采用如下参数 (xorder=1, yorder=0, aperture_>

核。

第二种对应:

核的选则依赖于图像原点的定义 (origin 来自 IplImage 结构的定义)。由于该函数不进行图像尺度变换,所以和输入图像(数组)相比,输出图像(数组)的元素通常具有更大的绝对数值(译者注:即像素的位深)。为防止溢出,当输入图像是 8 位的,要求输出图像是 16 位的。当然可以用函数 cvConvertScale 或 cvConvertScaleAbs 把运算结果(dst)转换为 8 位的。除了8-位图像,函数也接受 32-位 浮点数图像。所有输入和输出图像都必须是单通道的,并且具有相同的图像尺寸或者ROI尺寸。

Laplace :计算图像的 Laplacian 变换

void cvLaplace( const CvArr* src, CvArr* dst, int aperture_>

src : 输入图像.

dst : 输出图像.

aperture_size : 核大小 (与 cvSobel 中定义一样).

函数 cvLaplace 计算输入图像的 Laplacian变换,方法是先用 sobel 算子计算二阶 x- 和 y- 差分,再求和:

对 aperture_>

类似于 cvSobel 函数,该函数也不作图像的尺度变换,所支持的输入、输出图像类型的组合和cvSobel一致。


Canny:采用 Canny算法做边缘检测

void cvCanny( const CvArr* image, CvArr* edges, double threshold1,

              double threshold2, int aperture_>

image : 单通道输入图像.

edges : 单通道存储边缘的输出图像

threshold1 : 第一个阈值

threshold2 : 第二个阈值

aperture_size : Sobel 算子内核大小 (见 cvSobel).

函数 cvCanny 采用 CANNY 算法发现输入图像的边缘而且在输出图像中标识这些边缘。threshold1和threshold2 当中的小阈值用来控制边缘连接,大的阈值用来控制强边缘的初始分割。

  • 注意事项:cvCanny只接受单通道图像作为输入。


PreCornerDetect:计算用于角点检测的特征图,

void cvPreCornerDetect( const CvArr* image, CvArr* corners, int aperture_>

image : 输入图像.

corners : 保存候选角点的特征图

aperture_size : Sobel 算子的核大小(见cvSobel).

函数 cvPreCornerDetect 计算函数 其中 表示一阶图像差分, 表示二阶图像差分。 角点被认为是函数的局部最大值:

// 假设图像格式为浮点数

IplImage* corners = cvCloneImage(image);

IplImage* dilated_corners = cvCloneImage(image);

IplImage* corner_mask = cvCreateImage( cvGetSize(image), 8, 1 );

cvPreCornerDetect( image, corners, 3 );

cvDilate( corners, dilated_corners, 0, 1 );

cvSub( corners, dilated_corners, corners );

cvCmpS( corners, 0, corner_mask, CV_CMP_GE );

cvReleaseImage( &corners );

cvReleaseImage( &dilated_corners );

 

CornerEigenValsAndVecs:计算图像块的特征值和特征向量,用于角点检测

void cvCornerEigenValsAndVecs( const CvArr* image, CvArr* eigenvv,

                               int block_size, int aperture_>

image : 输入图像.

eigenvv : 保存结果的数组。必须比输入图像宽 6 倍。

block_size : 邻域大小 (见讨论).

aperture_size : Sobel 算子的核尺寸(见 cvSobel).

对每个象素,函数 cvCornerEigenValsAndVecs 考虑 block_size × block_size 大小的邻域 S(p),然后在邻域上计算图像差分的相关矩阵:


然后它计算矩阵的特征值和特征向量,并且按如下方式(λ1, λ2, x1, y1, x2, y2)存储这些值到输出图像中,其中

λ1, λ2 - M 的特征值,没有排序

(x1, y1) - 特征向量,对 λ1

(x2, y2) - 特征向量,对 λ2

 

CornerMinEigenVal:计算梯度矩阵的最小特征值,用于角点检测

void cvCornerMinEigenVal( const CvArr* image, CvArr* eigenval, int block_size, int aperture_>

image : 输入图像.

eigenval : 保存最小特征值的图像. 与输入图像大小一致

block_size : 邻域大小 (见讨论 cvCornerEigenValsAndVecs).

aperture_size : Sobel 算子的核尺寸(见 cvSobel). 当输入图像是浮点数格式时,该参数表示用来计算差分固定的浮点滤波器的个数.

函数 cvCornerMinEigenVal 与 cvCornerEigenValsAndVecs 类似,但是它仅仅计算和存储每个象素点差分相关矩阵的最小特征值,即前一个函数的 min(λ1, λ2)


CornerHarris

哈里斯(Harris)角点检测:void cvCornerHarris( const CvArr* image, CvArr* harris_responce, int block_size, int aperture_>

image : 输入图像。

harris_responce : 存储哈里斯(Harris)检测responces的图像。与输入图像等大。

block_size : 邻域大小(见关于cvCornerEigenValsAndVecs的讨论)。

aperture_size : 扩展 Sobel 核的大小(见 cvSobel)。格式. 当输入图像是浮点数格式时,该参数表示用来计算差分固定的浮点滤波器的个数。

k----harris 检测器的自由参数。参见下面的公式。

函数 cvCornerHarris 对输入图像进行 Harris 边界检测。类似于 cvCornerMinEigenVal 和 cvCornerEigenValsAndVecs。对每个像素,在 block_size*block_size 大小的邻域上,计算其2*2梯度共变矩阵(或相关异变矩阵)M。然后,将

det(M) - k*trace(M)2 (这里2是平方)

存到输出图像中。输入图像中的角点在输出图像中由局部最大值表示。

 

FindCornerSubPix:精确角点位置

void cvFindCornerSubPix( const CvArr* image, CvPoint2D32f* corners,

                         int count, CvSize win, CvSize zero_zone,

                         CvTermCriteria criteria );

image : 输入图像.

corners : 输入角点的初始坐标,也存储精确的输出坐标

count : 角点数目

win : 搜索窗口的一半尺寸。如果 win=(5,5) 那么使用 5*2+1 × 5*2+1 = 11 × 11 大小的搜索窗口

zero_zone : 死区的一半尺寸,死区为不对搜索区的中央位置做求和运算的区域。它是用来避免自相关矩阵出现的某些可能的奇异性。当值为 (-1,-1) 表示没有死区。

criteria:求角点的迭代过程的终止条件。即角点位置的确定,要么迭代数大于某个设定值,或者是精确度达到某个设定值。 criteria 可以是最大迭代数目,或者是设定的精确度,也可以是它们的组合。

函数 cvFindCornerSubPix 通过迭代来发现具有子象素精度的角点位置,或如图所示的放射鞍点(radial saddle points)。

子象素级角点定位的实现是基于对向量正交性的观测而实现的,即从中央点q到其邻域点p 的向量和p点处的图像梯度正交(服从图像和测量噪声)。考虑以下的表达式:

εi=DIpiT•(q-pi)

其中,DIpi表示在q的一个邻域点pi处的图像梯度,q的值通过最小化εi得到。通过将εi设为0,可以建立系统方程如下:

sumi(DIpi•DIpiT)•q - sumi(DIpi•DIpiT•pi) = 0

其中q的邻域(搜索窗)中的梯度被累加。调用第一个梯度参数G和第二个梯度参数b,得到:

q=G-1•b

该算法将搜索窗的中心设为新的中心q,然后迭代,直到找到低于某个阈值点的中心位置。


GoodFeaturesToTrack:确定图像的强角点

void cvGoodFeaturesToTrack( const CvArr* image, CvArr* eig_image, CvArr* temp_image,

                            CvPoint2D32f* corners, int* corner_count,

                            double quality_level, double min_distance,

                            const CvArr* mask=NULL );

image : 输入图像,8-位或浮点32-比特,单通道

eig_image : 临时浮点32-位图像,尺寸与输入图像一致

temp_image : 另外一个临时图像,格式与尺寸与 eig_image 一致

corners : 输出参数,检测到的角点

corner_count : 输出参数,检测到的角点数目

quality_level : 最大最小特征值的乘法因子。定义可接受图像角点的最小质量因子。

min_distance : 限制因子。得到的角点的最小距离。使用 Euclidian 距离

mask

ROI:感兴趣区域。函数在ROI中计算角点,如果 mask 为 NULL,则选择整个图像。 必须为单通道的灰度图,大小与输入图像相同。mask对应的点不为0,表示计算该点。

函数 cvGoodFeaturesToTrack 在图像中寻找具有大特征值的角点。该函数,首先用cvCornerMinEigenVal 计算输入图像的每一个象素点的最小特征值,并将结果存储到变量 eig_image 中。然后进行非最大值抑制(仅保留3x3邻域中的局部最大值)。下一步将最小特征值小于 quality_level•max(eig_image(x,y)) 排除掉。最后,函数确保所有发现的角点之间具有足够的距离,(最强的角点第一个保留,然后检查新的角点与已有角点之间的距离大于 min_distance )。

采样、插值和几何变换

InitLineIterator : 初始化线段迭代器

int cvInitLineIterator( const CvArr* image, CvPoint pt1, CvPoint pt2,

                        CvLineIterator* line_iterator, int connectivity=8 );

image : 带采线段的输入图像.

pt1 : 线段起始点

pt2 : 线段结束点

line_iterator : 指向线段迭代器状态结构的指针

connectivity : 被扫描线段的连通数,4 或 8.

函数 cvInitLineIterator 初始化线段迭代器,并返回两点之间的象素点数目。两个点必须在图像内。当迭代器初始化后,连接两点的光栅线上所有点,都可以连续通过调用 CV_NEXT_LINE_POINT 来得到。线段上的点是使用 4-连通或8-连通利用 Bresenham 算法逐点计算的。

例子:使用线段迭代器计算彩色线上象素值的和

CvScalar sum_line_pixels( IplImage* image, CvPoint pt1, CvPoint pt2 )

{

    CvLineIterator iterator;

    int blue_sum = 0, green_sum = 0, red_sum = 0;

    int count = cvInitLineIterator( image, pt1, pt2, &iterator, 8 );

 

    for( int i = 0; i < count; i++ ){

        blue_sum += iterator.ptr[0];

        green_sum += iterator.ptr[1];

        red_sum += iterator.ptr[2];

        CV_NEXT_LINE_POINT(iterator);

 

        /* print the pixel coordinates: demonstrates how to calculate the coordinates */

        {

        int offset, x, y;

        /* assume that ROI is not set, otherwise need to take it into account. */

        offset = iterator.ptr - (uchar*)(image->imageData);

        y = offset/image->widthStep;

        x = (offset - y*image->widthStep)/(3*sizeof(uchar) /* size of pixel */);

        printf("(%d,%d)\n", x, y );

        }

    }

    return cvScalar( blue_sum, green_sum, red_sum );

}

 

SampleLine : 将图像上某一光栅线上的像素数据读入缓冲区

int cvSampleLine( const CvArr* image, CvPoint pt1, CvPoint pt2,

                  void* buffer, int connectivity=8 );

image : 输入图像

pt1 : 光栅线段的起点

pt2 : 光栅线段的终点

buffer : 存储线段点的缓存区,必须有足够大小来存储点 max( |pt2.x-pt1.x|+1, |pt2.y-pt1.y|+1 ) :8-连通情况下,或者 |pt2.x-pt1.x|+|pt2.y-pt1.y|+1 : 4-连通情况下.

connectivity : 线段的连通方式, 4 or 8.

函数 cvSampleLine 实现了线段迭代器的一个特殊应用。它读取由 pt1 和 pt2 两点确定的线段上的所有图像点,包括终点,并存储到缓存中。

GetRectSubPix : 从图像中提取象素矩形,使用子象素精度

void cvGetRectSubPix( const CvArr* src, CvArr* dst, CvPoint2D32f center );

src : 输入图像.

dst : 提取的矩形.

center : 提取的象素矩形的中心,浮点数坐标。中心必须位于图像内部.

函数 cvGetRectSubPix 从图像 src 中提取矩形:

dst(x, y) = src(x + center.x - (width(dst)-1)*0.5, y + center.y - (height(dst)-1)*0.5)

其中非整数象素点坐标采用双线性插值提取。对多通道图像,每个通道独立单独完成提取。尽管函数要求矩形的中心一定要在输入图像之中,但是有可能出现矩形的一部分超出图像边界的情况,这时,该函数复制边界的模识(hunnish:即用于矩形相交的图像边界线段的象素来代替矩形超越部分的象素)。

 

GetQuadrangleSubPix : 提取象素四边形,使用子象素精度

void cvGetQuadrangleSubPix( const CvArr* src, CvArr* dst, const CvMat* map_matrix );

src : 输入图像.

dst : 提取的四边形.

map_matrix

3 × 2 变换矩阵 [A|b] (见讨论).

函数 cvGetQuadrangleSubPix 以子象素精度从图像 src 中提取四边形,使用子象素精度,并且将结果存储于 dst ,计算公式是:

dst(x + width(dst) / 2,y + height(dst) / 2) = src(A11x + A12y + b1,A21x + A22y + b2)

其中 A和 b 均来自映射矩阵(译者注:A, b为几何形变参数) ,映射矩阵为:

其中在非整数坐标 的象素点值通过双线性变换得到。当函数需要图像边界外的像素点时,使用重复边界模式(replication border mode)恢复出所需的值。多通道图像的每一个通道都单独计算。

例子:使用 cvGetQuadrangleSubPix 进行图像旋转

#include "cv.h"

#include "highgui.h"

#include "math.h"

 

int main( int argc, char** argv )

{

    IplImage* src;

    /* the first command line parameter must be image file name */

    if( argc==2 && (src = cvLoadImage(argv[1], -1))!=0)

    {

        IplImage* dst = cvCloneImage( src );

        int delta = 1;

        int angle = 0;

 

        cvNamedWindow( "src", 1 );

        cvShowImage( "src", src );

 

        for(;;)

        {

            float m[6];

            double factor = (cos(angle*CV_PI/180.) + 1.1)*3;

            CvMat M = cvMat( 2, 3, CV_32F, m );

            int w = src->width;

            int h = src->height;

 

            m[0] = (float)(factor*cos(-angle*2*CV_PI/180.));

            m[1] = (float)(factor*sin(-angle*2*CV_PI/180.));

            m[2] = w*0.5f;

            m[3] = -m[1];

            m[4] = m[0];

            m[5] = h*0.5f;

 

            cvGetQuadrangleSubPix( src, dst, &M, 1, cvScalarAll(0));

 

            cvNamedWindow( "dst", 1 );

            cvShowImage( "dst", dst );

 

            if( cvWaitKey(5) == 27 )

                break;

 

            angle = (angle + delta) % 360;

        }

    }

    return 0;

}

 

Resize 缩放

void cvResize( const CvArr* src, CvArr* dst, int interpolation=CV_INTER_LINEAR );

src : 输入图像.

dst : 输出图像.

interpolation : 插值方法:

·   CV_INTER_NN - 最近邻插值,

· CV_INTER_LINEAR - 双线性插值 (缺省使用)

·   CV_INTER_AREA - 使用象素关系重采样。当图像缩小时候,该方法可以避免波纹出现。当图像放大时,类似于 CV_INTER_NN 方法..

·   CV_INTER_CUBIC - 立方插值.

函数 cvResize 将图像 src 改变尺寸得到与 dst 同样大小。若设定 ROI,函数将按常规支持 ROI.


WarpAffine : 对图像做仿射变换

void cvWarpAffine( const CvArr* src, CvArr* dst, const CvMat* map_matrix,

                   int flags=CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS,

                   CvScalar fillval=cvScalarAll(0) );

src : 输入图像.

dst : 输出图像.

map_matrix : 2×3 变换矩阵

flags : 插值方法和以下开关选项的组合:

·   CV_WARP_FILL_OUTLIERS - 填充所有输出图像的象素。如果部分象素落在输入图像的边界外,那么它们的值设定为 fillval.

·   CV_WARP_INVERSE_MAP - 指定 map_matrix 是输出图像到输入图像的反变换,因此可以直接用来做象素插值。否则, 函数从 map_matrix 得到反变换。

fillval : 用来填充边界外面的值


函数 cvWarpAffine 利用下面指定的矩阵变换输入图像:

  • 如果没有指定 CV_WARP_INVERSE_MAP , ,
  • 否则,函数与 cvGetQuadrangleSubPix 类似,但是不完全相同。 cvWarpAffine 要求输入和输出图像具有同样的数据类型,有更大的资源开销(因此对小图像不太合适)而且输出图像的部分可以保留不变。而 cvGetQuadrangleSubPix 可以精确地从8位图像中提取四边形到浮点数缓存区中,具有比较小的系统开销,而且总是全部改变输出图像的内容。

要变换稀疏矩阵,使用 cxcore 中的函数 cvTransform 。

GetAffineTransform : 由三对点计算仿射变换

CvMat* cvGetAffineTransform( const CvPoint2D32f* src, const CvPoint2D32f* dst, CvMat* map_matrix );

src : 输入图像的三角形顶点坐标。

dst : 输出图像的相应的三角形顶点坐标。

map_matrix : 指向2×3输出矩阵的指针。

 

2DRotationMatrix : 计算二维旋转的仿射变换矩阵

CvMat* cv2DRotationMatrix( CvPoint2D32f center, double angle,

                           double scale, CvMat* map_matrix );

center : 输入图像的旋转中心坐标

angle : 旋转角度(度)。正值表示逆时针旋转(坐标原点假设在左上角).

scale : 各项同性的尺度因子

map_matrix : 输出 2×3 矩阵的指针

函数 cv2DRotationMatrix 计算矩阵:

[  α  β  |  (1-α)*center.x - β*center.y ]

[ -β  α  |  β*center.x + (1-α)*center.y ]

 

where α=scale*cos(angle), β=scale*sin(angle)

该变换并不改变原始旋转中心点的坐标,如果这不是操作目的,则可以通过调整平移量改变其坐标(译者注:通过简单的推导可知,仿射变换的实现是首先将旋转中心置为坐标原点,再进行旋转和尺度变换,最后重新将坐标原点设定为输入图像的左上角,这里的平移量是center.x, center.y).

 

WarpPerspective : 对图像进行透视变换

void cvWarpPerspective( const CvArr* src, CvArr* dst, const CvMat* map_matrix,

                        int flags=CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS,

                        CvScalar fillval=cvScalarAll(0) );

src : 输入图像.

dst : 图像.

map_matrix : 3×3 变换矩阵

flags : 插值方法和以下开关选项的组合:

·   CV_WARP_FILL_OUTLIERS - 填充所有缩小图像的象素。如果部分象素落在输入图像的边界外,那么它们的值设定为 fillval.

·   CV_WARP_INVERSE_MAP - 指定 matrix 是输出图像到输入图像的反变换,因此可以直接用来做象素插值。否则, 函数从 map_matrix 得到反变换。

fillval : 用来填充边界外面的值

函数 cvWarpPerspective 利用下面指定矩阵变换输入图像:如果没有指定 CV_WARP_INVERSE_MAP , ,

  • 否则,要变换稀疏矩阵,使用 cxcore 中的函数 cvTransform 。

 

WarpPerspectiveQMatrix : 用4个对应点计算透视变换矩阵

CvMat* cvWarpPerspectiveQMatrix( const CvPoint2D32f* src,

                                 const CvPoint2D32f* dst,

                                 CvMat* map_matrix );

src : 输入图像的四边形的4个点坐标

dst : 输出图像的对应四边形的4个点坐标

map_matrix : 输出的 3×3 矩阵

函数 cvWarpPerspectiveQMatrix 计算透视变换矩阵,使得:

(tix'i,tiy'i,ti)T=matrix•(xi,yi,1)T

其中 dst(i)=(x'i,y'i), src(i)=(xi,yi), i=0..3.

 

GetPerspectiveTransform : 由四对点计算透射变换

CvMat* cvGetPerspectiveTransform( const CvPoint2D32f* src, const CvPoint2D32f* dst,

                                  CvMat* map_matrix );

 

#define cvWarpPerspectiveQMatrix cvGetPerspectiveTransform

src : 输入图像的四边形顶点坐标。

dst : 输出图像的相应的四边形顶点坐标。

map_matrix : 指向3×3输出矩阵的指针。

函数cvGetPerspectiveTransform计算满足以下关系的透射变换矩阵:

这里,dst(i) = (x'i,y'i),src(i) = (xi,yi),i = 0..3.

 

 Remap : 对图像进行普通几何变换

void cvRemap( const CvArr* src, CvArr* dst,

              const CvArr* mapx, const CvArr* mapy,

              int flags=CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS,

              CvScalar fillval=cvScalarAll(0) );

src : 输入图像.

dst : 输出图像.

mapx : x坐标的映射 (32fC1 image).

mapy : y坐标的映射 (32fC1 image).

flags : 插值方法和以下开关选项的组合:

·   CV_WARP_FILL_OUTLIERS - 填充边界外的像素. 如果输出图像的部分象素落在变换后的边界外,那么它们的值设定为 fillval。

fillval : 用来填充边界外面的值.

函数 cvRemap 利用下面指定的矩阵变换输入图像:

dst(x,y)<-src(mapx(x,y),mapy(x,y))

与其它几何变换类似,可以使用一些插值方法(由用户指定,译者注:同cvResize)来计算非整数坐标的像素值。

 

LogPolar : 把图像映射到极指数空间

void cvLogPolar( const CvArr* src, CvArr* dst,

                 CvPoint2D32f center, double M,

                 int flags=CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS );

src : 输入图像。

dst : 输出图像。

center : 变换的中心,输出图像在这里最精确。

M : 幅度的尺度参数,见下面公式。

flags : 插值方法和以下选择标志的结合

·   CV_WARP_FILL_OUTLIERS -填充输出图像所有像素,如果这些点有和外点对应的,则置零。

·   CV_WARP_INVERSE_MAP - 表示矩阵由输出图像到输入图像的逆变换,并且因此可以直接用于像素插值。否则,函数从map_matrix中寻找逆变换。

fillval : 用于填充外点的值。

函数cvLogPolar用以下变换变换输入图像:

正变换 (CV_WARP_INVERSE_MAP 未置位):

dst(phi,rho)<-src(x,y)

逆变换 (CV_WARP_INVERSE_MAP 置位):

dst(x,y)<-src(phi,rho),

这里,

rho=M+log(sqrt(x2+y2))

phi=atan(y/x)

此函数模仿人类视网膜中央凹视力,并且对于目标跟踪等可用于快速尺度和旋转变换不变模板匹配。

Example. Log-polar transformation.

#include <cv.h>

#include <highgui.h>

 

int main(int argc, char** argv)

{

    IplImage* src;

 

    if(argc == 2 && ((src=cvLoadImage(argv[1],1)) != 0 ))

    {

        IplImage* dst = cvCreateImage( cvSize(256,256), 8, 3 );

        IplImage* src2 = cvCreateImage( cvGetSize(src), 8, 3 );

        cvLogPolar( src, dst, cvPoint2D32f(src->width/2,src->height/2), 40, CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS );

        cvLogPolar( dst, src2, cvPoint2D32f(src->width/2,src->height/2), 40, CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS+CV_WARP_INVERSE_MAP );

        cvNamedWindow( "log-polar", 1 );

        cvShowImage( "log-polar", dst );

        cvNamedWindow( "inverse log-polar", 1 );

        cvShowImage( "inverse log-polar", src2 );

        cvWaitKey();

    }

    return 0;

}

And this is what the program displays when OpenCV/samples/c/fruits.jpg is passed to it

 

形态学操作

 

CreateStructuringElementEx : 创建结构元素

IplConvKernel* cvCreateStructuringElementEx( int cols, int rows, int anchor_x, int anchor_y,

                                             int shape, int* values=NULL );

cols : 结构元素的列数目

rows : 结构元素的行数目

anchor_x : 锚点的相对水平偏移量

anchor_y : 锚点的相对垂直偏移量

shape : 结构元素的形状,可以是下列值:

·   CV_SHAPE_RECT, 长方形元素;

·   CV_SHAPE_CROSS, 交错元素 a cross-shaped element;

·   CV_SHAPE_ELLIPSE, 椭圆元素;

·   CV_SHAPE_CUSTOM, 用户自定义元素。这种情况下参数 values 定义了 mask,即象素的那个邻域必须考虑。

values : 指向结构元素的指针,它是一个平面数组,表示对元素矩阵逐行扫描。(非零点表示该点属于结构元)。如果指针为空,则表示平面数组中的所有元素都是非零的,即结构元是一个长方形(该参数仅仅当shape参数是 CV_SHAPE_CUSTOM 时才予以考虑)。

函数 cv CreateStructuringElementEx 分配和填充结构 IplConvKernel, 它可作为形态操作中的结构元素。

 

ReleaseStructuringElement : 删除结构元素

void cvReleaseStructuringElement( IplConvKernel** element );

element : 被删除的结构元素的指针

函数 cvReleaseStructuringElement 释放结构 IplConvKernel 。如果 *element 为 NULL, 则函数不作用。


Erode : 使用任意结构元素腐蚀图像

void cvErode( const CvArr* src, CvArr* dst, IplConvKernel* element=NULL, int iterations=1 );

src : 输入图像.

dst : 输出图像.

element : 用于腐蚀的结构元素。若为 NULL, 则使用 3×3 长方形的结构元素

iterations : 腐蚀的次数

函数 cvErode 对输入图像使用指定的结构元素进行腐蚀,该结构元素决定每个具有最小值象素点的邻域形状:

dst=erode(src,element):  dst(x,y)=min((x',y') in element))src(x+x',y+y')

函数可能是本地操作,不需另外开辟存储空间的意思。腐蚀可以重复进行 (iterations) 次. 对彩色图像,每个彩色通道单独处理。

 

Dilate : 使用任意结构元素膨胀图像

void cvDilate( const CvArr* src, CvArr* dst, IplConvKernel* element=NULL, int iterations=1 );

src : 输入图像.

dst : 输出图像.

element : 用于膨胀的结构元素。若为 NULL, 则使用 3×3 长方形的结构元素

iterations : 膨胀的次数

函数 cvDilate 对输入图像使用指定的结构元进行膨胀,该结构决定每个具有最小值象素点的邻域形状:

dst=dilate(src,element):  dst(x,y)=max((x',y') in element))src(x+x',y+y')

函数支持(in-place)模式。膨胀可以重复进行 (iterations) 次. 对彩色图像,每个彩色通道单独处理。

 

MorphologyEx : 高级形态学变换

void cvMorphologyEx( const CvArr* src, CvArr* dst, CvArr* temp,

                     IplConvKernel* element, int operation, int iterations=1 );

src : 输入图像.

dst : 输出图像.

temp : 临时图像,某些情况下需要

element : 结构元素

operation : 形态操作的类型:

CV_MOP_OPEN - 开运算

CV_MOP_CLOSE - 闭运算

CV_MOP_GRADIENT - 形态梯度

CV_MOP_TOPHAT - "顶帽"

CV_MOP_BLACKHAT - "黑帽"

iterations : 膨胀和腐蚀次数.

函数 cvMorphologyEx 在膨胀和腐蚀基本操作的基础上,完成一些高级的形态变换:

开运算 : dst=open(src,element)=dilate(erode(src,element),element)

闭运算 : dst=close(src,element)=erode(dilate(src,element),element)

形态梯度 : dst=morph_grad(src,element)=dilate(src,element)-erode(src,element)

"顶帽" : dst=tophat(src,element)=src-open(src,element)

"黑帽" : dst=blackhat(src,element)=close(src,element)-src

临时图像 temp 在形态梯度以及对“顶帽”和“黑帽”操作时的 in-place 模式下需要。

 

滤波器与色彩空间变换

 

Smooth : 各种方法的图像平滑

void cvSmooth( const CvArr* src, CvArr* dst,

               int smoothtype=CV_GAUSSIAN,

               int param1=3, int param2=0, double param3=0, double param4=0 );

src : 输入图像.

dst : 输出图像.

smoothtype : 平滑方法:

·   CV_BLUR_NO_SCALE (简单不带尺度变换的模糊) - 对每个象素的 param1×param2 领域求和。如果邻域大小是变化的,可以事先利用函数 cvIntegral 计算积分图像。

·   CV_BLUR (simple blur) - 对每个象素param1×param2邻域 求和并做尺度变换 1/(param1•param2).

·   CV_GAUSSIAN (gaussian blur) - 对图像进行核大小为 param1×param2 的高斯卷积

·   CV_MEDIAN (median blur) - 对图像进行核大小为param1×param1 的中值滤波 (i.e. 邻域是方的).

·   CV_BILATERAL (双向滤波) - 应用双向 3x3 滤波,彩色 sigma=param1,空间 sigma=param2. 关于双向滤波,可参考http://www.dai.ed.ac.uk/CVonline/LOCAL_COPIES/MANDUCHI1/Bilateral_Filtering.html

param1 : 平滑操作的第一个参数.

param2 : 平滑操作的第二个参数. 对于简单/非尺度变换的高斯模糊的情况,如果param2的值 为零,则表示其被设定为param1。

param3 : 对应高斯参数的 Gaussian sigma (标准差). 如果为零,则标准差由下面的核尺寸计算:

sigma = (n/2 - 1)*0.3 + 0.8, 其中 n=param1 对应水平核,

                                 n=param2 对应垂直核.

对小的卷积核 (3×3 to 7×7) 使用如上公式所示的标准 sigma 速度会快。如果 param3 不为零,而 param1 和 param2 为零,则核大小有 sigma 计算 (以保证足够精确的操作).

函数 cvSmooth 可使用上面任何一种方法平滑图像。每一种方法都有自己的特点以及局限。

没有缩放的图像平滑仅支持单通道图像,并且支持8位到16位的转换(与cvSobel和cvaplace相似)和32位浮点数到32位浮点数的变换格式。

简单模糊和高斯模糊支持 1- 或 3-通道, 8-比特 和 32-比特 浮点图像。这两种方法可以(in-place)方式处理图像。

中值和双向滤波工作于 1- 或 3-通道, 8-位图像,但是不能以 in-place 方式处理图像.

 

中值滤波

中值滤波法是一种非线性平滑技术,它将每一象素点的灰度值设置为该点某邻域窗口内的所有象素点灰度值的中值。实现方法:

  1. 通过从图像中的某个采样窗口取出奇数个数据进行排序
  2. 用排序后的中值取代要处理的数据即可

中值滤波法对消除椒盐噪音非常有效,在光学测量条纹图象的相位分析处理方法中有特殊作用,但在条纹中心分析方法中作用不大。中值滤波在图像处理中,常用于用来保护边缘信息,是经典的平滑噪声的方法

中值滤波原理

中值滤波是基于排序统计理论的一种能有效抑制噪声的非线性信号处理技术,中值滤波的基本原理是把数字图像或数字序列中一点的值用该点的一个拎域中各点值的中值代替,让周围的像素值接近的值,从而消除孤立的噪声点。方法是去某种结构的二维滑动模板,将板内像素按照像素值的大小进行排序,生成单调上升(或下降)的为二维数据序列。二维中值滤波输出为g(x,y)=med{f(x-k,y-l),(k,l∈W)} ,其中,f(x,y),g(x,y)分别为原始图像和处理后图像。W为二维模板,通常为2*2,3*3区域,也可以是不同的的形状,如线状,圆形,十字形,圆环形等。

高斯滤波

高斯滤波实质上是一种信号的滤波器,其用途是信号的平滑处理,我们知道数字图像用于后期应用,其噪声是最大的问题,由于误差会累计传递等原因,很多图像处理教材会在很早的时候介绍Gauss滤波器,用于得到信噪比SNR较高的图像(反应真实信号)。于此相关的有Gauss-Lapplace变换,其实就是为了得到较好的图像边缘,先对图像做Gauss平滑滤波,剔除噪声,然后求二阶导矢,用二阶导的过零点确定边缘,在计算时也是频域乘积=>空域卷积。

滤波器就是建立的一个数学模型,通过这个模型来将图像数据进行能量转化,能量低的就排除掉,噪声就是属于低能量部分

其实编程运算的话就是一个模板运算,拿图像的八连通区域来说,中间点的像素值就等于八连通区的像素值的均值,这样达到平滑的效果

若使用理想滤波器,会在图像中产生振铃现象。采用高斯滤波器的话,系统函数是平滑的,避免了振铃现象。

 

Filter2D : 对图像做卷积

void cvFilter2D( const CvArr* src, CvArr* dst,

                 const CvMat* kernel,

                 CvPoint anchor=cvPoint(-1,-1));

src : 输入图像.

dst : 输出图像.

kernel : 卷积核, 单通道浮点矩阵. 如果想要应用不同的核于不同的通道,先用 cvSplit 函数分解图像到单个色彩通道上,然后单独处理。

anchor : 核的锚点表示一个被滤波的点在核内的位置。锚点应该处于核内部。缺省值 (-1,-1) 表示锚点在核中心。

函数 cvFilter2D 对图像进行线性滤波,支持 In-place 操作。当核运算部分超出输入图像时,函数从最近邻的图像内部象素插值得到边界外面的象素值。

 

CopyMakeBorder : 复制图像并且制作边界。

void cvCopyMakeBorder( const CvArr* src, CvArr* dst, CvPoint offset,

                       int bordertype, CvScalar value=cvScalarAll(0) );

src : 输入图像。

dst : 输出图像。

offset :输入图像(或者其ROI)欲拷贝到的输出图像长方形的左上角坐标(或者左下角坐标,如果以左下角为原点)。长方形的尺寸要和原图像的尺寸的ROI分之一匹配。

bordertype : 已拷贝的原图像长方形的边界的类型:

IPL_BORDER_CONSTANT - 填充边界为固定值,值由函数最后一个参数指定。IPL_BORDER_REPLICATE -边界用上下行或者左右列来复制填充。(其他两种IPL边界类型, IPL_BORDER_REFLECT 和IPL_BORDER_WRAP现已不支持)。

value : 如果边界类型为IPL_BORDER_CONSTANT的话,那么此为边界像素的值。

函数cvCopyMakeBorder拷贝输入2维阵列到输出阵列的内部并且在拷贝区域的周围制作一个指定类型的边界。函数可以用来模拟和嵌入在指定算法实现中的边界不同的类型。例如:和opencv中大多数其他滤波函数一样,一些形态学函数内部使用复制边界类型,但是用户可能需要零边界或者填充为1或255的边界。

 

Integral : 计算积分图像

void cvIntegral( const CvArr* image, CvArr* sum, CvArr* sqsum=NULL, CvArr* tilted_sum=NULL );

image : 输入图像, W×H, 单通道,8位或浮点 (32f 或 64f).

sum : 积分图像, W+1×H+1(译者注:原文的公式应该写成(W+1)×(H+1),避免误会), 单通道,32位整数或 double 精度的浮点数(64f).

sqsum : 对象素值平方的积分图像,W+1×H+1(译者注:原文的公式应该写成(W+1)×(H+1),避免误会), 单通道,32位整数或 double 精度的浮点数 (64f).

tilted_sum : 旋转45度的积分图像,单通道,32位整数或 double 精度的浮点数 (64f).

函数 cvIntegral 计算一次或高次积分图像:

sum(X,Y) =

image(x,y)

 

x < X,y < Y

 

 

sqsum(X,Y) =

image(x,y)2

 

x < X,y < Y

 

 

tilted_sum(X,Y) =

image(x,y)

 

y < Y, | x − X | < y

 

利用积分图像,可以计算在某象素的上-右方的或者旋转的矩形区域中进行求和、求均值以及标准方差的计算,并且保证运算的复杂度为O(1)。例如:

因此可以在变化的窗口内做快速平滑或窗口相关等操作。

 

CvtColor : 色彩空间转换

void cvCvtColor( const CvArr* src, CvArr* dst, int code );

src : 输入的 8-bit , 16-bit 或 32-bit 单倍精度浮点数影像.

dst : 输出的 8-bit , 16-bit 或 32-bit 单倍精度浮点数影像.

code : 色彩空间转换,通过定义 CV_<src_color_space>2<dst_color_space> 常数 (见下面).

函数 cvCvtColor 将输入图像从一个色彩空间转换为另外一个色彩空间。函数忽略 IplImage 头中定义的 colorModel 和 channelSeq 域,所以输入图像的色彩空间应该正确指定 (包括通道的顺序,对RGB空间而言,BGR 意味着布局为 B0 G0 R0 B1 G1 R1 ... 层叠的 24-位格式,而 RGB 意味着布局为 R0 G0 B0 R1 G1 B1 ... 层叠的24-位格式. 函数做如下变换:

RGB 空间内部的变换,如增加/删除 alpha 通道,反相通道顺序,到16位 RGB彩色或者15位RGB彩色的正逆转换(Rx5:Gx6:Rx5),以及到灰度图像的正逆转换,使用:

RGB[A]->Gray: Y=0.212671*R + 0.715160*G + 0.072169*B + 0*A

Gray->RGB[A]: R=Y G=Y B=Y A=0

所有可能的图像色彩空间的相互变换公式列举如下:

RGB<=>XYZ (CV_BGR2XYZ, CV_RGB2XYZ, CV_XYZ2BGR, CV_XYZ2RGB):

|X|   |0.412411  0.357585  0.180454| |R|

|Y| = |0.212649  0.715169  0.072182|*|G|

|Z|   |0.019332  0.119195  0.950390| |B|

 

|R|   | 3.240479  -1.53715  -0.498535| |X|

|G| = |-0.969256   1.875991  0.041556|*|Y|

|B|   | 0.055648  -0.204043  1.057311| |Z|

RGB<=>YCrCb (CV_BGR2YCrCb, CV_RGB2YCrCb, CV_YCrCb2BGR, CV_YCrCb2RGB)

Y=0.299*R + 0.587*G + 0.114*B

Cr=(R-Y)*0.713 + 128

Cb=(B-Y)*0.564 + 128

 

R=Y + 1.403*(Cr - 128)

G=Y - 0.344*(Cr - 128) - 0.714*(Cb - 128)

B=Y + 1.773*(Cb - 128)

RGB=>HSV (CV_BGR2HSV,CV_RGB2HSV)

V=max(R,G,B)

S=(V-min(R,G,B))*255/V   if V!=0, 0 otherwise

 

       (G - B)*60/S,  if V=R

H= 180+(B - R)*60/S,  if V=G

   240+(R - G)*60/S,  if V=B

 

if H<0 then H=H+360

 

使用上面从 0° 到 360° 变化的公式计算色调(hue)值,确保它们被 2 除后能适用于8位。

RGB=>Lab (CV_BGR2Lab, CV_RGB2Lab)

|X|   |0.433910  0.376220  0.189860| |R/255|

|Y| = |0.212649  0.715169  0.072182|*|G/255|

|Z|   |0.017756  0.109478  0.872915| |B/255|

 

L = 116*Y1/3      for Y>0.008856

L = 903.3*Y      for Y<=0.008856

 

a = 500*(f(X)-f(Y))

b = 200*(f(Y)-f(Z))

where f(t)=t1/3              for t>0.008856

      f(t)=7.787*t+16/116   for t<=0.008856

 

上面的公式可以参考 http://www.cica.indiana.edu/cica/faq/color_spaces/color.spaces.html

RGB=>HLS (CV_BGR2HLS, CV_RGB2HLS)

HSL 表示 hue(色相)、saturation(饱和度)、lightness(亮度)。有的地方也称为HSI,其中I表示intensity(强度)

转换公式见http://zh.wikipedia.org/wiki/HSL_%E8%89%B2%E5%BD%A9%E7%A9%BA%E9%97%B4

 

Bayer=>RGB (CV_BayerBG2BGR, CV_BayerGB2BGR, CV_BayerRG2BGR, CV_BayerGR2BGR, CV_BayerBG2RGB, CV_BayerRG2BGR, CV_BayerGB2RGB, CV_BayerGR2BGR, CV_BayerRG2RGB, CV_BayerBG2BGR, CV_BayerGR2RGB, CV_BayerGB2BGR)

Bayer 模式被广泛应用于 CCD 和 CMOS 摄像头. 它允许从一个单独平面中得到彩色图像,该平面中的 R/G/B 象素点被安排如下:

R

G

R

G

R

G

B

G

B

G

R

G

R

G

R

G

B

G

B

G

R

G

R

G

R

G

B

G

B

G

对像素输出的RGB份量由该像素的1、2或者4邻域中具有相同颜色的点插值得到。以上的模式可以通过向左或者向上平移一个像素点来作一些修改。转换常量CV_BayerC1C22{RGB|RGB}中的两个字母C1和C2表示特定的模式类型:颜色份量分别来自于第二行,第二和第三列。比如说,上述的模式具有很流行的"BG"类型。

 

Threshold : 对数组元素进行固定阈值操作

void cvThreshold( const CvArr* src, CvArr* dst, double threshold,

                  double max_value, int threshold_type );

src : 原始数组 (单通道 , 8-bit of 32-bit 浮点数).

dst : 输出数组,必须与 src 的类型一致,或者为 8-bit.

threshold : 阈值

max_value : 使用 CV_THRESH_BINARY 和 CV_THRESH_BINARY_INV 的最大值.

threshold_type : 阈值类型 

函数 cvThreshold 对单通道数组应用固定阈值操作。该函数的典型应用是对灰度图像进行阈值操作得到二值图像。(cvCmpS 也可以达到此目的) 或者是去掉噪声,例如过滤很小或很大象素值的图像点。本函数支持的对图像取阈值的方法由 threshold_type 确定:

threshold_type=CV_THRESH_BINARY:

dst(x,y) = max_value, if src(x,y)>threshold

           0, otherwise

 

threshold_type=CV_THRESH_BINARY_INV:

dst(x,y) = 0, if src(x,y)>threshold

           max_value, otherwise

 

threshold_type=CV_THRESH_TRUNC:

dst(x,y) = threshold, if src(x,y)>threshold

           src(x,y), otherwise

 

threshold_type=CV_THRESH_TOZERO:

dst(x,y) = src(x,y), if (x,y)>threshold

           0, otherwise

 

threshold_type=CV_THRESH_TOZERO_INV:

dst(x,y) = 0, if src(x,y)>threshold

           src(x,y), otherwise

下面是图形化的阈值描述:

 

AdaptiveThreshold : 自适应阈值方法

void cvAdaptiveThreshold( const CvArr* src, CvArr* dst, double max_value,

                          int adaptive_method=CV_ADAPTIVE_THRESH_MEAN_C,

                          int threshold_type=CV_THRESH_BINARY,

                          int block_>

src : 输入图像.

dst : 输出图像.

max_value : 使用 CV_THRESH_BINARY 和 CV_THRESH_BINARY_INV 的最大值.

adaptive_method : 自适应阈值算法使用:CV_ADAPTIVE_THRESH_MEAN_C 或 CV_ADAPTIVE_THRESH_GAUSSIAN_C (见讨论).

threshold_type : 取阈值类型:必须是下者之一

·   CV_THRESH_BINARY,

·   CV_THRESH_BINARY_INV

block_size : 用来计算阈值的象素邻域大小: 3, 5, 7, ...

param1 : 与方法有关的参数。对方法 CV_ADAPTIVE_THRESH_MEAN_C 和 CV_ADAPTIVE_THRESH_GAUSSIAN_C, 它是一个从均值或加权均值提取的常数(见讨论), 尽管它可以是负数。

函数 cvAdaptiveThreshold 将灰度图像变换到二值图像,采用下面公式:

threshold_type=CV_THRESH_BINARY:

dst(x,y) = max_value, if src(x,y)>T(x,y)

           0, otherwise

 

threshold_type=CV_THRESH_BINARY_INV:

dst(x,y) = 0, if src(x,y)>T(x,y)

           max_value, otherwise

其中 TI 是为每一个象素点单独计算的阈值

对方法 CV_ADAPTIVE_THRESH_MEAN_C,先求出块中的均值,再减掉param1。

对方法 CV_ADAPTIVE_THRESH_GAUSSIAN_C ,先求出块中的加权和(gaussian), 再减掉param1。

 

金字塔及其应用

 

PyrDown : 图像的下采样

void cvPyrDown( const CvArr* src, CvArr* dst, int filter=CV_GAUSSIAN_5x5 );

src : 输入图像.

dst : 输出图像, 宽度和高度应是输入图像的一半 ,传入前必须已经完成初始化

filter : 卷积滤波器的类型,目前仅支持 CV_GAUSSIAN_5x5

函数 cvPyrDown 使用 Gaussian 金字塔分解对输入图像向下采样。首先它对输入图像用指定滤波器进行卷积,然后通过拒绝偶数的行与列来下采样图像。

 

PyrUp : 图像的上采样

void cvPyrUp( const CvArr* src, CvArr* dst, int filter=CV_GAUSSIAN_5x5 );

src : 输入图像.

dst : 输出图像, 宽度和高度应是输入图像的2倍

filter : 卷积滤波器的类型,目前仅支持 CV_GAUSSIAN_5x5

函数 cvPyrUp 使用Gaussian 金字塔分解对输入图像向上采样。首先通过在图像中插入 0 偶数行和偶数列,然后对得到的图像用指定的滤波器进行高斯卷积,其中滤波器乘以4做插值。所以输出图像是输入图像的 4 倍大小。(hunnish: 原理不清楚,尚待探讨)

 

 

连接部件

 

CvConnectedComp : 连接部件

typedef struct CvConnectedComp

{

    double area; /* 连通域的面积 */

    float value; /* 分割域的灰度缩放值 */

    CvRect rect; /* 分割域的 ROI */

} CvConnectedComp;

 

FloodFill : 用指定颜色填充一个连接域

void cvFloodFill( CvArr* image, CvPoint seed_point, CvScalar new_val,

                  CvScalar lo_diff=cvScalarAll(0), CvScalar up_diff=cvScalarAll(0),

                  CvConnectedComp* comp=NULL, int flags=4, CvArr* mask=NULL );

#define CV_FLOODFILL_FIXED_RANGE (1 << 16)

#define CV_FLOODFILL_MASK_ONLY   (1 << 17)

image : 输入的 1- 或 3-通道, 8-比特或浮点数图像。输入的图像将被函数的操作所改变,除非你选择 CV_FLOODFILL_MASK_ONLY 选项 (见下面).

seed_point : 开始的种子点.

new_val : 新的重新绘制的象素值

lo_diff : 当前观察象素值与其部件领域象素或者待加入该部件的种子象素之负差(Lower difference)的最大值。对 8-比特 彩色图像,它是一个 packed value.

up_diff : 当前观察象素值与其部件领域象素或者待加入该部件的种子象素之正差(upper difference)的最大值。 对 8-比特 彩色图像,它是一个 packed value.

comp : 指向部件结构体的指针,该结构体的内容由函数用重绘区域的信息填充。

flags : 操作选项. 低位比特包含连通值, 4 (缺省) 或 8, 在函数执行连通过程中确定使用哪种邻域方式。高位比特可以是 0 或下面的开关选项的组合:

·   CV_FLOODFILL_FIXED_RANGE - 如果设置,则考虑当前象素与种子象素之间的差,否则考虑当前象素与其相邻象素的差。(范围是浮点数).

·   CV_FLOODFILL_MASK_ONLY - 如果设置,函数不填充原始图像 (忽略 new_val), 但填充掩模图像 (这种情况下 MASK 必须是非空的).

mask : 运算掩模, 应该是单通道、8-比特图像, 长和宽上都比输入图像 image 大两个象素点。若非空,则函数使用且更新掩模, 所以使用者需对 mask 内容的初始化负责。填充不会经过 MASK 的非零象素, 例如,一个边缘检测子的输出可以用来作为 MASK 来阻止填充边缘。或者有可能在多次的函数调用中使用同一个 MASK,以保证填充的区域不会重叠。注意: 因为 MASK 比欲填充图像大,所以 mask 中与输入图像(x,y)像素点相对应的点具有(x+1,y+1)坐标。

函数 cvFloodFill 用指定颜色,从种子点开始填充一个连通域。连通性由象素值的接近程度来衡量。在点 (x, y) 的象素被认为是属于重新绘制的区域,如果:

src(x',y')-lo_diff<=src(x,y)<=src(x',y')+up_diff, 灰度图像,浮动范围

src(seed.x,seed.y)-lo<=src(x,y)<=src(seed.x,seed.y)+up_diff, 灰度图像,固定范围

src(x',y')r-lo_diffr<=src(x,y)r<=src(x',y')r+up_diffr 和

src(x',y')g-lo_diffg<=src(x,y)g<=src(x',y')g+up_diffg 和

src(x',y')b-lo_diffb<=src(x,y)b<=src(x',y')b+up_diffb, 彩色图像,浮动范围

src(seed.x,seed.y)r-lo_diffr<=src(x,y)r<=src(seed.x,seed.y)r+up_diffr 和

src(seed.x,seed.y)g-lo_diffg<=src(x,y)g<=src(seed.x,seed.y)g+up_diffg 和

src(seed.x,seed.y)b-lo_diffb<=src(x,y)b<=src(seed.x,seed.y)b+up_diffb, 彩色图像,固定范围

其中 src(x',y') 是象素邻域点的值。也就是说,为了被加入到连通域中,一个象素的彩色/亮度应该足够接近于:

  • 它的邻域象素的彩色/亮度值,当该邻域点已经被认为属于浮动范围情况下的连通域。
  • 固定范围情况下的种子点的彩色/亮度值

 

FindContours : 在二值图像中寻找轮廓

int cvFindContours( CvArr* image, CvMemStorage* storage, CvSeq** first_contour,

                    int header_>

                    int method=CV_CHAIN_APPROX_SIMPLE, CvPoint offset=cvPoint(0,0) );

image : 输入的 8-比特、单通道图像. 非零元素被当成 1, 0 象素值保留为 0 - 从而图像被看成二值的。为了从灰度图像中得到这样的二值图像,可以使用 cvThreshold, cvAdaptiveThreshold 或 cvCanny. 本函数改变输入图像内容。

storage : 得到的轮廓的存储容器

first_contour : 输出参数:包含第一个输出轮廓的指针

header_size : 如果 method=CV_CHAIN_CODE,则序列头的大小 >=sizeof(CvChain),否则 >=sizeof(CvContour) .

mode : 提取模式.

·   CV_RETR_EXTERNAL - 只提取最外层的轮廓

·   CV_RETR_LIST - 提取所有轮廓,并且放置在 list 中

·   CV_RETR_CCOMP - 提取所有轮廓,并且将其组织为两层的 hierarchy: 顶层为连通域的外围边界,次层为洞的内层边界。

·   CV_RETR_TREE - 提取所有轮廓,并且重构嵌套轮廓的全部 hierarchy

method : 逼近方法 (对所有节点, 不包括使用内部逼近的 CV_RETR_RUNS).

·   CV_CHAIN_CODE - Freeman 链码的输出轮廓. 其它方法输出多边形(定点序列).

·   CV_CHAIN_APPROX_NONE - 将所有点由链码形式翻译(转化)为点序列形式

·   CV_CHAIN_APPROX_SIMPLE - 压缩水平、垂直和对角分割,即函数只保留末端的象素点;

·   CV_CHAIN_APPROX_TC89_L1,

·   CV_CHAIN_APPROX_TC89_KCOS - 应用 Teh-Chin 链逼近算法. CV_LINK_RUNS - 通过连接为 1 的水平碎片使用完全不同的轮廓提取算法。仅有 CV_RETR_LIST 提取模式可以在本方法中应用.

offset : 每一个轮廓点的偏移量. 当轮廓是从图像 ROI 中提取出来的时候,使用偏移量有用,因为可以从整个图像上下文来对轮廓做分析.

函数 cvFindContours 从二值图像中提取轮廓,并且返回提取轮廓的数目。指针 first_contour 的内容由函数填写。它包含第一个最外层轮廓的指针,如果指针为 NULL,则没有检测到轮廓(比如图像是全黑的)。其它轮廓可以从 first_contour 利用 h_next 和 v_next 链接访问到。 在 cvDrawContours 的样例显示如何使用轮廓来进行连通域的检测。轮廓也可以用来做形状分析和对象识别 - 见CVPR2001 教程中的 squares 样例。该教程可以在 SourceForge 网站上找到。

 

StartFindContours : 初始化轮廓的扫描过程

CvContourScanner cvStartFindContours( CvArr* image, CvMemStorage* storage,

                                      int header_>

                                      int mode=CV_RETR_LIST,

                                      int method=CV_CHAIN_APPROX_SIMPLE,

                                      CvPoint offset=cvPoint(0,0) );

image : 输入的 8-比特、单通道二值图像

storage : 提取到的轮廓容器

header_size : 序列头的尺寸 >=sizeof(CvChain) 若 method=CV_CHAIN_CODE,否则尺寸 >=sizeof(CvContour) .

mode : 提取模式,见 cvFindContours.

method : 逼近方法。它与 cvFindContours 里的定义一样,但是 CV_LINK_RUNS 不能使用。

offset : ROI 偏移量,见 cvFindContours.

函数 cvStartFindContours 初始化并且返回轮廓扫描器的指针。扫描器在 cvFindNextContour 使用以提取其馀的轮廓。

 

FindNextContour : Finds next contour in the image

CvSeq* cvFindNextContour( CvContourScanner scanner );

scanner : 被函数 cvStartFindContours 初始化的轮廓扫描器.

函数 cvFindNextContour 确定和提取图像的下一个轮廓,并且返回它的指针。若没有更多的轮廓,则函数返回 NULL.

 

SubstituteContour : 替换提取的轮廓

void cvSubstituteContour( CvContourScanner scanner, CvSeq* new_contour );

scanner : 被函数 cvStartFindContours 初始化的轮廓扫描器 ..

new_contour : 替换的轮廓

函数 cvSubstituteContour 把用户自定义的轮廓替换前一次的函数 cvFindNextContour 调用所提取的轮廓,该轮廓以用户定义的模式存储在边缘扫描状态之中。轮廓,根据提取状态,被插入到生成的结构,List,二层 hierarchy, 或 tree 中。如果参数 new_contour=NULL, 则提取的轮廓不被包含入生成结构中,它的所有后代以后也不会被加入到接口中。

 

EndFindContours : 结束扫描过程

 CvSeq* cvEndFindContours( CvContourScanner* scanner );

scanner : 轮廓扫描的指针.

函数 cvEndFindContours 结束扫描过程,并且返回最高层的第一个轮廓的指针。

 

 PyrSegmentation : 用金字塔实现图像分割

void cvPyrSegmentation( IplImage* src, IplImage* dst,

                        CvMemStorage* storage, CvSeq** comp,

                        int level, double threshold1, double threshold2 );

src : 输入图像.

dst : 输出图像.

storage : Storage: 存储连通部件的序列结果

comp : 分割部件的输出序列指针 components.

level : 建立金字塔的最大层数

threshold1 : 建立连接的错误阈值

threshold2 : 分割簇的错误阈值

函数 cvPyrSegmentation 实现了金字塔方法的图像分割。金字塔建立到 level 指定的最大层数。如果 p(c(a),c(b))<threshold1,则在层 i 的象素点 a 和它的相邻层的父亲象素 b 之间的连接被建立起来,

定义好连接部件后,它们被加入到某些簇中。如果p(c(A),c(B))<threshold2,则任何两个分割 A 和 B 属于同一簇。

如果输入图像只有一个通道,那么

p(c¹,c²)=|c¹-c²|.

如果输入图像有单个通道(红、绿、兰),那幺

p(c¹,c²)=0,3·(c¹r-c²r)+0,59·(c¹g-c²g)+0,11·(c¹b-c²b) .

每一个簇可以有多个连接部件。图像 src 和 dst 应该是 8-比特、单通道 或 3-通道图像,且大小一样

 

PyrMeanShiftFiltering : Does meanshift image segmentation    meanshift图像分割

void cvPyrMeanShiftFiltering( const CvArr* src, CvArr* dst,

     double sp, double sr, int max_level=1,

     CvTermCriteria termcrit=cvTermCriteria(CV_TERMCRIT_ITER+CV_TERMCRIT_EPS,5,1));

src : 输入的8-比特,3-信道图象.

dst : 和源图象相同大小,相同格式的输出图象.

sp : The spatial window radius. :空间窗的半径

sr : The color window radius.色彩窗的半径

max_level : Maximum level of the pyramid for the segmentation.

termcrit : Termination criteria: when to stop meanshift iterations.

The function cvPyrMeanShiftFiltering implements the filtering stage of meanshift segmentation, that is, the output of the function is the filtered "posterized" image with color gradients and fine-grain texture flattened. At every pixel (X,Y) of the input image (or down-sized input image, see below) the function executes meanshift iterations, that is, the pixel (X,Y) neighborhood in the joint space-color hyperspace is considered:

{(x,y): X-sp≤x≤X+sp && Y-sp≤y≤Y+sp && ||(R,G,B)-(r,g,b)|| ≤ sr},where (R,G,B) and (r,g,b) are the vectors of color components at (X,Y) and (x,y), respectively (though, the algorithm does not depend on the color space used, so any 3-component color space can be used instead). Over the neighborhood the average spatial value (X',Y') and average color vector (R',G',B') are found and they act as the neighborhood center on the next iteration: (X,Y)~(X',Y'), (R,G,B)~(R',G',B').After the iterations over, the color components of the initial pixel (that is, the pixel from where the iterations started) are set to the final value (average color at the last iteration): I(X,Y) <- (R*,G*,B*).Then max_level>0, the gaussian pyramid of max_level+1 levels is built, and the above procedure is run on the smallest layer. After that, the results are propagated to the larger layer and the iterations are run again only on those pixels where the layer colors differ much (>sr) from the lower-resolution layer, that is, the boundaries of the color regions are clarified. Note, that the results will be actually different from the ones obtained by running the meanshift procedure on the whole original image (i.e. when max_level==0).

 

Watershed : 做分水岭图像分割

void cvWatershed( const CvArr* image, CvArr* markers );

image : 输入8比特3通道图像。

markers : 输入或输出的32比特单通道标记图像。

函数cvWatershed实现在[Meyer92]描述的变量分水岭,基于非参数标记的分割算法中的一种。在把图像传给函数之前,用户需要用正指标大致勾画出图像标记的感兴趣区域。比如,每一个区域都表示成一个或者多个像素值1,2,3的互联部分。这些部分将作为将来图像区域的种子。标记中所有的其他像素,他们和勾画出的区域关系不明并且应由算法定义,应当被置0。这个函数的输出则是标记区域所有像素被置为某个种子部分的值,或者在区域边界则置-1。

注:每两个相邻区域也不是必须有一个分水岭边界(-1像素)分开,例如在初始标记图像里有这样相切的部分。opencv例程文件夹里面有函数的视觉效果演示和用户例程。见watershed.cpp。

 

图像与轮廓矩

 

Moments : 计算多边形和光栅形状的最高达三阶的所有矩

void cvMoments( const CvArr* arr, CvMoments* moments, int binary=0 );

arr : 图像 (1-通道或3-通道,有COI设置) 或多边形(点的 CvSeq 或一族点的向量).

moments : 返回的矩状态接口的指针

binary : (仅对图像) 如果标识为非零,则所有零象素点被当成零,其它的被看成 1.

函数 cvMoments 计算最高达三阶的空间和中心矩,并且将结果存在结构 moments 中。矩用来计算形状的重心,面积,主轴和其它的形状特征,如 7 Hu 不变量等。

 

GetSpatialMoment : 从矩状态结构中提取空间矩

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

moments : 矩状态,由 cvMoments 计算

x_order : 提取的 x 次矩, x_order >= 0.

y_order : 提取的 y 次矩, y_order >= 0 并且 x_order + y_order <= 3.

函数 cvGetSpatialMoment 提取空间矩,当图像矩被定义为:

Mx_order,y_order=sumx,y(I(x,y)•xx_order•yy_order)

其中 I(x,y) 是象素点 (x, y) 的亮度值.

 

GetCentralMoment : 从矩状态结构中提取中心矩

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

moments : 矩状态结构指针

x_order : 提取的 x 阶矩, x_order >= 0.

y_order :  提取的 y 阶矩, y_order >= 0 且 x_order + y_order <= 3.

函数 cvGetCentralMoment 提取中心矩,其中图像矩的定义是:

μx_order,y_order=sumx,y(I(x,y)•(x-xc)x_order•(y-yc)y_order),

其中 xc=M10/M00, yc=M01/M00 - 重心坐标

 

GetNormalizedCentralMoment : 从矩状态结构中提取归一化的中心矩

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

moments : 矩状态结构指针

x_order : 提取的 x 阶矩, x_order >= 0.

y_order : 提取的 y 阶矩, y_order >= 0 且 x_order + y_order <= 3.

函数 cvGetNormalizedCentralMoment 提取归一化中心矩:

ηx_order,y_order= μx_order,y_order/M00((y_order+x_order)/2+1)

 

GetHuMoments : 计算 7 Hu 不变量

void cvGetHuMoments( CvMoments* moments, CvHuMoments* hu_moments );

moments : 矩状态结构的指针

hu_moments : Hu 矩结构的指针.

函数 cvGetHuMoments 计算 7 个 Hu 不变量,它们的定义是:

 h1=η20+η02

 h2=(η20-η02)²+4η11²

 h3=(η30-3η12)²+ (3η21-η03)²

 h4=(η30+η12)²+ (η21+η03)²

 h5=(η30-3η12)(η30+η12)[(η30+η12)²-3(η21+η03)²]+(3η21-η03)(η21+η03)[3(η30+η12)²-(η21+η03)²]

 h6=(η20-η02)[(η30+η12)²- (η21+η03)²]+4η11(η30+η12)(η21+η03)

 h7=(3η21-η03)(η21+η03)[3(η30+η12)²-(η21+η03)²]-(η30-3η12)(η21+η03)[3(η30+η12)²-(η21+η03)²]

这些值被证明为对图像缩放、旋转和反射的不变量。对反射,第7个除外,因为它的符号会因为反射而改变。

 

特殊图像变换

 

HoughLines : 利用 Hough 变换在二值图像中找到直线

CvSeq* cvHoughLines2( CvArr* image, void* line_storage, int method,

                      double rho, double theta, int threshold,

                      double param1=0, double param2=0 );

image : 输入 8-比特、单通道 (二值) 图像,当用CV_HOUGH_PROBABILISTIC方法检测的时候其内容会被函数改变

line_storage : 检测到的线段存储仓. 可以是内存存储仓 (此种情况下,一个线段序列在存储仓中被创建,并且由函数返回),或者是包含线段参数的特殊类型(见下面)的具有单行/单列的矩阵(CvMat*)。矩阵头为函数所修改,使得它的 cols/rows 将包含一组检测到的线段。如果 line_storage 是矩阵,而实际线段的数目超过矩阵尺寸,那么最大可能数目的线段被返回(对于标准hough变换,线段按照长度降序输出).

method : Hough 变换变量,是下面变量的其中之一:

·   CV_HOUGH_STANDARD - 传统或标准 Hough 变换. 每一个线段由两个浮点数 (ρ, θ) 表示,其中 ρ 是直线与原点 (0,0) 之间的距离,θ 线段与 x-轴之间的夹角。因此,矩阵类型必须是 CV_32FC2 type.

·   CV_HOUGH_PROBABILISTIC - 概率 Hough 变换(如果图像包含一些长的线性分割,则效率更高). 它返回线段分割而不是整个线段。每个分割用起点和终点来表示,所以矩阵(或创建的序列)类型是 CV_32SC4.

·   CV_HOUGH_MULTI_SCALE - 传统 Hough 变换的多尺度变种。线段的编码方式与 CV_HOUGH_STANDARD 的一致。

rho : 与象素相关单位的距离精度

theta : 弧度测量的角度精度

threshold : 阈值参数。如果相应的累计值大于 threshold, 则函数返回的这个线段.

param1 : 第一个方法相关的参数:

·   对传统 Hough 变换,不使用(0).

·   对概率 Hough 变换,它是最小线段长度.

·   对多尺度 Hough 变换,它是距离精度 rho 的分母 (大致的距离精度是 rho 而精确的应该是 rho / param1 ).

param2 : 第二个方法相关参数:

·   对传统 Hough 变换,不使用 (0).

·   对概率 Hough 变换,这个参数表示在同一条直线上进行碎线段连接的最大间隔值(gap), 即当同一条直线上的两条碎线段之间的间隔小于param2时,将其合二为一。

·   对多尺度 Hough 变换,它是角度精度 theta 的分母 (大致的角度精度是 theta 而精确的角度应该是 theta / param2).

函数 cvHoughLines2 实现了用于线段检测的不同 Hough 变换方法. Example. 用 Hough transform 检测线段

/* This is a standalone program. Pass an image name as a first parameter

   of the program.Switch between standard and probabilistic Hough transform

   by changing "#if 1" to "#if 0" and back */

#include <cv.h>

#include <highgui.h>

#include <math.h>

 

int main(int argc, char** argv)

{

    IplImage* src;

    if( argc == 2 && (src=cvLoadImage(argv[1], 0))!= 0)

    {

        IplImage* dst = cvCreateImage( cvGetSize(src), 8, 1 );

        IplImage* color_dst = cvCreateImage( cvGetSize(src), 8, 3 );

        CvMemStorage* storage = cvCreateMemStorage(0);

        CvSeq* lines = 0;

        int i;

        IplImage* src1=cvCreateImage(cvSize(src->width,src->height),IPL_DEPTH_8U,1);

 

        cvCvtColor(src, src1, CV_BGR2GRAY); 

        cvCanny( src1, dst, 50, 200, 3 );

 

        cvCvtColor( dst, color_dst, CV_GRAY2BGR );

#if 1

        lines = cvHoughLines2( dst, storage, CV_HOUGH_STANDARD, 1, CV_PI/180, 150, 0, 0 );

 

        for( i = 0; i < lines->total; i++ )

        {

            float* line = (float*)cvGetSeqElem(lines,i);

            float rho = line[0];

            float theta = line[1];

            CvPoint pt1, pt2;

            double a = cos(theta), b = sin(theta);

            if( fabs(a) < 0.001 )

            {

                pt1.x = pt2.x = cvRound(rho);

                pt1.y = 0;

                pt2.y = color_dst->height;

            }

            else if( fabs(b) < 0.001 )

            {

                pt1.y = pt2.y = cvRound(rho);

                pt1.x = 0;

                pt2.x = color_dst->width;

            }

            else

            {

                pt1.x = 0;

                pt1.y = cvRound(rho/b);

                pt2.x = cvRound(rho/a);

                pt2.y = 0;

            }

            cvLine( color_dst, pt1, pt2, CV_RGB(255,0,0), 3, 8 );

        }

#else

        lines = cvHoughLines2( dst, storage, CV_HOUGH_PROBABILISTIC, 1, CV_PI/180, 80, 30, 10 );

        for( i = 0; i < lines->total; i++ )

        {

            CvPoint* line = (CvPoint*)cvGetSeqElem(lines,i);

            cvLine( color_dst, line[0], line[1], CV_RGB(255,0,0), 3, 8 );

        }

#endif

        cvNamedWindow( "Source", 1 );

        cvShowImage( "Source", src );

 

        cvNamedWindow( "Hough", 1 );

        cvShowImage( "Hough", color_dst );

 

        cvWaitKey(0);

    }

}

这是函数所用的样本图像:

下面是程序的输出,采用概率 Hough transform ("#if 0" 的部分):

 

HoughCircles : 利用 Hough 变换在灰度图像中找圆

CvSeq* cvHoughCircles( CvArr* image, void* circle_storage,

                       int method, double dp, double min_dist,

                       double param1=100, double param2=100,

                       int min_radius=0, int max_radius=0 );

image : 输入 8-比特、单通道灰度图像.

circle_storage : 检测到的圆存储仓. 可以是内存存储仓 (此种情况下,一个线段序列在存储仓中被创建,并且由函数返回)或者是包含圆参数的特殊类型的具有单行/单列的CV_32FC3型矩阵(CvMat*). 矩阵头为函数所修改,使得它的 cols/rows 将包含一组检测到的圆。如果 circle_storage 是矩阵,而实际圆的数目超过矩阵尺寸,那么最大可能数目的圆被返回. 每个圆由三个浮点数表示:圆心坐标(x,y)和半径.

method : Hough 变换方式,目前只支持CV_HOUGH_GRADIENT, which is basically 21HT, described in [Yuen03].

dp : 累加器图像的分辨率。这个参数允许创建一个比输入图像分辨率低的累加器。(这样做是因为有理由认为图像中存在的圆会自然降低到与图像宽高相同数量的范畴)。如果dp设置为1,则分辨率是相同的;如果设置为更大的值(比如2),累加器的分辨率受此影响会变小(此情况下为一半)。dp的值不能比1小。

Resolution of the accumulator used to detect centers of the circles. For example, if it is 1, the accumulator will have the same resolution as the input image, if it is 2 - accumulator will have twice smaller width and height, etc.

min_dist : 该参数是让算法能明显区分的两个不同圆之间的最小距离。

Minimum distance between centers of the detected circles. If the parameter is too small, multiple neighbor circles may be falsely detected in addition to a true one. If it is too large, some circles may be missed.

param1 : 用于Canny的边缘阀值上限,下限被置为上限的一半。

The first method-specific parameter. In case of CV_HOUGH_GRADIENT it is the higher threshold of the two passed to Canny edge detector (the lower one will be twice smaller).

param2 : 累加器的阀值。

The second method-specific parameter. In case of CV_HOUGH_GRADIENT it is accumulator threshold at the center detection stage. The smaller it is, the more false circles may be detected. Circles, corresponding to the larger accumulator values, will be returned first.

min_radius : 最小圆半径。

Minimal radius of the circles to search for.

max_radius : 最大圆半径。

Maximal radius of the circles to search for. By default the maximal radius is set to max(image_width, image_height).

The function cvHoughCircles finds circles in grayscale image using some modification of Hough transform.

Example. Detecting circles with Hough transform.

#include <cv.h>

#include <highgui.h>

#include <math.h>

 

int main(int argc, char** argv)

{

    IplImage* img;

    if( argc == 2 && (img=cvLoadImage(argv[1], 1))!= 0)

    {

        IplImage* gray = cvCreateImage( cvGetSize(img), 8, 1 );

        CvMemStorage* storage = cvCreateMemStorage(0);

        cvCvtColor( img, gray, CV_BGR2GRAY );

        cvSmooth( gray, gray, CV_GAUSSIAN, 9, 9 ); // smooth it, otherwise a lot of false circles may be detected

        CvSeq* circles = cvHoughCircles( gray, storage, CV_HOUGH_GRADIENT, 2, gray->height/4, 200, 100 );

        int i;

        for( i = 0; i < circles->total; i++ )

        {

             float* p = (float*)cvGetSeqElem( circles, i );

             cvCircle( img, cvPoint(cvRound(p[0]),cvRound(p[1])), 3, CV_RGB(0,255,0), -1, 8, 0 );

             cvCircle( img, cvPoint(cvRound(p[0]),cvRound(p[1])), cvRound(p[2]), CV_RGB(255,0,0), 3, 8, 0 );

        }

        cvNamedWindow( "circles", 1 );

        cvShowImage( "circles", img );

    }

    return 0;

}

 

DistTransform : 计算输入图像的所有非零元素对其最近零元素的距离

void cvDistTransform( const CvArr* src, CvArr* dst, int distance_type=CV_DIST_L2,

                      int mask_>

src : 输入 8-比特、单通道 (二值) 图像.

dst : 含计算出的距离的输出图像(32-比特、浮点数、单通道).

distance_type : 距离类型; 可以是 CV_DIST_L1, CV_DIST_L2, CV_DIST_C 或 CV_DIST_USER.

mask_size : 距离变换掩模的大小,可以是 3 或 5. 对 CV_DIST_L1 或 CV_DIST_C 的情况,参数值被强制设定为 3, 因为 3×3 mask 给出 5×5 mask 一样的结果,而且速度还更快。

mask : 用户自定义距离情况下的 mask。 在 3×3 mask 下它由两个数(水平/垂直位量,对角线位移量)组成, 5×5 mask 下由三个数组成(水平/垂直位移量,对角位移和 国际象棋里的马步(马走日))

函数 cvDistTransform 二值图像每一个象素点到它最邻近零象素点的距离。对零象素,函数设置 0 距离,对其它象素,它寻找由基本位移(水平、垂直、对角线或knight's move,最后一项对 5×5 mask 有用)构成的最短路径。 全部的距离被认为是基本距离的和。由于距离函数是对称的,所有水平和垂直位移具有同样的代价 (表示为 a ), 所有的对角位移具有同样的代价 (表示为 b), 所有的 knight's 移动具有同样的代价 (表示为 c). 对类型 CV_DIST_C 和 CV_DIST_L1,距离的计算是精确的,而类型 CV_DIST_L2 (欧式距离) 距离的计算有某些相对误差 (5×5 mask 给出更精确的结果), OpenCV 使用 [Borgefors86] 推荐的值:

CV_DIST_C (3×3):

a=1, b=1

CV_DIST_L1 (3×3):

a=1, b=2

CV_DIST_L2 (3×3):

a=0.955, b=1.3693

CV_DIST_L2 (5×5):

a=1, b=1.4, c=2.1969

下面用户自定义距离的的距离域示例 (黑点 (0) 在白色方块中间): 用户自定义 3×3 mask (a=1, b=1.5)

4.5

4

3.5

3

3.5

4

4.5

4

3

2.5

2

2.5

3

4

3.5

2.5

1.5

1

1.5

2.5

3.5

3

2

1

0

1

2

3

3.5

2.5

1.5

1

1.5

2.5

3.5

4

3

2.5

2

2.5

3

4

4.5

4

3.5

3

3.5

4

4.5

用户自定义 5×5 mask (a=1, b=1.5, c=2)

4.5

3.5

3

3

3

3.5

4.5

3.5

3

2

2

2

3

3.5

3

2

1.5

1

1.5

2

3

3

2

1

0

1

2

3

3

2

1.5

1

1.5

2

3

3.5

3

2

2

2

3

3.5

4

3.5

3

3

3

3.5

4

典型的使用快速粗略距离估计 CV_DIST_L2, 3×3 mask , 如果要更精确的距离估计,使用 CV_DIST_L2, 5×5 mask。

When the output parameter labels is not NULL, for every non-zero pixel the function also finds the nearest connected component consisting of zero pixels. The connected components themselves are found as contours in the beginning of the function.

In this mode the processing time is still O(N), where N is the number of pixels. Thus, the function provides a very fast way to compute approximate Voronoi diagram for the binary image.

 

Inpaint : 修复图像中选择区域。

void cvInpaint( const CvArr* src, const CvArr* mask, CvArr* dst,

                int flags, double inpaintRadius );

src : 输入8比特单通道或者三通道图像。

mask : 修复图像的掩饰,8比特单通道图像。非零像素表示该区域需要修复。

dst : 输出图像,和输入图像相同格式相同大小。

flags : 修复方法,以下之一:

CV_INPAINT_NS - 基于Navier-Stokes的方法。

CV_INPAINT_TELEA - Alexandru Telea[Telea04]的方法。


inpaintRadius

算法考虑的每个修复点的圆形领域的半径。

函数cvInpaint从选择图像区域边界的像素重建该区域。函数可以用来去除扫描相片的灰尘或者刮伤,或者从静态图像或者视频中去除不需要的物体。

 

直方图

 CvHistogram : 多维直方图

typedef struct CvHistogram

{

    int     type;

    CvArr*  bins;

    float   thresh[CV_MAX_DIM][2]; /* for uniform histograms */

    float** thresh2; /* for non-uniform histograms */

    CvMatND mat; /* embedded matrix header for array histograms */

}

CvHistogram;

bins : 用于存放直方图每个灰度级数目的数组指针,数组在cvCreateHist 的时候创建,其维数由cvCreateHist 确定(一般以一维比较常见)

 

CreateHist : 创建直方图

CvHistogram* cvCreateHist( int dims, int* sizes, int type,

                           float** ranges=NULL, int uniform=1 );

dims : 直方图维数的数目

sizes : 直方图维数尺寸的数组

type : 直方图的表示格式: CV_HIST_ARRAY 意味着直方图数据表示为多维密集数组 CvMatND; CV_HIST_TREE 意味着直方图数据表示为多维稀疏数组 CvSparseMat.

ranges : 图中方块范围的数组. 它的内容取决于参数 uniform 的值。这个范围的用处是确定何时计算直方图或决定反向映射(backprojected ),每个方块对应于输入图像的哪个/哪组值。

uniform : 归一化标识。如果不为0,则ranges[i](0<=i<cDims,译者注:cDims为直方图的维数,对于灰度图为1,彩色图为3)是包含两个元素的范围数组,包括直方图第i维的上界和下界。在第i维上的整个区域 [lower,upper]被分割成 dims[i] 个相等的块(译者注:dims[i]表示直方图第i维的块数),这些块用来确定输入象素的第 i 个值(译者注:对于彩色图像,i确定R, G,或者B)的对应的块;如果为0,则ranges[i]是包含dims[i]+1个元素的范围数组,包括lower0, upper0, lower1, upper1 == lower2, ..., upperdims[i]-1, 其中lowerj 和upperj分别是直方图第i维上第 j 个方块的上下界(针对输入象素的第 i 个值)。任何情况下,输入值如果超出了一个直方块所指定的范围外,都不会被 cvCalcHist 计数,而且会被函数 cvCalcBackProject 置零。

函数 cvCreateHist 创建一个指定尺寸的直方图,并且返回创建的直方图的指针。如果数组的 ranges 是 0, 则直方块的范围必须由函数 cvSetHistBinRanges 稍后指定。虽然 cvCalcHist 和 cvCalcBackProject 可以处理 8-比特图像而无需设置任何直方块的范围,但它们都被假设等分 0..255 之间的空间。

 

SetHistBinRanges : 设置直方块的区间

void cvSetHistBinRanges( CvHistogram* hist, float** ranges, int uniform=1 );

hist : 直方图.

ranges : 直方块范围数组的数组,见 cvCreateHist.

uniform : 归一化标识,见 cvCreateHist.

函数 cvSetHistBinRanges 是一个独立的函数,完成直方块的区间设置。更多详细的关于参数 ranges 和 uniform 的描述,请参考函数 cvCalcHist , 该函数也可以初始化区间。直方块的区间的设置必须在计算直方图之前,或在计算直方图的反射图之前。

 

ReleaseHist : 释放直方图结构

void cvReleaseHist( CvHistogram** hist );

hist : 被释放的直方图结构的双指针.

函数 cvReleaseHist 释放直方图 (头和数据). 指向直方图的指针被函数所清空。如果 *hist 指针已经为 NULL, 则函数不做任何事情。

 

ClearHist : 清除直方图

void cvClearHist( CvHistogram* hist );

hist直方图.

函数 cvClearHist 当直方图是稠密数组时将所有直方块设置为 0,当直方图是稀疏数组时,除去所有的直方块。

 

MakeHistHeaderForArray : 从数组中创建直方图

CvHistogram*  cvMakeHistHeaderForArray( int dims, int* sizes, CvHistogram* hist,

                                        float* data, float** ranges=NULL, int uniform=1 );

dims : 直方图维数.

sizes : 直方图维数尺寸的数组

hist : 为函数所初始化的直方图头

data : 用来存储直方块的数组

ranges : 直方块范围,见 cvCreateHist.

uniform : 归一化标识,见 cvCreateHist.

函数 cvMakeHistHeaderForArray 初始化直方图,其中头和直方块为用户所分配。以后不需要调用 cvReleaseHist 只有稠密直方图可以采用这种方法,函数返回 hist.

 

QueryHistValue_1D : 查询直方块的值

#define cvQueryHistValue_1D( hist, idx0 ) \

    cvGetReal1D( (hist)->bins, (idx0) )

#define cvQueryHistValue_2D( hist, idx0, idx1 ) \

    cvGetReal2D( (hist)->bins, (idx0), (idx1) )

#define cvQueryHistValue_3D( hist, idx0, idx1, idx2 ) \

    cvGetReal3D( (hist)->bins, (idx0), (idx1), (idx2) )

#define cvQueryHistValue_nD( hist, idx ) \

    cvGetRealND( (hist)->bins, (idx) )

hist : 直方图

idx0, idx1, idx2, idx3 : 直方块的下标索引

idx : 下标数组

宏 cvQueryHistValue_*D 返回 1D, 2D, 3D 或 N-D 直方图的指定直方块的值。对稀疏直方图,如果方块在直方图中不存在,函数返回 0, 而且不创建新的直方块。

 

GetHistValue_1D : 返回直方块的指针

#define cvGetHistValue_1D( hist, idx0 ) \

    ((float*)(cvPtr1D( (hist)->bins, (idx0), 0 ))

#define cvGetHistValue_2D( hist, idx0, idx1 ) \

    ((float*)(cvPtr2D( (hist)->bins, (idx0), (idx1), 0 ))

#define cvGetHistValue_3D( hist, idx0, idx1, idx2 ) \

    ((float*)(cvPtr3D( (hist)->bins, (idx0), (idx1), (idx2), 0 ))

#define cvGetHistValue_nD( hist, idx ) \

    ((float*)(cvPtrND( (hist)->bins, (idx), 0 ))

hist : 直方图.

idx0, idx1, idx2, idx3 : 直方块的下标索引.

idx : 下标数组

宏 cvGetHistValue_*D 返回 1D, 2D, 3D 或 N-D 直方图的指定直方块的指针。对稀疏直方图,函数创建一个新的直方块,且设置其为 0,除非它已经存在。

 

GetMinMaxHistValue : 发现最大和最小直方块

void cvGetMinMaxHistValue( const CvHistogram* hist,

                           float* min_value, float* max_value,

                           int* min_idx=NULL, int* max_idx=NULL );

hist : 直方图

min_value : 直方图最小值的指针

max_value : 直方图最大值的指针

min_idx : 数组中最小坐标的指针

max_idx : 数组中最大坐标的指针

函数 cvGetMinMaxHistValue 发现最大和最小直方块以及它们的位置。任何输出变量都是可选的。在具有同样值几个极值中,返回具有最小下标索引(以字母排列顺序定)的那一个。

 

NormalizeHist : 归一化直方图

void cvNormalizeHist( CvHistogram* hist, double factor );

hist : 直方图的指针.

factor : 归一化因子

函数 cvNormalizeHist 通过缩放来归一化直方块,使得所有块的和等于 factor.

 

ThreshHist : 对直方图取阈值

void cvThreshHist( CvHistogram* hist, double threshold );

hist : 直方图的指针.

threshold : 阈值大小

函数 cvThreshHist 清除那些小于指定阈值得直方块

 

CompareHist : 比较两个稠密直方图

double cvCompareHist( const CvHistogram* hist1, const CvHistogram* hist2, int method );

hist1 : 第一个稠密直方图

hist2 : 第二个稠密直方图

method : 比较方法,采用其中之一:

·   CV_COMP_CORREL

·   CV_COMP_CHISQR

·   CV_COMP_INTERSECT

·   CV_COMP_BHATTACHARYYA

函数 cvCompareHist 采用下面指定的方法比较两个稠密直方图(H1 表示第一个, H2表示第二个):

Correlation (method=CV_COMP_CORREL)

其中

(N 是number of histogram bins)

  • Chi-square(method=CV_COMP_CHISQR):
  • 交叉 (method=CV_COMP_INTERSECT):

d(H1,H2) =

min(H1(i),H2(i))

 

i

 

  • Bhattacharyya 距离 (method=CV_COMP_BHATTACHARYYA):

函数返回 d(H1,H2) 的值。

注意:Bhattacharyya 距离只能应用到规一化后的直方图。

为了比较稀疏直方图或更一般的加权稀疏点集(译者注:直方图匹配是图像检索中的常用方法),考虑使用函数 cvCalcEMD 。

 

CopyHist : 拷贝直方图

void cvCopyHist( const CvHistogram* src, CvHistogram** dst );

src : 输入的直方图

dst : 输出的直方图指针

函数 cvCopyHist 对直方图作拷贝。如果第二个直方图指针 *dst 是 NULL, 则创建一个与 src 同样大小的直方图。否则,两个直方图必须大小和类型一致。然后函数将输入的直方块的值复制到输出的直方图中,并且设置取值范围与 src 的一致。

 

CalcHist : 计算图像image(s) 的直方图

void cvCalcHist( IplImage** image, CvHistogram* hist,

                 int accumulate=0, const CvArr* mask=NULL );

image : 输入图像s (虽然也可以使用 CvMat** ).

hist : 直方图指针

accumulate : 累计标识。如果设置,则直方图在开始时不被清零。这个特征保证可以为多个图像计算一个单独的直方图,或者在线更新直方图。

mask : 操作 mask, 确定输入图像的哪个象素被计数

函数 cvCalcHist 计算一张或多张单通道图像的直方图(注:若要计算多通道,可像以下例子那样用多个单通道图来表示)。用来增加直方块的数组元素可从相应输入图像的同样位置提取。 Sample. 计算和显示彩色图像的 2D 色调-饱和度图像

#include <cv.h>

#include <highgui.h>

 int main( int argc, char** argv )

{

    IplImage* src;

    if( argc == 2 && (src=cvLoadImage(argv[1], 1))!= 0)

    {

        IplImage* h_plane = cvCreateImage( cvGetSize(src), 8, 1 );

        IplImage* s_plane = cvCreateImage( cvGetSize(src), 8, 1 );

        IplImage* v_plane = cvCreateImage( cvGetSize(src), 8, 1 );

        IplImage* planes[] = { h_plane, s_plane };

        IplImage* hsv = cvCreateImage( cvGetSize(src), 8, 3 );

        int h_bins = 30, s_bins = 32;

        int hist_size[] = {h_bins, s_bins};

        /* hue varies from 0 (~0°red) to 180 (~360°red again) */

        float h_ranges[] = { 0, 180 };

        /* saturation varies from 0 (black-gray-white) to 255 (pure spectrum color) */

        float s_ranges[] = { 0, 255 };

        float* ranges[] = { h_ranges, s_ranges };

        int scale = 10;

        IplImage* hist_img = cvCreateImage( cvSize(h_bins*scale,s_bins*scale), 8, 3 );

        CvHistogram* hist;

        float max_value = 0;

        int h, s;

 

        cvCvtColor( src, hsv, CV_BGR2HSV );

        cvCvtPixToPlane( hsv, h_plane, s_plane, v_plane, 0 );

        hist = cvCreateHist( 2, hist_size, CV_HIST_ARRAY, ranges, 1 );

        cvCalcHist( planes, hist, 0, 0 );

        cvGetMinMaxHistValue( hist, 0, &max_value, 0, 0 );

        cvZero( hist_img );

 

        for( h = 0; h < h_bins; h++ )

        {

            for( s = 0; s < s_bins; s++ )

            {

                float bin_val = cvQueryHistValue_2D( hist, h, s );

                int intensity = cvRound(bin_val*255/max_value);

                cvRectangle( hist_img, cvPoint( h*scale, s*scale ),

                             cvPoint( (h+1)*scale - 1, (s+1)*scale - 1),

                             CV_RGB(intensity,intensity,intensity), /* graw a grayscale histogram.

                                                                       if you have idea how to do it

                                                                       nicer let us know */

                             CV_FILLED );

            }

        }

         cvNamedWindow( "Source", 1 );

        cvShowImage( "Source", src );

         cvNamedWindow( "H-S Histogram", 1 );

        cvShowImage( "H-S Histogram", hist_img );

         cvWaitKey(0);

    }

}

 

CalcBackProject : 计算反向投影

void cvCalcBackProject( IplImage** image, CvArr* back_project, const CvHistogram* hist );

image : 输入图像 (也可以传递 CvMat** ).

back_project : 反向投影图像,与输入图像具有同样类型.

hist : 直方图

函数 cvCalcBackProject 计算直方图的反向投影. 对于所有输入的单通道图像同一位置的象素数组,该函数根据相应的象素数组(RGB),放置其对应的直方块的值到输出图像中。用统计学术语,输出图像象素点的值是观测数组在某个分布(直方图)下的概率。例如,为了发现图像中的红色目标,可以这么做:

  1. 对红色物体计算色调直方图,假设图像仅仅包含该物体。则直方图有可能有极值,对应着红颜色。
  2. 对将要搜索目标的输入图像,使用直方图计算其色调平面的反向投影,然后对图像做阈值操作。
  3. 在产生的图像中发现连通部分,然后使用某种附加准则选择正确的部分,比如最大的连通部分。

这是 Camshift 彩色目标跟踪器中的一个逼进算法,除了第三步,CAMSHIFT 算法使用了上一次目标位置来定位反向投影中的目标。

 

CalcBackProjectPatch : 用直方图比较来定位图像中的模板

void cvCalcBackProjectPatch( IplImage** image, CvArr* dst,

                             CvSize patch_size, CvHistogram* hist,

                             int method, double factor );

image : 输入图像 (可以传递 CvMat** )

dst : 输出图像.

patch_size : 扫描输入图像的补丁尺寸

hist : 直方图

method : 比较方法,传递给 cvCompareHist (见该函数的描述).

factor : 直方图的归一化因子,将影响输出图像的归一化缩放。如果为 1,则不定。 /*归一化因子的类型实际上是double,而非float*/

函数 cvCalcBackProjectPatch 通过输入图像补丁的直方图和给定直方图的比较,来计算反向投影。提取图像在 ROI 中每一个位置的某种测量结果产生了数组 image. 这些结果可以是色调, x 差分, y 差分, Laplacian 滤波器, 有方向 Gabor 滤波器等中 的一个或多个。每种测量输出都被划归为它自己的单独图像。 image 图像数组是这些测量图像的集合。一个多维直方图 hist 从这些图像数组中被采样创建。最后直方图被归一化。直方图 hist 的维数通常很大等于图像数组 image 的元素个数。

在选择的 ROI 中,每一个新的图像被测量并且转换为一个图像数组。在以锚点为“补丁”中心的图像 image 区域中计算直方图 (如下图所示)。用参数 norm_factor 来归一化直方图,使得它可以与 hist 互相比较。计算出的直方图与直方图模型互相比较, (hist 使用函数 cvCompareHist ,比较方法是 method=method). 输出结果被放置到概率图像 dst 补丁锚点的对应位置上。这个过程随着补丁滑过整个 ROI 而重复进行。迭代直方图的更新可以通过在原直方图中减除“补丁”已复盖的尾象素点或者加上新复盖的象素点来实现,这种更新方式可以节省大量的操作,尽管目前在函数体中还没有实现。

Back Project Calculation by Patches

 

CalcProbDensity : 两个直方图相除

void  cvCalcProbDensity( const CvHistogram* hist1, const CvHistogram* hist2,

                         CvHistogram* dst_hist, double scale=255 );

hist1 : 第一个直方图(分母).

hist2 : 第二个直方图

dst_hist : 输出的直方图

scale : 输出直方图的尺度因子

函数 cvCalcProbDensity 从两个直方图中计算目标概率密度:

dist_hist(I)=0      if hist1(I)==0

             scale  if hist1(I)!=0 && hist2(I)>hist1(I)

             hist2(I)*scale/hist1(I) if hist1(I)!=0 && hist2(I)<=hist1(I)

所以输出的直方块小于尺度因子。

 

EqualizeHist : 灰度图象直方图均衡化

void cvEqualizeHist( const CvArr* src, CvArr* dst );

src : 输入的 8-比特 单信道图像

dst : 输出的图像与输入图像大小与数据类型相同

函数 cvEqualizeHist 采用如下法则对输入图像进行直方图均衡化:

  1. 计算输入图像的直方图 H
  2. 直方图归一化,因此直方块和为255
  3. 计算直方图积分:
  4. 采用H'作为查询表:dst(x,y)=H'(src(x,y))进行图像变换。

该方法归一化图像亮度和增强对比度。

例:彩色图像的直方图均衡化

int i;

IplImage *pImageChannel[4] = { 0, 0, 0, 0 };

pSrcImage = cvLoadImage( "test.jpg", 1 ) ;

IplImage *pImage = cvCreateImage(cvGetSize(pSrcImage), pSrcImage->depth, pSrcImage->nChannels);

if( pSrcImage )

{

        for( i = 0; i < pSrcImage->nChannels; i++ )

        {

               pImageChannel[i] = cvCreateImage( cvGetSize(pSrcImage), pSrcImage->depth, 1 );

        }

        // 信道分离

        cvSplit( pSrcImage, pImageChannel[0], pImageChannel[1],

               pImageChannel[2], pImageChannel[3] );

        for( i = 0; i < pImage->nChannels; i++ )

        {

               // 直方图均衡化

               cvEqualizeHist( pImageChannel[i], pImageChannel[i] );

        }

        // 信道组合

        cvMerge( pImageChannel[0], pImageChannel[1], pImageChannel[2],

               pImageChannel[3], pImage );

        // ……图像显示代码(略)

        // 释放资源

        for( i = 0; i < pSrcImage->nChannels; i++ )

        {

               if ( pImageChannel[i] )

               {

                       cvReleaseImage( &pImageChannel[i] );

                       pImageChannel[i] = 0;

               }

        }

        cvReleaseImage( &pImage );

        pImage = 0;

}

 

匹配

 

MatchTemplate : 比较模板和重叠的图像区域

void cvMatchTemplate( const CvArr* image, const CvArr* templ,

                      CvArr* result, int method );

image : 欲搜索的图像。它应该是单通道、8-比特或32-比特 浮点数图像

templ : 搜索模板,不能大于输入图像,且与输入图像具有一样的数据类型

result : 比较结果的映射图像。单通道、32-比特浮点数. 如果图像是 W×H 而 templ 是 w×h ,则 result 一定是 (W-w+1)×(H-h+1).

method : 指定匹配方法:

函数 cvMatchTemplate 与函数 cvCalcBackProjectPatch 类似。它滑动过整个图像 image, 用指定方法比较 templ 与图像尺寸为 w×h 的重叠区域,并且将比较结果存到 result 中。下面是不同的比较方法,可以使用其中的一种 (I 表示图像,T - 模板, R - 结果. 模板与图像重叠区域 x'=0..w-1, y'=0..h-1 之间求和):

method=CV_TM_SQDIFF:

R(x,y) =

[T(x',y') − I(x + x',y + y')]2

 

x',y'

 

method=CV_TM_SQDIFF_NORMED:

method=CV_TM_CCORR:

method=CV_TM_CCORR_NORMED:

method=CV_TM_CCOEFF:

,

其中

(mean template brightness=>0)

(mean patch brightness=>0)

method=CV_TM_CCOEFF_NORMED:

函数完成比较后,通过使用cvMinMaxLoc找全局最小值CV_TM_SQDIFF*) 或者最大值 (CV_TM_CCORR* and CV_TM_CCOEFF*)。

 

MatchShapes : 比较两个形状

double cvMatchShapes( const void* object1, const void* object2,

                      int method, double parameter=0 );

object1 : 第一个轮廓或灰度图像

object2 : 第二个轮廓或灰度图像

method : 比较方法,其中之一 CV_CONTOUR_MATCH_I1, CV_CONTOURS_MATCH_I2 or CV_CONTOURS_MATCH_I3.

parameter : 比较方法的参数 (目前不用).

函数 cvMatchShapes 比较两个形状。三个实现方法全部使用 Hu 矩 (见 cvGetHuMoments) (A ~ object1, B - object2):

method=CV_CONTOUR_MATCH_I1:

method=CV_CONTOUR_MATCH_I2:

method=CV_CONTOUR_MATCH_I3:

 

CalcEMD2

两个加权点集之间计算最小工作距离

float cvCalcEMD2( const CvArr* signature1, const CvArr* signature2, int distance_type,

                  CvDistanceFunction distance_func=NULL, const CvArr* cost_matrix=NULL,

                  CvArr* flow=NULL, float* lower_bound=NULL, void* userdata=NULL );

typedef float (*CvDistanceFunction)(const float* f1, const float* f2, void* userdata);

signature1

第一个签名,大小为 size1×(dims+1) 的浮点数矩阵,每一行依次存储点的权重和点的坐标。矩阵允许只有一列(即仅有权重),如果使用用户自定义的代价矩阵。

signature2

第二个签名,与 signature1 的格式一样size2×(dims+1),尽管行数可以不同(列数要相同)。当一个额外的虚拟点加入 signature1 或 signature2 中的时候,权重也可不同。

distance_type

使用的准则, CV_DIST_L1, CV_DIST_L2, 和 CV_DIST_C 分别为标准的准则。 CV_DIST_USER 意味着使用用户自定义函数 distance_func 或预先计算好的代价矩阵 cost_matrix 。

distance_func : 用户自定义的距离函数。用两个点的坐标计算两点之间的距离。

cost_matrix : 自定义大小为 size1×size2 的代价矩阵。 cost_matrix 和 distance_func 两者至少有一个必须为 NULL. 而且,如果使用代价函数,下边界无法计算,因为它需要准则函数。

flow : 产生的大小为 size1×size2 流矩阵(flow matrix): flowij 是从 signature1 的第 i 个点到 signature2 的第 j 个点的流(flow)。

lower_bound : 可选的输入/输出参数:两个签名之间的距离下边界,是两个质心之间的距离。如果使用自定义代价矩阵,点集的所有权重不等,或者有签名只包含权重(即该签名矩阵只有单独一列),则下边界也许不会计算。用户必须初始化 *lower_bound. 如果质心之间的距离大于获等于 *lower_bound (这意味着签名之间足够远), 函数则不计算 EMD. 任何情况下,函数返回时 *lower_bound 都被设置为计算出来的质心距离。因此如果用户想同时计算质心距离和T EMD, *lower_bound 应该被设置为 0.

userdata : 传输到自定义距离函数的可选数据指针

函数 cvCalcEMD2 计算两个加权点集之间的移动距离或距离下界。在 [RubnerSept98] 中所描述的其中一个应用就是图像提取得多维直方图比较。 EMD 是一个使用某种单纯形算法(simplex algorithm)来解决的交通问题。其计算复杂度在最坏情况下是指数形式的,但是平均而言它的速度相当快。对实的准则,下边界的计算可以更快(使用线性时间算法),且它可用来粗略确定两个点集是否足够远以至无法联系到同一个目标上。

2018-11-24 17:20:01 gwj992 阅读数 526

备注:本文源于一篇外文文献 此文档是我的学习翻译版,看原文应点击链接。

Deeplab Image Semantic Segmentation Network

Deeplab图像语义分割网络

Jan 29, 2018

   

Introduction

Deep Convolution Neural Networks (DCNNs) have achieved remarkable success in various Computer Vision applications. Like others, the task of semantic segmentation is not an exception to this trend.

深度卷积神经网络(DCNN)在各种计算机视觉应用中取得了显着的成功。 与其他人一样,语义分割的任务也不是这种趋势的例外。

This piece provides an introduction to Semantic Segmentation with a hands-on TensorFlow implementation. We go over one of the most relevant papers on Semantic Segmentation of general objects - Deeplab_v3. You can clone the notebook for this post here.

本文通过实际操作TensorFlow实现了语义分段的介绍。 我们回顾了一篇关于一般对象语义分割的最相关的论文 - Deeplab_v3。 您可以在此处克隆此帖子的笔记。

Semantic Segmentation 语义分割

Regular image classification DCNNs have similar structure. These models take images as input and output a single value representing the category of that image.

常规图像分类DCNN具有相似的结构。 这些模型将图像作为输入并输出表示该图像类别的单个值。

Usually, classification DCNNs have four main operations. Convolutionsactivation functionpooling, and fully-connected layers. Passing an image through a series of these operations outputs a feature vector containing the probabilities for each class label. Note that in this setup, we categorize an image as a whole. That is, we assign a single label to an entire image.

通常,分类DCNN有四个主要操作。 卷积,激活功能,池化和完全连接的层。 通过一系列这些操作传递图像输出包含每个类标签的概率的特征向量。 请注意,在此设置中,我们将图像整体分类。 也就是说,我们为整个图像分配一个标签。

ResNet bottleneck layer

Standard deep learning model for image recognition.

用于图像识别的标准深度学习模型。

Image credits: Convolutional Neural Network MathWorks.
图片来源:卷积神经网络MathWorks。

Different from image classification, in semantic segmentation we want to make decisions for every pixel in an image. So, for each pixel, the model needs to classify it as one of the pre-determined classes. Put another way, semantic segmentation means understanding images at a pixel level.

与图像分类不同,在语义分割中,我们希望为图像中的每个像素做出决策。 因此,对于每个像素,模型需要将其分类为预定类之一。 换句话说,语义分割意味着在像素级别理解图像。

Keep in mind that semantic segmentation doesn’t differentiate between object instances. Here, we try to assign an individual label to each pixel of a digital image. Thus, if we have two objects of the same class, they end up having the same category label. Instance Segmentation is the class of problems that differentiate instances of the same class.

请记住,语义分段不区分对象实例。 在这里,我们尝试为数字图像的每个像素分配单独的标签。 因此,如果我们有两个相同类的对象,它们最终会有相同的类别标签。 实例分段是区分同一类实例的一类问题。

ResNet bottleneck layer

Difference between Semantic Segmentation and Instance Segmentation. (middle) Although they are the same object (bus) they are classified as different objects. (left) Same object, equal category.

语义分割与实例分割的区别。 (中)虽然它们是同一个对象(总线),但它们被归类为不同的对象。 (左)相同的对象,相同的类别。

Yet, regular DCNNs such as the AlexNet and VGG aren’t suitable for dense prediction tasks. First, these models contain many layers designed to reduce the spatial dimensions of the input features. As a consequence, these layers end up producing highly decimated feature vectors that lack sharp details. Second, fully-connected layers have fixed sizes and loose spatial information during computation.

然而,诸如AlexNet和VGG的常规DCNN不适合于密集预测任务。 首先,这些模型包含许多层,旨在减少输入要素的空间维度。 因此,这些层最终会产生缺乏清晰细节的高度抽取的特征向量。 其次,完全连接的层在计算期间具有固定的大小和松散的空间信息。

As an example, instead of having pooling and fully-connected layers, imagine passing an image through a series of convolutions. We can set each convolution to have stride of 1 and “SAME” padding. Doing this, each convolution preserves the spatial dimensions of its input. We can stack a bunch of these convolutions and have a segmentation model.

作为一个例子,想象一下通过一系列卷积传递图像,而不是拥有池和完全连接的层。 我们可以将每个卷积设置为1和“SAME”填充。 这样做,每个卷积保留其输入的空间维度。 我们可以堆叠一堆这些卷积并具有分段模型。

ResNet bottleneck layer

Fully-Convolution neural network for dense prediction task. Note the non-existence of pooling and fully-connected layers.

This model could output a probability tensor with shape [W,H,C], where W and H represent the Width and Height. And C the number of class labels. Applying the argmax function (on the third axis) gives us a tensor shape of [W,H,1]. After, we compute the cross-entropy loss between each pixel of the ground-truth images and our predictions. In the end, we average that value and train the network using back prop.

用于密集预测任务的全卷积神经网络。 注意池和完全连接的层不存在。
该模型可以输出具有形状[W,H,C]的概率张量,其中W和H表示宽度和高度。 和C类标签的数量。 应用argmax函数(在第三轴上)给出了[W,H,1]的张量形状。 之后,我们计算地面实况图像的每个像素与我们的预测之间的交叉熵损失。 最后,我们平均该值并使用后支柱训练网络。

There is one problem with this approach though. As we mentioned, using convolutions with stride 1 and “SAME” padding preserves the input dimensions. However, doing that would make the model super expensive in both ways. Memory consumption and computation complexity.

但是这种方法存在一个问题。 正如我们所提到的,使用带有步幅1和“SAME”填充的卷积可以保留输入尺寸。 但是,这样做会使模型在两个方面都非常昂贵。 内存消耗和计算复杂性。

To ease that problem, segmentation networks usually have three main components. Convolutions, downsampling, and upsampling layers.

为了解决这个问题,分段网络通常有三个主要组件。 卷积,下采样和上采样层。

ResNet bottleneck layer

Encoder-decoder architecture for Image Semantic Segmentation.

There are two common ways to do downsampling in neural nets. By using convolution striding or regular pooling operations. In general, downsampling has one goal. To reduce the spatial dimensions of given feature maps. For that reason, downsampling allows us to perform deeper convolutions without much memory concerns. Yet, they do it in detriment of losing some features in the process.

用于图像语义分割的编码器 - 解码器架构。
在神经网络中进行下采样有两种常用方法。 通过使用卷积跨越或常规池操作。 通常,下采样有一个目标。 减少给定要素图的空间维度。 出于这个原因,下采样允许我们在没有太多内存问题的情况下执行更深层次的卷积。 然而,他们这样做不利于在此过程中失去一些功能。

Also, note that the first part of this architecture looks a lot like usual classification DCNNs. With one exception, they do not put in place fully-connected layers.

另请注意,此体系结构的第一部分看起来很像通常的分类DCNN。 除了一个例外,它们不会建立完全连接的层。

After the first part, we have a feature vector with shape [w,h,d] where w, h and d are the width, height and depth of the feature tensor. Note that the spatial dimensions of this compressed vector are smaller (yet denser) than the original input.

在第一部分之后,我们有一个形状为[w,h,d]的特征向量,其中w,h和d是特征张量的宽度,高度和深度。 请注意,此压缩矢量的空间维度比原始输入更小(但更密集)。

ResNet bottleneck layer

(Top) VGG-16 network on its original form. Note the 3 fully-connected layers on top of the convolution stack. (Down) VGG-16 model when substituting its fully-connected layers to 1x1 convolutions. This change allows the network to output a coarse heat-map.

(顶部)VGG-16网络的原始形式。 注意卷积堆栈顶部的3个完全连接的层。 (下)VGG-16模型在将其全连接层替换为1x1卷积时。 此更改允许网络输出粗略的热图

Image credits: Fully Convolutional Networks for Semantic Segmentation.

At this point, regular classification DCNNs would output a dense (non-spatial) vector containing probabilities for each class label. Instead, we feed this compressed feature vector to a series of upsampling layers. These layers work on reconstructing the output of the first part of the network. The goal is to increase the spatial resolution so the output vector has the same dimensions as the input.

此时,常规分类DCNN将输出包含每个类标签的概率的密集(非空间)向量。 相反,我们将此压缩特征向量提供给一系列上采样层。 这些层用于重建网络第一部分的输出。 目标是增加空间分辨率,使输出矢量具有与输入相同的尺寸。

Usually, upsampling layers are based on strided transpose convolutionsThese functions go from deep and narrow layers to wider and shallower ones. Here, we use transpose convolutions to increase feature vectors dimension to the desired value.

通常,上采样层基于跨步的转置卷积。 这些功能从深层和窄层变为更宽更浅的层。 在这里,我们使用转置卷积将特征向量维度增加到所需的值。

In most papers, these two components of a segmentation network are called: encoder and decoder. In short, the first, “encodes” its information into a compressed vector used to represent its input. The second (the decoder) works on reconstructing this signal to the desired outcome.

在大多数论文中,分段网络的这两个组件称为:编码器和解码器。 简而言之,第一个,将其信息“编码”为用于表示其输入的压缩矢量。 第二个(解码器)致力于将该信号重建为期望的结果。

There are many network implementations based on encoder-decoder architectures. FCNs, SegNetand UNet are some of the most popular ones. As a result, we have seen many successful segmentation models in a variety of fields.

存在许多基于编码器 - 解码器架构的网络实现。 FCN,SegNet和UNet是最受欢迎的一些。 因此,我们在各个领域都看到了许多成功的细分模型。

Model Architecture 模型架构

Different from most encoder-decoder designs, Deeplab offers a different approach to semantic segmentation. It presents an architecture for controlling signal decimation and learning multi-scale contextual features.
与大多数编码器 - 解码器设计不同,Deeplab提供了一种不同的语义分割方法。 它提出了一种用于控制信号抽取和学习多尺度上下文特征的架构。

ResNet bottleneck layer

Image credits: Rethinking Atrous Convolution for Semantic Image Segmentation.

Deeplab uses an ImageNet pre-trained ResNet as its main feature extractor network. However, it proposes a new Residual block for multi-scale feature learning. Instead of regular convolutions, the last ResNet block uses atrous convolutions. Also, each convolution (within this new block) uses different dilation rates to capture multi-scale context.

Deeplab使用ImageNet预训练的ResNet作为其主要特征提取器网络。 但是,它提出了一种用于多尺度特征学习的新残差块。 最后一个ResNet块不使用常规卷积,而是使用有害卷积。 此外,每个卷积(在此新块内)使用不同的扩张率来捕获多尺度上下文。

Additionally, on top of this new block, it uses Atrous Spatial Pyramid Pooling (ASPP). ASPP uses dilated convolutions with different rates as an attempt of classifying regions of an arbitrary scale.

此外,在这个新块的顶部,它使用了Atrous Spatial Pyramid Pooling(ASPP)。 ASPP使用具有不同速率的扩张卷积作为对任意尺度的区域进行分类的尝试。

To understand the deeplab architecture, we need to focus on three components. (i) The ResNet architecture, (ii) atrous convolutions and (iii) Atrous Spatial Pyramid Pooling (ASPP). Let’s go over each one of them.

要了解deeplab架构,我们需要关注三个组件。 (i)ResNet架构,(ii)痛苦的卷积和(iii)Atrous空间金字塔池(ASPP)。 让我们来看看他们中的每一个。

ResNets

ResNet is a very popular DCNN that won the ILSVRC 2015 classification task. One of the main contributions of ResNets was to provide a framework to ease the training of deeper models.

ResNet是一个非常受欢迎的DCNN,赢得了ILSVRC 2015分类任务。 ResNets的主要贡献之一是提供一个框架来简化深层模型的培训。

In its original form, ResNets contain 4 computational blocks. Each block contains a different number of Residual Units. These units perform a series of convolutions in a special way. Also, each block is intercalated with max-pooling operations to reduce spatial dimensions.

在其原始形式中,ResNets包含4个计算块。 每个块包含不同数量的剩余单元。 这些单元以特殊方式执行一系列卷积。 此外,每个块都插入了最大池操作以减少空间维度。

The original paper presents two types of Residual Units. The baseline and the bottleneck blocks.

在其原始形式中,ResNets包含4个计算块。 每个块包含不同数量的剩余单元。 这些单元以特殊方式执行一系列卷积。 此外,每个块都插入了最大池操作以减少空间维度。

The baseline unit contains two 3x3 convolutions with Batch Normalization(BN) and ReLU activations.

原始论文提出了两种类型的剩余单位。 基线和瓶颈块。基线单元包含两个3x3卷积,具有批量标准化(BN)和ReLU激活。
 

ResNet bottleneck layer

ResNet building blocks. (left) baseline; (right) bottleneck unit

Adapted from: Deep Residual Learning for Image Recognition.

The second, the bottleneck unit, consists of three stacked operations. A series of 1x13x3 and 1x1convolutions substitute the previous design. The two 1x1 operations are designed for reducing and restoring dimensions. This leaves the 3x3 convolution, in the middle, to operate on a less dense feature vector. Also, BN is applied after each convolution and before ReLU non-linearity.

第二个是瓶颈单元,由三个堆叠操作组成。一系列1x1,3x3和1x1转换代替了之前的设计。这两个1x1操作旨在减少和恢复尺寸。这使得中间的3x3卷积在较不密集的特征向量上运行。此外,在每个卷积之后和ReLU非线性之前应用BN。

To help to understand, let’s denote these group of operations as a function F of its input x.

为了帮助理解,让我们将这些操作组表示为其输入x的函数F.

After the non-linear transformations in F(x), the unit combines the result of F(x) with the original input x. This combination is done by adding the two functions. Merging the original input x with the non-linear function F(x) offers some advantages. It allows earlier layers to access the gradient signal from later layers. In other words, skipping the operations on F(x) allows earlier layers to have access to a stronger gradient signal. As a result, this type of connectivity has been shown to ease the training of deeper networks.

在F(x)中的非线性变换之后,该单元将F(x)的结果与原始输入x组合。通过添加两个函数来完成此组合。将原始输入x与非线性函数F(x)合并提供了一些优点。它允许较早的层访问后面的层的梯度信号。换句话说,跳过F(x)上的操作允许较早的层能够访问更强的梯度信号。因此,这种类型的连接已被证明可以简化对更深层网络的培训。

Non-bottleneck units also show gain in accuracy as we increase model capacity. Yet, bottleneck residual units have some practical advantages. First, it performs more computations having almost the same number of parameters. Second, they also perform in a similar computational complexity as its counterpart.

随着我们增加模型容量,非瓶颈单元也显示出准确性的提高。然而,瓶颈残留单元具有一些实际优势。首先,它执行具有几乎相同数量的参数的更多计算。其次,它们的执行速度与其对应的计算复杂度相似。

In practice, bottleneck units are more suitable for training deeper models because of less training time and computational resources need.

在实践中,由于较少的训练时间和计算资源需求,瓶颈单元更适合训练更深的模型。

For our implementation, we use the full pre-activation Residual Unit. The only difference, from the standard bottleneck unit, lies in the order in which BN and ReLU activations are placed. For the full pre-activation, BN and ReLU (in this order) occur before convolutions.

对于我们的实施,我们使用完整的预激活残留单元。 与标准瓶颈单元的唯一区别在于BN和ReLU激活的顺序。 对于完全预激活,BN和ReLU(按此顺序)在卷积之前发生。

ResNet bottleneck layer

Different ResNet building block architectures. (Left-most) the original ResNet block. (Right-most) the improved full pre-activation version.

Image credits: Identity Mappings in Deep Residual Networks.

As shown in Identity Mappings in Deep Residual Networks, the full pre-activation unit performs better than other variants.

如深度残留网络中的身份映射所示,完整的预激活单元比其他变体表现更好。

Note that the only difference between these designs is the order of BN and RELu in the convolution stack.

请注意,这些设计之间的唯一区别是卷积堆栈中BN和RELu的顺序。

Atrous Convolutions

Atrous (or dilated) convolutions are regular convolutions with a factor that allows us to expand the filter’s field of view.

Atrous(或扩张)卷积是常规卷积,其中一个因子允许我们扩展滤波器的视野。

Consider a 3x3 convolution filter for instance. When the dilation rate is equal to 1, it behaves like a standard convolution. But, if we set the dilation factor to 2, it has the effect of enlarging the convolution kernel.

例如,考虑一个3x3卷积滤波器。 当扩张率等于1时,它表现得像标准卷积。 但是,如果我们将扩张系数设置为2,它会产生扩大卷积核的效果。

In theory, it works like that. First, it expands (dilates) the convolution filter according to the dilation rate. Second, it fills the empty spaces with zeros - creating a sparse like filter. Finally, it performs regular convolution using the dilated filter.

从理论上讲,它就是这样的。 首先,它根据膨胀率扩展(扩张)卷积滤波器。 其次,它用零填充空白空间 - 创建稀疏的过滤器。 最后,它使用扩张的滤波器执行常规卷积。

ResNet bottleneck layer

Atrous convolutions with various rates.  各种费率的萎缩卷积。

As a consequence, a convolution with a dilated 2, 3x3 filter would make it able to cover an area equivalent to a 5x5. Yet, because it acts like a sparse filter, only the original 3x3 cells will do computation and produce results. I said “act” because most frameworks don’t implement atrous convolutions using sparse filters - because of memory concerns.

因此,使用扩张的2,3x3滤波器进行卷积将使其能够覆盖相当于5x5的面积。 然而,因为它的作用类似于稀疏滤波器,所以只有原始的3x3单元才能进行计算并产生结果。 我之所以说“行为”是因为大多数框架都没有使用稀疏过滤器来实现有害卷积 - 因为内存问题。

In a similar way, setting the atrous factor to 3 allows a regular 3x3 convolution to get signals from a 7x7 corresponding area.

以类似的方式,将atrous因子设置为3允许常规的3x3卷积从7x7对应区域获取信号。

This effect allows us to control the resolution at which we compute feature responses. Also, atrous convolution adds larger context without increasing the number of parameters or the amount of computation.

Deeplab also shows that the dilation rate must be tuned according to the size of the feature maps. They studied the consequences of using large dilation rates over small feature maps.

此效果允许我们控制计算特征响应的分辨率。 此外,萎缩卷积在不增加参数数量或计算量的情况下增加了更大的上下文。

Deeplab还表明,必须根据要素图的大小调整扩张率。 他们研究了在小特征图上使用大的扩张率的后果。

ResNet bottleneck layer

Side effects of setting larger dilation rates for smaller feature maps. For a 14x14 input image, a *3x3* filter with dilation rate of 15 makes the atrous convolution behaves like a regular 1x1 convolution.

为较小的特征图设置较大的扩张率的副作用。 对于14x14输入图像,扩散率为15的* 3x3 *滤波器使得迂回卷积的行为类似于常规的1x1卷积。

When the dilation rate is very close to the feature map’s size, a regular 3x3 atrous filter acts as a standard 1x1 convolution.

当扩张速率非常接近特征图的大小时,常规的3x3 atrous滤波器充当标准的1x1卷积。

Put in another way, the efficiency of atrous convolutions depends on a good choice of the dilation rate. Because of that, it is important to know the concept of output stride in neural networks.

换句话说,萎缩卷曲的效率取决于扩张率的良好选择。 因此,了解神经网络中输出步幅的概念非常重要。

Output stride explains the ratio of the input image size to the output feature map size. It defines how much signal decimation the input vector suffers as it passes the network.

输出步幅解释了输入图像大小与输出要素图大小的比率。它定义了输入向量在通过网络时遭受的信号抽取量。

For an output stride of 16, an image size of 224x224x3 outputs a feature vector with 16 times smaller dimensions. That is 14x14.

对于16的输出步幅,224x224x3的图像尺寸输出尺寸小16倍的特征向量。那是14x14。

Besides, Deeplab also debates the effects of different output strides on segmentation models. It argues that excessive signal decimation is harmful for dense prediction tasks. In short, models with smaller output stride - less signal decimation - tends to output finer segmentation results. Yet, training models with smaller output stride demand more training time.

此外,Deeplab还讨论了不同输出步幅对分割模型的影响。它认为过度的信号抽取对密集预测任务是有害的。简而言之,具有较小输出步幅的模型 - 较少的信号抽取 - 倾向于输出更精细的分割结果。然而,具有较小输出的训练模型需要更多的训练时间。Deeplab reports experiments with two configurations of output strides, 8 and 16. As expected, output stride = 8 was able to produce slightly better results. Here we choose output stride = 16 for practical reasons.

Deeplab报告了两种输出步幅配置的实验,8和16.正如预期的那样,输出stride = 8能够产生稍好的结果。这里我们选择输出stride = 16出于实际原因。

Also, because the atrous block doesn’t implement downsampling, ASPP also runs on the same feature response size. As a result, it allows learning features from multi-scale context using relative large dilation rates.

此外,由于atrous块没有实现下采样,ASPP也运行相同的功能响应大小。因此,它允许使用相对大的扩张率从多尺度背景学习特征。

The new Atrous Residual Block contains three residual units. In total, the 3 units have three 3x3convolutions. Motivated by multigrid methods, Deeplab proposes different dilation rates for each convolution. In summary, multigrid defines the dilation rates for each of the three convolutions.

新的Atrous残余块包含三个剩余单元。总共有3个单位有3个3x3对话。在多重网格方法的推动下,Deeplab为每个卷积提出了不同的扩张率。总之,多重网格定义了三个卷积中每个卷积的扩张率。
 

In practice:

For the new block4, when output stride = 16 and Multi Grid = (1, 2, 4), the three convolutions have rates = 2 · (1, 2, 4) = (2, 4, 8) respectively.

对于新的block4,当输出stride = 16且Multi Grid =(1,2,4)时,三个回旋分别具有速率= 2·(1,2,4)=(2,4,8)。

Atrous Spatial Pyramid Pooling

For ASPP, the idea is to provide the model with multi-scale information. To do that, ASPP adds a series atrous convolutions with different dilation rates. These rates are designed to capture long-range context. Also, to add global context information, ASPP incorporates image-level features via Global Average Pooling (GAP).

对于ASPP,其想法是为模型提供多尺度信息。为此,ASPP增加了一系列不同的扩张率的萎缩卷积。这些费率旨在捕捉长期背景。此外,为了添加全局上下文信息,ASPP通过全局平均池(GAP)合并了图像级功能。

This version of ASPP contains 4 parallel operations. These are a 1x1 convolution and three 3x3convolutions with dilation rates =(6,12,18). As we mentioned, at this point, the feature maps’ nominal stride is equal to 16.

此版本的ASPP包含4个并行操作。这些是1x1卷积和3个3x3对称,扩张率=(6,12,18)。正如我们所提到的,此时,特征图的标称步幅等于16。

Based on the original implementation, we use crop sizes of 513x513 for both: training and testing. Thus, using an output stride 16 means that ASPP receives feature vectors of size 32x32.

根据最初的实施,我们使用513x513的裁剪尺寸:训练和测试。因此,使用输出步幅16意味着ASPP接收大小为32×32的特征向量。Also, to add more global context information, ASPP incorporates image-level features. First, it applies GAP to the output features from the last atrous block. Second, the resulting features are fed to a 1x1 convolution with 256 filters. Finally, the result is bilinearly upsampled to the correct dimensions.

此外,为了添加更多全局上下文信息,ASPP包含图像级功能。首先,它将GAP应用于最后一个块的输出特征。其次,将得到的特征馈送到具有256个滤波器的1x1卷积。最后,结果以双线性上采样到正确的尺寸。

@slim.add_arg_scope
def atrous_spatial_pyramid_pooling(net, scope, depth=256):
    """
    ASPP consists of (a) one 1×1 convolution and three 3×3 convolutions with rates = (6, 12, 18) when output stride = 16
    (all with 256 filters and batch normalization), and (b) the image-level features as described in https://arxiv.org/abs/1706.05587
    :param net: tensor of shape [BATCH_SIZE, WIDTH, HEIGHT, DEPTH]
    :param scope: scope name of the aspp layer
    :return: network layer with aspp applyed to it.
    """

    with tf.variable_scope(scope):
        feature_map_size = tf.shape(net)

        # apply global average pooling
        image_level_features = tf.reduce_mean(net, [1, 2], name='image_level_global_pool', keep_dims=True)
        image_level_features = slim.conv2d(image_level_features, depth, [1, 1], scope="image_level_conv_1x1", activation_fn=None)
        image_level_features = tf.image.resize_bilinear(image_level_features, (feature_map_size[1], feature_map_size[2]))

        at_pool1x1 = slim.conv2d(net, depth, [1, 1], scope="conv_1x1_0", activation_fn=None)

        at_pool3x3_1 = slim.conv2d(net, depth, [3, 3], scope="conv_3x3_1", rate=6, activation_fn=None)

        at_pool3x3_2 = slim.conv2d(net, depth, [3, 3], scope="conv_3x3_2", rate=12, activation_fn=None)

        at_pool3x3_3 = slim.conv2d(net, depth, [3, 3], scope="conv_3x3_3", rate=18, activation_fn=None)

        net = tf.concat((image_level_features, at_pool1x1, at_pool3x3_1, at_pool3x3_2, at_pool3x3_3), axis=3,
                        name="concat")
        net = slim.conv2d(net, depth, [1, 1], scope="conv_1x1_output", activation_fn=None)
        return net

In the end, the features, from all the branches, are combined into a single vector via concatenation. This output is then convoluted with another 1x1 kernel - using BN and 256 filters.

最后,来自所有分支的特征通过连接组合成单个向量。 然后使用BN和256个过滤器将此输出与另一个1x1内核进行卷积。

After ASPP, we feed the result to another 1x1 convolution - to produce the final segmentation logits.

在ASPP之后,我们将结果提供给另一个1x1卷积 - 以产生最终的分段logits。

Implementation Details

Using the ResNet-50 as feature extractor, this implementation of Deeplab_v3 employs the following network configuration:

使用ResNet-50作为特征提取器,Deeplab_v3的这种实现采用以下网络配置:

  • output stride = 16     输出stride = 16
  • Fixed multi-grid atrous convolution rates of (1,2,4) to the new Atrous Residual block (block 4).   
  • 修复了(1,2,4)新的Atrous残余块的多网格迂回卷积率(方框4)。
  • ASPP with rates (6,12,18) after the last Atrous Residual block.
  • ASPP在最后一次Atrous Residual块之后的费率(6,12,18)。

Setting output stride to 16 gives us the advantage of substantially faster training. Comparing to output stride of 8, stride of 16 makes the Atrous Residual block deals with 4 times smaller feature maps than its counterpart.

将输出步幅设置为16使我们获得了大大加快训练的优势。与8的输出步幅相比,16的步幅使得Atrous Residual块处理的特征映射比其对应的要小4倍。

The multi-grid dilation rates are applied to the 3 convolutions inside the Atrous Residual block.

多网格膨胀率应用于Atrous残余块内的3个卷积。

Finally, each of the three parallel 3x3 convolutions in ASPP gets a different dilation rate - (6,12,18).

最后,ASPP中三个平行的3x3卷积中的每一个都得到不同的扩张率 - (6,12,18)。

Before computing the cross-entropy error, we resize the logits to the input’s size. As argued in the paper, it’s better to resize the logits than the ground-truth labels to keep resolution details.

在计算交叉熵错误之前,我们将logits的大小调整为输入的大小。正如论文中所论述的那样,最好调整logits的大小而不是地面实况标签以保持分辨率细节。

Based on the original training procedures, we scale each image using a random factor from 0.5 to 2. Also, we apply random left-right flipping to the scaled images.

基于原始训练程序,我们使用从0.5到2的随机因子来缩放每个图像。此外,我们将随机左右翻转应用于缩放图像。

Finally, we crop patches of size 513x513 for both training and testing.

最后,我们为训练和测试修剪了大小为513x513的补丁

def deeplab_v3(inputs, args, is_training, reuse):

    # mean subtraction normalization
    inputs = inputs - [_R_MEAN, _G_MEAN, _B_MEAN]

    # inputs has shape [batch, 513, 513, 3]
    with slim.arg_scope(resnet_utils.resnet_arg_scope(args.l2_regularizer, is_training,
                                                      args.batch_norm_decay,
                                                      args.batch_norm_epsilon)):
        resnet = getattr(resnet_v2, args.resnet_model) # get one of the resnet models: resnet_v2_50, resnet_v2_101 ...
        _, end_points = resnet(inputs,
                               args.number_of_classes,
                               is_training=is_training,
                               global_pool=False,
                               spatial_squeeze=False,
                               output_stride=args.output_stride,
                               reuse=reuse)

        with tf.variable_scope("DeepLab_v3", reuse=reuse):

            # get block 4 feature outputs
            net = end_points[args.resnet_model + '/block4']

            net = atrous_spatial_pyramid_pooling(net, "ASPP_layer", depth=256, reuse=reuse)

            net = slim.conv2d(net, args.number_of_classes, [1, 1], activation_fn=None,
                              normalizer_fn=None, scope='logits')

            size = tf.shape(inputs)[1:3]
            # resize the output logits to match the labels dimensions
            #net = tf.image.resize_nearest_neighbor(net, size)
            net = tf.image.resize_bilinear(net, size)
            return net

To implement atrous convolutions with multi-grid in the block4 of the resnet, we just changed this piece in the resnet_utils.py file.

为了在resnet的block4中使用多网格实现有害的卷积,我们只是在resnet_utils.py文件中更改了这个部分。

...
with tf.variable_scope('unit_%d' % (i + 1), values=[net]):
  # If we have reached the target output_stride, then we need to employ
  # atrous convolution with stride=1 and multiply the atrous rate by the
  # current unit's stride for use in subsequent layers.
  if output_stride is not None and current_stride == output_stride:
    # Only uses atrous convolutions with multi-graid rates in the last (block4) block
    if block.scope == "block4":
      net = block.unit_fn(net, rate=rate * multi_grid[i], **dict(unit, stride=1))
    else:
      net = block.unit_fn(net, rate=rate, **dict(unit, stride=1))
    rate *= unit.get('stride', 1)
...

Training

To train the network, we decided to use the augmented Pascal VOC dataset provided by Semantic contours from inverse detectors. 为了训练网络,我们决定使用来自反向探测器的语义轮廓提供的增强Pascal VOC数据集。

The training data is composed of 8,252 images. 5,623 from the training set and 2,299 from the validation set. To test the model using the original VOC 2012 val dataset, I removed 558 images from the 2,299 validation set. These 558 samples were also present on the official VOC validation set. Also, I added 330 images from the VOC 2012 train set that weren’t present either among the 5,623 nor the 2,299 sets. Finally, 10% of the 8,252 images (~825 samples) are held for validation, leaving the rest for training.

训练数据由8,252幅图像组成。 来自训练集的5,623和来自验证集的2,299。 为了使用原始VOC 2012 val数据集测试模型,我从2,299验证集中删除了558个图像。 这些558个样品也出现在官方VOC验证集上。 此外,我还添加了来自VOC 2012火车组的330张图像,这些图像在5,623和2,299套中都不存在。 最后,8,252张图像中的10%(约825个样本)被保留用于验证,剩下的用于训练。

Note that different from the original paper, this implementation is not pre-trained in the COCO dataset. Also, some of the techniques described in the paper for training and evaluation were not queried out.

请注意,与原始论文不同,此实现未在COCO数据集中预先训练。 此外,还没有查询文章中描述的用于培训和评估的一些技术。

Results

The model was able to achieve decent results on the PASCAL VOC validation set.

  • Pixel accuracy: ~91%
  • Mean Accuracy: ~82%
  • Mean Intersection over Union (mIoU): ~74%
  • Frequency weighed Intersection over Union: ~86%.

Bellow, you can check out some of the results in a variety of images from the PASCAL VOC validation set.

ResNet bottleneck layerResNet bottleneck layer

Concluding

The field of Semantic Segmentation is no doubt one of the hottest ones in Computer Vision. Deeplab presents an alternative to classic encoder-decoder architectures. It advocates the usage of atrous convolutions for feature learning in multi-range contexts. Feel free to clone the repo and tune the model to achieve closer results to the original implementation. The complete code is here.

Hope you like reading!

Cv图像处理

阅读数 3209