FasterRCNN GeneralizedRCNNTransform部分

我们上一次已经创建了自己的数据集,接下来就到了GeneralizedRCNNTransform部分,这部分包括了normalize和resize的操作。

首先说normalize部分,因为图像是三维,所以标准差和均值都要扩充到三维

    def normalize(self,image):

        #获取图片数据类型及设备信息,是在cpu还是在GPU上
        dtype,device=image.dtype,image.device
        #把均值和方差变为tensor格式【0,1】
        mean=torch.as_tensor(self.image_mean,dtype=dtype,device=device)
        std=torch.as_tensor(self.image_std,dtype=dtype,device=device)
        #图像-均值除标准差
        return (image-mean[:,None,None])/std[:,None,None]

然后是resize部分,这一部分就是把图片缩放成你指定的最小边长到最大边长范围内,同时把图片对应的bbox信息也按照比列缩放

    def resize(self,image,target):
        # type: (Tensor, Optional[Dict[str, Tensor]]) -> Tuple[Tensor, Optional[Dict[str, Tensor]]]

        #resize是指定一个长宽,在这个范围内,小的图片放大到指定的最小边长,大的图片缩小到指定的最大边长,然后bbox也要对应改变
        h,w=image.shape[-2:]
        # print("original",h,w)
        if self.training:
            #指定输入图片的最小边长
            size=float(self.torch_choice(self.min_size))
            # print("指定最小边长",size)
        else:
            #指定输入图片的最小边长
            size=float(self.min_size[-1])
        #if torchvision._is_tracing():不会进入这个函数,这个函数是可以不依赖pytorch框架,很多框架都能用
        # if torchvision._is_tracing():
        #     image=_resize_image_onnx(image,size,float(self.max_size))
        # else:
        image=_resize_image(image,size,float(self.max_size))
        if target is None:
            return image,target
        bbox=target["boxes"]
        # print("bbox",bbox)
        #根据图像缩放比例缩放bbox
        bbox=resize_boxes(bbox,original_size=[h,w],new_size=image.shape[-2:])
        target["boxes"]=bbox
        return image,target

测试代码,因为是从自己定义的数据集传入过来的,要用到images和targets信息

from torch.utils.data import DataLoader
data_transforms={
    "train": transforms.Compose([transforms.ToTensor(),
                                transforms.RandomHorizontalFlip(0.5)]),
    "val": transforms.Compose([transforms.ToTensor()])
}
train_data_set=VOCDataSet("../VOC",transforms=data_transforms["train"],train_set=True)
images=[images for images,targets in train_data_set]
targets=[targets for images,targets in train_data_set]
# train_data_loader = DataLoader(train_data_set,
#                                 batch_size=8,
#                                 shuffle=True,
#                                 num_workers=0,
#                                 collate_fn=train_data_set.collate_fn)
transform = GeneralizedRCNNTransform(min_size=800, max_size=1333, image_mean=[0.485, 0.456, 0.406],
                                     image_std=[0.229, 0.224, 0.225])
tran=transform(images,targets)

以下附上所有代码

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2022/5/19 22:09
# @Author  : 半岛铁盒
# @File    : transform.py
# @Software: win10  python3.6
import math

import torch.nn as nn
import torch
import torchvision
from PIL import Image
import numpy as np
from typing import List, Tuple, Dict, Optional
import torch.nn.functional as F
from mydataset import VOCDataSet
import transforms
from image_list import ImageList

def _resize_image_onnx(image,self_min_size,self_max_size):
    # type: (Tensor, float, float) -> Tensor
    from torch.onnx import operators
    #获取图像宽高
    im_shape=operators.shape_as_tensor(image)[-2:]
    # print("123",im_shape)
    #图像最小边,最大边
    min_size=torch.min(im_shape).to(dtype=torch.float32)
    max_size=torch.max(im_shape).to(dtype=torch.float32)
    #定义一个比列因子,是最小值范围除以最小值和最大值范围除以最大值中的最小的那个,保证图片在最小或最大边长范围内
    scale_factor=torch.min(self_min_size/min_size,self_max_size/max_size)
    # 这个函数是用来上采样或下采样,可以给定size或者scale_factor来进行上下采样。采样完后再把图片变回3d
    image=F.interpolate(image[None],scale_factor=scale_factor,mode="bilinear",recompute_scale_factor=True,
                        align_corners=False)[0]
    return image
def _resize_image(image,self_min_size,self_max_size):
    # type: (Tensor, float, float) -> Tensor
    im_shape=torch.tensor(image.shape[-2:])
    # print("im_shape",im_shape)
    # 获取高宽中的最小值
    min_size=float(torch.min(im_shape))
    # 获取高宽中的最大值
    max_size=float(torch.max(im_shape))
    # 根据指定最小边长和图片最小边长计算缩放比例
    scale_factor=self_min_size/min_size
    # print("scale_factor",scale_factor)
    # 如果使用该缩放比例计算的图片最大边长大于指定的最大边长
    if max_size*scale_factor>self_max_size:
        # 将缩放比例设为指定最大边长和图片最大边长之比
        scale_factor=self_max_size/max_size
    # interpolate利用插值的方法缩放图片
    # image[None]操作是在最前面添加batch维度[C, H, W] -> [1, C, H, W]
    # bilinear只支持4D Tensor
    #把图片缩放到指定的边长范围内
    image = torch.nn.functional.interpolate(
        image[None], scale_factor=scale_factor, mode="bilinear", recompute_scale_factor=True,
        align_corners=False)[0]
    # print("image",image.shape)
    return image

class GeneralizedRCNNTransform(nn.Module):
    def __init__(self,min_size,max_size,image_mean,image_std):
        super(GeneralizedRCNNTransform, self).__init__()
        #判断min_size是不是list或tuple类
        if not isinstance(min_size,(list,tuple)):
            #不是的话转为tuple
            min_size=(min_size,)
            # print("1",min_size)
        #指定图像的最小边长范围
        self.min_size=min_size
        #图像最大边范围
        self.max_size=max_size
        #均值方差
        self.image_mean=image_mean
        self.image_std=image_std

    def torch_choice(self,k):
        # type: (List[int]) -> int
        # uniform_随机生成一个(x,y)范围内的实数
        # torch.empty()创建任意数据类型的张量,是一个随机数,empty(1)创健一个随机一维张量
        #提取出0到len(k)长度中随机一个整数,返回k的索引
        #len(k)是看k也就是self.min_size元祖中有几个元素,默认为一个,是最小边长,所以为1
        index=int(torch.empty(1).uniform_(0.,float(len(k))).item())
        return k[index]

    def normalize(self,image):

        #获取图片数据类型及设备信息,是在cpu还是在GPU上
        dtype,device=image.dtype,image.device
        #把均值和方差变为tensor格式【0,1】
        mean=torch.as_tensor(self.image_mean,dtype=dtype,device=device)
        std=torch.as_tensor(self.image_std,dtype=dtype,device=device)
        #图像-均值除标准差
        return (image-mean[:,None,None])/std[:,None,None]

    def resize(self,image,target):
        # type: (Tensor, Optional[Dict[str, Tensor]]) -> Tuple[Tensor, Optional[Dict[str, Tensor]]]

        #resize是指定一个长宽,在这个范围内,小的图片放大到指定的最小边长,大的图片缩小到指定的最大边长,然后bbox也要对应改变
        h,w=image.shape[-2:]
        # print("original",h,w)
        if self.training:
            #指定输入图片的最小边长
            size=float(self.torch_choice(self.min_size))
            # print("指定最小边长",size)
        else:
            #指定输入图片的最小边长
            size=float(self.min_size[-1])
        #if torchvision._is_tracing():不会进入这个函数,这个函数是可以不依赖pytorch框架,很多框架都能用
        # if torchvision._is_tracing():
        #     image=_resize_image_onnx(image,size,float(self.max_size))
        # else:
        image=_resize_image(image,size,float(self.max_size))
        if target is None:
            return image,target
        bbox=target["boxes"]
        # print("bbox",bbox)
        #根据图像缩放比例缩放bbox
        bbox=resize_boxes(bbox,original_size=[h,w],new_size=image.shape[-2:])
        target["boxes"]=bbox
        return image,target
    def max_by_axis(self,the_list):
         # type: (List[List[int]]) -> List[int]
         #获得第一张图片信息
        maxes=the_list[0].copy()
         #遍历从第一张图片开始一直到最后的图片
        for sublist in the_list[1:]:
            for index,item in enumerate(sublist):
                maxes[index]=max(maxes[index],item)
        return maxes



    def batch_images(self,images,size_divisible=32):
        # type: (List[Tensor], int) -> Tensor
        """
        将一批图像打包成一个batch返回(注意batch中每个tensor的shape是相同的)
        Args:
            images: 输入的一批图片
            size_divisible: 将图像高和宽调整到该数的整数倍

        Returns:
            batched_imgs: 打包成一个batch后的tensor数据
        """
        # 分别计算一个batch中所有图片中的最大channel, height, width
        max_size=self.max_by_axis([list(img.shape) for img in images])
        stride=float(size_divisible)
        #math.ceil向上取整,将height调整到stride的整数倍
        max_size[1]=int(math.ceil(float(max_size[1])/stride)*stride)
        # 将width向上调整到stride的整数倍
        max_size[2]=int(math.ceil(float(max_size[2])/stride)*stride)
        #增加图片维度,N就是图片的个数[batch, channel, height, width]
        batch_shape=[len(images)]+max_size
        #创健shape为batch_shape且填充值都为0的tensor,这里images【】取几都行,只是要在一个tensor下创健一个新的tensor
        #这里就创建了一批图片中有着最大宽高的图片,所有的图片都在这个范围内
        batch_imgs=images[0].new_full(batch_shape,0)
        for img,pad_img in zip(images,batch_imgs):
            # 将输入images中的每张图片复制到新的batched_imgs的每张图片中,对齐左上角,保证bboxes的坐标不变
            # 这样保证输入到网络中一个batch的每张图片的shape相同
            pad_img[:img.shape[0],:img.shape[1],:img.shape[2]].copy_(img)

        return batch_imgs

    def postprocess(self,
                    result,     # type: List[Dict[str, Tensor]]  #每张图片的预测结果
                    images_shapes, # type: List[Tuple[int, int]]    #每张图片resize后的H和W
                    original_image_size # type: List[Tuple[int, int]]   #每张图片的原始尺寸
                    ):
        # type: (...) -> List[Dict[str, Tensor]]
        """
        对网络的预测结果进行后处理(主要将bboxes还原到原图像尺度上)
        Args:
            result: list(dict), 网络的预测结果, len(result) == batch_size
            image_shapes: list(torch.Size), 图像预处理缩放后的尺寸, len(image_shapes) == batch_size
            original_image_sizes: list(torch.Size), 图像的原始尺寸, len(original_image_sizes) == batch_size

        Returns:

        """
        #如果训练模式,直接返回结果
        if self.training:
            return result
        #遍历每一张图片的预测信息,resize前后的长宽, 将boxes信息还原回原尺度
        for i,(pred,im_s,o_im_s) in enumerate(zip(result,images_shapes,original_image_size)):
            boxes=pred["boxes"]
            #将bboxes缩放回原图像尺度
            boxes=resize_boxes(boxes,original_size=im_s,new_size=o_im_s)
            result[i]["boxes"]=boxes
        return result




    def forward(self,
                images,   # type: List[Tensor]
                targets=None   # type: Optional[List[Dict[str, Tensor]]]
                ):
        # type: (...) -> Tuple[ImageList, Optional[List[Dict[str, Tensor]]]]
        #把turple的image转为list
        images=[img for img in images]
        for i in range(len(images)):
            image=images[i]
            target_index=targets[i] if targets is not None else None
            if image.dim()!=3:
                raise ValueError("images is expected to be a list of 3d tensors "
                                 "of shape [C, H, W], got {}".format(image.shape))
            #图像标准化处理
            image=self.normalize(image)
            #对图像和对应的bboxes缩放到指定范围
            image,target_index=self.resize(image,target_index)
            images[i]=image
            if targets is not None and target_index is not None:
                targets[i]=target_index
        #记录一下resize后的长宽
        image_sizes=[img.shape[-2:] for img in images]
        #将images打包成一个batch[N,C,W,H]torch.Size([156, 3, 800, 1088])1088是32的倍数
        images=self.batch_images(images)
        #创健一个列表
        image_sizes_list=torch.jit.annotate(List[Tuple[int,int]],[])
        for image_size in image_sizes:
            #确保是长宽长度为2
            assert len(image_size)==2
            image_sizes_list.append((image_size[0],image_size[1]))
        #记录一下resize后的images[N,C,W,H],和image_sizes_list也就是全部图片的长宽
        #为了和原图映射
        image_list=ImageList(images,image_sizes_list)
        #image_list,targets即将输入到backbone targets就是原目标信息 image_list就是resize后的目标信息
        return image_list,targets









def resize_boxes(boxes,original_size,new_size):
    #type: (Tensor, List[int], List[int]) -> Tensor
    """
    将boxes参数根据图像的缩放情况进行相应缩放

    Arguments:
        original_size: 图像缩放前的尺寸
        new_size: 图像缩放后的尺寸
    """
    #比列因子是新图除以原图 得到的高度方向和宽度方向的缩放因子,缩放后的长除以缩放前的长,宽除以宽
    ratios=[torch.tensor(s,dtype=torch.float32,device=boxes.device)/
            torch.tensor(s_orig,dtype=torch.float32,device=boxes.device)
        for s,s_orig in zip(new_size,original_size)]
    # print("new_size,original_size",new_size,original_size)
    # print("ratios",ratios)
    ratios_height,ratios_width=ratios
    #unbind是在第一维度上展开,将所有目标的 xmin,ymin,xmax,ymax展开后x乘以宽度,y乘以高度
    xmin,ymin,xmax,ymax=boxes.unbind(1)
    # print("boxes.unbind(1)",boxes.unbind(1))
    xmin=xmin*ratios_width
    xmax=xmax*ratios_width
    ymin=ymin*ratios_height
    ymax=ymax*ratios_height
    #在沿第一维度拼接起来
    x=torch.stack((xmin,ymin,xmax,ymax),dim=1)
    # print("resize_boxes",x)
    return x


from torch.utils.data import DataLoader
data_transforms={
    "train": transforms.Compose([transforms.ToTensor(),
                                transforms.RandomHorizontalFlip(0.5)]),
    "val": transforms.Compose([transforms.ToTensor()])
}
train_data_set=VOCDataSet("../VOC",transforms=data_transforms["train"],train_set=True)
images=[images for images,targets in train_data_set]
targets=[targets for images,targets in train_data_set]
# train_data_loader = DataLoader(train_data_set,
#                                 batch_size=8,
#                                 shuffle=True,
#                                 num_workers=0,
#                                 collate_fn=train_data_set.collate_fn)
transform = GeneralizedRCNNTransform(min_size=800, max_size=1333, image_mean=[0.485, 0.456, 0.406],
                                     image_std=[0.229, 0.224, 0.225])
tran=transform(images,targets)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值