SSD目標檢測(3):使用自己的資料集做預測(詳細說明附原始碼)
前言:上兩章已經詳細介紹了SSD目標檢測(1):圖片+視訊版物體定位(附原始碼),SSD目標檢測(2):如何製作自己的資料集(詳細說明附原始碼)。由於SSD框架是開源的程式碼,自然有很多前輩研究後做了改進。我也不過是站在前輩的肩膀上才能完成這篇部落格,在這裡表示感謝。
這一章就是講解如何使用自己的資料集,讓SSD框架識別。原始碼也無償奉上了哦!
–—-—-—-—-—-—-—-—-—-—-—-—–—-—-—-—-—-—-—-——-—-—-—-—-—-—-—-—-—-—-—-—-—-——-
–—-—-—-—-—-—-—-—-—-—-—-—–—-—-—-—-—-—-—-——-—-—-—-—-—-—-
行文說明:
要用SSD訓練自己的資料集,首先要知道怎樣製作自己的資料集,上一章已經有詳細的介紹。有了準備好了的資料集後,再怎樣調整程式碼可以使SSD框架執行得到結果,這一章都有詳細介紹。所以行文大致分如下三個部分:
在本文中,筆者的作業系統是win10,用的IDE是PyCharm + python3.5,在此情況下都是執行完好。Linux系統下筆者也成功執行,只是小部分實現的方式不相同,這裡不做詳細講解,還望包涵。
–—-—-—-—-—-—-—-—-—-—-—-—–—-—-—-—-—-—-—-——-—-—-—-—-—-—-—-—-—-—-—-—-
–—-—-—-—-—-—-—-—-—-—-—-—–—-—-—-—-—-—-—-——-—-—-—-—-—-—-—-—-—-—-—-—-—-——-
一、SSD框架細節調整說明
1、前期準備工作
第一步:先將SSD框架下載到本地,解壓出來;SSD原始碼下載
第二步:在解壓出來的主目錄下依次建立tfrecords_
、train_model
、VOC2007
資料夾,再將之前自己製作資料集儲存下來的三個資料夾Annotations
、ImageSets
、JPEGImages
全都拖入VOC2007
資料夾內;
第2.5步:為方便操作不易混淆,請在PyCharm裡建立工程;得到的截圖如下,截圖說明如下:
- 請注意紅色框
VOCxxx
使用的是具體的名字,不過一般都是VOC2007
; - 目錄對應的從屬關係不要出錯
tfrecords_
資料夾是用來儲存.tfrecords檔案(後面有程式可以直接生成)train_model
資料夾是用來儲存模型的記錄與引數的
2、生成.tfrecords檔案的程式碼微調說明
第三步:修改標籤項——開啟datasets
資料夾中pascalvoc_common.py
檔案,將自己的標籤項填入,我之前做的圖片標籤.xml
檔案中,就只有一個標籤項,根據實際書寫;
第四步:修改讀取個數、讀取方式——開啟datasets
資料夾中的pascalvoc_to_tfrecords.py
檔案,
修改67行SAMPLES_PER_FILES
的個數;
修改83行讀取方式為'rb'
;
如果你的檔案不是.jpg
格式,也可以修改圖片的型別;
3、生成.tfrecords檔案
第五步:生成.tfrecords檔案——開啟tf_convert_data.py
檔案,依次點選:run
、Edit Configuration
,在Parameters
中填入以下內容,再執行tf_convert_data.py
檔案,在面板中得到成功資訊,可以在tfrecords_
資料夾下看到生成的.tfrecords
檔案;
--dataset_name=pascalvoc
--dataset_dir=./VOC2007/
--output_name=voc_2007_train
--output_dir=./tfrecords_
4、重新訓練模型的程式碼微調說明
第六步:修改訓練資料shape——開啟datasets
資料夾中的pascalvoc_2007.py
檔案,
根據自己訓練資料修改:NUM_CLASSES
= 類別數;
第七步:修改類別個數——開啟nets
資料夾中的ssd_vgg_300.py
檔案,
根據自己訓練類別數修改96 和97行:等於類別數+1;
第八步:修改類別個數——開啟eval_ssd_network.py
檔案,
修改66行的類別個數:等於類別數+1;
第九步:修改訓練步數epoch——開啟train_ssd_network.py
檔案,
修改27行的資料格式,改為'NHWC'
;
修改135行的類別個數:等於類別數+1;
修改154行訓練步數,None
會無限訓練下去;
說明:60行、63行是關於模型儲存的引數;
5、載入vgg_16,重新訓練模型
第十步:下載vgg_16模型——下載地址請點選,密碼:ge3x
下載完成解壓後存入checkpoint
檔案中;
最後一步:重新訓練模型——開啟train_ssd_network.py
檔案,依次點選:run
、Edit Configuration
,在Parameters
中填入以下內容,再執行train_ssd_network.py
檔案
--train_dir=./train_model/
--dataset_dir=./tfrecords_/
--dataset_name=pascalvoc_2007
--dataset_split_name=train
--model_name=ssd_300_vgg
--checkpoint_path=./checkpoints/vgg_16.ckpt
--checkpoint_model_scope=vgg_16
--checkpoint_exclude_scopes=ssd_300_vgg/conv6,ssd_300_vgg/conv7,ssd_300_vgg/block8,ssd_300_vgg/block9,ssd_300_vgg/block10,ssd_300_vgg/block11,ssd_300_vgg/block4_box,ssd_300_vgg/block7_box,ssd_300_vgg/block8_box,ssd_300_vgg/block9_box,ssd_300_vgg/block10_box,ssd_300_vgg/block11_box
--trainable_scopes=ssd_300_vgg/conv6,ssd_300_vgg/conv7,ssd_300_vgg/block8,ssd_300_vgg/block9,ssd_300_vgg/block10,ssd_300_vgg/block11,ssd_300_vgg/block4_box,ssd_300_vgg/block7_box,ssd_300_vgg/block8_box,ssd_300_vgg/block9_box,ssd_300_vgg/block10_box,ssd_300_vgg/block11_box
--save_summaries_secs=60
--save_interval_secs=600
--weight_decay=0.0005
--optimizer=adam
--learning_rate=0.001
--learning_rate_decay_factor=0.94
--batch_size=24
--gpu_memory_fraction=0.9
若得到下圖日誌,即說明模型開始訓練;
訓練結束可以在train_model
資料夾下看到生成的引數檔案;
到這裡,訓練終於結束了!!!
–—-—-—-—-—-—-—-—-—-—-—-—–—-—-—-—-—-—-—-——-—-—-—-—-—-—-—-—-—-—-—-—-—-——-
–—-—-—-—-—-—-—-—-—-—-—-—–—-—-—-—-—-—-—-——-—-—-—-—-—-—-—-—-—-—-—-—-—-——-
二、結果展示
1、首先在日誌中,選取loss
最小的模型作為測試模型進行測試;
2、在demo
資料夾下放入測試圖片;
3、最後在notebooks
資料夾下建立demo_test.py
測試檔案,程式碼如下:
4、注意第48行,匯入的新模型的名稱是否正確;
# -*- coding:utf-8 -*-
# -*- author:zzZ_CMing CSDN address:https://blog.csdn.net/zzZ_CMing
# -*- 2018/07/20; 15:19
# -*- python3.5
import os
import math
import random
import numpy as np
import tensorflow as tf
import cv2
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
from nets import ssd_vgg_300, ssd_common, np_methods
from preprocessing import ssd_vgg_preprocessing
from notebooks import visualization
import sys
sys.path.append('../')
slim = tf.contrib.slim
# TensorFlow session: grow memory when needed. TF, DO NOT USE ALL MY GPU MEMORY!!!
gpu_options = tf.GPUOptions(allow_growth=True)
config = tf.ConfigProto(log_device_placement=False, gpu_options=gpu_options)
isess = tf.InteractiveSession(config=config)
# 定義資料格式,設定佔位符
net_shape = (300, 300)
# 輸入影象的通道排列形式,'NHWC'表示 [batch_size,height,width,channel]
data_format = 'NHWC'
# 預處理,以Tensorflow backend, 將輸入圖片大小改成 300x300,作為下一步輸入
img_input = tf.placeholder(tf.uint8, shape=(None, None, 3))
# 資料預處理,將img_input輸入的影象resize為300大小,labels_pre,bboxes_pre,bbox_img待解析
image_pre, labels_pre, bboxes_pre, bbox_img = ssd_vgg_preprocessing.preprocess_for_eval(
img_input, None, None, net_shape, data_format, resize=ssd_vgg_preprocessing.Resize.WARP_RESIZE)
# 拓展為4維變數用於輸入
image_4d = tf.expand_dims(image_pre, 0)
# 定義SSD模型
# 是否複用,目前我們沒有在訓練所以為None
reuse = True if 'ssd_net' in locals() else None
# 調出基於VGG神經網路的SSD模型物件,注意這是一個自定義類物件
ssd_net = ssd_vgg_300.SSDNet()
# 得到預測類和預測座標的Tensor物件,這兩個就是神經網路模型的計算流程
with slim.arg_scope(ssd_net.arg_scope(data_format=data_format)):
predictions, localisations, _, _ = ssd_net.net(image_4d, is_training=False, reuse=reuse)
# 匯入新訓練的模型引數
ckpt_filename = '../train_model/model.ckpt-xxx' # 注意xxx代表的數字是否和資料夾下的一致
# ckpt_filename = '../checkpoints/VGG_VOC0712_SSD_300x300_ft_iter_120000.ckpt'
isess.run(tf.global_variables_initializer())
saver = tf.train.Saver()
saver.restore(isess, ckpt_filename)
# 在網路模型結構中,提取搜尋網格的位置
# 根據模型超引數,得到每個特徵層(這裡用了6個特徵層,分別是4,7,8,9,10,11)的anchors_boxes
ssd_anchors = ssd_net.anchors(net_shape)
"""
每層的anchors_boxes包含4個arrayList,前兩個List分別是該特徵層下x,y座標軸對於原圖(300x300)大小的對映
第三,四個List為anchor_box的長度和寬度,同樣是經過歸一化對映的,根據每個特徵層box數量的不同,這兩個List元素
個數會變化。其中,長寬的值根據超引數anchor_sizes和anchor_ratios制定。
"""
# 主流程函式
def process_image(img, select_threshold=0.5, nms_threshold=.01, net_shape=(300, 300)):
# select_threshold:box閾值——每個畫素的box分類預測資料的得分會與box閾值比較,高於一個box閾值則認為這個box成功框到了一個物件
# nms_threshold:重合度閾值——同一物件的兩個框的重合度高於該閾值,則執行下面去重函式
# 執行SSD模型,得到4維輸入變數,分類預測,座標預測,rbbox_img引數為最大檢測範圍,本文固定為[0,0,1,1]即全圖
rimg, rpredictions, rlocalisations, rbbox_img = isess.run([image_4d, predictions, localisations, bbox_img],
feed_dict={img_input: img})
# ssd_bboxes_select()函式根據每個特徵層的分類預測分數,歸一化後的對映座標,
# ancohor_box的大小,通過設定一個閾值計算得到每個特徵層檢測到的物件以及其分類和座標
rclasses, rscores, rbboxes = np_methods.ssd_bboxes_select(
rpredictions, rlocalisations, ssd_anchors,
select_threshold=select_threshold, img_shape=net_shape, num_classes=21, decode=True)
"""
這個函式做的事情比較多,這裡說的細緻一些:
首先是輸入,輸入的資料為每個特徵層(一共6個,見上文)的:
rpredictions: 分類預測資料,
rlocalisations: 座標預測資料,
ssd_anchors: anchors_box資料
其中:
分類預測資料為當前特徵層中每個畫素的每個box的分類預測
座標預測資料為當前特徵層中每個畫素的每個box的座標預測
anchors_box資料為當前特徵層中每個畫素的每個box的修正資料
函式根據座標預測資料和anchors_box資料,計算得到每個畫素的每個box的中心和長寬,這個中心座標和長寬會根據一個演算法進行些許的修正,
從而得到一個更加準確的box座標;修正的演算法會在後文中詳細解釋,如果只是為了理解演算法流程也可以不必深究這個,因為這個修正演算法屬於經驗算
法,並沒有太多邏輯可循。
修正完box和中心後,函式會計算每個畫素的每個box的分類預測資料的得分,當這個分數高於一個閾值(這裡是0.5)則認為這個box成功
框到了一個物件,然後將這個box的座標資料,所屬分類和分類得分匯出,從而得到:
rclasses:所屬分類
rscores:分類得分
rbboxes:座標
最後要注意的是,同一個目標可能會在不同的特徵層都被檢測到,並且他們的box座標會有些許不同,這裡並沒有去掉重複的目標,而是在下文
中專門用了一個函式來去重
"""
# 檢測有沒有超出檢測邊緣
rbboxes = np_methods.bboxes_clip(rbbox_img, rbboxes)
rclasses, rscores, rbboxes = np_methods.bboxes_sort(rclasses, rscores, rbboxes, top_k=400)
# 去重,將重複檢測到的目標去掉
rclasses, rscores, rbboxes = np_methods.bboxes_nms(rclasses, rscores, rbboxes, nms_threshold=nms_threshold)
# 將box的座標重新對映到原圖上(上文所有的座標都進行了歸一化,所以要逆操作一次)
rbboxes = np_methods.bboxes_resize(rbbox_img, rbboxes)
return rclasses, rscores, rbboxes
# 測試的資料夾
path = '../demo/'
image_names = sorted(os.listdir(path))
# 資料夾中的第幾張圖,-1代表最後一張
img = mpimg.imread(path + image_names[-1])
rclasses, rscores, rbboxes = process_image(img)
# visualization.bboxes_draw_on_img(img, rclasses, rscores, rbboxes, visualization.colors_plasma)
visualization.plt_bboxes(img, rclasses, rscores, rbboxes)
結果展示:以網上艾艾貼為例,得到的識別效果還算勉強吧,我的資料集總共就30張圖片,一共做了750輪訓練