语义分割——全卷积网络FCN

语义分割(Semantic Segmentation)是计算机视觉领域中的一项重要任务,它旨在将图像或视频中的每个像素点分类到特定的类别中。语义分割是计算机视觉的基础任务之一,图像分类的目的是对一副图像进行整体归类,判断这幅图像属于哪一类;更进一步的是目标识别,也就是识别图像中的某一类物体并把物体找出来,不过只要找到大致范围就可以了;再此基础上再进一步的就是语义分割,语义分割的目标也是把图像中的某类物体找出来,不过不仅仅框出范围,而且要把物体的边界精准的识别出来,好比把物体从图像背景中分割出来;再进一步的是实例分割,语义分割是分割出图像中的某类物体,实例分割不光要分割出这类物体,还要把一类物体中的各个实例区分开来,粒度更细,要求也更高。


全卷积网络FCN(fully Convolutional network)是应用深度学习进行语义分割的奠基之作。它定义总体的语义分割结构,后面的Unet,Deeplab系列,PSPnet,包括基于注意力机制的语义分割网络都遵循了编码——解码框架。语义分割在遥感影像以及医学等领域有非常广泛的应用,比如需要对遥感影像进行地物分类,判断哪一类是耕地,哪一类是建成区等等,通过对历史遥感影像的语义分割,发现某类地物的变化,从而制定相应的对策。
FCN模型比较简单,它的核心原理就是用卷积神经网络提取图像的特征,并在舍弃了图像分类网络中的最后一层分类层,替换成了一个1X1卷积,得到要分类的特征图,之后通过上采样恢复成原始图像的大小,从而实现每个像素点的分类,得到分割的结果。如下图所示:

FCN论文(https://arxiv.org/abs/1411.4038)里面给出了三种FCN的设计:
1.最简单的就是得到分类特征图后直接上采样,经过卷积后,特征图变成原始图像的32分之一,直接上采样32倍,恢复成原始图像的大小,得到结果,称为FCN-32s;
2.第二种,就是把特征图上采样到原图的16分之一,再加上原始图像在卷积过程中得到的原图16分之一的特征图,再恢复成原始图像的大小,得到结果,称为FCN-16s;
3.第三种,就是把特征图上采样到原图的16分之一,再加上原始图像在卷积过程中得到的原图16分之一的特征图,然后再上采样2倍,回复到原图的8分之一,此时再加上原始图像在卷积过程中得到的原图8分之一的特征图,最后再恢复成原始图像的大小,得到结果,称为FCN-8s;结构如下图所示:

其实很明显,直接恢复成原始图像大小的模型,效果肯定最差,第三种方法加入了更多的特征图信息,效果最好,所以在实际应用中,我们一般使用FCN-8s。
模型代码如下:

class FCN(nn.Module):
    def __init__(self, preTrained=True, n_class=5) -> None:
        super(FCN, self).__init__()
        self.n_class = n_class
        self.preTrained = preTrained
        if self.preTrained:
            vgg = models.vgg16(pretrained=True)
        else:
            vgg = models.vgg16(pretrained=False)
        features, classifier = list(vgg.features.children()), list(vgg.classifier.children())
        num_classes = self.n_class
        self.features3 = nn.Sequential(*features[: 17])  
        self.features4 = nn.Sequential(*features[17: 24])   
        self.features5 = nn.Sequential(*features[24:])  
        self.score_pool3 = nn.Conv2d(256, num_classes, kernel_size=1)
        self.score_pool4 = nn.Conv2d(512, num_classes, kernel_size=1)
        fc6 = nn.Conv2d(512, 4096, kernel_size=3, stride=1, padding=1)
        fc7 = nn.Conv2d(4096, 4096, kernel_size=1)
        score_fr = nn.Conv2d(4096, num_classes, kernel_size=1)
        self.score_fr = nn.Sequential(
            fc6, nn.ReLU(inplace=True), nn.Dropout(), 
            fc7, nn.ReLU(inplace=True), nn.Dropout(), 
            score_fr
        )
        self.upscore2 = nn.ConvTranspose2d(num_classes, num_classes, kernel_size=4, stride=2, padding=1, bias=True)
        self.upscore4 = nn.ConvTranspose2d(num_classes, num_classes, kernel_size=4, stride=2, padding=1, bias=True)
        self.upscore8 = nn.ConvTranspose2d(num_classes, num_classes, kernel_size=16, stride=8, padding=4, bias=True)

    def forward(self, x, y=None):
        pool3 = self.features3(x)
        print("pool3.shape: ", pool3.shape)
        pool4 = self.features4(pool3)
        print("pool4.shape: ", pool4.shape)
        pool5 = self.features5(pool4)
        print("pool5.shape: ", pool5.shape)
        score_fr = self.score_fr(pool5)
        print("score_fr.shape: ", score_fr.shape)
        upscore2 = self.upscore2(score_fr)
        print("upscore2.shape: ", upscore2.shape)
        score_pool4 = self.score_pool4(pool4)
        print("score_pool4.shape: ", score_pool4.shape)
        upscore4 = self.upscore4(upscore2 + score_pool4)
        score_pool3 = self.score_pool3(pool3)
        cls_pred = self.upscore8(upscore4 + score_pool3)
        print("cls_pred.shape: ", cls_pred.shape)
        return cls_pred
        
if __name__ == "__main__":
    net = FCN(True, 6)
    x = torch.ones((16, 3, 480, 320)) 
    x = net(x)
    print(x.shape)
#输出:
pool3.shape:  torch.Size([16, 256, 60, 40])
pool4.shape:  torch.Size([16, 512, 30, 20])
pool5.shape:  torch.Size([16, 512, 15, 10])
score_fr.shape:  torch.Size([16, 6, 15, 10])   
upscore2.shape:  torch.Size([16, 6, 30, 20])   
score_pool4.shape:  torch.Size([16, 6, 30, 20])
cls_pred.shape:  torch.Size([16, 6, 480, 320])
torch.Size([16, 6, 480, 320])

这里为了特征提取的简便,我没有从头开始写编码器,而是直接把vgg模型的特征提取部分拿过来了。下面说明一下代码的含义:
6~9行:获取vgg模型,如果preTrained=True就选择预训练过的模型,否则就选择没有预训练的模型,一般来说,我们不希望从头开始训练,所以都选择有预训练的模型;
10行:把vgg模型分为特征提取部分和分类部分,我们需要的就是特征提取部分
12~14行:把特征提取器分成三个部分,从第0层到16层称为feature3,从第17层到23层称为feature4,从24层到最后的特征提取层称为feature5。从最后的输出可以看到,feature3输出的通道是256,feature4和feature5的输出通道是512。
15~16行:这两个打分层是将feature3和feature4的输出直接做卷积,将通道变为num_classes,也即是分类数目,为的是上采样过程中,和传过来的分类结果相加,添加上特征提取过程中的信息,也就是我们前面说的FCN-8s的概念。
17~18行:对特征提取器输出的结果再做两次卷积,一次是卷积核为3,填充为1的卷积,步长为,根据卷积计算的公示可以知道,这个运算是不会改变特征图的大小的,接下来的一次卷积是1X1卷积,也不会改变大小。
19行:最后的打分层,也就是通过1X1卷积将特征图的通道数转换成num_classes。
20~24行:将最后两次卷积和打分层结合在一起。
25~27行:三个转置卷积,通过转置卷积实现上采样的操作,通道数不变,都是num_classes,但是特征图不断放大,直到恢复成原始图像的大小。
在forward函数中可以看到,40~44行在上采样过程中不断加入特征提取的信息,最后恢复成原图大小,得到分割的结果。
最后,我们构建一个类别数为6的预训练FCN网络,将一个batch_size=16,通道数为3,长宽为480X320的图像数据集输入到网络中,可以看到最终输出是[16,6,480,320],也就是图像大小不变,但把通道数变成了6。
我在GID数据集上做了测试,得到了不错的效果。GID 数据集(Gaofen Image Dataset)由武汉大学夏桂松团队制作。它包含从中国60多个不同城市获得的150张高分辨率Gaofen-2(GF-2)图像(7200×6800)。这些图像覆盖了超过50,000平方公里的地理区域。 GID中的图像具有高的类内分集以及较低的类间可分离性。分为建成区、农用地、林地、草地、水系与未标记区域,共6类。



我用FCN网络训练GID数据集,经过50个epoch的训练,总体训练精度达到了0.83,测试结果如下:



可以看到,经过50轮的训练后,分割的结果勉强可以接受,虽然很多细节都没有分割出来,因为首先FCN是一个比较简单的模型,特征提取能力不是特别强,其次训练次数也比较少只有50次,如果进一步训练的话,精度还可以再略微上升一些。下次我们可以看一下Unet的效果。总之,FCN是一种非常重要且经典的语义分割模型,提出了一些非常经典的思想,比如编码解码架构,以及把上采样部分结果和特征提取结果相结合的思想。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容