DETR :End to End Object Detection with Transformers

目标检测领域的里程碑式的工作

https://arxiv.org/abs/2005.12872

code:https://github.com/facebookresearch/detr Facebook AI(meta AI)

DETRDetection Transformers

文章题目:简单明了,包含两个关键词:端到端、transformer

目标检测领域:从目标检测开始火到detr都很少有端到端的方法,大部分方法最后至少需要后处理操作(NMS,non-maximum suppression非极大值抑制)。无论是proposal based方法、anchor based方法、non-anchor based方法,最后都会生成很多预测框,如何去除这些冗余的框就是NMS要做的事情。

问题:有了NMS,模型调参就会很复杂,而且即使训练好了一个模型,部署起来也非常困难(NMS不是所有硬件都支持)。所以一个简单的、端到端模型一直是大家梦寐以求的,而detr的出现解决了这些痛点。

一、Detr目标:

1、不需要proposal、不需要anchor,直接利用transformer这种全局建模的能力,把目标检测看做是集合预测问题

2、因为有了这种全局建模的能力,detr不会有那么多冗余框,最后出什么结果就是什么结果,不需要NMS做后处理,让模型的训练和部署简单不少

目的:不想让大家觉得目标检测是比图像分类难很多的任务,都可以用简单的,优雅的框架做出来

二、摘要

作者说,他们就是把目标检测的任务看成是一个集合预测问题:目标检测本来任务就是给定一个图像,预测一堆框,每个框不仅要知道的其坐标,还要知道框里包含物体的类别,这些框就是一个集合,不同的图像对应的集合也是不同的,给定一个图片,我要预测这个集合

因此这篇文章就是把目标检测做成一个端到端的框架,把之前特别依赖人的先验知识的部分删掉了(NMS部分、anchor),一旦把这两个部分拿掉之后,我们也不用费尽心思设计这种anchor,最后不会出现这么多框,不会用到NMS,也不会用到很多超参去调。两个贡献:1、使用新的目标函数,通过二分图匹配的方式,强制模型输出一组独一无二的预测(没有那么多冗余框,每个物体理想状态下就会生成一个框)。2、另外使用encoder-decoder的架构。

两个小贡献:

1、decoder还有另外一个输入learned object query,类似anchor的意思(给定这些object query之后,detr就可以把learned object query和全局图像信息结合一起,通过不同的做注意力操作,从而让模型直接输出最后的一组预测框)

2、想法&&实效性:并行比串行更合适,并不是检测一个大物体前必须先检测一个小物体,或从左到右检测,我们希望越快越好

DETR的好处:

1、简单性:想法上简单,不需要一个特殊的library,只要硬件支持transformer或CNN,就一定支持detr

2、性能:在coco数据集上,detr和一个训练非常好的faster RCNN基线网络取得了差不多的效果,模型内存和速度也和faster RCNN差不多

3、想法好,解决了目标检测领域很多痛点,写作好

4、别的任务:全景分割任务上detr效果很好,detr能够非常简单拓展到其他任务上

三、引言

1、目标检测任务:对每一个感兴趣的物体,去预测一些框,和物体类别,就是一个集合预测问题。

2、现在大多数好用的目标检测器,都是用间接的方式去处理集合预测问题,(1)比如proposal方式(如RCNN系列工作),(2)anchor方式(YOLO系列,focal loss),non-anchor based方法(物体中心点center net,FCOS),他们都没有直接做集合预测任务,而是设计一个替代(回归、分类)解决目标检测问题。所有这些方法性能受限于后处理操作(NMS),由于用了anchor和NMS导致检测器都非常复杂,难以优化和调参。

3、端到端的思想已经在别的很多任务里大范围使用,而且使任务更加简单好用,我们不要先验知识,就是要用一个端到端网络。

detr流程(训练):

1、CNN提特征

2、特征拉直,送到encoder-decoder中,encoder作用:进一步学习全局信息,为近下来的decoder,也就是最后出预测框做铺垫。直观的解释为什么需要使用transformer encoder呢?如果使用了transformer encoder,那么每一个点或者说每一个特征就会跟着图片里面的其他的特征有交互了,这样大概就知道那块是那个物体,对于同一个物体就应该只出一个框而不是好多框,所以全局的建模有利于移除冗余的框。

3、decoder生成框的输出,当你有了图像特征之后,还会有一个object query(限定了你要出多少框),通过query和特征在decoder里进行自注意力操作,得到输出的框(文中是100,无论是什么图片都会预测100个框)

4、生成的100个框如何与ground truth这个框做匹配并计算 loss? :二分图匹配,如上图,我们计算100个预测的框和2个GT框的matching loss,决定100个预测框哪两个是独一无二对应到红黄色的GT框,用匹配的框去算目标检测的loss。而没有匹配到的98个框就会被标记为没有物体。

5、推理1、2、3一致,第四步loss不需要,直接在最后的输出上用一个阈值卡一个输出的置信度,置信度比较大(>0.7的)保留,置信度小于0.7的当做背景物体。

结果:

1、detr对大物体预测很准,归功于transformer,能进行全局建模(原来使用anchor的话就会受限于anchor大小)

2、缺陷:对小物体效果不好(多尺度、多特征,可以提高小物体的检测)后续改进:Deformable DETR

3、detr训练很慢,500个epoch(coco大多数模型一般训练几十个epoch就行)

检测效果:

detr由于使用transformer全局建模,没有用anchor,想检测多大物体就检测多大,所以检测大物体效果较好。detr框架太简单,没有多尺度特征,没有FPN,没有复杂的目标检测头,所以在小目标检测效果不好

四、相关工作

目标检测:

目前大多数的检测器是根据初始猜测做预测:

1、two-stage:初始猜测是中间的proposal

2、one-stage:初始猜测是anchor或物体中心点

最近一篇论文做了详细比较,发现他们的性能和刚开始的初始猜测非常相关,怎么做后处理对性能影响至关重要

怎么后处理:

1、集合思想:可学习的NMS方法、关系型网络,可以利用自注意力方法去处理物体之间的联系,得出独一无二的预测,就不需要后处理的步骤(性能较低)

解决:人工干预:手工设计的场景特征帮助模型学习,但是detr目标是想让目标检测任务更加简单,不希望用到过多人工先验知识

2、循环检测器:encoder-decoder:让detr工作主要原因:transformer

五、方法

分两块:1、基于集合的目标函数怎么做,作者如何通过二分图匹配把预测的框和GT框连接在一起,算得目标函数 2、detr具体模型架构

目标函数部分:

detr模型最后输出是一个固定集合,无论图片是什么,最后都会输出n个(本文n=100)预测框

问题:detr每次都会出100个输出,但是实际上一个图片的GT的bounding box可能只有几个,如何匹配?如何计算loss?怎么知道哪个预测框对应GT框?

作者这里把这个问题转换成了一个二分图匹配的问题:

二分图又称作二部图,是图论中的一种特殊模型。 设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为一个二分图。简而言之,就是顶点集V可分割为两个互不相交的子集,并且图中每条边依附的两个顶点都分属于这两个互不相交的子集,两个子集内的顶点不相邻。

加权二分图匹配可以认为是有ABC三个工人,以及xyz三个工作,每个工人去做xyz工作的花费不同,如何去为每一个个人安排一个工作,使得最后我们的花费最低,可以使用遍历的方法,亦可以有很多高效的方法:匈牙利算法。

另外scipy包提供的linear sum assignment可以完成这个最优排列。detr论文里:代码也用的linear sum assignment函数来计算对应的匹配关系,只需要提供一个cost matrix矩阵就可以。a,b,c看成100个预测框,x,y,z看成GT框, cost matrix 损失矩阵未必都是正方形,最后丢到这个函数里面得到一个最优匹配。

那么对于目标检测任务,cost matrix 损失矩阵的值应该放些什么?loss包含两部分:分类loss、出框的准确度。所以也就是遍历所有的预测的框,那这些预测的框和gt框去算两个loss,然后把这个loss放到cost matrix矩阵 就可以了。这样就得到了对应gt的预测框(一对一),进而计算loss,梯度回传更新模型参数。

detr主体网络框架:

输入图片大小:3*800*1066(3:rgb),首先使用卷积网络获得特征:2048*25*34,然后降维变成256*25*34,然后给transformer添加位置信息:大小也是256*25*34,特征+位置作为transformer输入,特征拉直: 256*25*34 ==》 850*256,850就是序列长度,256是向量维度。后面的transformer encoder就跟普通的transformer encoder一样,输出==输出,仍然是850*256,接下来送入decoder里面。不同于一般的decoder,这里的object queries是一个可学习的,100*256大小的向量。在decoder里面做cross attension。输入 object queries ,另外一个输入是来自encoder的全局特征850*256.这两个去做自注意力操作,得到一个100*256的特征的decoder输出。最后添加一个检测头全连接层(FFN),获得类别预测(91类)和框预测(4:框的中心的+高度宽度),获得了100个框,利用匈牙利算法跟gt匹配,然后求loss,更新模型。

六、实验

检测效果:detr由于使用transformer全局建模,没有用anchor,想检测多大物体就检测多大,所以检测大物体效果较好。detr框架太简单,没有多尺度特征,没有FPN,没有复杂的目标检测头,所以在小目标检测效果不好。

下面的表格给出了 DETR 与基线 Faster RCNN 的定量性能对比。最上面一部分的 Faster RCNN 的性能结果是 Detectron2 的实现,之所以将 Faster RCNN 分成两部分,是因为 DETR 中使用了近年来很多新的训练 trick,如 GIoU loss、更强的数据增强策略、更长的训练时间,因此作者团队添加这些策略重新训练了 Faster RCNN,以作公平的对比。

近年来的新的训练策略对于目标检测模型的提升非常明显。对比表格的第一、第二部分,完全相同的模型,只是用了更优的训练策略,基本能稳定涨两个点。在同样的训练策略、网络规模大小的情况下,DETR 比 Faster RCNN 高 1-2 个点。对比表格的后两部分可以观察到这一点,DETR 对比基线的 Faster RCNN 还是还是有提升的。

DETR 在大物体的检测上远超 Faster RCNN,但是在小物体的检测上却也低了不少。

表格的后三列分别是小、中、大物体的检测性能,可以观察到 DETR 在大物体的检测上更出色,但是对于小物体的检测甚至远不如 Faster RCNN。大物体检测性能的提升得益于 Transformer 结构的全局建模能力,且没有预置的固定 anchor 的限制,因此预测框想多大就多大。而 DETR 在小物体上表现不佳,是因为本文中 DETR 的模型还是一个比较简单的模型,没有做很多针对目标检测的优化设计,比如针对小物体、多尺度的 FPN 设计。DETR 的网络结构还有待后续工作来改进。

表 1 detr和faster RCNN的对比,+表示用更好的训练策略把三个模型重新训练一遍

gflops参数:每秒进行的浮点运算次数,flops越小,模型越小,跑起来越快?X。如果更关心速度,比较fps

首先我们来看对于 Encoder 的可视化,下图展示了对于一组参考点的 Encoder 注意力热力图的可视化,即参考点对于图像中所有其他点自注意力值的大小。可以观察到,Transformer Encoder 基本已经能够非常清晰地区分开各个物体了,甚至热力图已经有一点实例分割的 mask 图的意思了。在有一定遮挡的情况下(左侧两头牛),也能够清楚地分开哪个是哪个。这种效果正是 Transformer Encoder 的全局建模能力所带来的,每个位置能够感知到图像中所有的其他位置。因此能够区分出图像中的不同物体,从而对于一个物体,尽量只出一个预测框。

通过前面的可视化,我们已经看到,Encoder 学习了一个全局的特征,基本已经能够区分开图中不同的物体。但是对于目标检测来说,大致地区分开不同的物体是不够的,我们还需要精确的物体的边界框坐标,这部分就由 Decoder 来做。

下图在 Decoder 特征中对每个不同的物体做了注意力的可视化,比如左图中的两头大象分别由蓝色和橙色表示。可以观察到,Decoder 网络中对于每个物体的注意力都集中在物体的边界位置,如大象的鼻子、尾巴、象腿等处。作者认为这是 Decoder 在区分不同物体边界的极值点(extremities),在 Encoder 能够区分开不同的物体之后,Decoder 再来关注不同物体边界的具体位置,最终精准地预测出不同物体的边框位置。因此,Encoder-Decoder 的结构是必要的,它们各司其职,一个都不能少。

扩展到全景分割任务

作者同时还将该网络应用于全景分割任务中.增加一个分割的head就可以。

下图是20个object query可视化(n=100,这里只有20个)

object query 到底学了什么(绿色代表小的bounding box,红色代表大的横向bounding box,蓝色代表大的竖向bounding box)object query和anchor有些像,anchor是提前定一些bounding box,把预测和这些提前定好的bounding box对比,object query是可以学习的。以第一个 object query 来说:对于一个图片, object query 会去问图片的左下角有没有小物体,以及中间有没有横向的大物体。

为了说明端到端的 DETR 框架的简洁性,作者在论文末尾给出了 DETR 模型定义、推理的 “伪代码”,总共不到 50 行。之所以这里的伪代码要加引号,是因为其实这已经不算是伪代码了,而是直接可运行的 PyTorch 代码。当然这个版本缺少了一些细节,但也完全能够展现出 DETR 的流程了。该版本直接用来训练,最终也能达到 40 的 AP。读者可以对应伪代码再过一遍刚才介绍的 DETR 完成流程,体会一下一个端到端的目标检测框架有多幺简洁。

代码实现:

后续关于detr的改进工作:

1、Deformable DETR: Deformable Transformers for End-to-End Object Detection

2、Omni-DETR: Omni-Supervised Object Detection with Transformers

3、UP-DETR: Unsupervised Pre-training for Object Detection with Transformers

4、PnP-DETR: Towards Efficient Visual Analysis with Transformers

5、SMAC-DETR

6、DAB-DETR: Dynamic Anchor Boxes are Better Queries for DETR

7、Accelerating DETR Convergence via Semantic-Aligned Matching

8、DN-DETR: Accelerate DETR Training by Introducing Query DeNoising

9、Open-Vocabulary DETR with Conditional Matching

10、OW-DETR: Open-world Detection Transformer

多模态| ALBEF

视频:多模态论文串讲

https://arxiv.org/abs/2107.07651

code: https://github.com/salesforce/ALBEF

写在前面:最近看了很多多模态的工作,现有的设计有哪些不足?我们又该如何去改进呢?首先来看模型的结构,因为需要处理文本和图片,所以模型开始需要有两个分支,分别抽取图像和文本特征。但是在多模态领域,视觉特征的重要性远远大于文本特征,所以要使用更强大的vision Embed,比如vit,同时对于多模态任务,多模态之间的融合也是十分重要的,也要保证模态融合的模型也要尽可能的大,因此网络应该跟(c)相近。模型确定了,接下来如何去训练呢?我们知道CLIP模型使用了一个对比学习的loss:ITCloss,这个效果很好,所以可以使用。另外常见的两个loss: image text matching(ITM),另一个是masked language modeling(MLM) 也可以继续使用。再回到ALBEF论文,其实它就是按照上述思路进行的设计。

它由一个图像编码器、一个文本编码器和一个多模态编码器组成。文章提出了一种图像文本对比损失,在图像文本融合之前对图像文本进行统一表示建模。图像文本匹配损失和掩码语言建模损失被应用于学习图像和文本之间的多模态交互。为了改进噪声数据的学习,我们使用动量模型生成伪目标来作为训练期间的额外监督。

为了改进在噪声监督下的学习,作者提出了动量蒸馏(MoD) ,使模型能够利用一个更大的web数据集。在训练过程中,作者通过取模型参数的移动平均来保持模型的动量版本,并使用动量模型生成伪目标作为额外的监督。 动量蒸馏可以解释为一种在线自我蒸馏的形式,其中使用学生模型组成的集合作为老师。类似的方法已经在半监督学习、标签噪声学习以及最近的对比学习中进行了探索应用。与现有研究不同,本文从理论上和实验上表明,动量蒸馏是一种通用的学习算法,可以提高模型在许多V+L 任务上的性能。

Pre-training Objectives

作者对ALBEF进行了三个目标的预训练:单模态编码器上的图像-文本对比学习(ITC) 、掩蔽语言建模(MLM) 和多模态编码器上的图像-文本匹配(ITM) 。作者通过在线对比 hard negative挖掘来改进ITM。

Image-Text Contrastive Learning

图像-文本对比学习的目的是在融合预训练更好的单模态表示。它学习了一个相似性函数,使匹配的图像-文本对具有更高的相似性得分。和是将[CLS]嵌入映射到标准化的低维(256d)表示的线性变换。

受MoCo的启发,作者维护了两个队列来存储动量单模态编码器的最新的M个图像-文本表示。动量编码器的归一化特征记为和。作者定义了和。

对于每个图像和文本,作者计算softmax归一化的图像到文本和文本到图像的相似度如下:

其中,τ是一个可学习的温度参数。设和表示ground truth的one-hot形式相似性,其中负对的概率为0,正对的概率为1。图像文本对比损失定义为p和y之间的交叉熵H:

Masked Language Modeling

Masked Language Modeling 同时利用图像和上下文文本来预测mask词。作者以15%的概率随机mask输入token,并用特殊token [MASK]替换它们。设表示mask文本,表示模型对mask token的预测概率。MLM使交叉熵损失最小化:

其中是一个one-hot形式的词汇分布,ground truth token的概率为1。

Image-Text Matching

图像-文本匹配可以预测一对图像和文本是正的(匹配)还是负的(不匹配)。作者使用多模态编码器的输出嵌入的[CLS] token作为图像-文本对的联合表示,并附加一个全连接(FC)层,然后是softmax来预测一个两类概率。

其中,是一个表示ground truth标签的二维one-hot向量。

作者提出了一种基于零计算开销的ITM任务进行 hard negatives采样的策略。如果负的图像-文本对共享相似的语义,但细粒度细节不同,那么它们是很难的。作者利用对比相似性来寻找batch内的 hard negatives。

对于一个batch中的每一幅图像,作者按照对比相似性分布从同一batch中抽取一个负文本,其中与图像更相似的文本有更高的机会被采样。同样地,作者还为每个文本采样一个hard negative图像。

Momentum Distillation

用于预训练的图像-文本对大多是从网络中收集起来的,而且它们往往会有噪声。正样本对通常是弱相关的:文本可能包含与图像无关的单词,或者图像可能包含文本中没有描述的实体 。

对于ITC学习,图像的负样本文本也可能与图像的内容相匹配。对于MLM,可能存在其他与描述图像相同(或更好)的标注不同的词。然而,ITC和MLM的one-hot标签会惩罚所有负标签预测,不管它们的正确性如何。为了解决这个问题,作者提出从动量模型生成的伪目标中学习。动量模型是一个连续发展的教师模型,它由单模态和多模态编码器的指数移动平均版本组成。

在训练过程中,训练基础模型,使其预测与动量模型的预测相匹配。具体来说,对于ITC,作者首先使用动量单模态编码器的特征计算图像-文本相似性,这个可以认为是一个softmax score,不再是一个 one hot向量。这样在模型训练的时候,我们希望在训练原始model的时候,不只是让预测跟目标值one hot尽可能接近,也希望能够和动量模型的输出保持一致,这样就能达到一个比较好的折中点,很多信息从one hot label来学习,但是当one hot label是错误的或者是有噪声的时候,我们希望这个稳定的动量模型提供一些改进。

多模态预训练 | ViLT

paper: https://arxiv.org/abs/2102.03334 ICML 2021

code: https://github.com/dandelin/ViLT

图1 Visual comparison of conventional VLP architectures
and our proposed ViLT.

视觉文本多模态任务,极其简单的多模态结构。模态的特征抽取做到了极小化,主要的计算量放在后边的模态融合上,提高了推理速度。多模态领域里程碑式工作。将区域特征,region 从多模态框架中移除。

Vision and Language Pre-training(VLP)已经已经在视觉语言的多模态下游任务中发展的很好。然而,当前VLP的工作主要集中在图像特征抽取上,一般来讲,图像特征抽取的越好,下游任务中的表现就越好。但是,现在主要有两个问题,一是效率太低,速度太慢,抽取图像特征花费大量时间,比多模态融合都多。我们应该花费更多时间在融合上。第二个是,你用一个预训练好的模型去抽取特征,表达能力受限。目标检测数据集不够大,规模不够大。如果模型不是端到端学习,只是从预训练模型抽取特征,大概率来说不是最优解。

Motivation

目前参数量最小的多模态Transformer方法。ViLT使用预训练的ViT来初始化交互的transformer,这样就可以直接利用交互层来处理视觉特征,不需要额外增加一个视觉encoder(如Faster-RCNN)。

Contribution

  1. 第一个基于patch projection的多模态预训练模型,其是首个使用patch projection来做visual embedding的方法。
  2. 证明了可以将BERT的方法和Vison Transformer结合起来用于多模态transformer
  3. 体现了全词掩码在预训练时以及图像增强在微调时的重要性。

Method

现有的视觉语言模型的三种结构类别:

VE = Vision Embedding

TE = Text Embedding

MI = Modality Interaction

上图是4种不同类型的VLP模型示意图。其中每个矩形的高表示相对计算量大小,VE、TE和MI分别是visual embedding、text embedding和modality interaction的简写。

作者提出这4种类型的主要依据有两点:

1.在参数或者计算上,两种模态是否保持平衡。

2.在网络深层中,两种模态是否相互作用。

VSE、VSE++和SCAN属于(a)类型。对图像和文本独立使用encoder,图像的更重,文本的更轻,使用简单的点积或者浅层attention层来表示两种模态特征的相似性。

CLIP属于(b)类型。每个模态单独使用重的transformer encoder,使用池化后的图像特征点积计算特征相似性。

ViLBERT、UNTER和Pixel-BERT属于(c)类型。这些方法使用深层transformer进行交互作用,但是由于VE仍然使用重的卷积网络进行特征抽取,导致计算量依然很大。

作者提出的ViLT属于(d)类型。ViLT是首个将VE设计的如TE一样轻量的方法,该方法的主要计算量都集中在模态交互上。

Modality Interaction Schema

模态交互部分可以分成两种方式:一种是single-stream(如BERT和UNITER),另一种是dual-stream(如ViLBERT和LXMERT)。其中single-stream是对图像和文本concate然后进行交互操作,而dual-stream是不对图像和文本concate然后进行交互操作。ViLT延用single-stream的交互方式,因为dual-stream会引入额外的计算量。

现有的VLP模型的text embedding基本上都使用类BERT结构(图1),但是visual embedding存在着差异。在大多数情况下,visual embedding是现有VLP模型的瓶颈。visual embedding的方法总共有三大类,其中region feature方法通常采用Faster R-CNN二阶段检测器提取region的特征,grid feature方法直接使用CNN提取grid的特征,patch projection方法将输入图片切片投影提取特征。ViLT是首个使用patch projection来做visual embedding的方法。

网络结构ViLT

作者提出的ViLT可以认为是目前最简单的多模态Transformer方法。ViLT使用预训练的ViT来初始化交互的transformer,这样就可以直接利用交互层来处理视觉特征,不需要额外增加一个视觉encoder。

文本特征输入部分,将文本看成一个词序列,通过word embedding matrix转化成word embedding,然后和position embedding进行相加,最后和modal-type embedding进行concate。

图像特征输入部分,将图像切块看成一个图像块序列,通过linear projection转化成visual embedding,然后和postion embedding进行相加,最后和modal-type embedding进行concate。

其中word embedding和visual embedding通过可学习的modal-type embedding标志位来区分,其中0标志位表示word embedding部分,1标志位表示visual embedding部分。

wrod embedding和visual embedding分别都嵌入了一个额外的可学习[class] embedding,方便和下游任务对接。

Pretraining Objectives

ViLT预训练的优化目标有两个:一个是image text matching(ITM),另一个是masked language modeling(MLM)

ImageText Matching:随机以0.5的概率将文本对应的图片替换成不同的图片,然后对文本标志位对应输出使用一个线性的ITM head将输出feature映射成一个二值logits,用来判断图像文本是否匹配。另外ViLT还设计了一个word patch alignment (WPA)来计算teextual subset和visual subset的对齐分数。

Masked Language Modeling:MLM的目标是通过文本的上下文信息去预测masked的文本tokens。随机以0.15的概率mask掉tokens,然后文本输出接两层MLP与车mask掉的tokens。

Whole Word Masking:另外ViLT还使用了whole word masking技巧。whole word masking是将连续的子词tokens进行mask的技巧,避免了只通过单词上下文进行预测。比如将“giraffe”词tokenized成3个部分[“gi”, “##raf”, “##fe”],可以mask成[“gi”, “[MASK]”, “##fe”],模型会通过mask的上下文信息[“gi”,“##fe”]来预测mask的“##raf”,就会导致不利用图像信息。

Experiment

本文提出的方法在效率上大大提升且表现出相似的性能,相比于region feature的方法速度快了60倍,相比于grid feature的方法快了4倍,而且下游任务表现出相似甚至更好的性能。

如图所示,ViLT相比于region feature的方法速度快了60倍,相比于grid feature的方法快了4倍,而且下游任务表现出相似甚至更好的性能。

缺点:

1、性能不够高,在一些数据集上得表现比不过C类方法,有可能因为对于现有的任务来说,因为数据集的bias,或者这个任务需要更多的视觉信息,因此需要更多得视觉部分,最后的效果才能好。

2、虽然推理时间快,但是训练速度很慢。只是结构上简化了多模态学习,但一般人还是玩不起。

GIRAFFE—NeRF、GRAF工作的延申

GIRAFFE: Representing Scenes as Compositional Generative Neural Feature Fields

论文报告: https://www.bilibili.com/video/BV1TX4y1P7ou/

github: https://github.com/autonomousvision/giraffe

GIRAFFE:composition方向的代表作

2021CVPR的最佳论文奖得主GIRAFFE是NeRF、GRAF工作的延申。

在NeRF之后,有人提出了GRAF(Generative Radiance Fields),关键点在于引入了GAN来实现Neural Radiance Fields;并使用conditional GAN实现对渲染内容的可控性。

在GRAF之后,GIRAFFE实现了composition(创作)。在NeRF、GRAF中,一个Neural Radiance Fields表示一个场景,one model per scene。而在GIRAFFE中,一个Neural Radiance Fields只表示一个物体,one object per scene(背景也算一个物体)。这样做的妙处在于可以随意组合不同场景的物体,可以改变同一场景中不同物体间的相对位置,渲染生成更多训练数据中没有的全新图像。

GIRAFFE实现composition

如图所示,GIRAFFE可以平移、旋转场景中的物体,还可以在场景中增添原本没有的新物体。另外,GIRAFFE还可以改变物体的形状和外观,因为网络中加入了形状编码、外观编码变量(shape codes zsi , appearance codes zai )。

GIRAFFE是一个基于学习的、完全可微的渲染引擎,用于将场景合成为多个“特征域”的总和。

简单回顾一下nerf,它们是一种描述和渲染3D场景的方法,在3D体积中任何给定的点上它的密度和辐射。它与光场的概念密切相关,光场是表达光如何流经给定空间的函数。对于空间中给定的(x,y,z)视点,图像将方向(θ, φ)的射线投射到一个场景中。对于这条线上的每个点,我们收集其密度和视相关的发射辐射,并以类似于传统光线追踪的方式将这些光线合成为单个像素值。这些NeRF场景是从各种姿势拍摄的图像收集学习,你会使用在结构从运动应用程序。

传统的GAN架构使用编码器和解码器设置,就像下图这样。在训练过程中,编码器接收一个图像,将其编码成一个压缩的表征,解码器利用这个表征来创建一个改变样式的新图像。在我们的训练数据集中的所有图像中重复多次,以便编码器和解码器学习如何在训练期间最大化我们想要实现的任务的结果。一旦训练完成,你可以发送一个图像到编码器,它会做同样的过程,生成一个新的和看不见的图像,根据你的需要。无论做什么工作,它都会起到非常相似的作用,不管是把一张脸的图像翻译成卡通画家那样的另一种风格,还是用草图创造出一幅美丽的风景。仅使用解码器,我们也称之为生成器,因为它是负责创建新图像的模型,我们可以在这个编码信息空间中行走,并对发送给生成器的信息进行采样,以生成无限量的新图像。这种编码的信息空间通常被称为潜在空间,而我们用来生成新图像的信息就是潜在代码。我们基本上是在这个最优空间内随机选择一些潜在的代码,然后它会根据我们想要完成的任务生成一个新的随机图像,当然,也会遵循这个生成器的训练过程。这是难以置信的酷,但正如我刚才所说,图像是完全随机的,我们没有或很少的想法,它看起来像什么,这已经是一个非常少有用的创造者。

这就是他们用这篇论文解决的问题。实际上,通过获取物体形状和外观的潜在代码并将其发送给解码器或生成器,他们能够控制物体的姿势,这意味着他们可以移动物体,改变物体的外观,添加其他物体,改变背景,甚至改变相机的姿势。所有这些变换都可以在每个对象或背景上独立完成,而不会影响图像中的任何其他内容!

如你看到的那样子,它比其他基于GAN的方法要好得多,这些方法通常无法将对象彼此分离,并且都会受到特定对象修改的影响。

与他们的方法不同的是,他们在三维场景表示中解决这个问题,就像我们如何看待现实世界一样,而不是像其他GANs那样停留在二维图像世界中。但除此之外,过程非常相似。它们对信息进行编码,识别对象,在潜在空间内对其进行编辑,然后解码生成新的图像。在这里,在这个潜在的空间里还有更多的步骤要做。我们可以将其视为经典GAN图像合成网络与神经渲染器的结合,神经渲染器用于从发送到网络的图像生成3D场景,正如我们看到的。

实现这一目标主要有三个步骤。对输入图像进行编码后,意味着我们已经处于潜在空间中,第一步是将图像转换为三维场景。但不仅仅是一个简单的3D场景,一个由3D元素组成的3D场景,即物体和背景。这种将图像视为由生成的体渲染组成的场景的方式允许它们更改生成图像中的摄影机角度并独立地控制对象。这是通过使用模型NERV来实现的,但是它们没有使用一个模型从输入图像生成整个锁定场景,而是使用两个单独的模型独立地生成对象和背景。这里称为采样特征字段。该网络的参数也在训练过程中学习。它与NERF非常相似。

有了这个场景和分离的元素,我们可以单独编辑它们而不影响图像的其余部分。这是第二步。他们可以对物体做任何他们想做的事情,比如改变它的位置和方向。换句话说,它们改变了物体或背景的姿势。在这一点上,他们甚至可以添加新的对象放置在他们想要的任何地方。然后,通过将所有特征字段添加到一起,将它们简单地组合到包含所有对象和背景的最终三维场景中。

最后,我们必须回到自然图像的二维世界。所以最后一步是把这个3D场景渲染成一个规则的图像。由于我们仍然处于三维世界中,我们可以改变相机的视点来决定我们将如何看待场景。然后,我们根据该相机光线和其他参数(如alpha值和透射率)对每个像素进行评估。这就是他们所说的特征图像,但是这个特征图像是由每个像素的特征向量组成的图像。由于我们仍处于潜在空间,这些特征需要转化为RGB颜色和高分辨率图像。这是通过使用典型的解码器来完成的,就像其他GAN架构一样,将其放大到原始尺寸,同时学习RGB通道的特征转换。瞧à, 你有你的新形象,有更多的控制,什么是生成!

当然,正如你所看到的,它在实际数据中使用时仍然不是完美的。尽管如此,它仍然令人印象深刻,是朝着正确方向迈出的重要一步,特别是考虑到这些都是完全由GANs生成的合成图像,而且它只是第一篇能够以这种精度控制生成图像的论文。

参考资料:

  • Michael Niemeyer and Andreas Geiger, (2021), “GIRAFFE: Representing Scenes as Compositional Generative Neural Feature Fields”, Published in CVPR 2021.
  • Project link with paper and more: https://m-niemeyer.github.io/project-pages/giraffe/index.html
  • Code: https://github.com/autonomousvision/giraffe
  • NERF video: https://youtu.be/ZkaTyBvS2w4

Cityscapes数据集

官网:https://www.cityscapes-dataset.com/

这个大型数据集包含了来自50个不同城市的街景场景中记录的多样化的双目视频序列,除了20000个弱注释帧以外,还有5000帧的高质量像素级注释。

主要参考如下:

下载前3个文件即可。其中3文件代表训练使用的原图,1文件代表精细标注label,2文件代表非精细标注label。有的同学要问了,那我下载1、3不就行了吗?我要这2有何用?其实Cityscapes数据集提供了34种分类,但有时我们不需要那么多,比如仅需要19分类(默认的)或任意多个分类,进行图像语意分割的神经网络训练,我们就需要用到他Cityscapes提供的自带工具进行label的转换,若缺少2文件,转换代码会报错无法进行。

该数据集由gtFine和leftImg8bit这两个目录组成,结构如下所示,其中aachen等表示拍摄场景的城市名:

├── gtFine
│   ├── train
│   │   ├── aachen
│   │   ├── bochum
│   │   └── bremen
│   └── val
│       └── frankfurt
└── leftImg8bit
    ├── train
    │   ├── aachen
    │   ├── bochum
    │   └── bremen
    └── val
        └── frankfurt

   原图存放在leftImg8bit文件夹中,精细标注的数据存放在gtFine (gt : ground truth) 文件夹中 。其中训练集共2975张(train),验证集500张(val),都是有相应的标签的。但测试集(test)只给了原图,没有给标签,官方用于线上评估大家提交的代码(防止有人用test集训练刷指标)。因此,实际使用中可以用validation集做test使用。

   标签文件中每张图像对应4个文件,其中_gtFine_polygons.json存储的是各个类和相应的区域(用多边形顶点的位置表示区域的边界);_gtFine_labelIds.png的值是0-33,不同的值代表不同的类,值和类的对应关系在代码中cityscapesscripts/helpers/labels.py中定义;_gtFine_instaceIds.png是示例分割的; _gtFine_color.png是给大家可视化的,不同颜色与类别的对应关系也在labels.py文件中说明。

可以使用Cityscapes的coarse标签做初步训练然后再用精细标签训练

这个数据集包含语义分割,实例分割,深度估计等标签数据,对应的训练标签如下所示:

cd $CITYSCAPES_ROOT
# 训练和校准对应的数据集
ls leftImg8bit/train/*/*.png > trainImages.txt
ls leftImg8bit/val/*/*.png > valImages.txt

# 训练和校准标签对应的数据集
ls gtFine/train/*/*labelIds.png > trainLabels.txt
ls gtFine/val/*/*labelIds.png.png > valLabels.txt

# 训练和校准实例标签对应的数据集
ls gtFine/train/*/*instanceIds.png > trainInstances.txt
ls gtFine/val/*/*instanceIds.png.png > valInstances.txt

# 训练和校准深度标签对应的数据集
ls disparity/train/*/*.png > trainDepth.txt
ls disparity/val/*/*.png.png > valDepth.txt

另外,torchvision支持很多现成数据集:

Class Definitions:

cityscapesscripts 脚本工具:

cityscapes scripts公开以下工具:

  • csDownload: 命令行下载cityscapes包
  • csViewer: 查看图像并覆盖批注(overlay the annotations)。
  • csLabelTool: 标注工具.
  • csEvalPixelLevelSemanticLabeling: Evaluate pixel-level semantic labeling results on the validation set. This tool is also used to evaluate the results on the test set.像素级评估
  • csEvalInstanceLevelSemanticLabeling: Evaluate instance-level semantic labeling results on the validation set. This tool is also used to evaluate the results on the test set.实例级评估
  • csEvalPanopticSemanticLabeling: Evaluate panoptic segmentation results on the validation set. This tool is also used to evaluate the results on the test set.全景分割评估
  • csCreateTrainIdLabelImgs: Convert annotations in polygonal format to png images with label IDs, where pixels encode “train IDs” that you can define in labels.py.将多边形格式的注释转换为带标签ID的png图像,其中像素编码“序列ID”,可以在labels.py中定义。
  • csCreateTrainIdInstanceImgs: Convert annotations in polygonal format to png images with instance IDs, where pixels encode instance IDs composed of “train IDs”.将多边形格式的注释转换为具有实例ID的png图像,其中像素对由“序列ID”组成的实例ID进行编码。
  • csCreatePanopticImgs: Convert annotations in standard png format to COCO panoptic segmentation format.将标准png格式的注释转换为COCO全景分割格式。

cityscapes scripts文件夹

文件夹内容如下:

  • helpers: 被其他脚本文件调用的帮助文件
  • viewer: 用于查看图像和标注的脚本
  • preparation: 用于将GroundTruth注释转换为适合您的方法的格式的脚本
  • evaluation: 评价你的方法的脚本
  • annotation: 被用来标注数据集的标注工具
  • download: 下载Cityscapes packages

请注意,所有文件顶部都有一个小型documentation。 非常重要

  • helpers/labels.py: 定义所有语义类ID的中心文件,并提供各种类属性之间的映射。
  • helpers/labels_cityPersons.py: 文件定义所有CityPersons行人类的ID并提供各种类属性之间的映射。
  • viewer/cityscapesViewer.py 查看图像并覆盖注释。
  • preparation/createTrainIdLabelImgs.py 将多边形格式的注释转换为带有标签ID的png图像,其中像素编码可以在“labels.py”中定义的“训练ID”。
  • preparation/createTrainIdInstanceImgs.py 将多边形格式的注释转换为带有实例ID的png图像,其中像素编码由“train ID”组成的实例ID。
  • evaluation/evalPixelLevelSemanticLabeling.py 该脚本来评估验证集上的像素级语义标签结果。该脚本还用于评估测试集的结果。
  • evaluation/evalInstanceLevelSemanticLabeling.py 该脚本来评估验证集上的实例级语义标签结果。该脚本还用于评估测试集的结果。
  • setup.py 运行 setup.py build_ext --inplace 启用cython插件以进行更快速的评估。仅针对Ubuntu进行了测试。

CCF(中国计算机学会)推荐国际学术会议和期刊目录(2022年拟定)

CCF(中国计算机学会)推荐国际学术会议和期刊目录(2022年拟定)终于来了!较前一版本(2019)拟新增期刊4个,会议6个;升级期刊18个,会议19个;移除期刊1个。值得注意的是:当前的目录正在公示期,并非2022最终版本,所以后面会再次发布最终版本

https://www.ccf.org.cn/Academic_Evaluation/By_category/

本文将重点关注 AI 领域,特别是计算机视觉方向的会议/期刊变动情况

【重点总结—拟定】

  • MICCAI 首次被收录,空降B类会议
  • PRCV 首次被收录,空降C类会议
  • NAACL 从C类升级到B类会议
  • ICLR 继续陪跑,没有被收录推荐目录
  • AI和图形学/多媒体两大领域的A类会议/期刊均没有变化(无新增/无降级/无移除)
  • WINE(Conference on Web and Internet Economics)空降A类期刊(属于交叉/综合/新兴领域)
  • SCIS(Science China Information Sciences)从B类期刊升级到A类期刊(属于交叉/综合/新兴领域)

仅以当前目录(拟定)来看,Amusi猜测:MICCAI、PRCV 的投稿量一定会爆炸增长。

Amusi侃侃:2019-2022这几年里,关于CCF 人工智能会议中争议最多的应该就是:建议ICLR新增到A类,而这次ICLR居然还是连目录(拟定)都没有进,A类/B类/C类中都没有;如果最终版确定没有,那不知道CCF目录下次更新是何时了…

下面完整罗列了推荐目录(拟定)中的【人工智能】和【计算机图形学与多媒体】两大领域的会议和期刊:

人工智能会议

A类不变,仍是7个会议:AAAI、NeurIPS、ACL、CVPR、ICCV、ICML和IJCAI,如下图所示:

B类有13个会议,新增一个NAACL(NAACL从C类升级到B类),如下图所示:

C类有20个会议,减少一个NAACL(NAACL从C类升级到B类),如下图所示:

人工智能期刊

A类不变,仍是4个期刊:AI、TPAMI、IJCV、JMLR,如下图所示:

B类有22个期刊,新增(空降)TACL 期刊,如下图所示:

C类有37个期刊,新增(空降)TIIS 期刊,如下图所示:

计算机图形学与多媒体会议

A类不变,仍是4个会议:ACM MM、SIGGRAPH、VR和IEEE VIS,如下图所示:

B类有14个会议,新增(空降)MICCAI,如下图所示:

C类有15个会议,新增(空降)PRCV、ICVRV和CVM,如下图所示:

计算机图形学与多媒体期刊

A类不变,仍是3个期刊:TOG、TIP和TCVG,如下图所示:

B类不变,仍是10个期刊,如下图所示:

C类有13个期刊,新增(空降)CVMJ,如下图所示:

Visual Prompt–视觉模板

Visual Prompt Tuning

https://github.com/KMnP/vpt

Exploring Visual Prompts for Adapting Large-Scale Models

https://github.com/hjbahng/visual_prompting

Visual Prompt Tuning

最近NLP领域提出了Prompt范新式,企图革新原先的Fine-tuning方法,而在CV领域中,Prompt其实可以理解为图像label的设计,从这个角度看,Prompt(预测文本中mask的字符,类似完形填空)其实是介于Image caption(迭代预测出每一个字符)和one-hot label(one-hot可以认为是prompt的特例,单字符通过text encoder成one-hot)之间的任务。最近在Visual-Language Model(缩写VLM)任务中,prompt开始展现出强大的能力.

Fine-tuning中:是预训练语言模型“迁就“各种下游任务。具体体现就是上面提到的通过引入各种辅助任务loss,将其添加到预训练模型中,然后继续pre-training,以便让其更加适配下游任务。总之,这个过程中,预训练语言模型做出了更多的牺牲。

Prompting中,是各种下游任务“迁就“预训练语言模型。具体体现也是上面介绍的,我们需要对不同任务进行重构,使得它达到适配预训练语言模型的效果。总之,这个过程中,是下游任务做出了更多的牺牲。

Abstract

目前调整预训练模型的方法是full fine-tuning,即完全微调。本文介绍Visual Prompt Tuning(VPT)作为一种有效的用于大规模Transformer的视觉微调。它只需要在输入空间引入少量(不到1%的模型参数)的可训练参数,同时冻结backbone。会发现在很多情况下,优于完全微调。

Introduction

对于大规模模型适应下游任务时,通常的策略是进行端到端的全面微调,然而这种策略需要为每个人物存储部署单独的主干参数,代价比较高,毕竟现在的Transformer体系结构比较大。

一种简单的方法是使用已经完善的其他策略,如下图(a):仅微调参数的子集,如分类器头部或者偏差项。之前的研究还会试着向主干添加额外的残差结构或者adapter,可以对Transformer实施类似的策略。然而,这些策略会在准确度上执行完全微调。

作者试图探索一种不同的方法:并不通过改变或者微调预训练好的Transformer本身,而是修改其输入。如下图(b)所示:将少量特定任务的可学习参数引入输入空间,同时在下游训练期间冻结backbone。实践中,这些附加参数只是预先加入到Transformer每层输入序列中,并在微调时和线性头一起学习。

在预训练主干用ViT的24个跨域的下游任务中,VPT优于了其他迁移学习的baseline,有20个超过了完全微调,同时保持了为每个单独任务储存较少参数的优势。(NLP任务中,prompt tuning旨在某些情况下才匹配完全微调的性能)。如下图(c)所示,VPT在地数据区尤其有效,结果也进一步表明,VPT是适应不断增长的视觉主干的最有效方法之一。

Visual-Prompt Tuning (VPT) vs . other transfer learning methods. (a) Current
transfer learning protocols are grouped based on the tuning scope: Full fine-tuning,
Head-oriented, and Backbone-oriented approaches. (b) VPT instead adds extra pa-
rameters in the input space. (c) Performance of different methods on a wide range
of downstream classification tasks adapting a pre-trained ViT-B backbone, with mean
and standard deviation annotated. VPT outperforms Full fine-tuning 20 out of 24 cases
while using less than 1% of all model parameters

Related Work

迁移学习两种代表性方法:Adapter在每个Transformer层后插入一个额外的小模块。通常包含一个线性向下头像、线性向上投影及一个残差连接。BitFit是在微调网络时更新偏置项并冻结其余backbone参数。这些方法在NLP已经成熟运用。作者则进一步实验表明了VPT在视觉任务的Transformer的模型调整上性能更加良好。

Prompt最初指的是在输入文本中预编语言汁了,以便预训练好的LM(Language Model)能够“理解”任务。最近的工作则是将prompt视为任务特定的连续向量,并在微调过程中通过梯度直接优化,即Prompt Tuning。prompting依然局限于文本编码器的输入。作者是第一个解决(同样的方法能成功的应用到视觉主干)并研究视觉prompt的普遍性和可行性的工作。

Approach

整体框架图如下图所示:

Visual-Prompt Tuning:

给定一个预先训练好的Transformer,在Embed层后的输入空间引入一组d维的p连续embedding。在微调过程中,只有prompt会被更新,主干将会冻结,根据加入prompt的层数量分为浅VPT和深VPT。

浅VPT :

Prompt仅插入第一层。每一个prompt token都是一个可学习的d维参数。集合和浅VPT表示如下:

VPT-Deep: Prompt被插入每一层的输入控件。集合和深VPT表示如下:

VPT对于多个下游任务都是有帮助的,只需要为每个任务存储学习到的prompt和分类头,重新使用预训练的Transformer,从而显着降低存储成本。

Experiments:

上图是关于prompt的位置,本文提出的是prepend,与直接在embedding上添加对比效果更好。除此之外,作为前置像素或者concat通道的效果也都在下降。

下图则是对prompt长度、深度的消融实验:

最佳提示长度因任务而异,即使只有一个prompt,深VPT的效果仍显着优于另外两种方法。

下图为最终输出的消融实验:

补充:

CoOp

CoOp明显是受到了AutoPrompt的启发,并且CoOp发现CLIP实际上就是prompt在visual-language model中的一个应用,于是CoOp在CLIP的基础上进一步进行改进。

CoOp先在四个数据集上做实验,发现更合理的prompt能够大幅度的提升分类精度尤其是使用了本文提出的CoOp之后,最终的分类精度远超CLIP人为设计的prompt。

和CLIP的主要不同之处在于,CoOp在CLIP的第二个阶段中引入了context optimization。具体的,CoOp将prompt设计为:

t=[V]1[ V]2…[V]M[CLASS]

其中每个[V]M向量跟word embedding的维度相同,可以理解为可学习的context,并且所有类别对应的context共享参数。

Exploring Visual Prompts for Adapting Large-Scale Models

这篇文章参考了Ian Goodfellow等人(2018)对抗样本中的对抗重编程思想, 对抗重编程的目标是我使用一个任务A的网络(无需重新训练该网络)来做任务B。我们设一个经过训练的模型,其原本的任务是,给定输入x,会得到输出f(x)现在有一个攻击者,其对抗任务是对于给定的输入x~,会得到输出g(x~),这里x~和x不一定需要是同域的。这看起来是不可行的,但是攻击者通过学习对抗重编程函数hf(.;θ)和hg(.;θ)来实现这两个任务之间的映射。hf(.;θ)用于将输入从x~所在的域转换到x所在的域,也就是说,经过hf的处理后得到的hf(x~;θ)对于f而言是有效的输入;而hg则将f的输出f(h(x~;θ))映射会g(x~)的输出。

图 1:对抗重编程图示。(a)将 ImageNet 标签映射至对抗任务标签(图像中的方块)。(b)来自对抗任务的图像(左)被嵌入对抗程序的中心(中),得到对抗图像(右)。该对抗程序使 Inception V3 网络执行计算图像中方块数量的任务。(c)使用对抗图像进行推断的图示。把对抗图像输入该网络时,网络预测映射至对抗任务的 ImageNet 标签。

图2提供了适应预训练模型的不同方法的摘要。微调和线性探测的用法非常灵活:它们可用于使模型适应新的输入域或具有不同输出语义的新任务。但是,在线性探针的情况下,在微调和模型输出(通常在倒数第二层的激活)的情况下,他们还需要一定程度的访问模型:参数。域的适应性是模型适应的有趣替代方法,因为它仅使用图像到图像翻译等技术来修改模型的输入[50,19]。像域的适应性一样,视觉提示也将输入修改为模型。因此,一旦最终用户找到了视觉提示,就不需要在测试时间控制模型本身。这打开了独特的应用程序;例如,用户可以将适应域的图像馈送到只能通过输入来操纵的在线API。域的适应性重点是调整源域以看起来像目标域,同时需要源和目标数据集。另一方面,我们认为视觉提示可以以更任意的方式引导模型。例如,可以通过扰动输入像素来使用新的输出语义进行一个完全不同的分类任务来执行完全不同的分类任务。同样,尽管域适应方法通常是输入条件,但我们在本文中探索的视觉提示是固定的(即输入 – agnostic),如NLP,例如在NLP中,将相同的自然语言提示添加到所有模型查询中

CLIPSeg:一个使用文本和图像prompt能同时作三个分割任务的模型

Image Segmentation Using Text and Image Prompts (CVPR 2022).

论文地址:https://arxiv.org/abs/2112.10003

代码地址:https://github.com/timojl/clipseg

图像分割通常是通过为一组固定的对象类训练模型来解决的。之后合并其他类或更复杂的查询是昂贵的,因为它需要在包含这些表达式的数据集上重新训练模型。在本文中,作者提出了一个可以在测试时根据任意prompt生成图像分割的系统。prompt可以是文本或图像。这种方法使模型能够为三个常见的分割任务创建一个统一的模型(只训练一次),这些任务具有不同的挑战:引用表达式分割、zero-shot分割和one-shot分割。本文以 CLIP 模型为骨干,使用基于Transformer的解码器进行扩展,以实现密集预测。在对 PhraseCut 数据集的扩展版本进行训练后,本文的系统会根据自由文本prompt或表达查询的附加图像为图像生成二进制分割图。这种新颖的混合输入不仅可以动态适应上述三个分割任务,还可以适应任何可以制定文本或图像查询的二进制分割任务。最后,作者发现本文的系统能够很好地适应通用查询。

图像分割需要一个模型来输出每个像素的预测。与图像分类相比,分割不仅需要预测可以看到的内容,还需要预测可以找到的位置。经典语义分割模型仅限于分割训练集中的类别。目前,已经出现了不同的方法来扩展这种相当受限的设置:

1)在广义的zero-shot分割中,需要通过将未见类别与已见类别相关联来分割已见和未见类别。

2)在one-shot分割中,除了要分割的查询图像之外,还以图像的形式提供所需的类。

3)在引用表达式分割(RES)中,模型在复杂的文本查询上进行训练,但在训练期间可以看到所有类(即没有对未见过的类进行泛化)。

在这项工作中,作者引入了 CLIPSeg 模型(如上图),该模型能够基于任意文本查询或图像进行分割。 CLIPSeg 可以解决上述所有三个任务。这种多模态输入格式超越了现有的多任务基准,例如 Visual Decathlon,其中输入始终以图像的形式提供。为了实现这个系统,作者使用预训练的 CLIP 模型作为主干,并在顶部训练一个轻量的条件分割层(解码器)。作者使用CLIP 的联合文本-视觉嵌入空间来调节本文的模型,这使模型能够处理文本形式的prompt以及图像。本文的想法是教解码器将 CLIP 内的激活与输出分割相关联,同时允许尽可能少的数据集偏差并保持 CLIP 的出色和广泛的预测能力。

本文采用通用的二进制预测设置,其中与prompt匹配的前景必须与背景区分开来。这种二进制设置可以适应Pascal zero-shot分割所需的多标签预测。虽然本文工作的重点是建设一个通用模型,作者发现 CLIPSeg 在三个low-shot分割任务中实现了竞争性能。此外,它能够泛化到它从未见过分割的类和表达式。

本文的主要技术贡献是 CLIPSeg 模型,它通过提出一种基于Transformer的轻量级解码器,扩展了著名的 CLIP Transformer用于zero-shot和one-shot分割任务。该模型的一个关键新颖之处在于分割目标可以通过不同的方式指定:通过文本或图像。这使模型能够为多个基准训练一个统一的模型。对于基于文本的查询,与在 PhraseCut 上训练的网络不同,本文的模型能够泛化到涉及看不见的单词的新查询。对于基于图像的查询,作者探索了各种形式的视觉prompt 工程——类似于语言建模中的文本prompt 工程。

方法

作者使用基于视觉Transformer的 (ViT-B/16) CLIP模型作为主干,并使用小型、参数高效的Transformer解码器对其进行扩展。解码器在自定义数据集上进行训练以执行分割,而 CLIP 编码器保持冻结状态。一个关键的挑战是避免在分割训练期间对预测施加强烈的bias并保持 CLIP 的多功能性。

考虑到这些需求,作者提出了 CLIPSeg:一个简单的、纯基于Transformer的解码器。当查询图像通过 CLIP 视觉Transformer时,某些层 S 的激活被读取并投影到解码器的token嵌入大小 D。然后,这些提取的激活(包括 CLS  token)在每个Transformer之前添加到解码器的内部激活中。

解码器具有与提取的 CLIP 激活一样多的Transformer块。解码器通过在其Transformer(最后一层) 的token上应用线性投影来生成二进制分割,其中 P 是 CLIP 的patch大小。为了告知解码器分割目标,作者使用 FiLM通过条件向量调制解码器的输入激活。

这个条件向量可以通过两种方式获得:(1) 使用文本查询的 CLIP 文本Transformer嵌入和 (2) 在特征工程prompt图像上使用 CLIP 视觉Transformer。CLIP 本身没有经过训练,仅用作冻结特征提取器。由于紧凑的解码器,对于 D = 64,CLIPSeg 只有 1,122,305 个可训练参数。

由于学习到的位置嵌入,原始 CLIP 被限制为固定的图像大小。本文通过插入位置嵌入来启用不同的图像大小(包括更大的图像)。为了验证这种方法的可行性,作者比较了不同图像尺寸的预测质量,发现对于大于 350 像素的图像,ViT-B/16 的性能只会降低。

在本文的实验中,作者使用 CLIP ViT-B/16,patch大小 P 为 16,如果没有另外说明,则使用 D = 64 的投影尺寸。作者在 S = [3 , 7 , 9] 层提取 CLIP 激活,因此本文的解码器只有三层。

模型通过条件向量接收有关分割目标的信息(“要分割什么?”),这可以通过文本或图像(通过视觉prompt工程)提供。由于 CLIP 为图像和文本标题使用共享嵌入空间,可以在嵌入空间和插值向量上的条件之间进行插值。形式上,设是支持图像的嵌入,是样本 i 的文本嵌入,作者通过线性插值获得条件向量 ,其中 a 是从[0 , 1]均匀采样 。作者在训练期间使用这种随机插值作为数据增强策略。

1 PhraseCut + Visual prompts (PC+)

本文使用 PhraseCut 数据集,其中包含超过 340,000 个具有相应图像分割的短语。最初,该数据集不包含视觉支持,而仅包含短语,并且每个短语都存在相应的对象。作者以两种方式扩展这个数据集:视觉支持样本和负样本。为了为prompt p 添加视觉支持图像,作者从共享prompt p 的所有样本的集合Sp中随机抽取。

此外,作者将负样本引入数据集,即没有对象与prompt匹配的样本。为此,样本的短语被替换为概率为qneg的不同短语。短语使用一组固定前缀随机扩充。在考虑到对象位置的情况下,作者在图像上应用随机裁剪,确保对象至少部分可见。在本文的其余部分,将此扩展数据集称为 PhraseCut+(缩写为 PC+)。与仅使用文本来指定目标的原始 PhraseCut 数据集相比,PC+ 支持使用图像-文本插值进行训练。这样,本文可以训练一个对文本和视觉输入进行操作的联合模型。

2 Visual Prompt Engineering

在传统的基于 CNN 的one-shot语义分割中,masked pooling 已成为计算用于条件的原型向量的标准技术。提供的支持mask被下采样并与来自 CNN 沿空间维度的后期特征图相乘,然后沿空间维度汇集。这样,只有与支持对象有关的特征才被考虑在原型向量中。

这种方法不能直接应用于基于Transformer的架构,因为语义信息也在整个层次结构中的 CLS  token中积累,而不仅仅是在特征图中。绕过 CLS  token并直接从特征图的masked pooling中导出条件向量也是不可能的,因为它会破坏文本嵌入和 CLIP 视觉嵌入之间的兼容性。

为了更多地了解如何将目标信息整合到 CLIP 中,作者在一个没有分割的简单实验中比较了几个变体及其混杂效应。作者考虑视觉和基于文本的嵌入之间的余弦距离(对齐),并使用原始 CLIP 权重而无需任何额外的训练。


具体来说,作者使用 CLIP 来计算对应于图像中对象名称的文本嵌入ti。然后,将它们与原始图像的视觉嵌入s0和使用修改后的 RGB 图像或注意力mask突出显示目标对象的视觉嵌入sh进行比较。通过对对齐向量[sht0,sht1,…]进行softmax,获得了如上图所示的分布。

对于定量分数,作者只考虑目标对象名称嵌入t0,希望它与突出显示的图像嵌入sh比与原始图像嵌入s0具有更强的对齐。这意味着,如果突出显示技术改进了对齐方式,则对象概率的增加应该很大。作者基于LVIS 数据集进行分析,因为它的图像包含多个对象和一组丰富的类别。

CLIP-Based Masking


直接等效于视觉Transformer中的masked pooling是将mask应用于token。通常,视觉Transformer由一组固定的token组成,这些token可以通过多头注意力在每一层进行交互:用于读取的 CLS  token和最初从图像patch中获得的与图像区域相关的token。

现在,可以通过将一个或多个Transformer层的交互约束到mask内patch token以及仅 CLS  token来合并mask。上表(左)表明这种引入mask的形式效果不佳。通过限制与 CLS token的交互(上表 左,顶部两行),仅实现了小的改进,而限制所有交互会显着降低性能。由此得出结论,在内部结合图像和mask需要更复杂的策略。

Visual Prompt Engineering

除了在模型中应用mask,还可以将mask和图像组合成一个新图像,然后由视觉Transformer处理。类似于 NLP 中的prompt工程(例如在 GPT-3 中),作者将此过程称为视觉prompt工程。由于这种形式的prompt设计是新颖的,并且在这种情况下表现最好的策略是未知的,作者对设计视觉prompt的不同变体进行了广泛的评估。

发现mask和图像如何组合的确切形式非常重要。作者确定了三种图像操作来改善对象文本prompt和图像之间的对齐:降低背景亮度、模糊背景(使用高斯滤波器)和裁剪到对象。所有三者的组合表现最好。因此在其余部分,将使用这个变体。

实验


上表展示了在原始 PhraseCut 数据集上评估referring expression segmentation(RES)的性能对比。

在广义zero-shot分割中,测试图像除了包含已知类别外,还包含以前从未见过的类别。作者使用 Pascal-VOC 基准评估模型的zero-shot分割性能,性能如上表。

在 Pascal-5i 上,本文的通用模型 CLIPSeg (PC+) 在最先进的方法中实现了具有竞争力的性能,只有最近的 HSNet 表现更好。COCO-20i 上的结果表明 CLIPSeg 在除 PhraseCut(+) 之外的其他数据集上训练时也能很好地工作。

上图展示了CLIPSeg(PC+)对各种prompt的定性预测,深色表示预测强度。

从上表中,可以发现在 PC+ 上训练的 CLIPSeg 版本的性能优于 CLIP-Deconv baseline和在 L VIS 上训练的版本,后者仅包含对象标签而不是复杂的短语。这一结果表明,数据集的可变性和模型的复杂性都是泛化所必需的。

为了确定 CLIPSeg 性能的关键因素,作者对 PhraseCut 进行了消融研究。,如上表所示,作者分别评估基于文本和基于视觉prompt的性能以获得完整的图片。当使用随机权重而不是 CLIP 权重时(“无 CLIP 预训练”),基于文本的性能和视觉性能都会下降。当参数数量减少到 16 个(“D = 16”)时,性能大幅下降,这表明解码器中信息处理的重要性。使用不利的视觉prompt技术会降低视觉输入的性能。

SegFix: Model-Agnostic Boundary Refinement for Segmentation

paper:https://arxiv.org/abs/2007.04269

code:https://github.com/openseg-group/openseg.pytorch

本文提出了一种模型无关的后处理方案,即用内部像素的预测代替原来不可靠的边界像素预测,以提高由任何现有分割模型生成的分割结果的边界质量。该方法仅对输入图像进行两步处理:(i)定位边界像素;(ii)识别每个边界像素对应的内部像素(通过学习从边界像素到内部像素的方向来建立对应关系)。

该方法不需要先验信息的分割模型,达到接近实时的速度。实验验证,SegFix减少了cityspace数据集中各种先进模型(ADE20K和GTA5)所生成的分割结果的边界误差。

动机:现有的分割模型大多不能很好地处理边界上的误差预测。作者对比了三个模型的直方图误差,如图1(错误像素数到物体边缘的距离的统计)可以看到,距离边界越远的像素越有可能被很好地分类,并且有很多误差分布在沿边界约为5像素的范围内。

图1

提出了一种新方法来学习边界像素和内部像素的对应关系,即提出了一种新的模型无关的后处理方法来减少边缘误差,即用对应的内部像素的标签代替边缘像素的标签。使用边界映射和方向映射隐式编码边界像素的相对距离信息,偏移映射可以直接应用于各种方法,而不需要进行任何再训练。

主要包括2步:第一步是定位边界像素,第二步是找出每一个边界像素所关联的内部像素。我们采用一个向量去表示每个边界像素跟其对应内部像素的对应关系,这个向量从边界像素出发,指向一个内部像素。

1)首先要先确定物体边界:用卷积来预测一个binary mask来表示边界。1表示边界,0表示other
2)学习一个从边界pixel到内(外)部pixel的方向
3)让边界pixel沿着这个方向移动特定的距离即可确定该边界pixel。

训练阶段,我们首先将图像输入到主干网络中来预测特征图,然后应用边界分支预测二值边界图和应用方向分支预测方向图,我们将边界损失和方向损失分别应用到预测的边界图和方向图上。在测试阶段,我们首先将模型执行到图像上,以生成偏移量映射,然后根据偏移量图对现有方法的分割结果进行细化。

 边缘预测分支

边界分支实现为1×1conv→BN→ReLU,输出通道256个。然后我们应用线性分类器和上采样预测,生成最终的边界图B;训练时由真值boundary map监督约束,使用二值交叉熵损失作为边界损失,学习良好的边缘检测。

 方向预测分支

边界分支实现为1×1conv→BN→ReLU,输出通道256个。然后我们应用线性分类器和上采样预测,生成最终的方向图D;预测所有位置的像素与之最近的同类像素的方向,这里的方向不是连续的,而是对[0,2π)量化成m个值后的结果;

训练时使用真值direction map监督该分支生成的离散方向,使用多分类交叉熵损失(standard category-wise cross-entropy loss)来作为方向预测损失。

offset branch

用来转换预测得到的D(已经乘过了B )到坐标偏移图offset_map:不同的方向会被映射到不同的坐标偏移值。重新对现有方法在边界区域的预测结果进行调整。

最终通过坐标映射(the grid-sample scheme [Spatial transformer networks]),重新对现有方法在边界区域的预测结果进行调整。基于偏移映射对粗略标记图进行细化。用不同的箭头表示不同的偏移向量。在粗略标签图中红色标记错误位置,在精确标签图中箭头方向标记相应的修正位置。

  真值生成和分析

先从ground-truth分割图生成distance map,再在此基础上生成boundary map和direction map,过程如图3(b)。

(1)    distance map

对于每个像素而言,distance map上都记录了它相距属于其他类别像素的最小欧式距离。这实际上也表示了像素到边界的距离。

首先将真值mask分解成K个binary map (0 or 1),每个map关联着不同的类别(语义类别、实例类别),K表示图像中包含的类别数量。之后在每个binary map上独立计算distance map,这里使用了scipy的函数scipy.ndimage.morphology.distance_transform_edt() 。这个函数用于距离转换,计算图像中非零点到最近背景点(即0)的距离。它被用在每个类别独立的binary mask上,正好计算的就是相距于其他类别(每个binary mask上的0表示的就是“其他类别”)的最小欧氏距离。

计算完各个mask后,再计算一个融合的distance map来实现对于所有的K个distance map的集成。

(2)    boundary map

使用融合后的distance map,对其使用一个预设的阈值进行划分,小于阈值的作为边界区域的像素,大于阈值的认为在特定目标区域内部。因为距离值越小,说明越接近边界。

(3)    direction map

这里在未合并的K层distance map上,分别使用9×9的Sobel滤波器。基于Sobel滤波器的方向是在[0°,360°)内,并且每个像素位置的方向都指向邻域内部距离目标边界最远的像素。整个方向范围被均匀划分成m=8类,然后每个像素的方向被赋值成对应的方向类别

  实验结果

backboneHRNet、DeepLab V3等。

数据集:Cityscapes and GTA5

参数设置:初始学习率为0.04,权值衰减为0.0005,裁剪大小为512×512,批量大小为16,并训练80K次迭代。0.9的“poly”学习率策略。

数据增强:随机水平翻转、随机裁剪和随机亮度抖动。

评价指标:mask F-score and top-1 direction accuracy 对预测的二值边界图进行mask F-score,对预测的方向图进行方向精度评定。

定量评价:用mIoU分类度量区域分割的整体性能边界;F-score用于测量距离上有小松弛的预测掩模的边界质量。

语义分割任务
实例分割任务

albumentations 数据增强工具

简介 & 安装

albumentations 是一个给予 OpenCV的快速训练数据增强库,拥有非常简单且强大的可以用于多种任务(分割、检测)的接口,易于定制且添加其他框架非常方便。支持所有常见的计算机视觉任务,例如分类,语义分割,实例分割,对象检测和姿势估计。

albumentations包是一种针对数据增强专门写的API,里面基本包含大量的数据增强手段,其特点:

1、Albumentations支持所有常见的计算机视觉任务,如分类、语义分割、实例分割、目标检测和姿态估计

2、该库提供了一个简单统一的API,用于处理所有数据类型:图像(rbg图像、灰度图像、多光谱图像)、分割掩码、边界框和关键点。

3、该库包含70多种不同的增强功能,可以从现有数据中生成新的训练样本。

4、Albumentations快。我们对每个新版本进行基准测试,以确保增强功能提供最大的速度。

5、它与流行的深度学习框架(如PyTorch和TensorFlow)一起工作。顺便说一下,Albumentations是PyTorch生态系统的一部分。

6、由专家写的。作者既有生产计算机视觉系统的工作经验,也有参与竞争性机器学习的经验。许多核心团队成员是Kaggle Masters和Grandmasters。

7、该库广泛应用于工业、深度学习研究、机器学习竞赛和开源项目。

github及其示例地址如下:

可以通过 pip 的方式直接安装,也可以通过 pip + github 的方式,或者conda

  • pip 方式:pip install albumentations
  • pip + github:pip install -U git+https://github.com/albu/albumentations
  • conda方式,此方式需要先安装 imgaug,然后在安装 albumentations
conda install -c conda-forge imgaug
conda install albumentations -c albumentations
安装问题

在安装部分有个小问题,我的本机已经安装完opencv-python,然后我们再去安装albumentations的时候,出现了一个问题,就是我们的opencv-python阻止albumentations的安装,报错如下:# Could not install packages due to anEnvironmentError: [WinError 5] 拒绝访问,是因为在安装albumentations的时候还要安装opencv-python-headless,这个库和opencv冲突。

解决方式

对于这个问题的解决方式是我们在新的虚拟环境中,先安装albumentations,安装albumentations的时候,我们会伴随安装上一个opencv-python-headless,这个完全可以替代opencv-python的功能,所以我们就不用安装opencv了。

Spatial-level transforms(空间层次转换)

空间级转换将同时改变输入图像和附加目标,如掩模、边界框和关键点。下表显示了每个转换支持哪些附加目标。

Spatial-level transforms(空间层次转换) 空间级转换将同时改变输入图像和附加目标,如掩模、边界框和关键点。下表显示了每个转换支持哪些附加目标。

支持的列表

Blur
CLAHE
ChannelDropout
ChannelShuffle
ColorJitter
Downscale
Emboss
Equalize
FDA
FancyPCA
FromFloat
GaussNoise
GaussianBlur
GlassBlur
HistogramMatching
HueSaturationValue
ISONoise
ImageCompression
InvertImg
MedianBlur
MotionBlur
MultiplicativeNoise
Normalize
Posterize
RGBShift
RandomBrightnessContrast
RandomFog
RandomGamma
RandomRain
RandomShadow
RandomSnow
RandomSunFlare
RandomToneCurve
Sharpen
Solarize
Superpixels
ToFloat
ToGray
ToSepia

how to use:类似totch中的 transform 模块

import albumentations as A
import cv2
 
import matplotlib.pyplot as plt
 
# Declare an augmentation pipeline
transform = A.Compose([
    A.RandomCrop(width=512, height=512),
    A.HorizontalFlip(p=0.8),
    A.RandomBrightnessContrast(p=0.5),
])
 
# Read an image with OpenCV and convert it to the RGB colorspace
image = cv2.imread("aa.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
 
# Augment an image
transformed = transform(image=image)
transformed_image = transformed["image"]
plt.imshow(transformed_image)
plt.show()

详细使用案例:

1、VerticalFlip 围绕X轴垂直翻转输入

import albumentations as A
import cv2
import numpy as np
import matplotlib.pyplot as plt
#解决中文显示问题
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# Read an image with OpenCV and convert it to the RGB colorspace
image = cv2.imread("aa.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Augment an image
transformed = A.VerticalFlip(always_apply=False, p=1)(image=image)
transformed_image = transformed["image"]
plt.subplot(1, 2, 1)
plt.title('原图')   #第一幅图片标题
plt.imshow(image)
plt.subplot(1, 2, 2)
plt.title('Blur后的图像')
plt.imshow(transformed_image)
plt.show()

2、Blur模糊输入图像

import albumentations as A
import cv2
import numpy as np
import matplotlib.pyplot as plt
#解决中文显示问题
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# Read an image with OpenCV and convert it to the RGB colorspace
image = cv2.imread("aa.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Augment an image
transformed = A.Blur(blur_limit=15,always_apply=False, p=1)(image=image) 
transformed_image = transformed["image"]
plt.subplot(1, 2, 1)
plt.title('原图')   #第一幅图片标题
plt.imshow(image)
plt.subplot(1, 2, 2)
plt.title('Blur后的图像')
plt.imshow(transformed_image)
plt.show()

3、HorizontalFlip 围绕y轴水平翻转输入

import albumentations as A
import cv2
import numpy as np
import matplotlib.pyplot as plt
#解决中文显示问题
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# Read an image with OpenCV and convert it to the RGB colorspace
image = cv2.imread("aa.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Augment an image
transformed = A.HorizontalFlip(always_apply=False, p=1)(image=image) 
transformed_image = transformed["image"]
plt.subplot(1, 2, 1)
plt.title('原图')   #第一幅图片标题
plt.imshow(image)
plt.subplot(1, 2, 2)
plt.title('HorizontalFlip后的图像')
plt.imshow(transformed_image)
plt.show()

4、Flip水平,垂直或水平和垂直翻转输入

import albumentations as A
import cv2
import numpy as np
import matplotlib.pyplot as plt
#解决中文显示问题
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# Read an image with OpenCV and convert it to the RGB colorspace
image = cv2.imread("aa.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Augment an image
transformed = A.Flip(always_apply=False, p=1)(image=image) 
transformed_image = transformed["image"]
plt.subplot(1, 2, 1)
plt.title('原图')   #第一幅图片标题
plt.imshow(image)
plt.subplot(1, 2, 2)
plt.title('Flip后的图像')
plt.imshow(transformed_image)
plt.show()

5、Transpose, 通过交换行和列来转置输入

import albumentations as A
import cv2
import numpy as np
import matplotlib.pyplot as plt
#解决中文显示问题
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# Read an image with OpenCV and convert it to the RGB colorspace
image = cv2.imread("aa.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Augment an image
transformed = A.Transpose(always_apply=False, p=1)(image=image)
transformed_image = transformed["image"]
plt.subplot(1, 2, 1)
plt.title('原图')   #第一幅图片标题
plt.imshow(image)
plt.subplot(1, 2, 2)
plt.title('Transpose后的图像')
plt.imshow(transformed_image)
plt.show()

6、RandomCrop 随机裁剪

import albumentations as A
import cv2
import numpy as np
import matplotlib.pyplot as plt
#解决中文显示问题
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# Read an image with OpenCV and convert it to the RGB colorspace
image = cv2.imread("aa.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Augment an image
transformed = A.RandomCrop(512, 512,always_apply=False, p=1)(image=image)
transformed_image = transformed["image"]
plt.subplot(1, 2, 1)
plt.title('原图')   #第一幅图片标题
plt.imshow(image)
plt.subplot(1, 2, 2)
plt.title('RandomCrop后的图像')
plt.imshow(transformed_image)
plt.show()

7、RandomGamma 随机灰度系数

import albumentations as A
import cv2
import numpy as np
import matplotlib.pyplot as plt
#解决中文显示问题
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# Read an image with OpenCV and convert it to the RGB colorspace
image = cv2.imread("aa.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Augment an image
transformed = A.RandomGamma(gamma_limit=(20, 20), eps=None, always_apply=False, p=1)(image=image)
transformed_image = transformed["image"]
plt.subplot(1, 2, 1)
plt.title('原图')   #第一幅图片标题
plt.imshow(image)
plt.subplot(1, 2, 2)
plt.title('RandomGamma后的图像')
plt.imshow(transformed_image)
plt.show()

8、RandomRotate90 将输入随机旋转90度,N次

import albumentations as A
import cv2
import numpy as np
import matplotlib.pyplot as plt
#解决中文显示问题
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# Read an image with OpenCV and convert it to the RGB colorspace
image = cv2.imread("aa.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Augment an image
transformed = A.RandomRotate90(always_apply=False, p=1)(image=image)
transformed_image = transformed["image"]
plt.subplot(1, 2, 1)
plt.title('原图')   #第一幅图片标题
plt.imshow(image)
plt.subplot(1, 2, 2)
plt.title('RandomRotate90后的图像')
plt.imshow(transformed_image)
plt.show()

10、ShiftScaleRotate 随机平移,缩放和旋转输入

import albumentations as A
import cv2
import numpy as np
import matplotlib.pyplot as plt
#解决中文显示问题
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# Read an image with OpenCV and convert it to the RGB colorspace
image = cv2.imread("aa.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Augment an image
transformed = A.ShiftScaleRotate(shift_limit=0.0625, scale_limit=0.1, rotate_limit=45, interpolation=1, border_mode=4, value=None, mask_value=None, always_apply=False, p=1)(image=image)
transformed_image = transformed["image"]
plt.subplot(1, 2, 1)
plt.title('原图')   #第一幅图片标题
plt.imshow(image)
plt.subplot(1, 2, 2)
plt.title('ShiftScaleRotate后的图像')
plt.imshow(transformed_image)
plt.show()

11、CenterCrop 裁剪图像的中心部分


# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']import albumentations as A
import cv2
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['axes.unicode_minus'] = False
# Read an image with OpenCV and convert it to the RGB colorspace
image = cv2.imread("aa.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Augment an image
transformed = A.CenterCrop(256, 256, always_apply=False, p=1)(image=image)
transformed_image = transformed["image"]
plt.subplot(1, 2, 1)
plt.title('原图')  # 第一幅图片标题
plt.imshow(image)
plt.subplot(1, 2, 2)
plt.title("CenterCrop后的图像")
plt.imshow(transformed_image)
plt.show()

12、GridDistortion网格失真

import albumentations as A
import cv2
import numpy as np
import matplotlib.pyplot as plt
 
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# Read an image with OpenCV and convert it to the RGB colorspace
image = cv2.imread("aa.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Augment an image
transformed = A.GridDistortion(num_steps=10, distort_limit=0.3,border_mode=4, always_apply=False, p=1)(image=image)
transformed_image = transformed["image"]
plt.subplot(1, 2, 1)
plt.title('原图')  # 第一幅图片标题
plt.imshow(image)
plt.subplot(1, 2, 2)
plt.title("GridDistortion后的图像")
plt.imshow(transformed_image)
plt.show()

13、ElasticTransform 弹性变换

import albumentations as A
import cv2
import numpy as np
import matplotlib.pyplot as plt
 
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# Read an image with OpenCV and convert it to the RGB colorspace
image = cv2.imread("aa.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Augment an image
transformed = A.ElasticTransform(alpha=5, sigma=50, alpha_affine=50, interpolation=1, border_mode=4,always_apply=False, p=1)(image=image)
transformed_image = transformed["image"]
plt.subplot(1, 2, 1)
plt.title('原图')  # 第一幅图片标题
plt.imshow(image)
plt.subplot(1, 2, 2)
plt.title("ElasticTransform后的图像")
plt.imshow(transformed_image)
plt.show()

14、RandomGridShuffle把图像切成网格单元随机排列

import albumentations as A
import cv2
import numpy as np
import matplotlib.pyplot as plt
 
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# Read an image with OpenCV and convert it to the RGB colorspace
image = cv2.imread("aa.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Augment an image
transformed = A.RandomGridShuffle(grid=(3, 3), always_apply=False, p=1) (image=image)
transformed_image = transformed["image"]
plt.subplot(1, 2, 1)
plt.title('原图')  # 第一幅图片标题
plt.imshow(image)
plt.subplot(1, 2, 2)
plt.title("RandomGridShuffle后的图像")
plt.imshow(transformed_image)
plt.show()

15、HueSaturationValue随机更改图像的颜色,饱和度和值

import albumentations as A
import cv2
import numpy as np
import matplotlib.pyplot as plt
 
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# Read an image with OpenCV and convert it to the RGB colorspace
image = cv2.imread("aa.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Augment an image
transformed = A.HueSaturationValue(hue_shift_limit=20, sat_shift_limit=30, val_shift_limit=20, always_apply=False, p=1)(image=image)
transformed_image = transformed["image"]
plt.subplot(1, 2, 1)
plt.title('原图')  # 第一幅图片标题
plt.imshow(image)
plt.subplot(1, 2, 2)
plt.title("HueSaturationValue后的图像")
plt.imshow(transformed_image)
plt.show()

16、PadIfNeeded 填充图像

import albumentations as A
import cv2
import numpy as np
import matplotlib.pyplot as plt
 
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# Read an image with OpenCV and convert it to the RGB colorspace
image = cv2.imread("aa.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Augment an image
transformed = A.PadIfNeeded(min_height=2048, min_width=2048, border_mode=4, always_apply=False, p=1)(image=image)
transformed_image = transformed["image"]
plt.subplot(1, 2, 1)
plt.title('原图')  # 第一幅图片标题
plt.imshow(image)
plt.subplot(1, 2, 2)
plt.title("PadIfNeeded后的图像")
plt.imshow(transformed_image)
plt.show()

17、RGBShift,对图像RGB的每个通道随机移动值

import albumentations as A
import cv2
import numpy as np
import matplotlib.pyplot as plt
 
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# Read an image with OpenCV and convert it to the RGB colorspace
image = cv2.imread("aa.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Augment an image
transformed = A.RGBShift(r_shift_limit=10, g_shift_limit=20, b_shift_limit=20, always_apply=False, p=1)(image=image)
transformed_image = transformed["image"]
plt.subplot(1, 2, 1)
plt.title('原图')  # 第一幅图片标题
plt.imshow(image)
plt.subplot(1, 2, 2)
plt.title("RGBShift后的图像")
plt.imshow(transformed_image)
plt.show()

18、GaussianBlur 使用随机核大小的高斯滤波器对图像进行模糊处理

import albumentations as A
import cv2
import numpy as np
import matplotlib.pyplot as plt
 
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# Read an image with OpenCV and convert it to the RGB colorspace
image = cv2.imread("aa.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Augment an image
transformed = A.GaussianBlur(blur_limit=11, always_apply=False, p=1)(image=image)
transformed_image = transformed["image"]
plt.subplot(1, 2, 1)
plt.title('原图')  # 第一幅图片标题
plt.imshow(image)
plt.subplot(1, 2, 2)
plt.title("GaussianBlur后的图像")
plt.imshow(transformed_image)
plt.show()

CLAHE自适应直方图均衡

import albumentations as A
import cv2
import numpy as np
import matplotlib.pyplot as plt
 
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# Read an image with OpenCV and convert it to the RGB colorspace
image = cv2.imread("aa.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Augment an image
transformed = A.CLAHE(clip_limit=4.0, tile_grid_size=(8, 8), always_apply=False, p=0.5)(image=image)
transformed_image = transformed["image"]
plt.subplot(1, 2, 1)
plt.title('原图')  # 第一幅图片标题
plt.imshow(image)
plt.subplot(1, 2, 2)
plt.title("CLAHE后的图像")
plt.imshow(transformed_image)
plt.show()

ChannelShuffle随机重新排列输入RGB图像的通道

import albumentations as A
import cv2
import numpy as np
import matplotlib.pyplot as plt
 
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# Read an image with OpenCV and convert it to the RGB colorspace
image = cv2.imread("aa.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Augment an image
transformed = A.ChannelShuffle(always_apply=False, p=0.5)(image=image)
transformed_image = transformed["image"]
plt.subplot(1, 2, 1)
plt.title('原图')  # 第一幅图片标题
plt.imshow(image)
plt.subplot(1, 2, 2)
plt.title("ChannelShuffle后的图像")
plt.imshow(transformed_image)
plt.show()

InvertImg反色

import albumentations as A
import cv2
import numpy as np
import matplotlib.pyplot as plt
 
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# Read an image with OpenCV and convert it to the RGB colorspace
image = cv2.imread("aa.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Augment an image
transformed = A.InvertImg(always_apply=False, p=0.5)(image=image)
transformed_image = transformed["image"]
plt.subplot(1, 2, 1)
plt.title('原图')  # 第一幅图片标题
plt.imshow(image)
plt.subplot(1, 2, 2)
plt.title("InvertImg后的图像")
plt.imshow(transformed_image)
plt.show()

Cutout 随机擦除

import albumentations as A
import cv2
import numpy as np
import matplotlib.pyplot as plt
 
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# Read an image with OpenCV and convert it to the RGB colorspace
image = cv2.imread("aa.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Augment an image
transformed = A.Cutout(num_holes=20, max_h_size=20, max_w_size=20, fill_value=0, always_apply=False, p=1)(image=image)
transformed_image = transformed["image"]
plt.subplot(1, 2, 1)
plt.title('原图')  # 第一幅图片标题
plt.imshow(image)
plt.subplot(1, 2, 2)
plt.title("Cutout后的图像")
plt.imshow(transformed_image)
plt.show()

RandomFog随机雾化

import albumentations as A
import cv2
import numpy as np
import matplotlib.pyplot as plt
 
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# Read an image with OpenCV and convert it to the RGB colorspace
image = cv2.imread("aa.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Augment an image
transformed = A.RandomFog(fog_coef_lower=0.3, fog_coef_upper=1, alpha_coef=0.08, always_apply=False, p=1)(image=image)
transformed_image = transformed["image"]
plt.subplot(1, 2, 1)
plt.title('原图')  # 第一幅图片标题
plt.imshow(image)
plt.subplot(1, 2, 2)
plt.title("RandomFog后的图像")
plt.imshow(transformed_image)
plt.show()

GridDropout网格擦除

import albumentations as A
import cv2
import numpy as np
import matplotlib.pyplot as plt
 
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# Read an image with OpenCV and convert it to the RGB colorspace
image = cv2.imread("aa.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Augment an image
transformed = A.GridDropout(ratio=0.5, unit_size_min=None, unit_size_max=None, holes_number_x=None, holes_number_y=None,
                            shift_x=0, shift_y=0, always_apply=False, p=0.5)(image=image)
transformed_image = transformed["image"]
plt.subplot(1, 2, 1)
plt.title('原图')  # 第一幅图片标题
plt.imshow(image)
plt.subplot(1, 2, 2)
plt.title("GridDropout后的图像")
plt.imshow(transformed_image)
plt.show()

组合变换(Compose)

变换不仅可以单独使用,还可以将这些组合起来,这就需要用到 Compose 类,该类继承自 BaseCompose。Compose 类含有以下参数:

  • transforms:转换类的数组,list类型
  • bbox_params:用于 bounding boxes 转换的参数,BboxPoarams 类型
  • keypoint_params:用于 keypoints 转换的参数, KeypointParams 类型
  • additional_targets:key新target 名字,value 为旧 target 名字的 dict,如 {‘image2’: ‘image’},dict 类型
  • p:使用这些变换的概率,默认值为 1.0
image3 = Compose([
        # 对比度受限直方图均衡
            #(Contrast Limited Adaptive Histogram Equalization)
        CLAHE(),
        # 随机旋转 90°
        RandomRotate90(),
        # 转置
        Transpose(),
        # 随机仿射变换
        ShiftScaleRotate(shift_limit=0.0625, scale_limit=0.50, rotate_limit=45, p=.75),
        # 模糊
        Blur(blur_limit=3),
        # 光学畸变
        OpticalDistortion(),
        # 网格畸变
        GridDistortion(),
        # 随机改变图片的 HUE、饱和度和值
        HueSaturationValue()
    ], p=1.0)(image=image)['image']

随机选择(OneOf)

它同Compose一样,都是做组合的,都有概率。区别就在于:Compose组合下的变换是要挨着顺序做的,而OneOf组合里面的变换是系统自动选择其中一个来做,而这里的概率参数p是指选定后的变换被做的概率。例:

image4 = Compose([
        RandomRotate90(),
        # 翻转
        Flip(),
        Transpose(),
        OneOf([
            # 高斯噪点
            IAAAdditiveGaussianNoise(),
            GaussNoise(),
        ], p=0.2),
        OneOf([
            # 模糊相关操作
            MotionBlur(p=.2),
            MedianBlur(blur_limit=3, p=0.1),
            Blur(blur_limit=3, p=0.1),
        ], p=0.2),
        ShiftScaleRotate(shift_limit=0.0625, scale_limit=0.2, rotate_limit=45, p=0.2),
        OneOf([
            # 畸变相关操作
            OpticalDistortion(p=0.3),
            GridDistortion(p=.1),
            IAAPiecewiseAffine(p=0.3),
        ], p=0.2),
        OneOf([
            # 锐化、浮雕等操作
            CLAHE(clip_limit=2),
            IAASharpen(),
            IAAEmboss(),
            RandomBrightnessContrast(),            
        ], p=0.3),
        HueSaturationValue(p=0.3),
    ], p=1.0)(image=image)['image']

在程序中的使用

def get_transform(phase: str):
    if phase == 'train':
        return Compose([
            A.RandomResizedCrop(height=CFG.img_size, width=CFG.img_size),
            A.Flip(p=0.5),
            A.RandomRotate90(p=0.5),
            A.ShiftScaleRotate(p=0.5),
            A.HueSaturationValue(p=0.5),
            A.OneOf([
                A.RandomBrightnessContrast(p=0.5),
                A.RandomGamma(p=0.5),
            ], p=0.5),
            A.OneOf([
                A.Blur(p=0.1),
                A.GaussianBlur(p=0.1),
                A.MotionBlur(p=0.1),
            ], p=0.1),
            A.OneOf([
                A.GaussNoise(p=0.1),
                A.ISONoise(p=0.1),
                A.GridDropout(ratio=0.5, p=0.2),
                A.CoarseDropout(max_holes=16, min_holes=8, max_height=16, max_width=16, min_height=8, min_width=8, p=0.2)
            ], p=0.2),
            A.Normalize(
                mean=[0.485, 0.456, 0.406],
                std=[0.229, 0.224, 0.225],
            ),
            ToTensorV2(),
        ])
    else:
        return Compose([
            A.Resize(height=CFG.img_size, width=CFG.img_size),
            A.Normalize(
                mean=[0.485, 0.456, 0.406],
                std=[0.229, 0.224, 0.225],
            ),
            ToTensorV2(),
        ])

分类问题中的使用

在 albumentations 中可以用于分类问题中的操作包括:

HorizontalFlip, IAAPerspective, ShiftScaleRotate, CLAHE, RandomRotate90,
Transpose, ShiftScaleRotate, Blur, OpticalDistortion, GridDistortion, HueSaturationValue, IAAAdditiveGaussianNoise, GaussNoise, MotionBlur, MedianBlur, RandomBrightnessContrast, IAAPiecewiseAffine, IAASharpen, IAAEmboss, Flip, OneOf, Compose

分割问题中的使用

在此示例中,需要使用到如下的类:

PadIfNeeded, HorizontalFlip, VerticalFlip, CenterCrop, Crop, Compose, Transpose, RandomRotate90, ElasticTransform, GridDistortion, OpticalDistortion, RandomSizedCrop, OneOf, CLAHE, RandomBrightnessContrast, RandomGamma

填充(Padding)

在 Unet 这样的网络架构中,输入图片的尺寸需要尺寸需要能被 $2^N$ 整除,其中 $N$ 是池化层(maxpooling)的层数。在最简单的 Unet 结构中 $N$ 的值为 5,那么我们就需要将输入的图片填充到能被 $2^5=32$ 除尽的数字,应该上面图片的大小为 101,因此最接近的大小为 128。要进行此操作就需要用到 PadIfNeeded 类,其含有如下参数:

  • min_height:最终图片的最小高度,int 类型
  • min_width:最终图片的最小宽度,int 类型
  • border_mode:OpenCV 边界模式,默认值为 cv2.BORDER_REFLECT_101
  • value:如果border_mode 值为 cv2.BORDER_CONSTANT 时的填充值,int、float或者 int、float数组类型
  • mask_value:如果border_mode 值为 cv2.BORDER_CONSTANT 时 mask 的填充值,int、float或者 int、float数组类型
  • p:进行此转换的概率,默认值为 1.0

默认条件下 PadIfNeeded 会对图片和mask的四条边都进行填充,填充的类型包括零填充(zero)、常量填充(constant)和反射填充(reflection),默认为反射填充。使用方法如下:

image1 = PadIfNeeded(p=1, min_height=128, min_width=128)(image=image, mask=mask)
image11_padded = image1['image']
mask11_padded = image1['mask']
# (128, 128, 3) (128, 128)
print(image11_padded.shape, mask11_padded.shape)

裁剪与中心裁剪(Crop & CenterCrop)

上面我们使用了 PadIfNeeded 对图片进行了填充,想要恢复原始的大小这时候就可以使用相关的裁剪方法:CenterCropCrop 等类。

先来看 CenterCrop 的使用,它主要从输入的图片中间进行裁剪,主要含有以下参数:

  • height:裁剪的高度,int 类型
  • width:裁剪的宽度,int 类型
  • p:使用此转换方法的概率,默认值为 1.0

原始的图片和mask大小为 101,因此此处设置需要裁剪的宽高(original_height/original_width)为 101,使用方法如下:

image2 = CenterCrop(p=1.0, height=original_height, 
                    width=original_width)(image=image11_padded, mask=mask11_padded)

image22_center_cropped = image2['image']
mask22_center_cropped = image2['mask']
# (101, 101, 3) (101, 101)
print(image22_center_cropped.shape, mask22_center_cropped.shape)

非破坏性转换

从上面的转换操作中可以看到操作破坏了图像的空间信息,对于想卫星、航空或者医学图片我们并不希望破坏它原有的空间结构,如以下的八种操作就不会破坏原有图片的空间结构。

通过 HorizontalFlipVerticalFlipTransposeRandomRotate90 四种操作的组合就可以得到上面的八种操作。这些操作可以参考上面《分类问题中的使用》章节。

非刚体转换

在医学影像问题中非刚体装换可以帮助增强数据。albumentations 中主要提供了以下几种非刚体变换类:ElasticTransformGridDistortion 和 OpticalDistortion。三个类的主要参数如下:

ElasticTransform 类参数:

  • alphasigma:高斯过滤参数,float类型
  • alpha_affine:范围为 (-alpha_affine, alpha_affine),float 类型
  • interpolationborder_modevaluemask_value:与其他类含义一样
  • approximate:是否应平滑具有固定大小核的替换映射(displacement map),若启用此选项,在大图上会有两倍的速度提升,boolean类型。
  • p:使用此转换的概率,默认值为 0.5

GridDistortion 类参数:

  • num_steps:在每一条边上网格单元的数量,默认值为 5,int 类型
  • distort_limit:如果是单值,那么会被转成 (-distort_limit, distort_limit),默认值为 (-0.03, 0.03),float或float数组类型
  • interpolationborder_modevaluemask_value:与其他类含义一样
  • p:使用此转换的概率,默认值为 0.5

OpticalDistortion 类参数:

  • distort_limit:如果是单值,那么会被转成 (-distort_limit, distort_limit),默认值为 (-0.05, 0.05),float或float数组类型
  • shift_limit:如果是单值,那么会被转成 (-shift_limit, shift_limit),默认值为 (-0.05, 0.05),float或float数组类型
  • interpolationborder_modevaluemask_value:与其他类含义一样
  • p:使用此转换的概率,默认值为 0.5

使用方式如下:

# 弹性装换
image41 = ElasticTransform(p=1, alpha=120, sigma=120 * 0.05, 
                          alpha_affine=120 * 0.03)(image=image, mask=mask)
image_elastic = image41['image']
mask_elastic = image41['mask']

# 网格畸变
image42 = GridDistortion(p=1, num_steps=10)(image=image, mask=mask)

image_grid = image42['image']
mask_grid = image42['mask']

# 光学畸变
image43 = OpticalDistortion(p=1, distort_limit=2, shift_limit=0.5)(image=image, mask=mask)

image_optical = image43['image']
mask_optical = image43['mask']

效果如下:

组合多种转换

我们可以将上面的填充、裁剪、非刚体转换、非破坏性转换组合起来:

image5 = Compose([
   # 非刚体转换
    OneOf([RandomSizedCrop(min_max_height=(50, 101), 
                           height=original_height, width=original_width, p=0.5),
          PadIfNeeded(min_height=original_height, 
                      min_width=original_width, p=0.5)], p=1),
    # 非破坏性转换
    VerticalFlip(p=0.5),              
    RandomRotate90(p=0.5),
    # 非刚体转换
    OneOf([
        ElasticTransform(p=0.5, alpha=120, sigma=120 * 0.05, alpha_affine=120 * 0.03),
        GridDistortion(p=0.5),
        OpticalDistortion(p=1, distort_limit=2, shift_limit=0.5)                  
        ], p=0.8),
    # 非空间性转换
    CLAHE(p=0.8),
    RandomBrightnessContrast(p=0.8),    
    RandomGamma(p=0.8)])(image=image, mask=mask)

image_heavy = image5['image']
mask_heavy = image5['mask']

运行的效果如下: