基於VGG16的垃圾分類系統

目錄

1.VGG結構

2.塊結構

3.權重參數

3.特點

4.代碼實現

4.1 數據集介紹

4.2 代碼

4.3 實驗結果


1.VGG結構

VGG16 是基於大量真實圖像的 ImageNet 圖像庫預訓練的網絡。VGG是由Simonyan 和Zisserman在文獻《Very Deep Convolutional Networks for Large Scale Image Recognition》中提出卷積神經網絡模型,其名稱來源於作者所在的牛津大學視覺幾何組(Visual Geometry Group)的縮寫。該模型參加2014年的 ImageNet圖像分類與定位挑戰賽,取得了優異成績:在分類任務上排名第二,在定位任務上排名第一。

VGG中根據卷積核大小和卷積層數目的不同,可分爲A,A-LRN,B,C,D,E共6個配置(ConvNet Configuration),其中以D,E兩種配置較爲常用,分別稱爲VGG16和VGG19。下圖給出了VGG的六種結構配置:

下圖 VGG六種結構配置圖:

上圖中,每一列對應一種結構配置。例如,圖中綠色部分即指明瞭VGG16所採用的結構。

我們針對VGG16進行具體分析發現,VGG16共包含:

  • 13個卷積層(Convolutional Layer),分別用conv3-XXX表示
  • 3個全連接層(Fully connected Layer),分別用FC-XXXX表示
  • 5個池化層(Pool layer),分別用maxpool表示

其中,卷積層和全連接層具有權重係數,因此也被稱爲權重層,總數目爲13+3=16,這即是VGG16中16的來源。(池化層不涉及權重,因此不屬於權重層,不被計數)。

VGG16整個架構圖如下圖所示:

從左至右,一張彩色圖片輸入到網絡,白色框是卷積層,紅色是池化,藍色是全連接層,棕色框是預測層。預測層的作用是將全連接層輸出的信息轉化爲相應的類別概率,而起到分類作用。

可以看到 VGG16 是13個卷積層+3個全連接層疊加而成。

網絡開始輸入(3,224,224)的圖像數據,即一張寬224,高244的彩色RGB圖片,同時補了一圈0。接着是卷積層,有64個(3,3)的卷積核,一個卷積核掃完圖片,生成一個新的矩陣,64個就生成64 層。接着是補0,接着再來一次卷積。此時圖像數據是64*224*224,接着是池化,小矩陣是(2,2)。按照這樣池化之後,數據變成了64*112*112,矩陣的寬高由原來的224減半,變成了112。再往下,同理,只不過是卷積核個數依次變成128,256,512,而每次按照這樣池化之後,矩陣都要縮小一半。13層卷積和池化之後,數據變成了 512*7*7。

2.塊結構

觀察上面第一幅圖右側,VGG16的卷積層和池化層可以劃分爲不同的塊(Block),從前到後依次編號爲block1-block5。每一個塊內包含若干卷積層和一個池化層。例如:block4包含:3個卷積層,conv3-512;1個池化層,maxpool。

並且同一塊內,卷積層的通道數是相同的,例如:block2中包含2個卷積層,每個卷積層用conv3-128表示,即卷積核爲:3x3x3,通道數都是128;block3中包含3個卷積層,每個卷積層用conv3-256表示,即卷積核爲:3x3x3,通道數都是256

下面給出按照塊劃分的VGG16的結構圖,可以結合下圖進行理解:

VGG的輸入圖像是 224x224x3:通道數翻倍,由64依次增加到128,再到256,直至512保持不變,不再翻倍;高和寬變減半,由 224→112→56→28→14→7。

3.權重參數

儘管VGG的結構簡單,但是所包含的權重數目卻很大,達到了驚人的139,357,544個參數。這些參數包括卷積核權重和全連接層權重。

例如,對於第一層卷積,由於輸入圖的通道數是3,網絡必須學習大小爲3x3,通道數爲3的的卷積核,這樣的卷積核有64個,因此總共有(3x3x3)x64 = 1728個參數。

計算全連接層的權重參數數目的方法爲:前一層節點數×本層的節點數前一層節點數×本層的節點數。因此,全連接層的參數分別爲:

  • 7x7x512x4096 = 1027,645,444
  • 4096x4096 = 16,781,321
  • 4096x1000 = 4096000

FeiFei Li在CS231的課件中給出了整個網絡的全部參數的計算過程(不考慮偏置),如下圖所示:

圖中藍色是計算權重參數數量的部分;紅色是計算所需存儲容量的部分。

VGG16具有如此之大的參數數目,可以預期它具有很高的擬合能力;但同時缺點也很明顯,即訓練時間過長,調參難度大;需要的存儲容量大,不利於部署。例如存儲VGG16權重值文件的大小爲500多MB,不利於安裝到嵌入式系統中。

3.特點

VGG16的突出特點是簡單,體現在:

(1)卷積層均採用相同的卷積核參數:卷積層均表示爲conv3-XXX,其中conv3說明該卷積層採用的卷積核的尺寸(kernel size)是3,即寬(width)和高(height)均爲3,3*3是很小的卷積核尺寸,結合其它參數(步幅stride=1,填充方式padding=same),這樣就能夠使得每一個卷積層(張量)與前一層(張量)保持相同的寬和高。XXX代表卷積層的通道數。

(2)池化層均採用相同的池化核參數:池化層的參數均爲2×。

(3)模型是由若干卷積層和池化層堆疊(stack)的方式構成,比較容易形成較深的網絡結構(在2014年,16層已經被認爲很深了)。

綜合上述分析,可以概括VGG的優點爲: Small filters, Deeper networks.

4.代碼實現

4.1 數據集介紹

該數據集包含了2507 個生活垃圾圖片,物品都是物品放在白板上在日光/室內光源下拍攝的,壓縮後的尺寸爲 512 * 384,垃圾識別分類數據集中包括玻璃 (glass) 、硬紙板 (cardboard) 、金屬 (metal) 、紙 (paper) 、塑料 (plastic) 、一般垃圾 (trash) ,共6個類別,如下表3-1所示:

序號

中文名

英文名

數據集大小

1

玻璃

glass

497張圖片

2

paper

590張圖片

3

硬紙板

cardboard

400張圖片

4

塑料

plastic

479張圖片

5

金屬

metal

407張圖片

6

一般垃圾

trash

134張圖片 

數據集鏈接:

鏈接:https://pan.baidu.com/s/144CJwIe_f3IBqktodFtYSA 
提取碼:g16r

4.2 代碼

model_cre.py 數據預處理-構建VGG16模型

# coding=utf-8
from keras.layers import Dropout
from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
from keras.layers import Flatten, Dense
from keras.optimizers import SGD
from keras.applications.vgg16 import VGG16
import matplotlib.pyplot as plt
import time

"""
1.數據預處理
data_path: 數據集路徑
return: train, test:處理後的訓練集數據、測試集數據
"""
def processing_data(data_path):
    # 生成訓練集
    train_data = ImageDataGenerator(
            # 浮點數,圖片寬度的某個比例,數據提升時圖片水平偏移的幅度
            width_shift_range=0.1,
            # 浮點數,圖片高度的某個比例,數據提升時圖片豎直偏移的幅度
            height_shift_range=0.1,
            # 浮點數,剪切強度(逆時針方向的剪切變換角度)
            shear_range=0.1,  
            # 隨機縮放的幅度,若爲浮點數,則相當於[lower,upper] = [1 - zoom_range, 1+zoom_range]
            zoom_range=0.1,
            # 布爾值,進行隨機水平翻轉
            horizontal_flip=True,
            # 對圖片的每個像素值均乘上這個放縮因子,把像素值放縮到0和1之間有利於模型的收斂
            vertical_flip=True,
            # 在 0 和 1 之間浮動。用作驗證集的訓練數據的比例
            rescale=1. / 225,
            # 布爾值,進行隨機豎直翻轉
            validation_split=0.1)

    # 生成測試集
    validation_data = ImageDataGenerator(
            rescale=1. / 255,
            validation_split=0.1)

    # 以文件夾路徑爲參數,生成經過歸一化後的數據,在一個無限循環中無限產生batch數據
    train_generator = train_data.flow_from_directory(
            # 提供的路徑下面需要有子目錄
            data_path, 
            # 整數元組 (height, width),默認:(256, 256)。 所有的圖像將被調整到的尺寸。
            target_size=(150, 150),
            # 一批數據的大小
            batch_size=16,
            # "categorical", "binary", "sparse", "input" 或 None 之一。
            # 默認:"categorical",返回one-hot 編碼標籤。
            class_mode='categorical',
            # 數據子集 ("training" 或 "validation")
            subset='training', 
            seed=0)
    validation_generator = validation_data.flow_from_directory(
            data_path,
            target_size=(150, 150),
            batch_size=16,
            class_mode='categorical',
            subset='validation',
            seed=0)

    return train_generator, validation_generator

def model(train_generator, validation_generator, save_model_path, epochs):
    start = time.time()

    '''
    使用keras的已封裝的vgg16建立一個模型,構建的模型會將VGG16頂層(全連接層)
去掉只保留其餘的網絡結構(去掉全連接層,前面都是卷積層)
    weights='imagenet' 使用ImageNet上預訓練的模型,用ImageNet的參數初始化模型的參數
    include_top = False 是否包含最上層的全連接層 表明遷移除頂層以外的其餘網絡結構到自己的模型中
    input_shape 輸入進來的數據是150*150 3通道
    通過.add()方法一個個的將layer加入模型中
    '''
    vgg16_model = VGG16(weights='imagenet', include_top=False, input_shape=(150,150,3))
    top_model = Sequential()
    # Flatten層用來將輸入“壓平”,即把多維的輸入一維化,常用在從卷積層到全連接層的過渡
    top_model.add(Flatten(input_shape=vgg16_model.output_shape[1:]))
    # dense 全連接 activation: 激活函數
    top_model.add(Dense(256, activation='relu'))
    top_model.add(Dropout(0.5))
    top_model.add(Dense(6, activation='softmax'))

    model = Sequential()
    model.add(vgg16_model)
    model.add(top_model)

    # 配置模型學習過程
    model.compile(
             # 優化器, 主要有Adam、sgd、rmsprop等方式
            # SGD 每次更新時對每個樣本進行梯度更新, 一次只進行一次更新,就沒有冗餘,而且比較快,並且可以新增樣本
            # lr: float >= 0. 學習率, momentum: float >= 0. 參數,用於加速 SGD 在相關方向上前進,並抑制震盪
            optimizer=SGD(lr=1e-3, momentum=0.9),
            # 損失函數,多分類採用 categorical_crossentropy(亦稱作多類的對數損失)
            loss='categorical_crossentropy',
            # 評價函數 訓練和測試期間的模型評估標準 除了損失函數值之外的特定指標, 分類問題一般都是準確率
            metrics=['accuracy'])

    # 訓練
    # 返回一個history 記錄了訓練過程 fit_generator分批次讀取 逐個生成數據的batch並進行訓練
    model.fit_generator(
            # generator:生成器函數 一個生成器或 Sequence 對象的實例
            generator=train_generator,
            # epochs: 整數,數據的迭代總輪數。
            epochs=epochs,
            # 每輪步數 一個epoch包含的步數,通常應該等於你的數據集的樣本數量除以批量大小。
            steps_per_epoch=2259 // 16,
            # 驗證集
            validation_data=validation_generator,
             # 在驗證集上,一個epoch包含的步數,通常應該等於你的數據集的樣本數量除以批量大小。
            validation_steps=248 // 16,
            )
    model.save(save_model_path)
    end = time.time()
    print("訓練完畢,模型已保存!總耗時:%d 秒" % (end - start))


    return model

epochs = 20
# 數據集路徑
data_path = 'dataset/'
# 保存模型路徑和名稱
save_model_path = 'results/model_20.h5'
# 獲取數據
train_generator, validation_generator = processing_data(data_path)
# 創建、訓練和保存模型
model(train_generator, validation_generator, save_model_path, epochs)

model_evaluate.py 模型評估

# coding=utf-8
from keras.models import load_model
from model_cret import processing_data
import matplotlib.pyplot as plt

# 評估模型
def evaluate_mode(validation_generator):
    # 加載模型
    model = load_model('results/model_20.h5')

    history = model.fit_generator(
        # 一個生成器或 Sequence 對象的實例
        generator=train_generator,
        # epochs: 整數,數據的迭代總輪數。
        epochs=20,
        # 一個epoch包含的步數,通常應該等於你的數據集的樣本數量除以批量大小。
        steps_per_epoch=2259 // 16,
        # 驗證集
        validation_data=validation_generator,
        # 在驗證集上,一個epoch包含的步數,通常應該等於你的數據集的樣本數量除以批量大小。
        validation_steps=248 // 16,
    )

    plt.figure()
    plt.plot(history.history['acc'])
    plt.plot(history.history['val_acc'])
    plt.title('model accuracy')
    plt.ylabel('accuracy')
    plt.xlabel('epoch')
    plt.legend(['train', 'test'], loc='upper left')
    plt.savefig('model_accuracy.png')

    plt.figure()
    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.title('model loss')
    plt.ylabel('loss')
    plt.xlabel('epoch')
    plt.legend(['train', 'test'], loc='upper left')
    plt.savefig('model_loss.png')
    plt.show()

    # 獲取驗證集的 loss 和 accuracy
    loss, accuracy = model.evaluate_generator(validation_generator)
    print("模型評估:")
    print("Loss: %.2f, Accuracy: %.2f%%" % (loss, accuracy * 100))

    print(model.summary())

# 數據集路徑
data_path = 'dataset/'
train_generator, validation_generator = processing_data(data_path)
evaluate_mode(validation_generator)

model_pred.py 預測

# coding=utf-8
from keras.preprocessing import image
from keras.models import load_model
import numpy as np
import cv2

"""
    加載模型和模型預測
    主要步驟:
        1.加載模型
        2.圖片處理
        3.用加載的模型預測圖片的類別
    :param img: PIL.Image 對象
    :return: string, 模型識別圖片的類別, 
            共 'cardboard','glass','metal','paper','plastic','trash' 6 個類別
"""
def predict(img_path):
    # 把圖片轉換成爲numpy數組
    img = image.load_img(img_path, target_size=(150, 150))
    img = image.img_to_array(img)

    # 加載模型,加載請注意 model_path 是相對路徑, 與當前文件同級。
    # 如果你的模型是在 results 文件夾下的 dnn.h5 模型,則 model_path = 'results/dnn.h5'
    model_path = 'results/model_20.h5'
    # 加載模型
    model = load_model(model_path)

    # expand_dims的作用是把img.shape轉換成(1, img.shape[0], img.shape[1], img.shape[2])
    # 給函數增加維度
    x = np.expand_dims(img, axis=0)
    # 模型預測
    y = model.predict(x)

    # 獲取labels
    labels = {0: 'cardboard', 1: 'glass', 2: 'metal', 3: 'paper', 4: 'plastic', 5: 'trash'}
    predict = labels[np.argmax(y)]

    # 返回圖片的類別
    return predict

img_path = 'testImg/cardboard2.jpg'
frame = cv2.imread(img_path)
# 定義字體
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(frame, predict(img_path), (10, 140), font, 3, (255, 0, 0), 2, cv2.LINE_AA)
cv2.imwrite('results/pred.png', frame)
cv2.imshow('img', frame)
cv2.waitKey(0)
cv2.destroyAllWindows()

model_ui.py 可視化界面

# coding=utf-8
import tkinter as tk
from tkinter import *
from tkinter import ttk
from PIL import Image, ImageTk

# 創建窗口 設定大小並命名
window = tk.Tk()
window.title('垃圾分類')
window.geometry('1050x500')
global img_png           # 定義全局變量 圖像的
var = tk.StringVar()    # 這時文字變量儲存器

mainframe = ttk.Frame(window, padding="5 4 12 12")
mainframe.grid(column=0, row=0, sticky=(N, W, E, S))
mainframe.columnconfigure(0, weight=1)
mainframe.rowconfigure(0, weight=1)

def openImg():
    global img_png
    var.set('已打開')
    Img = Image.open('testImg/cardboard2.jpg')
    img_png = ImageTk.PhotoImage(Img)
    label_Img2 = tk.Label(image=img_png).grid(column=2, row=2, sticky=W)

num = 1
def change():       #更新圖片操作
    global num
    var.set('已預測')
    num=num+1
    if num%3==0:
        url1="results/pred.png"
        pil_image = Image.open(url1)
        img= ImageTk.PhotoImage(pil_image)
        label_img.configure(image = img)
    window.update_idletasks()   #更新圖片,必須update

# row = 1
# epochs = StringVar()
# ttk.Label(mainframe, text="epochs:").grid(column=1, row=1, sticky=W)
# addr_entry = ttk.Entry(mainframe, width=7, textvariable=epochs)
# addr_entry.grid(column=2, row=1, sticky=(W, E))
#
# ttk.Button(mainframe, text="Train").grid(column=4, row=1, sticky=W)

# row = 2
ttk.Button(mainframe, text="打開", command=openImg).grid(column=1, row=2, sticky=W)
ttk.Button(mainframe, text="預測", command=change).grid(column=3, row=2, sticky=W)

# row = 3 創建文本窗口,顯示當前操作狀態
ttk.Label(mainframe, text="狀態").grid(column=1, row=3, sticky=W)
ttk.Label(mainframe, textvariable=var).grid(column=3, row=3, sticky=W)

url = "testImg/logo.jpg"
pil_image = Image.open(url)
img= ImageTk.PhotoImage(pil_image)
label_img = ttk.Label(window, image = img ,compound=CENTER)
label_img.grid(column=0, row=2, sticky=W)

# 運行整體窗口
window.mainloop()

4.3 實驗結果

本實驗的GUI設計採用TK實現,主界面包含選擇要預測的圖片和預測兩個按鈕,具體頁面設計如下:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章