VGG16/19 是 ImageNet 每年舉辦的模型演算競賽(ILSVRC)中,於2014年獲得亞軍的模型。以前曾說明 VGG16 模型的運用,此篇改成 VGG19,其實用法差不多。VGG16/19 用來偵測圖片,將圖片進行分類,是由牛津大學計算機視覺組對 100 萬張圖片進行訓練,然後分成1000 個種類。
安裝套件
開始執行此範例前,請先安裝如下套件
pip install tensorflow opencv-python Pillow matplotlib
簡易偵測
底下代碼為最簡易的偵測圖片方式。請注意,如果電腦裝有nVidia顯卡並且有安裝 CUDA的話,就會自動換到GPU進行偵測。
model=VGG19() 會建立模型,若是第一次執行,會到網路上下載模型,並儲存在 C:\Users\mahal\.keras\models 目錄中,所以會比較久。第二次執行,就不會重複下載,所以速度就快多了。
在取得圖片的numpy陣列後,必需使用 np.expand_dims(x, axis=0) 由 3 維的最前面一維擴展成 4 維,原因在於 VGG19 規定傳入 predict 開始偵測時,必需是 (1, 224,224, 3) 的陣列格式。
preprocess_input()稱為圖片預處理。預處理有三種方式,分別為 caffe, tf, troch。
VGG19採用 caffe方式,先將RGB 轉成 BGR格式,然後將所有點的B值減掉B的平均數,G及R亦同。如此就可以讓每個點的每個顏色值分佈在 0 的左右二邊。
predict() 偵測後,會傳回 1000 個分數,分別代表每一個種類的分數,分數愈高表示愈接近此分類。所以取得分數最高的那一個分類即可。
import os os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' import numpy as np from PIL import Image, ImageFont, ImageDraw from keras.applications import VGG19, vgg19 import cv2 from keras.applications.vgg19 import preprocess_input import pylab as plt model=VGG19(weights='imagenet', include_top=True) img=cv2.imdecode(np.fromfile('tiger.jpg', dtype=np.uint8), cv2.IMREAD_UNCHANGED) x=cv2.resize(img, (224, 224), interpolation=cv2.INTER_LINEAR) x=cv2.cvtColor(x, cv2.COLOR_BGR2RGB) #VGG19規定在偵測(predict)時,必需傳入 #(1,224,224,3)的資料格式 x=np.expand_dims(x, axis=0)
#預處理圖片及偵測圖片 x=preprocess_input(x) out=model.predict(x) #decode_predictions轉成標籤及分數 results=vgg19.decode_predictions(out, top=3) result=results[0][0] print('Predicted:', result) #寫入文字並顯示圖片 pil = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) font = ImageFont.truetype('simsun.ttc', 100) txt=f'名稱 : {result[1]}\n信心度 : {result[2]*100:.2f}%' ImageDraw.Draw(pil).text((5, 5), txt, font=font, fill=(255,255,0)) plt.figure(figsize=(12,9)) plt.imshow(pil) plt.show()
自定訓練 5 種花卉
VGG19可以分辨1000個種類,但如果我們要辨識的,並不在這1000個之內,就要自已訓練。訓練的步驟為 :
收集圖片資料,訓練,偵測。
收集圖片
收集圖片是一項大工程,還好 tensorflow 幫我們收集了一大堆圖片。請到 http://download.tensorflow.org/example_images/flower_photos.tgz 下載圖片。下載後置於專案之下並解開,就會多一個 flower_photos 目錄。
flower_photos 裏面 有 5 個資料夾,分別是 daisy(雛菊),dandelion(蒲公英),roses(玫瑰),sunflowers(向日葵),tulips(鬱金香)
Dense 層
Dense 層只會出現在全連接層,也就是輸出層。Dense 的作用就是由原本的 x 種狀況 (特徵),經過某種演算後,變成下一層的 y 種狀況。
剛剛講的某種演算,通常就是用捲積的方式來計算下一層的結果。
訓練模型
把每張圖片讀入縮小後放入 data 中,並同時由目錄名稱記錄每張圖的類別放在 labels 中。然後將VGG19前三個連接層抽出,再加入自已的全連接層。
一開始始用 GlobalAveragePooling2D 將 (長*寬*通道) 轉換成 (1*1*通道),其方法是將每個通道中的權重作平均值。
Dense 種類為 5 種,激活方式為 relu (線性整流, 將負值變為0),最後的輸出層激活含數為 softmax (轉換為機率,總合為1)。
在每個隱藏層( Dense) 之後,都需作 BatchNormalization,將數字集中在平均為 0,標準差為 1 的常態分佈。
模型中所加入的各層,不一定要按這個方式,也可以改用其它層,可自行測試看看。
訓練時要注意 batch_size,如果太大會造成顯卡記憶体不足,就需往下調整。訓練的時間依硬体等級有所不同。訓練好的模型會儲存在 flower 目錄中。
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
import random
import numpy as np
import cv2
from keras import Sequential, Model
from keras.applications import VGG19
from keras.layers import GlobalAveragePooling2D, Dense, Dropout, Flatten, BatchNormalization
from keras.optimizers import SGD, Adam
import pylab as plt
import shutil
data=[]
labels=[]
for name in os.listdir("flower_photos"):
for flower in os.listdir(os.path.join('./flower_photos', name)):
img=cv2.imdecode(np.fromfile(os.path.join('./flower_photos', name, flower), dtype=np.uint8), cv2.IMREAD_UNCHANGED)
img=cv2.resize(img, (224,224), interpolation=cv2.INTER_LINEAR)
data.append(img)
labels.append(name)
kind={'daisy':0,'dandelion':1,'roses':2,'sunflowers':3,'tulips':4}
upset=list(zip(data, labels))
random.shuffle(upset)
data[:], labels[:]=zip(*upset)
train_data=np.array([data[i] for i in range(len(labels)) if i %9>1])
train_label=[labels[i] for i in range(len(labels)) if i %9 >1]
test_data=np.array([data[i] for i in range(len(labels)) if i %9<=1])
test_label=[labels[i] for i in range(len(labels)) if i %9 <=1]
train_labels=np.zeros([len(train_label),5])
test_labels=np.zeros([len(test_label),5])
for i in range(len(train_labels)):
train_labels[i][kind[train_label[i]]]=1
for i in range(len(test_labels)):
test_labels[i][kind[test_label[i]]]=1
#sgd=SGD(lr=0.001, decay=1e-6, momentum=0.9, nesterov=True)
model_base=VGG19(weights='imagenet', include_top=False, input_shape=(224,224,3))
for layer in model_base.layers:
layer.trainable=False
# model=Flatten(name='flatten')(model_base.output)
# model=Dense(64,activation='relu')(model)
# model=BatchNormalization()(model)
# model=Dropout(0.5)(model)
# model=Dense(32, activation='relu')(model)
# model=BatchNormalization()(model)
# model=Dropout(0.5)(model)
# model=Dense(5, activation='softmax')(model)
# model=Model(inputs=model_base.input, outputs=model, name='vgg19')
model=Sequential()
model.add(model_base)
model.add(GlobalAveragePooling2D())
model.add(Dense(256,activation='relu'))
model.add(BatchNormalization())
model.add(Dense(64,activation='relu'))
model.add(BatchNormalization())
model.add(Dropout(0.2))
model.add(Dense(5, activation='softmax'))
model.compile(optimizer=Adam(learning_rate=0.0001),
loss='categorical_crossentropy',
metrics=['accuracy']
)
history=model.fit(
train_data,
train_labels,
batch_size=64,
epochs=50,
validation_data=(test_data, test_labels)
)
if os.path.exists('./flower'):
shutil.rmtree('./flower')
model.save("flower")
p1=plt.plot(history.history['accuracy'], label='training acc')#訓練時的準確度
p2=plt.plot(history.history['val_accuracy'], label='val acc')#測試時的準確度
p3=plt.plot(history.history['loss'], label='training loss')#訓練時的損失率
p4=plt.plot(history.history['val_loss'], label='val loss')#測試時的損失率
plt.legend()
plt.show()
辨識圖片
如果無法訓練模型,可下載人本已訓練好的模型 : flower_5_model.zip
載入模型後,再載入圖片即可辨識。記得圖片需縮小為 224*224,然後擴展為4維。
import os os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' import keras import cv2 import numpy as np from keras.applications.vgg19 import preprocess_input model=keras.models.load_model('flower') img=cv2.imdecode(np.fromfile('./flower_photos/roses/394990940_7af082cf8d_n.jpg', dtype=np.uint8), cv2.IMREAD_UNCHANGED) x=cv2.resize(img, (224, 224), interpolation=cv2.INTER_LINEAR) x=cv2.cvtColor(x, cv2.COLOR_BGR2RGB) #VGG19規定在偵測(predict)時,必需傳入 #(1,224,224,3)的資料格式 x=np.expand_dims(x, axis=0) #預處理圖片及偵測圖片 x=preprocess_input(x) out=model.predict(x) print(out) kind={0:'daisy',1:'dandelion',2:'roses',3:'sunflowers',4:'tulips'} idx=out[0].argmax() print(kind[idx]) 結果 : 1/1 [==============================] - 2s 2s/step [[7.9912643e-05 5.3283002e-04 9.9877006e-01 3.8993327e-04 2.2729916e-04]] roses
辨識 17 種花卉
網路上有另一種辨識17種花卉的討論,
收集圖片
請由如下網址下載 flows_17.zip,然後解開後置於專案之下。
下載 : flowers_17.zip
label.txt
flowers圖片共有17種,每種 80 張圖片,所以共有1360張圖片,1~80是水仙(Narcissus),81~160是雪花蓮(Snowdrop),請先於專案下新增 label.txt,然後輸入如下資料。
1 80 Narcissus 81 160 Snowdrop 161 240 LilyValley 241 320 Bluebell 321 400 Crocus 401 480 Iris 481 560 Tigerlily 561 640 Daffodil 641 720 Fritillary 721 800 Sunflower 801 880 Daisy 881 960 ColtsFoot 961 1040 Dandelion 1041 1120 Cowslip 1121 1200 Buttercup 1201 1280 Windflower 1281 1360 Pansy
分類訓練圖片及驗証圖片
將所有的圖片分類成 train_images及 test_images二個目錄,每個目錄又有17種花卉目錄。
import os os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' import random import shutil import numpy as np import cv2 from keras import Sequential from keras.applications import VGG19 from keras.layers import GlobalAveragePooling2D, Dense, BatchNormalization, Dropout from keras.optimizers import SGD, Adam import pylab as plt flowers=[] dirs=np.empty(0, dtype=object) with open('label.txt')as file: for line in file: line=line.strip() cols=line.split() s=int(cols[0]) e=int(cols[1]) flowers.append(cols[2]) dirs=np.r_[dirs, [cols[2]]*(e-s+1)] in_dir="flowers_17" train_dir='train_images' test_dir='test_images' if os.path.exists(train_dir): shutil.rmtree(train_dir) os.mkdir(train_dir) if os.path.exists(test_dir): shutil.rmtree(test_dir) os.mkdir(test_dir) for flower in flowers: if not os.path.exists(os.path.join(train_dir, flower)): os.mkdir(os.path.join(train_dir, flower)) if not os.path.exists(os.path.join(test_dir, flower)): os.mkdir(os.path.join(test_dir, flower)) for file, dir in zip(sorted(os.listdir(in_dir)),dirs): source = os.path.join(in_dir, file) dest = os.path.join(train_dir, dir, file) print(f'copy {source} => {dest}') shutil.copy(source, dest) for flower in os.listdir(train_dir): files=os.listdir(os.path.join(train_dir, flower)) random.shuffle(files) for file in files[:10]: source=os.path.join(train_dir, flower, file) dest=os.path.join(test_dir, flower) print(f'move {source} => {dest}') shutil.move(source, dest)
訓練模型
建立模型跟上述的5種花卉雷同。
#準備訓練資料 train_data=[] train_labels=[] path='train_images' for flower in flowers: for file in os.listdir(os.path.join(path, flower)): img=cv2.imdecode(np.fromfile(os.path.join(path, flower, file), dtype=np.uint8), cv2.IMREAD_UNCHANGED) img=cv2.resize(img, (224,224), interpolation=cv2.INTER_LINEAR) train_data.append(img) train_labels.append(flower) train_data=np.array(train_data) #準備驗証資料 test_data=[] test_labels=[] path='test_images' for flower in flowers: for file in os.listdir(os.path.join(path, flower)): img=cv2.imdecode(np.fromfile(os.path.join(path, flower, file), dtype=np.uint8), cv2.IMREAD_UNCHANGED) img=cv2.resize(img, (224,224), interpolation=cv2.INTER_LINEAR) test_data.append(img) test_labels.append(flower) test_data=np.array(test_data) train_labels_onehot=np.zeros([len(train_labels),17], dtype=np.int32) test_labels_onehot=np.zeros([len(test_labels),17], dtype=np.int32) for i in range(len(train_labels_onehot)): train_labels_onehot[i][flowers.index(train_labels[i])]=1 print(train_labels_onehot[i]) for i in range(len(test_labels_onehot)): test_labels_onehot[i][flowers.index(test_labels[i])]=1 print(test_labels[i],test_labels_onehot[i]) model_base=VGG19(weights='imagenet', include_top=False, input_shape=(224,224,3)) for layer in model_base.layers: layer.trainable=False model=Sequential() model.add(model_base) model.add(GlobalAveragePooling2D()) model.add(Dense(256,activation='relu')) model.add(BatchNormalization()) model.add(Dense(64,activation='relu')) model.add(BatchNormalization()) model.add(Dropout(0.2)) model.add(Dense(17, activation='softmax')) model.compile(optimizer=Adam(learning_rate=0.001), loss='categorical_crossentropy', metrics=['accuracy'] ) history=model.fit( train_data, train_labels_onehot, batch_size=64, epochs=50, validation_data=(test_data, test_labels_onehot) ) if os.path.exists('./flower_17_model'): shutil.rmtree('./flower_17_model') model.save("flower_17_model") p1=plt.plot(history.history['accuracy'], label='training acc') p2=plt.plot(history.history['val_accuracy'], label='val acc') p3=plt.plot(history.history['loss'], label='training loss') p4=plt.plot(history.history['val_loss'], label='val loss') plt.legend() plt.show()
辨識圖片
上述模型若是無法訓練的話,請由如下網址下載本人已訓練好的模型
下載模型 : flower_17_model
將要辨識的圖片放在 ./images裏面,然後開始辨識。
import os os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' import keras import cv2 import numpy as np from keras.applications.vgg19 import preprocess_input model=keras.models.load_model('flower_17_model') path = 'train_images' flowers=[] with open('label.txt')as file: for line in file: line=line.strip() cols=line.split() flowers.append(cols[2]) print(flowers) test_path='./images' for file in os.listdir(test_path): img=cv2.imdecode(np.fromfile(os.path.join(test_path, file), dtype=np.uint8), cv2.IMREAD_UNCHANGED) x=cv2.resize(img, (224, 224), interpolation=cv2.INTER_LINEAR) x=cv2.cvtColor(x, cv2.COLOR_BGR2RGB) #VGG19規定在偵測(predict)時,必需傳入 #(1,224,224,3)的資料格式 x=np.expand_dims(x, axis=0) #預處理圖片及偵測圖片 x=preprocess_input(x) out=model.predict(x) print(out) idx=out[0].argmax() print(flowers[idx])