使用預訓練模型進行影像分類
在本課中,您將學習如何使用預訓練模型來檢測給定影像中的物件。您將使用squeezenet預訓練模組,該模組可以高精度地檢測和分類給定影像中的物件。
開啟一個新的Juypter notebook並按照步驟開發此影像分類應用程式。
匯入庫
首先,我們使用以下程式碼匯入所需的包:
from caffe2.proto import caffe2_pb2 from caffe2.python import core, workspace, models import numpy as np import skimage.io import skimage.transform from matplotlib import pyplot import os import urllib.request as urllib2 import operator
接下來,我們設定一些變數:
INPUT_IMAGE_SIZE = 227 mean = 128
用於訓練的影像顯然會有各種尺寸。為了進行準確的訓練,所有這些影像都必須轉換為固定尺寸。同樣,測試影像和您希望在生產環境中預測的影像也必須轉換為與訓練期間使用的相同的尺寸。因此,我們在上面建立了一個名為INPUT_IMAGE_SIZE的變數,其值為227。因此,在將所有影像用於分類器之前,我們將將其轉換為227x227尺寸。
我們還聲明瞭一個名為mean的變數,其值為128,稍後用於改進分類結果。
接下來,我們將開發兩個用於處理影像的函式。
影像處理
影像處理包括兩個步驟。第一個是調整影像大小,第二個是中央裁剪影像。對於這兩個步驟,我們將編寫兩個用於調整大小和裁剪的函式。
影像大小調整
首先,我們將編寫一個用於調整影像大小的函式。如前所述,我們將影像大小調整為227x227。因此,讓我們定義如下resize函式:
def resize(img, input_height, input_width):
我們透過將寬度除以高度來獲得影像的縱橫比。
original_aspect = img.shape[1]/float(img.shape[0])
如果縱橫比大於1,則表示影像較寬,也就是說它是橫向模式。我們現在調整影像高度並使用以下程式碼返回調整大小後的影像:
if(original_aspect>1): new_height = int(original_aspect * input_height) return skimage.transform.resize(img, (input_width, new_height), mode='constant', anti_aliasing=True, anti_aliasing_sigma=None)
如果縱橫比小於1,則表示縱向模式。我們現在使用以下程式碼調整寬度:
if(original_aspect<1): new_width = int(input_width/original_aspect) return skimage.transform.resize(img, (new_width, input_height), mode='constant', anti_aliasing=True, anti_aliasing_sigma=None)
如果縱橫比等於1,則我們不會進行任何高度/寬度調整。
if(original_aspect == 1): return skimage.transform.resize(img, (input_width, input_height), mode='constant', anti_aliasing=True, anti_aliasing_sigma=None)
為了方便您快速參考,下面給出了完整的函式程式碼:
def resize(img, input_height, input_width):
original_aspect = img.shape[1]/float(img.shape[0])
if(original_aspect>1):
new_height = int(original_aspect * input_height)
return skimage.transform.resize(img, (input_width,
new_height), mode='constant', anti_aliasing=True, anti_aliasing_sigma=None)
if(original_aspect<1):
new_width = int(input_width/original_aspect)
return skimage.transform.resize(img, (new_width,
input_height), mode='constant', anti_aliasing=True, anti_aliasing_sigma=None)
if(original_aspect == 1):
return skimage.transform.resize(img, (input_width,
input_height), mode='constant', anti_aliasing=True, anti_aliasing_sigma=None)
現在,我們將編寫一個用於裁剪影像中心的函式。
影像裁剪
我們如下宣告crop_image函式:
def crop_image(img,cropx,cropy):
我們使用以下語句提取影像的尺寸:
y,x,c = img.shape
我們使用以下兩行程式碼建立影像的新起點:
startx = x//2-(cropx//2) starty = y//2-(cropy//2)
最後,我們透過使用新的尺寸建立一個影像物件來返回裁剪後的影像:
return img[starty:starty+cropy,startx:startx+cropx]
為了方便您快速參考,下面給出了整個函式程式碼:
def crop_image(img,cropx,cropy): y,x,c = img.shape startx = x//2-(cropx//2) starty = y//2-(cropy//2) return img[starty:starty+cropy,startx:startx+cropx]
現在,我們將編寫程式碼來測試這些函式。
處理影像
首先,將影像檔案複製到專案目錄中的images子資料夾中。tree.jpg檔案已複製到專案中。以下 Python 程式碼載入影像並將其顯示在控制檯上:
img = skimage.img_as_float(skimage.io.imread("images/tree.jpg")).astype(np.float32)
print("Original Image Shape: " , img.shape)
pyplot.figure()
pyplot.imshow(img)
pyplot.title('Original image')
輸出如下:
請注意,原始影像的大小為600 x 960。我們需要將其調整為我們的規範227 x 227。呼叫我們之前定義的resize函式可以完成此操作。
img = resize(img, INPUT_IMAGE_SIZE, INPUT_IMAGE_SIZE)
print("Image Shape after resizing: " , img.shape)
pyplot.figure()
pyplot.imshow(img)
pyplot.title('Resized image')
輸出如下所示:
請注意,現在影像大小為227 x 363。我們需要將其裁剪為227 x 227,以便最終饋送到我們的演算法中。為此,我們呼叫之前定義的裁剪函式。
img = crop_image(img, INPUT_IMAGE_SIZE, INPUT_IMAGE_SIZE)
print("Image Shape after cropping: " , img.shape)
pyplot.figure()
pyplot.imshow(img)
pyplot.title('Center Cropped')
下面是程式碼的輸出:
此時,影像大小為227 x 227,並已準備好進行進一步處理。我們現在交換影像軸以將三種顏色提取到三個不同的區域。
img = img.swapaxes(1, 2).swapaxes(0, 1)
print("CHW Image Shape: " , img.shape)
輸出如下所示:
CHW Image Shape: (3, 227, 227)
請注意,最後一個軸現在已成為陣列中的第一個維度。我們現在將使用以下程式碼繪製三個通道:
pyplot.figure()
for i in range(3):
pyplot.subplot(1, 3, i+1)
pyplot.imshow(img[i])
pyplot.axis('off')
pyplot.title('RGB channel %d' % (i+1))
輸出如下所示:
最後,我們對影像進行一些額外的處理,例如將紅綠藍轉換為藍綠紅 (RGB to BGR),去除均值以獲得更好的結果,並使用以下三行程式碼新增批次大小軸:
# convert RGB --> BGR img = img[(2, 1, 0), :, :] # remove mean img = img * 255 - mean # add batch size axis img = img[np.newaxis, :, :, :].astype(np.float32)
此時,您的影像已採用NCHW格式,並已準備好饋送到我們的網路中。接下來,我們將載入我們的預訓練模型檔案並將上述影像饋送到其中進行預測。
預測處理後的影像中的物件
我們首先為Caffe預訓練模型中定義的init和predict網路設定路徑。
設定模型檔案路徑
請記住,我們之前討論過,所有預訓練模型都安裝在models資料夾中。我們如下設定此資料夾的路徑:
CAFFE_MODELS = os.path.expanduser("/anaconda3/lib/python3.7/site-packages/caffe2/python/models")
我們如下設定squeezenet模型的init_net protobuf 檔案的路徑:
INIT_NET = os.path.join(CAFFE_MODELS, 'squeezenet', 'init_net.pb')
同樣,我們如下設定predict_net protobuf 檔案的路徑:
PREDICT_NET = os.path.join(CAFFE_MODELS, 'squeezenet', 'predict_net.pb')
我們列印這兩條路徑以進行診斷:
print(INIT_NET) print(PREDICT_NET)
為了方便您快速參考,此處提供了上述程式碼及其輸出:
CAFFE_MODELS = os.path.expanduser("/anaconda3/lib/python3.7/site-packages/caffe2/python/models")
INIT_NET = os.path.join(CAFFE_MODELS, 'squeezenet', 'init_net.pb')
PREDICT_NET = os.path.join(CAFFE_MODELS, 'squeezenet', 'predict_net.pb')
print(INIT_NET)
print(PREDICT_NET)
輸出如下所示:
/anaconda3/lib/python3.7/site-packages/caffe2/python/models/squeezenet/init_net.pb /anaconda3/lib/python3.7/site-packages/caffe2/python/models/squeezenet/predict_net.pb
接下來,我們將建立一個預測器。
建立預測器
我們使用以下兩條語句讀取模型檔案:
with open(INIT_NET, "rb") as f: init_net = f.read() with open(PREDICT_NET, "rb") as f: predict_net = f.read()
透過將指向這兩個檔案的指標作為引數傳遞給Predictor函式來建立預測器。
p = workspace.Predictor(init_net, predict_net)
p物件是預測器,用於預測任何給定影像中的物件。請注意,每個輸入影像都必須採用NCHW格式,就像我們之前對tree.jpg檔案所做的那樣。
預測物件
預測給定影像中的物件非常簡單 - 只需執行一行命令即可。我們對predictor物件呼叫run方法以在給定影像中進行物件檢測。
results = p.run({'data': img})
預測結果現在可在results物件中獲得,為了便於閱讀,我們將將其轉換為陣列。
results = np.asarray(results)
使用以下語句列印陣列的維度,以方便您理解:
print("results shape: ", results.shape)
輸出如下所示:
results shape: (1, 1, 1000, 1, 1)
現在我們將刪除不必要的軸:
preds = np.squeeze(results)
現在可以透過獲取preds陣列中的max值來檢索最頂層的預測。
curr_pred, curr_conf = max(enumerate(preds), key=operator.itemgetter(1))
print("Prediction: ", curr_pred)
print("Confidence: ", curr_conf)
輸出如下:
Prediction: 984 Confidence: 0.89235985
如您所見,模型已預測到索引值為984、置信度為89%的物件。索引984對於我們理解檢測到的物件型別沒有多大意義。我們需要使用其索引值獲取物件的字串化名稱。模型識別的物件型別及其對應的索引值可在github儲存庫中找到。
現在,我們將瞭解如何檢索索引值為984的物件的名稱。
字串化結果
我們如下建立指向github儲存庫的URL物件:
codes = "https://gist.githubusercontent.com/aaronmarkham/cd3a6b6ac0 71eca6f7b4a6e40e6038aa/raw/9edb4038a37da6b5a44c3b5bc52e448ff09bfe5b/alexnet_codes"
我們讀取URL的內容:
response = urllib2.urlopen(codes)
響應將包含所有程式碼及其描述的列表。為了方便您瞭解其包含的內容,下面顯示了響應的幾行:
5: 'electric ray, crampfish, numbfish, torpedo', 6: 'stingray', 7: 'cock', 8: 'hen', 9: 'ostrich, Struthio camelus', 10: 'brambling, Fringilla montifringilla',
我們現在使用for迴圈迭代整個陣列,以找到我們所需的程式碼984:
for line in response:
mystring = line.decode('ascii')
code, result = mystring.partition(":")[::2]
code = code.strip()
result = result.replace("'", "")
if (code == str(curr_pred)):
name = result.split(",")[0][1:]
print("Model predicts", name, "with", curr_conf, "confidence")
執行程式碼後,您將看到以下輸出:
Model predicts rapeseed with 0.89235985 confidence
您現在可以嘗試在其他影像上使用模型。
預測不同的影像
要預測另一幅影像,只需將影像檔案複製到專案目錄的images資料夾中即可。這是我們之前儲存tree.jpg檔案的目錄。在程式碼中更改影像檔案的名稱。只需更改一行程式碼,如下所示
img = skimage.img_as_float(skimage.io.imread("images/pretzel.jpg")).astype(np.float32)
原始圖片和預測結果如下所示:
輸出如下所示:
Model predicts pretzel with 0.99999976 confidence
如您所見,預訓練模型能夠以極高的準確度檢測給定影像中的物件。
完整原始碼
為了方便您快速參考,此處提供了上述使用預訓練模型在給定影像中進行物件檢測的程式碼的完整原始碼:
def crop_image(img,cropx,cropy):
y,x,c = img.shape
startx = x//2-(cropx//2)
starty = y//2-(cropy//2)
return img[starty:starty+cropy,startx:startx+cropx]
img = skimage.img_as_float(skimage.io.imread("images/pretzel.jpg")).astype(np.float32)
print("Original Image Shape: " , img.shape)
pyplot.figure()
pyplot.imshow(img)
pyplot.title('Original image')
img = resize(img, INPUT_IMAGE_SIZE, INPUT_IMAGE_SIZE)
print("Image Shape after resizing: " , img.shape)
pyplot.figure()
pyplot.imshow(img)
pyplot.title('Resized image')
img = crop_image(img, INPUT_IMAGE_SIZE, INPUT_IMAGE_SIZE)
print("Image Shape after cropping: " , img.shape)
pyplot.figure()
pyplot.imshow(img)
pyplot.title('Center Cropped')
img = img.swapaxes(1, 2).swapaxes(0, 1)
print("CHW Image Shape: " , img.shape)
pyplot.figure()
for i in range(3):
pyplot.subplot(1, 3, i+1)
pyplot.imshow(img[i])
pyplot.axis('off')
pyplot.title('RGB channel %d' % (i+1))
# convert RGB --> BGR
img = img[(2, 1, 0), :, :]
# remove mean
img = img * 255 - mean
# add batch size axis
img = img[np.newaxis, :, :, :].astype(np.float32)
CAFFE_MODELS = os.path.expanduser("/anaconda3/lib/python3.7/site-packages/caffe2/python/models")
INIT_NET = os.path.join(CAFFE_MODELS, 'squeezenet', 'init_net.pb')
PREDICT_NET = os.path.join(CAFFE_MODELS, 'squeezenet', 'predict_net.pb')
print(INIT_NET)
print(PREDICT_NET)
with open(INIT_NET, "rb") as f:
init_net = f.read()
with open(PREDICT_NET, "rb") as f:
predict_net = f.read()
p = workspace.Predictor(init_net, predict_net)
results = p.run({'data': img})
results = np.asarray(results)
print("results shape: ", results.shape)
preds = np.squeeze(results)
curr_pred, curr_conf = max(enumerate(preds), key=operator.itemgetter(1))
print("Prediction: ", curr_pred)
print("Confidence: ", curr_conf)
codes = "https://gist.githubusercontent.com/aaronmarkham/cd3a6b6ac071eca6f7b4a6e40e6038aa/raw/9edb4038a37da6b5a44c3b5bc52e448ff09bfe5b/alexnet_codes"
response = urllib2.urlopen(codes)
for line in response:
mystring = line.decode('ascii')
code, result = mystring.partition(":")[::2]
code = code.strip()
result = result.replace("'", "")
if (code == str(curr_pred)):
name = result.split(",")[0][1:]
print("Model predicts", name, "with", curr_conf, "confidence")
至此,您已經瞭解瞭如何使用預訓練模型對資料集進行預測。
接下來,我們將學習如何在Caffe2中定義神經網路 (NN)架構並在您的資料集上對其進行訓練。現在,我們將學習如何建立一個簡單的單層神經網路。