數(shù)據(jù)讀取與數(shù)據(jù)擴(kuò)增方法(附代碼)

本文對圖像數(shù)據(jù)讀取及圖像數(shù)據(jù)擴(kuò)增方法進(jìn)行了總結(jié),并以阿里天池零基礎(chǔ)入門CV賽事為實(shí)踐,利用Pytorch對數(shù)據(jù)進(jìn)行了讀取和擴(kuò)增講解。
數(shù)據(jù)及背景
數(shù)據(jù)讀取方法
接下來將簡單介紹五種目前較為主流的Python圖像庫的基本使用方法:matplotlib、PIL(pillow)、OpenCV、skimage、imageio。
1. matplotlib
matplotlib是Python的繪圖庫,與numpy一起使用可以算是一種matlab開源替代方案,在科學(xué)繪圖領(lǐng)域被廣泛使用。當(dāng)然,用來讀取圖像自然不在話下。
使用plt.imread()讀取圖片將其儲(chǔ)存為一個(gè)RGB像素值矩陣,再進(jìn)行處理。故其可以與opencv或pillow結(jié)合使用,只需要傳入像素值矩陣,matplotlib便可以接手處理接下來想要完成的操作。
import matplotlib.pyplot as plt #導(dǎo)入matplotlib庫import numpy as np #導(dǎo)入numpy庫img = plt.imread('PicPath/PicName.jpg') # 讀取圖片print(img.shape) # 輸出(高度h,寬度w,通道數(shù)c)print(img.size) # 輸出像素總數(shù)目print(img.dtype) # 輸出圖片類型,uint8為[0-255]print(img) # 輸出所有像素的RGB值,一個(gè)像素RGB為[0-255 0-255 0-255]plt.imshow(img) # 將圖片img插入畫布plt.axis('off') # 坐標(biāo)軸刻度不顯示plt.show() # 展示畫布imgR = image[:,:,0] # R通道,熱量圖plt.imshow(imgR) # 將熱量圖插入畫布plt.show() # 展示畫布plt.imshow(imgR,cmap='Greys_r') # 將灰度圖插入畫布plt.show() # 展示畫布figure = plt.figure(figsize=(80,40)) # 調(diào)整顯示畫布寬80,高40/英寸img1 = plt.imread('PicPath/PicName1.jpg') # 讀取圖片1img2 = plt.imread('PicPath/PicName2.jpg') # 讀取圖片2plt.axis("off") # 畫布坐標(biāo)軸刻度不顯示ax = figure.add_subplot(121) # 畫布以1行2列的形式顯示,設(shè)置圖片定位為序列1plt.axis('off') # 子圖1坐標(biāo)軸刻度不顯示ax.imshow(img1) # 將圖片1插入子圖1ax.set_title('title1') # 給子圖1加標(biāo)題ax = figure.add_subplot(122) # 畫布以1行2列的形式顯示,設(shè)置圖片定位為序列2plt.axis('off') # 子圖2坐標(biāo)軸刻度不顯示ax.imshow(img2) # 將圖片2插入子圖2ax.set_title('title2') # 給子圖2加標(biāo)題plt.savefig('PicX.jpg') # 保存畫布命名為PicX.jpgplt.show() # 展示畫布
2. PIL(pillow)
PIL即Python Imaging Library,而pillow是PIL的一個(gè)分支。pillow提供了常見的圖像讀取和處理的操作,它比opencv更為輕巧,且可以與ipython notebook無縫集成。
使用Image.open()讀取圖片儲(chǔ)存為一個(gè)對象,并非是numpy矩陣。
from PIL import Image # 導(dǎo)入PIL庫import numpy as np # 導(dǎo)入numpy庫img = Image.open('PicPath/PicName.jpg') # 讀取圖片imgL = Image.open('PicName.jpg').convert('L') # 讀取圖片灰度圖imgL.show() # 展示灰度圖img1 = img.copy() # 復(fù)制圖片print(img.format) # 輸出圖片格式print(img.size) # 輸出圖片(寬度w,高度h)print(img.mode) # 輸出圖片類型,L為灰度圖,RGB為真彩色,RGBA為RGB+Alpha透明度im.show() # 展示畫布imgData = np.array(img) # 將對象img轉(zhuǎn)化為RGB像素值矩陣print(imgData.shape) # 輸出圖片(寬度w,高度h,通道c)print(imgData.dtype) # 輸出圖片類型,uint8為[0-255]print(imgData) # 輸出所有像素的RGB值imgN = Image.fromarray(imgData) # 將RGB像素值矩陣轉(zhuǎn)化為對象imgNimgN.save('PicName.jpg') # 儲(chǔ)存為文件PicName.jpgr, g, b = img.split() # 分離通道img = Image.merge("RGB", (b, g, r)) # 合并通道# ROI(region of interest),只對ROI區(qū)域操作roi = img.crop((0,0,300,300)) # (左上x,左上y,右下x,右下y)坐標(biāo)roi.show() # 展示ROI區(qū)域#捕捉異IOError,為讀取圖片失敗try:img = Image.open('PicName.jpg')except IOError:print('image failed to load')
3. OpenCV
OpenCV是一個(gè)跨平臺(tái)的計(jì)算機(jī)視覺庫。其發(fā)展非常早,擁有眾多的計(jì)算機(jī)視覺、數(shù)字圖像處理和機(jī)器視覺等功能,OpenCV是今天介紹得所有圖像庫中最全面也最強(qiáng)大的庫,學(xué)習(xí)成本也相對要高很多。
使用cv2.imread讀取圖片將其儲(chǔ)存為一個(gè)BGR像素值矩陣,故若要結(jié)合使用matplotlib則要先進(jìn)行轉(zhuǎn)化。
import cv2 # 導(dǎo)入OpenCV庫import numpy as np # 導(dǎo)入numpy庫img = cv2.imread('PicName.jpg',0) # 讀取圖片:灰度模式img = cv2.imread('PicName.jpg',-1) # 讀取圖片:BRGA模式(BRG+Alpha通道)img = cv2.imread('PicName.jpg',1) # 讀取圖片:BRG模式img = cv2.imread('PicName.jpg') # 讀取圖片:第二參數(shù)默認(rèn)為1,BRG模式img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB) # 將顏色通道從BRG轉(zhuǎn)為RGBif img == None: # 讀取圖片失敗print('image failed to load')cv2.imshow('src',img) # 圖片源src為imgprint(img.shape) # 輸出圖片(高度h,寬度w,通道c)print(img.size) # 像素總數(shù)目print(img.dtype) # 輸出圖片類型,uint8為[0-255]print(img) # 輸出所有像素的RGB值cv2.waitKey() # 按鍵關(guān)閉窗口# waitKey(delay)函數(shù)的功能是不斷刷新圖像,頻率時(shí)間為delay,單位為ms,返回值為當(dāng)前鍵盤按鍵值# waitKey() 是在一個(gè)給定的時(shí)間內(nèi)(單位ms)等待用戶按鍵觸發(fā); 如果用戶沒有按下鍵,則接續(xù)等待(循環(huán))imgL = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) # 讀取img灰度圖cv2.imshow('gray',imgL) # 圖片源gray為imgLcv2.imwrite('imgL.jpg',imgL) # 將imgL儲(chǔ)存名為imgL.jpg的圖片print(imgL.shape) # 輸出圖片(高度h,寬度w)print(imgL.size) # 像素總數(shù)目print(imgL) # 輸出所有像素的灰度值cv2.waitKey() # 按鍵關(guān)閉窗口img = img.transpose(2,0,1) # 圖片矩陣變換為(通道c,高度h,寬度w)img = np.expand_dims(img, axis=0) # 圖片矩陣擴(kuò)展維度添加在第一維print(img.shape) # (1,通道c,高度h,寬度w)# 圖片歸一化處理img = cv2.imread('PicName.jpg')img = img.astype("float") / 255.0 # 轉(zhuǎn)化數(shù)據(jù)類型為float后進(jìn)行歸一化print(img.dtype) # 輸出為:float64print(img) # 輸出為[0-1 0-1 0-1]print(img[10,10]) # 訪問圖片img像素[10,10],輸出 [0-255 0-255 0-255]print(imgL[10,10]) # 訪問灰色圖片img像素[10,10],輸出 0-255img[10,10] = [255,255,255] # 修改圖片img像素點(diǎn)[10,10]為[255,255,255]imgL[10,10] = 255 # 修改灰色圖片img像素點(diǎn)[10,10]為255img[:,:,2] = 0 # 將R通道全部修改為0roi = img[200:550,100:450,:] # ROI操作,坐標(biāo)(高度范圍,寬度范圍,通道范圍)cv2.imshow('roi',roi) # 圖片源roi為roicv2.waitKey() # 按鍵關(guān)閉窗口
4. skimage
skimage包的全稱是scikit-image SciKit (toolkit for SciPy) ,它對scipy.ndimage進(jìn)行了擴(kuò)展,提供了更多的圖片處理功能。它是由python語言編寫的,由scipy 社區(qū)開發(fā)和維護(hù)。skimage包由許多的子模塊組成,各個(gè)子模塊提供不同的功能。
使用io.imread()讀取圖片將其儲(chǔ)存為一個(gè)RGB像素值矩陣。
from skimage import io #導(dǎo)入skimage庫from skimage import colorimport numpy as np #導(dǎo)入numpy庫img = io.imread('PicName.jpg') #讀取圖片imgL = io.imread('PicName.jpg',as_grey=True) #讀取圖片:灰度模式print(img.shape) #輸出圖片img(高度h,寬度w,通道c)print(imgL.shape) #輸出圖片imgL(高度h,寬度w)print(img.size) #img像素總數(shù)目print(img.dtype) #輸出img圖片類型,uint8為[0-255]print(imgL.dtype) #輸出imgL圖片類型,float64為[0-1],已經(jīng)被歸一化print(img) #輸出img所有像素的RGB值print(imgL) #輸出imgL所有灰度值,長度為imgL.size的numpy數(shù)組io.imsave('img.png',img) #將img儲(chǔ)存名為img.png的圖片io.imshow(img) #圖片img插入畫板io.show() #展示畫板imgl = io.imread('PicName.jpg') #讀取圖片imgl = color.rgb2grey(imgl) #轉(zhuǎn)換為灰度模式print(imgl.dtype) #以下數(shù)據(jù)同imgLprint(imgl.size)print(imgl.shape)io.imshow(imgl)io.show()'''skimage.color.rgb2grey(rgb)skimage.color.rgb2hsv(rgb)skimage.color.rgb2lab(rgb)skimage.color.gray2rgb(image)skimage.color.hsv2rgb(hsv)skimage.color.lab2rgb(lab)'''
5. imageio
Imageio是一個(gè)Python庫,提供了一個(gè)簡單的接口用于讀取和寫入各種圖像數(shù)據(jù),包括動(dòng)畫圖像,視頻,體積數(shù)據(jù)和科學(xué)格式。
使用io.imread()讀取圖片將其儲(chǔ)存為一個(gè)RGB像素值矩陣。
import imageio #導(dǎo)入imageio庫img = imageio.imread('PicName.jpg') # 讀取圖片imageio.imsave('img.png',img)# 將img儲(chǔ)存名為img.png的文件print(img.shape) # 輸出圖片img(高度h,寬度w,通道c)print(img.size) # img像素總數(shù)目print(img.dtype) # 輸出img圖片類型,uint8為[0-255]print(img) # 輸出img所有像素的RGB值plt.imshow(img) # 圖片img插入畫板plt.show() #展示畫板
6. 總結(jié)
其他圖像庫讀取彩色圖片都以RGB形式儲(chǔ)存,而OpenCV則是以BGR形式存儲(chǔ)。其他圖像庫讀取圖片都以numpy十六進(jìn)制彩色值形式儲(chǔ)存,而PIL讀取圖片是以對象形式儲(chǔ)存。
數(shù)據(jù)擴(kuò)增
為了增加數(shù)據(jù)量、豐富數(shù)據(jù)多樣性、提高模型的泛化能力,同時(shí)也可以有效緩解模型過擬合的情況,給模型帶來的更強(qiáng)的泛化能力。我們可以不實(shí)際增加原始數(shù)據(jù),只是對原始數(shù)據(jù)做一些變換,從而創(chuàng)造出更多的數(shù)據(jù)。我們只需要對現(xiàn)有數(shù)據(jù)集進(jìn)行微小改動(dòng),例如裁剪或灰度變換或翻轉(zhuǎn)(數(shù)字6與9翻轉(zhuǎn)會(huì)發(fā)生交換) 。無論如何,我們的神經(jīng)網(wǎng)絡(luò)會(huì)認(rèn)為這些是不同的圖像。從而完成數(shù)據(jù)擴(kuò)增(Data Augmentation)操作。

1. 數(shù)據(jù)擴(kuò)增為什么有用?
在深度學(xué)習(xí)模型的訓(xùn)練過程中,數(shù)據(jù)擴(kuò)增是必不可少的環(huán)節(jié)?,F(xiàn)有深度學(xué)習(xí)的參數(shù)非常多,一般的模型可訓(xùn)練的參數(shù)量基本上都是萬到百萬級(jí)別,而訓(xùn)練集樣本的數(shù)量很難有這么多。
其次數(shù)據(jù)擴(kuò)增可以擴(kuò)展樣本空間,假設(shè)現(xiàn)在的分類模型需要對汽車進(jìn)行分類,左邊的是汽車A,右邊為汽車B。如果不使用任何數(shù)據(jù)擴(kuò)增方法,深度學(xué)習(xí)模型會(huì)從汽車車頭的角度來進(jìn)行判別,而不是汽車具體的區(qū)別。
2. 有哪些數(shù)據(jù)擴(kuò)增方法?
數(shù)據(jù)擴(kuò)增方法有很多:從顏色空間、尺度空間到樣本空間,同時(shí)根據(jù)不同任務(wù)數(shù)據(jù)擴(kuò)增都有相應(yīng)的區(qū)別。
對于圖像分類,數(shù)據(jù)擴(kuò)增一般不會(huì)改變標(biāo)簽;對于物體檢測,數(shù)據(jù)擴(kuò)增會(huì)改變物體坐標(biāo)位置;對于圖像分割,數(shù)據(jù)擴(kuò)增會(huì)改變像素標(biāo)簽。
以torchvision.transforms為例,首先整體了解數(shù)據(jù)擴(kuò)增的方法,包括:
2.1 裁剪
中心裁剪:transforms.CenterCrop;
隨機(jī)裁剪:transforms.RandomCrop;
隨機(jī)長寬比裁剪:transforms.RandomResizedCrop;
上下左右中心裁剪:transforms.FiveCrop;
上下左右中心裁剪后翻轉(zhuǎn): transforms.TenCrop。
2.2 翻轉(zhuǎn)和旋轉(zhuǎn)
依概率p水平翻轉(zhuǎn):transforms.RandomHorizontalFlip(p=0.5);
依概率p垂直翻轉(zhuǎn):transforms.RandomVerticalFlip(p=0.5);
隨機(jī)旋轉(zhuǎn):transforms.RandomRotation。
2.3 隨機(jī)遮擋
對圖像進(jìn)行隨機(jī)遮擋: transforms.RandomErasing。
2.4 圖像變換
尺寸變換:transforms.Resize;
標(biāo)準(zhǔn)化:transforms.Normalize;
填充:transforms.Pad;
修改亮度、對比度和飽和度:transforms.ColorJitter;
轉(zhuǎn)灰度圖:transforms.Grayscale;
依概率p轉(zhuǎn)為灰度圖:transforms.RandomGrayscale;
線性變換:transforms.LinearTransformation();
仿射變換:transforms.RandomAffine;
將數(shù)據(jù)轉(zhuǎn)換為PILImage:transforms.ToPILImage;
轉(zhuǎn)為tensor,并歸一化至[0-1]:transforms.ToTensor;
用戶自定義方法:transforms.Lambda。
2.5 對transforms操作,使數(shù)據(jù)增強(qiáng)更靈活
transforms.RandomChoice(transforms): 從給定的一系列transforms中選一個(gè)進(jìn)行操作;
transforms.RandomApply(transforms, p=0.5): 給一個(gè)transform加上概率,依概率進(jìn)行操作;
transforms.RandomOrder: 將transforms中的操作隨機(jī)打亂。
3. 常用的數(shù)據(jù)擴(kuò)增庫?
3.1 torchvision
pytorch官方提供的數(shù)據(jù)擴(kuò)增庫,提供了基本的數(shù)據(jù)擴(kuò)增方法,可以無縫與torch進(jìn)行集成;但數(shù)據(jù)擴(kuò)增方法種類較少,且速度中等;
3.2 imgaug
imgaug是常用的第三方數(shù)據(jù)擴(kuò)增庫,提供了多樣的數(shù)據(jù)擴(kuò)增方法,且組合起來非常方便,速度較快;
鏈接:
https://github.com/aleju/imgaug
3.3 albumentations
是常用的第三方數(shù)據(jù)擴(kuò)增庫,提供了多樣的數(shù)據(jù)擴(kuò)增方法,對圖像分類、語義分割、物體檢測和關(guān)鍵點(diǎn)檢測都支持,速度較快。
鏈接:
https://albumentations.readthedocs.io
Pytorch讀取數(shù)據(jù)
由于本次賽題我們使用Pytorch框架講解具體的解決方案,接下來將是解決賽題的第一步使用Pytorch讀取賽題數(shù)據(jù)。
在Pytorch中數(shù)據(jù)是通過Dataset進(jìn)行封裝,并通過DataLoder進(jìn)行并行讀取。所以我們只需要重載一下數(shù)據(jù)讀取的邏輯就可以完成數(shù)據(jù)的讀取。
import os, sys, glob, shutil, jsonimport cv2from PIL import Imageimport numpy as npimport torchfrom torch.utils.data.dataset import Datasetimport torchvision.transforms as transformsclass SVHNDataset(Dataset):def __init__(self, img_path, img_label, transform=None):self.img_path = img_pathself.img_label = img_labelif transform is not None:self.transform = transformelse:self.transform = Nonedef __getitem__(self, index):img = Image.open(self.img_path[index]).convert('RGB')if self.transform is not None:img = self.transform(img)# 原始SVHN中類別10為數(shù)字0lbl = 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')train_path.sort()train_json = json.load(open('input/train.json'))train_label = [train_json[x]['label'] for x in train_json]data = SVHNDataset(train_path, train_label,transforms.Compose([# 縮放到固定尺寸transforms.Resize((64, 128)),# 隨機(jī)顏色變換transforms.ColorJitter(0.2, 0.2, 0.2),# 加入隨機(jī)旋轉(zhuǎn)transforms.RandomRotation(5),# 將圖片轉(zhuǎn)換為pytorch 的tesntor# transforms.ToTensor(),# 對圖像像素進(jìn)行歸一化# transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])]))
通過上述代碼,可以將賽題的圖像數(shù)據(jù)和對應(yīng)標(biāo)簽進(jìn)行讀取,在讀取過程中的進(jìn)行數(shù)據(jù)擴(kuò)增,效果如下所示:

接下來我們將在定義好的Dataset基礎(chǔ)上構(gòu)建DataLoder,你可以會(huì)問有了Dataset為什么還要有DataLoder?其實(shí)這兩個(gè)是兩個(gè)不同的概念,是為了實(shí)現(xiàn)不同的功能。
Dataset:對數(shù)據(jù)集的封裝,提供索引方式的對數(shù)據(jù)樣本進(jìn)行讀取
DataLoder:對Dataset進(jìn)行封裝,提供批量讀取的迭代讀取
加入DataLoder后,數(shù)據(jù)讀取代碼改為如下:
import os, sys, glob, shutil, jsonimport cv2from PIL import Imageimport numpy as npimport torchfrom torch.utils.data.dataset import Datasetimport torchvision.transforms as transformsclass SVHNDataset(Dataset):def __init__(self, img_path, img_label, transform=None):self.img_path = img_pathself.img_label = img_labelif transform is not None:self.transform = transformelse:self.transform = Nonedef __getitem__(self, index):img = Image.open(self.img_path[index]).convert('RGB')if self.transform is not None:img = self.transform(img)# 原始SVHN中類別10為數(shù)字0lbl = 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')train_path.sort()train_json = json.load(open('input/train.json'))train_label = [train_json[x]['label'] for x in train_json]train_loader = torch.utils.data.DataLoader(SVHNDataset(train_path, train_label,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])])),batch_size=10, # 每批樣本個(gè)數(shù)shuffle=False, # 是否打亂順序num_workers=10, # 讀取的線程個(gè)數(shù))for data in train_loader:break
在加入DataLoder后,數(shù)據(jù)按照批次獲取,每批次調(diào)用Dataset讀取單個(gè)樣本進(jìn)行拼接。此時(shí)data的格式為:
torch.Size([10, 3, 64, 128]), torch.Size([10, 6])前者為圖像文件,為batchsize * chanel * height * width次序;后者為字符標(biāo)簽。
