当前位置: 首页 > news >正文

CNN(卷积网络)如何处理Size(Shape)大小可变的输入图像数据

C N N (卷积网络)如何处理 S i z e ( S h a p e ) 大小可变的输入图像数据 CNN(卷积网络)如何处理Size(Shape)大小可变的输入图像数据 CNN(卷积网络)如何处理Size(Shape)大小可变的输入图像数据


为什么要输入图像数据大小可变?

因为纯ReSize之后图像变形可能非常严重,导致训练效果不佳。保证输入图像size可变,可以尽可能保持原图数据分布等特征。

为什么一般输入图像需要固定大小?

全连接层的输入是固定大小的,如果前一层输出的输入向量的维数不固定,根本连不上全连接层,无法训练模型。


方法1:去掉Dense层,使用kernel的数量来对应类别的数量(代表网络FCNN,全卷积神经网络),之后使用全局池化——GAP(Global Average Pooling),将每个kernel对应的feature map都转化成一个值,接softmax激活就能完成需求

卷积层讲解

in_channels, # 输入通道数
out_channels, # 输出通道数,即卷积核个数
kernel_size, # 卷积核尺寸

nn.Conv2d(512, num_classes, 1),所以卷积后的一张特征图就对应了一个类别,kernel_size = 1,表示输入图像宽高在卷积后不变

import numpy as np
import torch
from torchvision import models
from torch import nn


def bilinear_kernel(in_channels, out_channels, kernel_size):
    """Define a bilinear kernel according to in channels and out channels.
    Returns:
        return a bilinear filter tensor
    """
    factor = (kernel_size + 1) // 2
    if kernel_size % 2 == 1:
        center = factor - 1
    else:
        center = factor - 0.5
    og = np.ogrid[:kernel_size, :kernel_size]
    bilinear_filter = (1 - abs(og[0] - center) / factor) * (1 - abs(og[1] - center) / factor)
    weight = np.zeros((in_channels, out_channels, kernel_size, kernel_size), dtype=np.float32)
    weight[range(in_channels), range(out_channels), :, :] = bilinear_filter
    return torch.from_numpy(weight)


pretrained_net = models.vgg16_bn(pretrained=False)


class FCN(nn.Module):
    def __init__(self, num_classes):
        super().__init__()

        self.stage1 = pretrained_net.features[:7]
        self.stage2 = pretrained_net.features[7:14]
        self.stage3 = pretrained_net.features[14:24]
        self.stage4 = pretrained_net.features[24:34]
        self.stage5 = pretrained_net.features[34:]

        self.scores1 = nn.Conv2d(512, num_classes, 1)
        self.scores2 = nn.Conv2d(512, num_classes, 1)
        self.scores3 = nn.Conv2d(128, num_classes, 1)

        self.conv_trans1 = nn.Conv2d(512, 256, 1)
        self.conv_trans2 = nn.Conv2d(256, num_classes, 1)

        self.upsample_8x = nn.ConvTranspose2d(num_classes, num_classes, 16, 8, 4, bias=False)
        self.upsample_8x.weight.data = bilinear_kernel(num_classes, num_classes, 16)

        self.upsample_2x_1 = nn.ConvTranspose2d(512, 512, 4, 2, 1, bias=False)
        self.upsample_2x_1.weight.data = bilinear_kernel(512, 512, 4)

        self.upsample_2x_2 = nn.ConvTranspose2d(256, 256, 4, 2, 1, bias=False)
        self.upsample_2x_2.weight.data = bilinear_kernel(256, 256, 4)

    def forward(self, x):
        # print('image:', x.size())

        s1 = self.stage1(x)
        # print('pool1:', s1.size())

        s2 = self.stage2(s1)
        # print('pool2:', s2.size())

        s3 = self.stage3(s2)
        # print('pool3:', s3.size())

        s4 = self.stage4(s3)
        # print('pool4:', s4.size())

        s5 = self.stage5(s4)
        # print('pool5:', s5.size())

        scores1 = self.scores1(s5)  # self.scores1 = nn.Conv2d(512, num_classes, 1); 这里进行了一次通道数的变化
        # print('scores1:', scores1.size())

        s5 = self.upsample_2x_1(s5)  # nn.ConvTranspose2d(512, 512, 4, 2, 1, bias=False); 转置卷积进行第一次上采样
        # print('s5:', s5.size())

        ##############融合##################
        add1 = s5 + s4  # 第一次上采样 与 s4进行融合
        # print('add1:', add1.size())

        scores2 = self.scores2(add1)  # self.scores2 = nn.Conv2d(512, num_classes, 1)  将融合后的add1进行一次通道数变化为num_classes
        # print('scores2:', scores2.size())

        add1 = self.conv_trans1(add1)  # self.conv_trans1 = nn.Conv2d(512, 256, 1) 将融合后的add1进行一次通道数变化为256
        # print('add1:', add1.size())

        add1 = self.upsample_2x_2(
            add1)  # self.upsample_2x_2 = nn.ConvTranspose2d(256, 256, 4, 2, 1, bias=False) 将通道256的add1 ,上采样为add1
        # print('add1:', add1.size())

        add2 = add1 + s3  # 将add1  和 s3 进行融合
        # print('add2:', add2.size())

        output = self.conv_trans2(add2)  # self.conv_trans2 = nn.Conv2d(256, num_classes, 1) 改变add2的通道数
        # print('output:', output.size())

        output = self.upsample_8x(
            output)  # self.upsample_8x = nn.ConvTranspose2d(num_classes, num_classes, 16, 8, 4, bias=False)
        # 使用转置卷积进行上采样
        # print('output:', output.size())

        return output


if __name__ == "__main__":
    # 随机生成输入数据
    rgb = torch.randn(1, 3, 480, 480)
    # 定义网络
    net = FCN(12)
    # 前向传播
    out = net(rgb)
    # 打印输出大小
    print('-----' * 5)
    print(out.shape)
    print('-----' * 5)







方法2:使用空间金字塔池化-SSP,一个固定输出大小的pooling(池化)操作,拥有了处理可变大小输入的能力,从而不固定输入大小,而是有固定输出大小

关键问题:SSP如何做到输出大小能够固定,其实是通过特定计算核大小(kernel_size)、步长(stride)、填充(padding)从而使输出为固定的大小的特征图

特定计算公式

:

代码实现

num_level = 3 # 3层池化卷积
N, C, H, W = input_img.size()
for i in range(num_level):
    level = i + 1
    print('第',level,'次计算池化核:')
    kernel_size = (ceil(H / level), ceil(W / level))
    print('核大小(kernel_size): ',kernel_size)
    stride = (ceil(H / level), ceil(W / level))
    print('步长(stride): ',stride)
    padding = (floor((kernel_size[0] * level - H + 1) / 2), floor((kernel_size[1] * level - W + 1) / 2))
    print('填充(padding): ',padding)
    # 池化
    res= F.max_pool2d(input_img, kernel_size=kernel_size, stride=stride, padding=padding)

示例如下:输入数据:torch.rand((1, 3, 256, 256))

在这里插入图片描述

示例如下:输入数据:torch.rand((1, 3, 512, 512))

在这里插入图片描述

from math import floor, ceil
import torch
import torch.nn as nn
import torch.nn.functional as F


class SSP2d(nn.Module):
    def __init__(self, num_level, pool_type='max_pool'):
        super(SSP2d, self).__init__()
        self.num_level = num_level
        self.pool_type = pool_type

    def forward(self, x):
        N, C, H, W = x.size()
        # print('多尺度获取信息,并进行特征融合...')
        print()
        for i in range(self.num_level):
            level = i + 1
            print('第',level,'次计算池化核:')
            kernel_size = (ceil(H / level), ceil(W / level))
            print('核大小(kernel_size): ',kernel_size)
            stride = (ceil(H / level), ceil(W / level))
            print('步长(stride): ',stride)
            padding = (floor((kernel_size[0] * level - H + 1) / 2), floor((kernel_size[1] * level - W + 1) / 2))
            print('填充(padding): ',padding)

            # print('进行最大池化并将提取特征展开:')
            # print()
            ttt = F.max_pool2d(x, kernel_size=kernel_size, stride=stride, padding=padding)
            print('第',level,'次SSP输出特征图:', ttt.size())
            if self.pool_type == 'max_pool':
                # 拉成一维
                tensor = (F.max_pool2d(x, kernel_size=kernel_size, stride=stride, padding=padding)).view(N, -1)
            else:
                tensor = (F.avg_pool2d(x, kernel_size=kernel_size, stride=stride, padding=padding)).view(N, -1)
            if i == 0:
                res = tensor
               # print('展开大小为: ',res.size())
            else:
                res = torch.cat((res, tensor), 1)
               # print('合并为: ',res.size())
        return res


class SPPNet(nn.Module):
    def __init__(self, num_level=3, pool_type='max_pool'):
        super(SPPNet, self).__init__()
        self.num_level = num_level
        self.pool_type = pool_type
        self.feature = nn.Sequential(nn.Conv2d(3, 64, 3),
                                     nn.ReLU(),
                                     nn.MaxPool2d(2),
                                     nn.Conv2d(64, 64, 3),
                                     nn.ReLU())
        # num_grid = 1 + 4 + 9 = 14
        self.num_grid = self._cal_num_grids(num_level)
        self.spp_layer = SSP2d(num_level)
        self.linear = nn.Sequential(nn.Linear(self.num_grid * 64, 512),
                                    nn.Linear(512, 10))

    def _cal_num_grids(self, level):
        count = 0
        for i in range(level):
            count += (i + 1) * (i + 1)
        return count

    def forward(self, x):
        #print('x初始大小为:')
        N, C, H, W = x.size()
        print('N:', N, ' C:', C, ' H', H, ' W:', W)
        x = self.feature(x)
        #print('x经过卷积、激活、最大池化、卷积、激活变成:')
        N, C, H, W = x.size()
        # print('64(conv)->62(maxpool)->31(conv)->29')
        # print('N:', N, ' C:', C, ' H', H, ' W:', W)
        # print('x进行空间金字塔池化:')
        x = self.spp_layer(x)
        # print('空间金字塔池化后,x进入全连接层:')
        x = self.linear(x)
        return x


if __name__ == '__main__':
    a = torch.rand((1, 3, 512, 512))
    net = SPPNet()
    output = net(a)
    # print(output)

相关文章:

  • 小鱼的一键安装系列
  • Spring Cloud Alibaba-Ribbon的源码分析
  • MASA MAUI Plugin IOS蓝牙低功耗(三)蓝牙扫描
  • 30岁以上的程序员还死磕技术,别说拿高薪,可能连饭碗都会保不住
  • numpy快速处理数据学习笔记
  • C++多态详解
  • sqlserver sa 密码忘记 处理
  • 自定义流程,匹配业务需求,还得是专业流程引擎
  • 【Redis】八股文必背
  • 从0开始学c语言-总结04-一维、二维数组简单汇总
  • C/C++的类型转换
  • java基础10题
  • SpringBoot开发之Spring Boot入门
  • OpenCV数字图像处理基于C++:图像分割
  • asp.net投票管理系统VS开发sqlserver数据库web结构c#编程计算机网页项目
  • .pyc 想到的一些问题
  • “Material Design”设计规范在 ComponentOne For WinForm 的全新尝试!
  • CentOS6 编译安装 redis-3.2.3
  • EOS是什么
  • Javascript编码规范
  • JavaScript设计模式之工厂模式
  • js面向对象
  • NLPIR语义挖掘平台推动行业大数据应用服务
  • Spark RDD学习: aggregate函数
  • 编写高质量JavaScript代码之并发
  • 第13期 DApp 榜单 :来,吃我这波安利
  • 前端每日实战:70# 视频演示如何用纯 CSS 创作一只徘徊的果冻怪兽
  • 前端面试之CSS3新特性
  • 如何合理的规划jvm性能调优
  • 通过npm或yarn自动生成vue组件
  • 微服务入门【系列视频课程】
  • 2017年360最后一道编程题
  • #ubuntu# #git# repository git config --global --add safe.directory
  • #常见电池型号介绍 常见电池尺寸是多少【详解】
  • #单片机(TB6600驱动42步进电机)
  • (C++17) std算法之执行策略 execution
  • (第9篇)大数据的的超级应用——数据挖掘-推荐系统
  • (十)DDRC架构组成、效率Efficiency及功能实现
  • (提供数据集下载)基于大语言模型LangChain与ChatGLM3-6B本地知识库调优:数据集优化、参数调整、Prompt提示词优化实战
  • .【机器学习】隐马尔可夫模型(Hidden Markov Model,HMM)
  • .dwp和.webpart的区别
  • .L0CK3D来袭:如何保护您的数据免受致命攻击
  • .NET Core中的去虚
  • .net 打包工具_pyinstaller打包的exe太大?你需要站在巨人的肩膀上-VC++才是王道
  • .net 提取注释生成API文档 帮助文档
  • .NET/C# 使窗口永不获得焦点
  • .NET牛人应该知道些什么(2):中级.NET开发人员
  • ?.的用法
  • @transactional 方法执行完再commit_当@Transactional遇到@CacheEvict,你的代码是不是有bug!...
  • [2016.7.Test1] T1 三进制异或
  • [acwing周赛复盘] 第 69 场周赛20220917
  • [C#]winform部署PaddleOCRV3推理模型
  • [docker] Docker的数据卷、数据卷容器,容器互联
  • [gdc19]《战神4》中的全局光照技术
  • [github全教程]github版本控制最全教学------- 大厂找工作面试必备!