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)
  • 2
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
卷积神经网络CNN)是一类包含卷积计算的前馈神经网络,专门用于图像处理等任务。CNN的设计基于图像任务中的平移不变性,也就是说图像中的对象在不同位置具有相同的含义。 CNN图像处理领域得到了广泛应用,它能够有效处理高维的图像数据(如RGB矩阵表示),避免了传统的前馈网络需要大量输入神经元的问题。 使用CNN进行图像识别基本流程如下: 1. 数据准备:收集并标注大量的图像数据集,将其划分为训练集和测试集。 2. 网络构建:使用CNN的核心概念,如卷积层(Conv2D)、池化层(MaxPool2D)、展平层(Flatten)和全连接层(Dense),构建一个适合图像识别任务的网络结构。 3. 训练模型:使用训练集的图像数据作为输入,通过反向传播算法和优化器(如Adam)对网络进行训练,使其能够学习图像中的特征和模式。 4. 评估与调优:使用测试集的图像数据对训练好的模型进行评估,并根据评估结果进行调优,如调整网络参数、增加数据量等。 5. 应用模型:在实际应用中,使用训练好的模型对新的图像数据进行识别,实现图像分类、目标检测等任务。 综上所述,CNN卷积神经网络)是一种用于图像识别的神经网络模型,通过对图像进行卷积计算和特征提取,能够有效地进行图像分类和目标检测等任务。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [基于CNN图像识别(Tensorflow)](https://blog.csdn.net/first_bug/article/details/124938432)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [一文弄懂CNN图像识别](https://blog.csdn.net/m0_61899108/article/details/122401122)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值