我们上一次已经创建了自己的数据集,接下来就到了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)