1 目的
- PIL读取数据
- PIL+Torch生成小批量图像数据样本
- 理解Baseline中torchvision.transforms进行图像增广的基本使用方法。
2 图像读取
Python中完成数据读取操作,比较常用的是Pillow和OpenCV、。注意:Image.open()读取的通道顺序是RGB,cv2.imread()读取的通道顺序为BGR。
单一图像读取:
2.1 Pillow
- Pillow的官方文档
- 读取及图像增强操作示例
- 图像格式转换.
注:模式“RGB”转换为模式“L”以后,像素值为[0,255]之间的某个数值。而从模式“L”转换成“RGB”时,“RGB”的三个通道都是模式“L”的像素值的拷贝。
这里有个问题:
- 原始数据是L模式,CHANEL为1?PIL数据已经转成RGB,这时候数据是CHANEL为3吗?
from PIL import Image
# 读取图片
im =Image.open('lena.jpg’)
# 应用模糊滤镜:
im2 = im.filter(ImageFilter.BLUR)
#缩放原图
im.thumbnail((w//2, h//2))
im.save('thumbnail.jpg', 'jpeg')
from PIL.ImageFilter import BLUR,GaussianBlur #模糊滤波
这里提到模糊滤波是为后期的模型优化做准备,阿水的第一期分享中提到模糊优化,大概是因为OCR识别等图像识别中在图像特征提取之前经常通过smooth/blur来使图像噪声降低、在二值化之前通过smooth/blur把不必要的噪声去掉,以免影响对准确的对象的提取。
2.2 OpenCV
OpenCV在功能上比Pillow更加强大。
- 图像读入[cv2.imread()]、显示[cv2.imshow()]与保存[cv2.imwrite()]
- 变换操作:翻转图片[cv2.flip()]、颜色空间转换[cv2.cvtColor()]
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('lena.jpg',0)
edges = cv2.Canny(img, 100, 200)
plt.subplot(121),plt.imshow(img,cmap = 'gray')
plt.title('Original Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(edges,cmap = 'gray')
plt.title('Edge Image'), plt.xticks([]), plt.yticks([])
batch图像读取:
这个说法并不准确,batch样本是为了训练模型,实际还是基于单体图像读取(这里使用PIL),封装成一个torch.utils.data.dataset的子类,然后将其传入torch.utils.data.DataLoader来创建一个读取小批量数据样本的DataLoader实例,完成batch样本提取。
import torch,glob
from PIL import Image
'''第一步 生成数据集'''
class SVHNDataset(torch.utils.data.dataset.Dataset):
def __init__(self,img_path,img_label,transform=None):
'''
Args:
img_path 单个图像路径
img_label 单个图像标签
transform 为图像增广做准备
'''
self.img_path = img_path
self.img_label = img_label
if transform is not None:
self.transform = transform
else:
self.transform = None
def __getitem__(self, index):
'''
Args:
index 图像序号
output:
img 单一图像特征序列
torch.from_numpy(np.array(lbl[:5])) 单一图像的定长字符标签
'''
# 读取图片并转换为RGB格式
img = Image.open(self.img_path[index]).convert('RGB')
if self.transform is not None:
img = self.transform(img)
# 将不定长的变迁转换成设置长度为5的定长字符串
lbl = np.array(self.img_label[index], dtype=np.int)
lbl = list(lbl) + (5 - len(lbl)) * [10]
return img, torch.from_numpy(np.array(lbl[:5]))
def __len__(self):
return len(self.img_path)
train_path = glob.glob('../input/train/*.png') #提取所有训练集png文件路径
train_path.sort() #路径排序
train_json = json.load(open('../input/train.json')) #读取训练集标签及边界框信息
train_label = [train_json[x]['label'] for x in train_json] #生成训练集标签
print(len(train_path), len(train_label))
dataset = SVHNDataset(train_path, train_label,transform=None) #定义训练数据集。
'''!!!这里transform=None应该会出错【待确认】,至少需要指定参数 transform = transforms.ToTensor() 使所有数据转换为 Tensor ,如果不进行转换则返回的是PIL图片。 transforms.ToTensor() 将尺寸为 (H x W x C) 且数据位于[0, 255]的PIL 图片或者数据类型为 np.uint8 的 NumPy 数组转换为尺寸为 (C x H x W) 且数据类型为 torch.float32 且位于[0.0, 1.0]的 Tensor 。'''
'''第二步 读取小批量'''
batch_size=40 #小批量样本量
shuffle=True #样本的读取顺序是随机的
train_loader = torch.utils.data.DataLoader(
dataset,
batch_size=batch_size,
shuffle=shuffle,
num_workers=0)
for i, (inputs, targets) in enumerate(train_loader):
print(inputs,targets) #inputs是随机读取的长度为batch_size的Tensor,其中每一个分别为各个图像的尺寸为 (C x H x W) 且数据类型为 torch.float32 且位于[0.0, 1.0]的 Tensor。
#targetss是对应的40个标签。
break
3 图像增广
3.1 基础知识
数据扩增(Data Augmentation)操作的目的是为了增加训练集的数量,缓解过拟合。
大规模数据集是成功应用深度神经网络的前提。图像增 广(image augmentation)技术通过对训练图像做一系列随机改变,来产生相似但又不同的训练样本,从而扩大训练数据集的规模。图像增广的另一种解释是,随机改变训练样本可以降低模型对某些属性的依赖,从而提高模型的泛化能力。例如,我们可以对图像进行不同方式的裁剪,使感兴趣的物体出现在不同位置,从而减轻模型对物体出现位置的依赖性。我们也可以调整亮度、色彩等因素来降低模型对色彩的敏感度。可以说,在当年AlexNet的成功中,图像增⼴技术功不可没。
——《动手学深度学习》李沐
目前Baseline里用到transfrom的顺序依次是Resize、RandomCrop、ColorJitter、RandomRotation、ToTensor、Normalize。
- Resize(size=(h, w)):resize输出尺寸为(h, w).
- RandomCrop(size=(h, w)):crop时的中心点坐标是随机的,每次crop生成的图像都是有差异的,输出尺寸为(h, w)。
- ColorJitter(brightness,contrast,saturation):修改输入图像的3大参数值:brightness, contrast and saturation,也就是亮度,对比度,饱和度。
- RandomRotation(degrees):随机旋转输入图像。
- ToTensor():Convert a PIL Image((H x W x C),[0, 255]) or numpy.ndarray to tensor 的过程, 输出尺寸为 (C x H x W) 且数据类型为 torch.float32 且位于 [0.0, 1.0] 的 Tensor 。在PyTorch中常用PIL库来读取图像数据,因此这个方法相当于搭建了PIL Image和Tensor的桥梁。 另外要强调的是在做数据归一化之前必须要把PIL Image转成Tensor,而其他resize或crop操作则不需要。
- Normalize(mean,std):数据标准化【(X-mean)/std】,主要是取消梯度量级的差异,使得剃度始终朝着最小值的方向前进,加速收敛。在调用Normalize的时候,输入得是Tensor。
3.2 torchvision.transforms进行图像增广
这里存在一些问题,比如
- Resize的Size(64,128)有没有什么依据?PIL转成RGB后数据CHANEL影响这个数字吗?验证集(60,120)是搞混了吧?是否需要训练集、验证集、测试集数据都一样?
- RandomCrop不应该对测试集进行处理吧?改成对训练集处理?
- RandomRotation里旋转5度会不会影响6和9的分类?
transform类型 | 训练集 | 验证集 | 测试集 |
---|---|---|---|
Resize(size=(h, w)) | (64, 128) | (60, 120) | (64, 128) |
RandomCrop(size=(h, w)) | × | × | (60, 120) |
ColorJitter(brightness,contrast,saturation) | (0.3, 0.3, 0.2) | × | × |
RandomRotation(degrees) | 5 | × | × |
ToTensor() | √ | √ | √ |
Normalize(mean,std) | [0.485, 0.456, 0.406], [0.229, 0.224, 0.225] | [0.485, 0.456, 0.406], [0.229, 0.224, 0.225] | [0.485, 0.456, 0.406], [0.229, 0.224, 0.225] |
import torchvision.transforms as transforms
train_trans = transforms.Compose([
transforms.Resize((64, 128)),
transforms.ColorJitter(0.3, 0.3, 0.2),
transforms.RandomRotation(5),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])
val_trans = transforms.Compose([
transforms.Resize((60, 120)),
# transforms.ColorJitter(0.3, 0.3, 0.2),
# transforms.RandomRotation(5),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])
test_trans = transforms.Compose([
transforms.Resize((64, 128)),
transforms.RandomCrop((60, 120)),
# transforms.ColorJitter(0.3, 0.3, 0.2),
# transforms.RandomRotation(5),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])
4 小结
学到知识心里高兴。
5 参考
动手学深度学习 PYTORCH 版(DEMO)
链接已各处贴上。
Have fun~
2020.5.23