更新流程↓
Task01:賽題理解
Task02:數據讀取與數據擴增
Task03:字符識別模型
Task04:模型訓練與驗證
Task05:模型集成
底到鏡一
比賽鏈接
1. 數據讀取
1.1. 圖像讀取
接下來將簡單介紹以下目前較爲主流的Python圖像庫的基本使用方法
matplotlib | PIL(pillow) | OpenCV | skimage | imageio |
---|
1.1.1. matplotlib
matplotlib是Python的繪圖庫,與numpy一起使用可以算是一種matlab開源替代方案,在科學繪圖領域被廣泛使用。當然,用來讀取圖像自然不在話下。
使用plt.imread()
讀取圖片將其儲存爲一個RGB像素值矩陣,再進行處理。故其可以與opencv或pillow結合使用,只需要傳入像素值矩陣,matplotlib便可以接手處理接下來想要完成的操作。
import matplotlib.pyplot as plt # 導入matplotlib庫
import numpy as np # 導入numpy庫
img = plt.imread('PicPath/PicName.jpg') # 讀取圖片
print(img.shape) # 輸出(高度h,寬度w,通道數c)
print(img.size) # 輸出像素總數目
print(img.dtype) # 輸出圖片類型,uint8爲[0-255]
print(img) # 輸出所有像素的RGB值,一個像素RGB爲[0-255 0-255 0-255]
plt.imshow(img) # 將圖片img插入畫布
plt.axis('off') # 座標軸刻度不顯示
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)) # 調整顯示畫布寬80,高40/英寸
img1 = plt.imread('PicPath/PicName1.jpg') # 讀取圖片1
img2 = plt.imread('PicPath/PicName2.jpg') # 讀取圖片2
plt.axis("off") # 畫布座標軸刻度不顯示
ax = figure.add_subplot(121) # 畫布以1行2列的形式顯示,設置圖片定位爲序列1
plt.axis('off') # 子圖1座標軸刻度不顯示
ax.imshow(img1) # 將圖片1插入子圖1
ax.set_title('title1') # 給子圖1加標題
ax = figure.add_subplot(122) # 畫布以1行2列的形式顯示,設置圖片定位爲序列2
plt.axis('off') # 子圖2座標軸刻度不顯示
ax.imshow(img2) # 將圖片2插入子圖2
ax.set_title('title2') # 給子圖2加標題
plt.savefig('PicX.jpg') # 保存畫布命名爲PicX.jpg
plt.show() # 展示畫布
1.1.2. PIL(pillow)
PIL即Python Imaging Library,而pillow是PIL的一個分支。pillow提供了常見的圖像讀取和處理的操作,它比opencv更爲輕巧,且可以與ipython notebook無縫集成。
使用Image.open()
讀取圖片儲存爲一個對象,並非是numpy矩陣。
from PIL import Image # 導入PIL庫
import numpy as np # 導入numpy庫
img = Image.open('PicPath/PicName.jpg') # 讀取圖片
imgL = Image.open('PicName.jpg').convert('L') # 讀取圖片灰度圖
imgL.show() # 展示灰度圖
img1 = img.copy() # 複製圖片
print(img.format) # 輸出圖片格式
print(img.size) # 輸出圖片(寬度w,高度h)
print(img.mode) # 輸出圖片類型,L爲灰度圖,RGB爲真彩色,RGBA爲RGB+Alpha透明度
im.show() # 展示畫布
imgData = np.array(img) # 將對象img轉化爲RGB像素值矩陣
print(imgData.shape) # 輸出圖片(寬度w,高度h,通道c)
print(imgData.dtype) # 輸出圖片類型,uint8爲[0-255]
print(imgData) # 輸出所有像素的RGB值
imgN = Image.fromarray(imgData) # 將RGB像素值矩陣轉化爲對象imgN
imgN.save('PicName.jpg') # 儲存爲文件PicName.jpg
r, g, b = img.split() # 分離通道
img = Image.merge("RGB", (b, g, r)) # 合併通道
# ROI(region of interest),只對ROI區域操作
roi = img.crop((0,0,300,300)) # (左上x,左上y,右下x,右下y)座標
roi.show() # 展示ROI區域
#捕捉異IOError,爲讀取圖片失敗
try:
img = Image.open('PicName.jpg')
except IOError:
print('image failed to load')
1.1.3. OpenCV
OpenCV是一個跨平臺的計算機視覺庫。其發展非常早,擁有衆多的計算機視覺、數字圖像處理和機器視覺等功能,OpenCV是今天介紹得所有圖像庫中最全面也最強大的庫,學習成本也相對要高很多。
使用cv2.imread
讀取圖片將其儲存爲一個BGR像素值矩陣,故若要結合使用matplotlib則要先進行轉化。
import cv2 # 導入OpenCV庫
import numpy as np # 導入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') # 讀取圖片:第二參數默認爲1,BRG模式
img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB) # 將顏色通道從BRG轉爲RGB
if img == None: # 讀取圖片失敗
print('image failed to load')
cv2.imshow('src',img) # 圖片源src爲img
print(img.shape) # 輸出圖片(高度h,寬度w,通道c)
print(img.size) # 像素總數目
print(img.dtype) # 輸出圖片類型,uint8爲[0-255]
print(img) # 輸出所有像素的RGB值
cv2.waitKey() # 按鍵關閉窗口
# waitKey(delay)函數的功能是不斷刷新圖像,頻率時間爲delay,單位爲ms,返回值爲當前鍵盤按鍵值
# waitKey() 是在一個給定的時間內(單位ms)等待用戶按鍵觸發; 如果用戶沒有按下鍵,則接續等待(循環)
imgL = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) # 讀取img灰度圖
cv2.imshow('gray',imgL) # 圖片源gray爲imgL
cv2.imwrite('imgL.jpg',imgL) # 將imgL儲存名爲imgL.jpg的圖片
print(imgL.shape) # 輸出圖片(高度h,寬度w)
print(imgL.size) # 像素總數目
print(imgL) # 輸出所有像素的灰度值
cv2.waitKey() # 按鍵關閉窗口
img = img.transpose(2,0,1) # 圖片矩陣變換爲(通道c,高度h,寬度w)
img = np.expand_dims(img, axis=0) # 圖片矩陣擴展維度添加在第一維
print(img.shape) # (1,通道c,高度h,寬度w)
# 圖片歸一化處理
img = cv2.imread('PicName.jpg')
img = img.astype("float") / 255.0 # 轉化數據類型爲float後進行歸一化
print(img.dtype) # 輸出爲:float64
print(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-255
img[10,10] = [255,255,255] # 修改圖片img像素點[10,10]爲[255,255,255]
imgL[10,10] = 255 # 修改灰色圖片img像素點[10,10]爲255
img[:,:,2] = 0 # 將R通道全部修改爲0
roi = img[200:550,100:450,:] # ROI操作,座標(高度範圍,寬度範圍,通道範圍)
cv2.imshow('roi',roi) # 圖片源roi爲roi
cv2.waitKey() # 按鍵關閉窗口
1.1.4. skimage
skimage包的全稱是scikit-image SciKit (toolkit for SciPy) ,它對scipy.ndimage進行了擴展,提供了更多的圖片處理功能。它是由python語言編寫的,由scipy 社區開發和維護。skimage包由許多的子模塊組成,各個子模塊提供不同的功能。
使用io.imread()
讀取圖片將其儲存爲一個RGB像素值矩陣。
from skimage import io # 導入skimage庫
from skimage import color
import numpy as np # 導入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像素總數目
print(img.dtype) # 輸出img圖片類型,uint8爲[0-255]
print(imgL.dtype) # 輸出imgL圖片類型,float64爲[0-1],已經被歸一化
print(img) # 輸出img所有像素的RGB值
print(imgL) # 輸出imgL所有灰度值,長度爲imgL.size的numpy數組
io.imsave('img.png',img) # 將img儲存名爲img.png的圖片
io.imshow(img) # 圖片img插入畫板
io.show() # 展示畫板
imgl = io.imread('PicName.jpg') # 讀取圖片
imgl = color.rgb2grey(imgl) # 轉換爲灰度模式
print(imgl.dtype) # 以下數據同imgL
print(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)
'''
1.1.5. imageio
Imageio是一個Python庫,提供了一個簡單的接口用於讀取和寫入各種圖像數據,包括動畫圖像,視頻,體積數據和科學格式。
使用io.imread()
讀取圖片將其儲存爲一個RGB像素值矩陣。
import imageio # 導入imageio庫
img = imageio.imread('PicName.jpg') # 讀取圖片
imageio.imsave('img.png',img) # 將img儲存名爲img.png的文件
print(img.shape) # 輸出圖片img(高度h,寬度w,通道c)
print(img.size) # img像素總數目
print(img.dtype) # 輸出img圖片類型,uint8爲[0-255]
print(img) # 輸出img所有像素的RGB值
plt.imshow(img) # 圖片img插入畫板
plt.show() # 展示畫板
1.2. 總結
- 其他圖像庫讀取彩色圖片都以RGB形式儲存,而OpenCV則是以BGR形式存儲。
- 其他圖像庫讀取圖片都以numpy十六進制彩色值形式儲存,而PIL讀取圖片是以對象形式儲存。
2. 數據擴增
爲了增加數據量、豐富數據多樣性、提高模型的泛化能力,同時也可以有效緩解模型過擬合的情況,給模型帶來的更強的泛化能力。我們可以不實際增加原始數據,只是對原始數據做一些變換,從而創造出更多的數據。我們只需要對現有數據集進行微小改動,例如裁剪或灰度變換或翻轉(數字6與9翻轉會發生交換) 。無論如何,我們的神經網絡會認爲這些是不同的圖像。從而完成數據擴增(Data Augmentation)操作。
2.1. 數據擴增方法
數據擴增方法有很多:從顏色空間、尺度空間到樣本空間,同時根據不同任務數據擴增都有相應的區別。
對於圖像分類,數據擴增一般不會改變標籤;對於物體檢測,數據擴增會改變物體座標位置;對於圖像分割,數據擴增會改變像素標籤。
- 幾何變換:彈性變換(Elastic Transform)、透視變換(Perspective Transform)、分段仿射變換(Piecewise Affine transforms)、枕形畸變(Pincushion Distortion);隨機改變大小(resize),隨機縮放、水平翻轉(flip),豎直翻轉(mirror);
- 加噪聲:對主成分做一個(0, 0.1)的高斯擾動。
- PCA Jittering(顏色改變):首先按照RGB三個顏色通道計算均值和標準差,對網絡的輸入數據進行規範化,隨後我們在整個訓練集上計算了協方差矩陣,進行特徵分解,得到特徵向量和特徵值,用來做PCA Jittering。對RGB空間做PCA,然後對主成分做一個(0, 0.1)的高斯擾動。結果讓錯誤率又下降了1%。
- 對比度和亮度:給圖像增加一些隨機的光照,對比度受限自適應直方圖均衡化算法(Clahe),銳化(Sharpen),凸點(Emboss)
- 隨機色相、飽和度、明度(HSV)變換
- 彩圖到灰度轉換(Color to Gray)
- 將灰度圖重新映射到隨機顏色的圖像中
- 模糊(Blur)、一般模糊(Median Blur)、非常模糊(Motion Blur)
方法 | 中文說明 |
---|---|
transforms.CenterCrop | 對圖片中心進行裁剪 |
transforms.ColorJitter | 對圖像顏色的對比度、飽和度和零度進行變換 |
transforms.FiveCrop | 對圖像四個角和中心進行裁剪得到五分圖像 |
transforms.Grayscale | 對圖像進行灰度變換 |
transforms.Pad | 使用固定值進行像素填充 |
transforms.RandomAffine | 隨機仿射變換 |
transforms.RandomCrop | 隨機區域裁剪 |
2.2. 常用的數據擴增庫
2.2.1.torchvision
pytorch官方提供的數據擴增庫,提供了基本的數據數據擴增方法,可以無縫與torch進行集成;但數據擴增方法種類較少,且速度中等;
2.2.2. imgaug
imgaug是常用的第三方數據擴增庫,提供了多樣的數據擴增方法,且組合起來非常方便,速度較快;
2.2.3. albumentations
是常用的第三方數據擴增庫,提供了多樣的數據擴增方法,對圖像分類、語義分割、物體檢測和關鍵點檢測都支持,速度較快。
3. Pytorch讀取數據
本次賽題我們使用Pytorch讀取賽題數據。
在Pytorch中數據是通過Dataset進行封裝,並通過DataLoder進行並行讀取。所以我們只需要重載一下數據讀取的邏輯,之後我們將在定義好的Dataset基礎上構建DataLoder,DataLoder是爲了實現不同於Dataset的功能。
- Dataset:對數據集的封裝,提供索引方式的對數據樣本進行讀取
- DataLoder:對Dataset進行封裝,提供批量讀取的迭代讀取
import os, sys, glob, shutil, json
import cv2
from PIL import Image
import numpy as np
import torch
from torch.utils.data.dataset import Dataset
import torchvision.transforms as transforms
class SVHNDataset(Dataset):
def __init__(self, img_path, img_label, transform=None):
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):
img = Image.open(self.img_path[index]).convert('RGB')
if self.transform is not None:
img = self.transform(img)
# 原始SVHN中類別10爲數字0
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')
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(), # 將圖片轉換爲pytorch 的tesntor
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) # 對圖像像素進行歸一化
])),
batch_size=10, # 每批樣本個數
shuffle=False, # 是否打亂順序
num_workers=10, # 讀取的線程個數
)
for data in train_loader:
break
在加入DataLoder後,數據按照批次獲取,每批次調用Dataset讀取單個樣本進行拼接。
此時data的格式爲:torch.Size([10, 3, 64, 128]), torch.Size([10, 6])
。
前者爲圖像文件,爲batchsize * chanel * height * width
次序,
後者爲字符標籤。