yolov3+tensorflow+keras實現吸煙的訓練全流程及識別檢測


yolov3+tensorflow+keras實現吸煙的訓練全流程及識別檢測

弈休丶 2019-12-30 23:29:54 1591 收藏 19
分類專欄: 基於yolov3+tensorflow+keras實現吸煙的訓練全流程
版權
一.前言
近期,在研究人工智能機器視覺領域,拜讀了深度學習相關資料,在練手期間比較了各前沿的網絡架構,個人認為基於darknet53網絡結構的yolov3以及retinanet的faster rcnn最合適深度學習工程落地的技術選型。以下是整理的對yolov3的認知解析,同時有個基於人員吸煙檢測識別的小工程練手,以望溝通學習交流。后期會繼續更新對faster rcnn的認知解析。

二.yolov3理解
you only look once.采用的是多尺度預測,類似FPN;更好的基礎分類網絡(類ResNet)和分類器。yolov3使用邏輯回歸預測每個邊界框(bounding box)的對象分數。如果先前的邊界框比之前的任何其他邊界框重疊ground truth對象,則該值應該為1。如果以前的邊界框不是最好的,但是確實將ground truth對象重疊了一定的閾值以上,我們會忽略這個預測。yolov3只為給每個groun truth對象分配一個邊界框,如果之前的邊界框未分配給grounding box對象,則不會對坐標或類別預測造成損失。yolo3在訓練過程中,使用二元交叉熵損失來進行類別預測。yolo3創新地使用了金字塔網絡,是端到端,輸入圖像,一次性輸出每個柵格預測的一種或多種物體。每個格子可以預測B個bounding box,但是最終只選擇IOU最高的bounding box作為物體檢測輸出,即每個格子最多只預測出一個問題。當物體占畫面比例較小,如圖像中包含牲畜或鳥群時,每個格子包含多個物體,但只能檢測出其中一個。

1. 分類器:
YOLOv3不使用Softmax對每一個框進行分類,而使用多個logistic分類器,因為Softmax不適用於多標簽分類,用獨立的多個logistic分類器准確率也不會下降。分類損失采用binary cross-entropy loss.

2. 多尺度預測
每種尺度預測3個box,anchor的設計方式仍然適用聚類,得到9個聚類中心,將其按照大小均分給3種尺度。尺度1:在基礎網絡之后添加一些卷積層再輸出box信息;尺度2:在尺度1中的倒數第二層卷積層上采樣(×2)再與最后一個16×16大小的特征圖相加,再次通過多個卷積后輸出box信息。相比尺度1變大兩倍;尺度3:與尺度2類似,使用了32×32大小的特征圖。

3.基礎網絡
基礎網絡采用Darknet-53(53個卷積層),仿RestNet,與ResNet-10或ResNet-152正確率接近,采用2562563作為輸入。基礎網絡如下:

 

4.YOLO3算法的基本思想
首先通過特征提取網絡對輸入圖像提取特征,得到一定大小的特征圖,比如13×13(相當於416416圖片大小 ),然后將輸入圖像分成13 ×13個grid cells,接着如果GT中某個目標的中心坐標落在哪個grid cell中,那么就由該grid cell來預測該目標。每個grid cell都會預測3固定數量的邊界框(YOLO v1中是2個,YOLO v2中是5個,YOLO v3中是3個,這幾個邊界框的初始大小是不同的)
預測得到的輸出特征圖有兩個維度是提取到的特征的維度,比如13 × 13,還有一個維度(深度)是 B ×(5+C),注:YOLO v1中是(B×5+C),其中B表示每個grid cell預測的邊界框的數量(比如YOLO v1中是2個,YOLO v2中是5個,YOLO v3中是3個); C表示邊界框的類別數(沒有背景類,所以對於VOC數據集是20),5表示4個坐標信息和一個目標性得分(objectness score)
5.類別預測
大多數分類器假設輸出標簽是互斥的。 如果輸出是互斥的目標類別,則確實如此。 因此,YOLO應用softmax函數將得分轉換為總和為1的概率。 而YOLOv3使用多標簽分類。 例如,輸出標簽可以是“行人”和“兒童”,它們不是非排他性的。 (現在輸出的總和可以大於1)
YOLOv3用多個獨立的邏輯(logistic)分類器替換softmax函數,以計算輸入屬於特定標簽的可能性。在計算分類損失時,YOLOv3對每個標簽使用二元交叉熵損失。 這也避免使用softmax函數而降低了計算復雜度。
三.yolov3對比情況
算法很快,因為我們把目標檢測問題看做一個回歸問題,在測試時,我們在整個圖片上運行我們的神經網絡來進行目標檢測
在檢測過程中,因為是在整個圖片上運行網絡,和滑動窗口方法和區域提議方法不同,這些方法都是以圖片的局部作為輸入,即已經划分好的可能存在目標的區域;而yolo則是近以整張圖片作為輸入,此yolo在檢測物體時能很好的利用上下文信息,從而不容易在背景上預測出錯誤的物體信息。
yolo可以學到物體的泛化特征,當yolo在自然圖像上做訓練,在藝術品上做測試時,YOLO表現的性能比DPM、R-CNN等物體檢測系統要好,因為yolo可以學習到高度泛化的特征,從而遷移到其他領域。
在輸入 320 × 320 的圖片后,YOLOv3 能在 22 毫秒內完成處理,並取得 28.2 mAP 的檢測精准度,它的精准度和 SSD 相當,但是速度要快 3 倍。當我們用老的 .5 IOU mAP 檢測指標時,YOLOv3 的精准度也是相當好的。在 Titan X 環境下,YOLOv3 在 51 毫秒內實現了 57.9 AP50 的精准度,和 RetinaNet 在 198 毫秒內的 57.5 AP50 相當,但是 YOLOv3 速度要快 3.8 倍。
區域建議方法將分類器限制在特定區域。YOLO在預測邊界框時訪問整個圖像,YOLO在背景區域顯示的假陽性更少。
YOLO每個網格單元檢測一個對象。它加強了預測的空間多樣性。
Darknet53新網絡比Darknet-19功能強大,也比ResNet-101或ResNet-152更有效,以下是一些ImageNet結果

就COCO的mAP指標而言,yolo3與ssd及faster rcnn比較如下


yolo3與Faster R-CNN、ResNet、SSD等訓練算法比較,yolo3的速度與精確度遠遠大於他們,具體如下:

四.練手工程
1. 基本環境、工具准備:
win10(x64)
顯卡gtx1060
python3.6
cuda_9.0.176_win10
cudnn-9.0-windows10-x64-v7
tensorflow-gpu1.7.0
pycharm
labelImg
voc2007數據集
2. 數據集說明:
PASCAL VOC challenge: voc挑戰在2005年至2012年間展開,該數據集中有20個分類,該數據集包含11530張用於訓練和驗證的圖像,以下是數據集中20個分類:人、鳥、貓、牛、狗、馬、羊、飛機、自行車、船、巴士、汽車、摩托車、火車、瓶、椅子、餐桌、盆栽植物、沙發、電視/監視器,平均每個圖像有2.4個目標。下載鏈接:http://host.robots.ox.ac.uk/pascal/VOC/voc2012/
ImageNet數據集:ImageNet擁有分類、定位和檢測任務評估的數據。與分類數據相似,定位任務有1000個類別。正確率是根據Top5檢測結果計算出來的,對200個檢測問題有470000個圖像,平均每個圖像有1.1個目標。下載鏈接:http://image-net.org/download-images
COCO:MS coco的全稱是Microsoft Common Objects in Context,起源於微軟與2014年出資標注的Microsoft COCO數據集,與ImageNet競賽一樣,被視為是計算機視覺領域最受關注和權威的比賽之一。在ImageNet競賽停辦后,COCO競賽就成為是當前目標識別、檢測等領域的一個最權威、最重要的標桿,也是目前該領域在國際上唯一能匯集Google、微軟、Facebook以及國內外眾多頂尖院校和優秀創新企業共同參與的大賽。COO數據集包含20萬個圖像,80個類別中有超過50萬個目標標注,他是廣泛公開的目標檢測數據庫,平均每個圖像的目標數為7.2下載鏈接:http://cocodataset.org/ 類別包含:person,bicycle,car,motorbike,aeroplane,bus,train,truck,boat,traffic light,fire hydrant,stop sign,parking meter,bench,bird,cat,dog,horse,sheep,cow,elephant,bear,zebra,giraffe,backpack,umbrella,handbag,tie,suitcase,frisbee,skis,snowboard,sports ball,kite,baseball bat,baseball glove,skateboard,surfboard,tennis racket,bottle,wine glass,cup,fork,knife,spoon,bowl,banana,apple,sandwich,orange,broccoli,carrot,hot dog,pizza,donut,cake,chair,sofa,pottedplant,bed,diningtable,toilet,tvmonitor,laptop,mouse,remote,keyboard,cell phone,microwave,oven,toaster,sink,refrigerator,book,clock,vase,scissors,teddy bear,hair drier,toothbrush
本文練手工具是基於VOC2007格式,通過爬蟲工具建立的自己的數據集
3. 訓練前准備:
建立VOC2007數據集文件夾(ImageSets下還需建立Main文件集),如下:
import os
from config import cfg

def mkdir(path):

# 去除首位空格
path = path.strip()
# 去除尾部 \ 符號
path = path.rstrip("\\")

# 判斷路徑是否存在
# 存在 True
# 不存在 False
isExists = os.path.exists(path)

# 判斷結果
if not isExists:
# 如果不存在則創建目錄
# 創建目錄操作函數
os.makedirs(path)

print(path + ' 創建成功')
return True
else:
# 如果目錄存在則不創建,並提示目錄已存在
print(path + ' 目錄已存在')
return False

if __name__=='__main__':
rootPath = cfg.ROOT.PATH
mkdir(os.path.join(rootPath, 'Annotations'))
mkdir(os.path.join(rootPath,'ImageSets'))
mkdir(os.path.join(rootPath,'JPEGImages'))
mkdir(os.path.join(rootPath,'ImageSets/Main'))



把所有的圖片都復制到JPEGImages里面
利用LabelImg生成每張圖片的配置文件

划分訓練集、驗證集、測試集:過去,人們運用機器學習傳統的方法,一般將訓練集和測試集划分為7:3,若有驗證集,則划分為6:2:2這樣划分確實很科學(萬級別以下),但到了大數據時代,數據量徒增為百萬級別,此時我們不需要那么多的驗證集和訓練集。這時只需要拉出來1W條當驗證集,1W條來當測試集,就能很好的工作了,,甚至可以達到99.5:0.3:0.2。(訓練集是用來訓練模型的,通過嘗試不同的方法和思路使用訓練集來訓練不同的模型,再通過驗證集使用交叉驗證來挑選最優的模型,通過不斷的迭代來改善模型在驗證集上的性能,最后再通過測試集來評估模型的性能。如果數據集划分的好,可以提高模型的應用速度。如果划分的不好則會大大影響模型的應用的部署,甚至可能會使得我們之后所做的工作功虧一簣。),這里因為數據集不多,我們采用9:1開展訓練工作
在Imagesets根據標注結果xml生成yolo3所需的train.txt,val.txt,test.txt,trainval.txt 保存至ImageSets/Main
import os
import random
from config import cfg

# # trainval集占整個數據集的百分比,剩下的就是test集所占的百分比
trainval_percent = cfg.ROOT.TRAIN_VAL_PERCENT
# train集占trainval集的百分比, 剩下的就是val集所占的百分比
train_percent = cfg.ROOT.TRAIN_PERCENT
rootPath = cfg.ROOT.PATH
xmlfilepath = os.path.join(rootPath, 'Annotations')
txtsavepath = 'ImageSets'
total_xml = os.listdir(os.path.join(rootPath, xmlfilepath))

# 總數據集個數
num = len(total_xml)
list = range(num)
tv = int(num * trainval_percent)
tr = int(tv * train_percent)
trainval = random.sample(list, tv)
train = random.sample(trainval, tr)

#用來訓練和驗證的圖片文件的文件名列表
ftrainval = open(os.path.join(rootPath, 'ImageSets/Main/trainval.txt'), 'w')
#用來測試的圖片文件的文件名列表
ftest = open(os.path.join(rootPath, 'ImageSets/Main/test.txt'), 'w')
#是用來訓練的圖片文件的文件名列表
ftrain = open(os.path.join(rootPath, 'ImageSets/Main/train.txt'), 'w')
#是用來驗證的圖片文件的文件名列表
fval = open(os.path.join(rootPath, 'ImageSets/Main/val.txt'), 'w')

for i in list:
name = total_xml[i][:-4] + '\n'
if i in trainval:
# 寫到訓練和驗證集
ftrainval.write(name)
if i in train:
# 在訓練集里的寫到測試集里
ftest.write(name)
else:
# 不在訓練集里,寫到驗證集
fval.write(name)
else:
# 寫到訓練集
ftrain.write(name)
ftrainval.close()
ftrain.close()
fval.close()
ftest.close()


修改yolov3源碼的voc_annotation.py文件,執行生產新的三個txt文件:test.txt,train.txt,val.txt
使用k-means聚類算法生成對應自己樣本的anchor box尺寸
修改參數文件yolo3.cfg,找到[yolo]節點,修改以下三個地方,共計三個節點: fiters: 3*(5+len(classes))、classes:len(classes)=1(因為只需要識別煙)、random:默認1,顯卡內存小改為0
新增model_data下的文件,放入你的類別,coco、voc這兩個文件都需要放入,修改類別名稱為smoking
4. 修改代碼,開始訓練。
以下代碼是基於原有權重的.weights文件繼續訓練,需要執行python convert.py -w yolov3.cfg model/yolov3.weights model/yolo_weights.h5 轉成keras可用.h5權重文件,然后在源碼的train.py訓練。若需全新訓練的代碼,可在我的github下載train.py。(batch_size設為8,epoch設為5000,連續運行18個小時左右,loss將為0.01以下即可。)


import numpy as np
import keras.backend as K
from keras.layers import Input, Lambda
from keras.models import Model
from keras.optimizers import Adam
from keras.callbacks import TensorBoard, ModelCheckpoint, ReduceLROnPlateau, EarlyStopping

import os
import sys
sys.path.append('..')
from yolov3.model import preprocess_true_boxes, yolo_body, tiny_yolo_body, yolo_loss
from yolov3.utils import get_random_data
from config import cfg

def _main():
annotation_path = os.path.join(cfg.ROOT.PATH, 'train.txt')
log_dir = os.path.join(cfg.ROOT.PATH, 'logs/000/')
classes_path = os.path.join(cfg.ROOT.PATH, 'Model/voc_classes.txt')
anchors_path = os.path.join(cfg.ROOT.PATH, 'Model/yolo_anchors.txt')
class_names = get_classes(classes_path)
num_classes = len(class_names)
anchors = get_anchors(anchors_path)

input_shape = cfg.ROOT.INPUT_SHAPE # multiple of 32, hw

model = create_model(input_shape, anchors, num_classes,
freeze_body=2, weights_path=os.path.join(cfg.ROOT.PATH, cfg.ROOT.PRE_TRAIN_MODEL)) # make sure you know what you freeze

logging = TensorBoard(log_dir=log_dir)
checkpoint = ModelCheckpoint(log_dir + 'ep{epoch:03d}-loss{loss:.3f}-val_loss{val_loss:.3f}.h5',
monitor='val_loss', save_weights_only=True, save_best_only=True, period=3)
# reduce_lr:當評價指標不在提升時,減少學習率,每次減少10%,當驗證損失值,持續3次未減少時,則終止訓練。
reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.1, patience=3, verbose=1)

# early_stopping:當驗證集損失值,連續增加小於0時,持續10個epoch,則終止訓練。
# monitor:監控數據的類型,支持acc、val_acc、loss、val_loss等;
# min_delta:停止閾值,與mode參數配合,支持增加或下降;
# mode:min是最少,max是最多,auto是自動,與min_delta配合;
# patience:達到閾值之后,能夠容忍的epoch數,避免停止在抖動中;
# verbose:日志的繁雜程度,值越大,輸出的信息越多。
# min_delta和patience需要相互配合,避免模型停止在抖動的過程中。min_delta降低,patience減少;而min_delta增加,
# 則patience增加。
early_stopping = EarlyStopping(monitor='val_loss', min_delta=0, patience=10, verbose=1)
# 訓練和驗證的比例
val_split = 0.1
with open(annotation_path) as f:
lines = f.readlines()
np.random.seed(10101)
np.random.shuffle(lines)
np.random.seed(None)
num_val = int(len(lines)*val_split)
num_train = len(lines) - num_val

'''
把目標當成一個輸入,構成多輸入模型,把loss寫成一個層,作為最后的輸出,搭建模型的時候,
就只需要將模型的output定義為loss,而compile的時候,
直接將loss設置為y_pred(因為模型的輸出就是loss,所以y_pred就是loss),
無視y_true,訓練的時候,y_true隨便扔一個符合形狀的數組進去就行了。
'''
# Train with frozen layers first, to get a stable loss.
# Adjust num epochs to your dataset. This step is enough to obtain a not bad model.
if True:
model.compile(optimizer=Adam(lr=1e-3), loss={
# use custom yolo_loss Lambda layer.
'yolo_loss': lambda y_true, y_pred: y_pred})

batch_size = 16
print('Train on {} samples, val on {} samples, with batch size {}.'.format(num_train, num_val, batch_size))
model.fit_generator(data_generator_wrapper(lines[:num_train], batch_size, input_shape, anchors, num_classes),
steps_per_epoch=max(1, num_train//batch_size),
validation_data=data_generator_wrapper(lines[num_train:], batch_size, input_shape, anchors, num_classes),
validation_steps=max(1, num_val//batch_size),
epochs=50,
initial_epoch=0,
callbacks=[logging, checkpoint])
# 存儲最終的參數,再訓練過程中,通過回調存儲
model.save_weights(log_dir + 'trained_weights_stage_1.h5')

# Unfreeze and continue training, to fine-tune.
# Train longer if the result is not good.
# 全部訓練
if True:
for i in range(len(model.layers)):
model.layers[i].trainable = True
model.compile(optimizer=Adam(lr=1e-4), loss={'yolo_loss': lambda y_true, y_pred: y_pred}) # recompile to apply the change
print('Unfreeze all of the layers.')

batch_size = 32 # note that more GPU memory is required after unfreezing the body
print('Train on {} samples, val on {} samples, with batch size {}.'.format(num_train, num_val, batch_size))
'''
在訓練中,模型調用fit_generator方法,按批次創建數據,輸入模型,進行訓練。其中,數據生成器wrapper是data_generator_
wrapper,用於驗證數據格式,最終調用data_generator
annotation_lines:標注數據的行,每行數據包含圖片路徑,和框的位置信息;
batch_size:批次數,每批生成的數據個數;
input_shape:圖像輸入尺寸,如(416, 416);
anchors:anchor box列表,9個寬高值;
num_classes:類別的數量;
'''
model.fit_generator(data_generator_wrapper(lines[:num_train], batch_size, input_shape, anchors, num_classes),
steps_per_epoch=max(1, num_train//batch_size),
validation_data=data_generator_wrapper(lines[num_train:], batch_size, input_shape, anchors, num_classes),
validation_steps=max(1, num_val//batch_size),
epochs=100,
initial_epoch=50,
callbacks=[logging, checkpoint, reduce_lr, early_stopping])
model.save_weights(os.path.join(cfg.ROOT.PATH, cfg.ROOT.MODEL_RSLT_NAME))

# Further training if needed.


def get_classes(classes_path):
'''loads the classes'''
with open(classes_path) as f:
class_names = f.readlines()
class_names = [c.strip() for c in class_names]
return class_names

def get_anchors(anchors_path):
'''loads the anchors from a file'''
with open(anchors_path) as f:
anchors = f.readline()
anchors = [float(x) for x in anchors.split(',')]
return np.array(anchors).reshape(-1, 2)


def create_model(input_shape, anchors, num_classes, load_pretrained=True, freeze_body=2,
weights_path=os.path.join(cfg.ROOT.PATH, cfg.ROOT.PRE_TRAIN_MODEL)):
'''
create the training model
input_shape:輸入圖片的尺寸,默認是(416, 416);
anchors:默認的9種anchor box,結構是(9, 2);
num_classes:類別個數,在創建網絡時,只需類別數即可。在網絡中,類別值按0~n排列,同時,輸入數據的類別也是用索引表示;
load_pretrained:是否使用預訓練權重。預訓練權重,既可以產生更好的效果,也可以加快模型的訓練速度;
freeze_body:凍結模式,1或2。其中,1是凍結DarkNet53網絡中的層,2是只保留最后3個1x1的卷積層,其余層全部凍結;
weights_path:預訓練權重的讀取路徑;
'''
K.clear_session() # 清除session
h, w = input_shape # 尺寸
image_input = Input(shape=(w, h, 3)) # 圖片輸入格式
num_anchors = len(anchors) # anchor數量

# YOLO的三種尺度,每個尺度的anchor數,類別數+邊框4個+置信度1
y_true = [Input(shape=(h//{0:32, 1:16, 2:8}[l], w//{0:32, 1:16, 2:8}[l], \
num_anchors//3, num_classes+5)) for l in range(3)]

model_body = yolo_body(image_input, num_anchors//3, num_classes)
print('Create YOLOv3 model with {} anchors and {} classes.'.format(num_anchors, num_classes))
# 加載預訓練模型
if load_pretrained:
model_body.load_weights(weights_path, by_name=True, skip_mismatch=True)
print('Load weights {}.'.format(weights_path))
if freeze_body in [1, 2]:
# Freeze darknet53 body or freeze all but 3 output layers.
num = (185, len(model_body.layers)-3)[freeze_body-1]
for i in range(num): model_body.layers[i].trainable = False
print('Freeze the first {} layers of total {} layers.'.format(num, len(model_body.layers)))
# 構建 yolo_loss
# model_body: [(?, 13, 13, 18), (?, 26, 26, 18), (?, 52, 52, 18)]
# y_true: [(?, 13, 13, 18), (?, 26, 26, 18), (?, 52, 52, 18)]
model_loss = Lambda(yolo_loss, output_shape=(1,), name='yolo_loss',
arguments={'anchors': anchors, 'num_classes': num_classes, 'ignore_thresh': 0.5})(
[*model_body.output, *y_true])
model = Model([model_body.input, *y_true], model_loss)

return model

def data_generator(annotation_lines, batch_size, input_shape, anchors, num_classes):
'''
data generator for fit_generator
annotation_lines: 所有的圖片名稱
batch_size:每批圖片的大小
input_shape: 圖片的輸入尺寸
anchors: 大小
num_classes: 類別數
'''
n = len(annotation_lines)
i = 0
while True:
image_data = []
box_data = []
for b in range(batch_size):
if i==0:
# 隨機排列圖片順序
np.random.shuffle(annotation_lines)
# image_data: (16, 416, 416, 3)
# box_data: (16, 20, 5) # 每個圖片最多含有20個框
# 獲取圖片和盒子
image, box = get_random_data(annotation_lines[i], input_shape, random=True)
# 獲取真實的數據根據輸入的尺寸對原始數據進行縮放處理得到input_shape大小的數據圖片,
# 隨機進行圖片的翻轉,標記數據數據也根據比例改變
# 添加圖片
image_data.append(image)
# 添加盒子
box_data.append(box)
i = (i+1) % n
image_data = np.array(image_data)
box_data = np.array(box_data)
# y_true是3個預測特征的列表
y_true = preprocess_true_boxes(box_data, input_shape, anchors, num_classes)
# y_true的第0和1位是中心點xy,范圍是(0~13/26/52),第2和3位是寬高wh,范圍是0~1,
# 第4位是置信度1或0,第5~n位是類別為1其余為0。
# [(16, 13, 13, 3, 6), (16, 26, 26, 3, 6), (16, 52, 52, 3, 6)]
yield [image_data, *y_true], np.zeros(batch_size)

def data_generator_wrapper(annotation_lines, batch_size, input_shape, anchors, num_classes):
"""
用於條件檢查
"""
# 標注圖片的行數
n = len(annotation_lines)
if n==0 or batch_size<=0: return None
return data_generator(annotation_lines, batch_size, input_shape, anchors, num_classes)

if __name__ == '__main__':
_main()

5. 訓練過程可視化分析:
訓練過程可視化查看(命令行直接輸入tensorboard --host=? --port=?–logdir=?,命令行會返回查看地址,在谷歌瀏覽器輸入即可查看)


6. 模型效果測試:
import colorsys
import os
from timeit import default_timer as timer

import numpy as np
from keras import backend as K
from keras.models import load_model
from keras.layers import Input
from PIL import Image, ImageFont, ImageDraw

from yolov3.model import yolo_eval, yolo_body, tiny_yolo_body
from yolov3.utils import letterbox_image
import os
from keras.utils import multi_gpu_model

rootPath = "train/smoking"

class YOLO(object):
_defaults = {
"model_path": os.path.join(rootPath, 'Model/smoking.h5'),
"anchors_path": os.path.join(rootPath, 'Model/yolo_anchors.txt'),
"classes_path": os.path.join(rootPath, 'Model/coco_classes.txt'),
"score" : 0.3,
"iou" : 0.45,
"model_image_size" : (416, 416),
"gpu_num" : 1,
}

@classmethod
def get_defaults(cls, n):
if n in cls._defaults:
return cls._defaults[n]
else:
return "Unrecognized attribute name '" + n + "'"

def __init__(self, **kwargs):
self.__dict__.update(self._defaults) # set up default values
self.__dict__.update(kwargs) # and update with user overrides
self.class_names = self._get_class()
self.anchors = self._get_anchors()
self.sess = K.get_session()
self.boxes, self.scores, self.classes = self.generate()

def _get_class(self):
classes_path = os.path.expanduser(self.classes_path)
with open(classes_path) as f:
class_names = f.readlines()
class_names = [c.strip() for c in class_names]
return class_names

def _get_anchors(self):
anchors_path = os.path.expanduser(self.anchors_path)
with open(anchors_path) as f:
anchors = f.readline()
anchors = [float(x) for x in anchors.split(',')]
return np.array(anchors).reshape(-1, 2)

def generate(self):
model_path = os.path.expanduser(self.model_path)
assert model_path.endswith('.h5'), 'Keras model or weights must be a .h5 file.'

# Load model, or construct model and load weights.
num_anchors = len(self.anchors)
num_classes = len(self.class_names)
is_tiny_version = num_anchors==6 # default setting
try:
self.yolo_model = load_model(model_path, compile=False)
except:
self.yolo_model = tiny_yolo_body(Input(shape=(None,None,3)), num_anchors//2, num_classes) \
if is_tiny_version else yolo_body(Input(shape=(None,None,3)), num_anchors//3, num_classes)
self.yolo_model.load_weights(self.model_path) # make sure model, anchors and classes match
else:
assert self.yolo_model.layers[-1].output_shape[-1] == \
num_anchors/len(self.yolo_model.output) * (num_classes + 5), \
'Mismatch between model and given anchor and class sizes'

print('{} model, anchors, and classes loaded.'.format(model_path))

# Generate colors for drawing bounding boxes.
hsv_tuples = [(x / len(self.class_names), 1., 1.)
for x in range(len(self.class_names))]
self.colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), hsv_tuples))
self.colors = list(
map(lambda x: (int(x[0] * 255), int(x[1] * 255), int(x[2] * 255)),
self.colors))
np.random.seed(10101) # Fixed seed for consistent colors across runs.
np.random.shuffle(self.colors) # Shuffle colors to decorrelate adjacent classes.
np.random.seed(None) # Reset seed to default.

# Generate output tensor targets for filtered bounding boxes.
self.input_image_shape = K.placeholder(shape=(2, ))
if self.gpu_num>=2:
self.yolo_model = multi_gpu_model(self.yolo_model, gpus=self.gpu_num)
boxes, scores, classes = yolo_eval(self.yolo_model.output, self.anchors,
len(self.class_names), self.input_image_shape,
score_threshold=self.score, iou_threshold=self.iou)
return boxes, scores, classes

def detect_image(self, image):
start = timer()

if self.model_image_size != (None, None):
assert self.model_image_size[0]%32 == 0, 'Multiples of 32 required'
assert self.model_image_size[1]%32 == 0, 'Multiples of 32 required'
boxed_image = letterbox_image(image, tuple(reversed(self.model_image_size)))
else:
new_image_size = (image.width - (image.width % 32),
image.height - (image.height % 32))
boxed_image = letterbox_image(image, new_image_size)
image_data = np.array(boxed_image, dtype='float32')

print(image_data.shape)
image_data /= 255.
image_data = np.expand_dims(image_data, 0) # Add batch dimension.

out_boxes, out_scores, out_classes = self.sess.run(
[self.boxes, self.scores, self.classes],
feed_dict={
self.yolo_model.input: image_data,
self.input_image_shape: [image.size[1], image.size[0]],
K.learning_phase(): 0
})

print('Found {} boxes for {}'.format(len(out_boxes), 'img'))

font = ImageFont.truetype(font='../resources/font/FiraMono-Medium.otf',
size=np.floor(3e-2 * image.size[1] + 0.5).astype('int32'))
thickness = (image.size[0] + image.size[1]) // 300

for i, c in reversed(list(enumerate(out_classes))):
predicted_class = self.class_names[c]
box = out_boxes[i]
score = out_scores[i]

label = '{} {:.2f}'.format(predicted_class, score)
draw = ImageDraw.Draw(image)
label_size = draw.textsize(label, font)

top, left, bottom, right = box
top = max(0, np.floor(top + 0.5).astype('int32'))
left = max(0, np.floor(left + 0.5).astype('int32'))
bottom = min(image.size[1], np.floor(bottom + 0.5).astype('int32'))
right = min(image.size[0], np.floor(right + 0.5).astype('int32'))
print(label, (left, top), (right, bottom))

if top - label_size[1] >= 0:
text_origin = np.array([left, top - label_size[1]])
else:
text_origin = np.array([left, top + 1])

# My kingdom for a good redistributable image drawing library.
for i in range(thickness):
draw.rectangle(
[left + i, top + i, right - i, bottom - i],
outline=self.colors[c])
draw.rectangle(
[tuple(text_origin), tuple(text_origin + label_size)],
fill=self.colors[c])
draw.text(text_origin, label, fill=(0, 0, 0), font=font)
del draw

end = timer()
print(end - start)
return image

def close_session(self):
self.sess.close()

def detect_video(yolo, video_path, output_path=""):
import cv2
vid = cv2.VideoCapture(video_path)
if not vid.isOpened():
raise IOError("Couldn't open webcam or video")
video_FourCC = int(vid.get(cv2.CAP_PROP_FOURCC))
video_fps = vid.get(cv2.CAP_PROP_FPS)
video_size = (int(vid.get(cv2.CAP_PROP_FRAME_WIDTH)),
int(vid.get(cv2.CAP_PROP_FRAME_HEIGHT)))
isOutput = True if output_path != "" else False
if isOutput:
print("!!! TYPE:", type(output_path), type(video_FourCC), type(video_fps), type(video_size))
out = cv2.VideoWriter(output_path, video_FourCC, video_fps, video_size)
accum_time = 0
curr_fps = 0
fps = "FPS: ??"
prev_time = timer()
while True:
return_value, frame = vid.read()
image = Image.fromarray(frame)
image = yolo.detect_image(image)
result = np.asarray(image)
curr_time = timer()
exec_time = curr_time - prev_time
prev_time = curr_time
accum_time = accum_time + exec_time
curr_fps = curr_fps + 1
if accum_time > 1:
accum_time = accum_time - 1
fps = "FPS: " + str(curr_fps)
curr_fps = 0
cv2.putText(result, text=fps, org=(3, 15), fontFace=cv2.FONT_HERSHEY_SIMPLEX,
fontScale=0.50, color=(255, 0, 0), thickness=2)
cv2.namedWindow("result", cv2.WINDOW_NORMAL)
cv2.imshow("result", result)
if isOutput:
out.write(result)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
yolo.close_session()

def detect_img(yolo):
#img = input('Input image filename:')
img = 'testImg/7_17.jpg'
try:
image = Image.open(img)
except:
print('Open Error! Try again!')
else:
r_image = yolo.detect_image(image)
r_image.show()
yolo.close_session()

if __name__ == '__main__':
#detect_img(YOLO())
detect_video(YOLO(), 'smoking_detect.mp4')

 


五.如何提高檢測識別效果及效率
在.cfg文件中設置flag random=1, 它將通過不同分辨率來訓練yolo以提高精度
分類識別用不同的識別檢測引擎(如:貓狗分兩個模型)
提高.cfg文件中網絡的分辨率(例如h,w=608,或者任意32的倍數),這樣可以提高精度
確保數據集中每個類都帶有標簽,並且確保標簽正確
優化標注內容(避免漏標、錯標),去除數據集中重復的圖片或者多余的標注文件
豐富數據集(達到10W+)
完善網絡模型結構
優化特征提取、比對算法,優化邊界識別算法
識別圖片預處理:去噪、加強、抖動、雜線干
針對不同應用場景,訓練不同場景應用的模型,以降低識別速度換取提高識別率
訓練過程加大learning_rate、識別過程略調scores、iou
對於要檢測的每一個對象,訓練數據集中必須至少有一個類似的對象,其形狀、對象側面、相對大小、旋轉角度、傾斜、照明等條件大致相同
數據集中應包括對象的不同縮放、旋轉、照明、不同的面、不同的背景的圖像,最好為每個類提供2000個不同的圖像,並且訓練(2000*類的數量)的迭代次數加多
對於目標物體較多的圖像,在.cfg文件中最后一個[yolo]層和[region]層加入max=200參數或者更高的值。(yolov3可以檢測到的對象的全局最大數目是0.0615234375 (widthheight),其中width和height是.cfg文件中[net]部分的參數)
訓練小物體時(圖像調整到416x416后物體小於16x16),將[route]參數替換為layers=-1,11,將[upsample]參數改為stride=4
對於都包含小對象和大對象可以使用以下的修改模型:Full-model: 5 yolo layers: https://raw.githubusercontent.com/AlexeyAB/darknet/master/cfg/yolov3_5l.cfg Tiny-model: 3 yolo layers: https://raw.githubusercontent.com/AlexeyAB/darknet/master/cfg/yolov3-tiny_3l.cfg Spatial-full-model: 3 yolo layers: https://raw.githubusercontent.com/AlexeyAB/darknet/master/cfg/yolov3-spp.cfg
如果要訓練區分左右的對象(例如左手右手,道路上左轉右轉標志等),需要禁用數據翻轉增強,在.cfg文件中17行左右的位置加入flip=0
一般規律——訓練數據集應該包含一組您想要檢測的對象的相對大小。簡單來說,就是需要檢測的對象在圖像中的百分比是多少,那么訓練的圖像中也應該包含這個百分比的對象。例如:如果訓練的時候目標在圖像中都占80-90%,那檢測的時候很可能就檢測不出目標占0-10%情況。
同一個對象在不同的光照條件、側面、尺寸、傾斜或旋轉30度的情況下,對於神經網絡的內部來說,都是不同的對象。因此,如果想檢測更多的不同對象,就應該選擇更復雜的神經網絡。
在.cfg中重新計算錨(anchors)的width和height:在.cfg文件中的3個[yolo]層中的每個層中設置相同的9個錨,但是應該為每個[yolo]層更改錨點masks=的索引,以便[yolo]第一層的錨點大於60x60,第二層大於30x30,第三層剩余。此外,還應在每個[yolo]層之前更改過濾器filters=(classes + 5) * 。如果許多計算出的錨找不到適當的層,那么只需嘗試使用所有的默認錨。這里提高了網絡的分辨率,但是可以不需要重新訓練(即使之前使用416*416分辨率訓練的)。但是為了提高精度,還是建議使用更高的分辨率來重新訓練。注意:如果出現了Out of memory的錯誤,建議提高.cfg文件中的subdivisions=16參數,改成32或者64等
在檢測時,減小 yolo. py 文件參數中的 score 和 iou,如果這時候能出現框了,但是會發現效果不是很好,或者說置信度特別低,說明檢測是成功的,但是訓練做得比較差;如果還是不能出現框,考慮有可能檢測方法錯了(路徑設置等),或者是訓練過程錯誤(類名稱和索引沒配置好等)。
增加業務流程控制,對識別結果進行業務處理,對識別過程進行時序分析、控制。
六.loss收斂情況解析
訓練過程loss異常說明(train loss:使用訓練集的樣本來計算的損失,val loss使用驗證集的樣本來計算的損失, 過擬合:為了得到一致假設而使假設變得更嚴格)

train loss val loss 說明
下降 下降 網絡在不斷學習收斂
下降 不變 網絡過擬合
不變 下降 數據集有問題
不變 不變 此次條件下學習達到瓶頸
上升 上升 網絡結構或設計參數有問題,無法收斂
若一開始loss就無窮變大,可能是訓練集和驗證集的數據不匹配,或者歸一化量綱不對或者參數設置錯誤。
train loss 不斷下降,test loss不斷下降,說明網絡仍在學習。
train loss 不斷下降,test loss趨於不變,說明網絡過擬合。
train loss 趨於不變,test loss不斷下降,說明數據集100%有問題。
train loss 趨於不變,test loss趨於不變,說明學習遇到瓶頸,需要減小學習率或批量數目。
train loss 不斷上升,test loss不斷上升,說明網絡結構設計不當,訓練超參數設置不當,數據集經過清洗等問題。
loss值不下降問題:模型結構和特征工程存在問題;權重初始化方案有問題;正則化過度;選擇合適的激活函數、損失函數;選擇合適的優化器和學習速率;訓練時間不足;模型訓練遇到瓶頸;batch size太大或者太小,數據導入不對,比如維度錯了;數據集未打亂;數據集有問題;未進行歸一化;特征工程中對數據特征的選取有問題。
LOSS持續不降,第一步先減小數據量,比方說只在單張圖片上跑,使用小epochsize,觀察每次LOSS下降情況,此時如果LOSS還是不下降說明網絡沒有學習能力,應該調整模型,一般是先把網絡規模縮小,因為任何一個網絡都會有學習能力,然而此時你的網絡沒有學習能力,則一定是你的模型有地方出錯,而神經網絡又是個黑盒,你只能摘除一部分網絡,以排除“壞的”部分。此時網絡的規模小了,又在一個相對較小的數據集上跑,必然會有個很好的學習能力。此時可以不斷增加網絡部件,來提高學習能力。
從訓練開始就一直震盪或者發散:圖片質量極差,人眼幾乎無法識別其中想要識別的特征,對於網絡來說相當於輸入的一直都是噪音數據,比如通過resize的時候,圖片的長寬比改變特別大,使圖片喪失對應特征,或者tfrecord中圖片大小是(m,n),但是讀取的時候,按照(n,m)讀取,所以loss一直震盪無法收斂;大部分標簽都是對應錯誤的標簽;leaning rate 設置過大。
訓練開始會有所下降,然后出現發散:數據標簽中有錯誤,甚至所有標簽都有一定的錯誤,比如生成的標簽文件格式和讀取標簽時設置的文件格式不一樣,導致讀取的標簽是亂碼;或者為標簽中存在的空格未分配對應的編碼,導致讀取的空格為亂碼(在OCR問題中);learning rate 設置過大。
訓練開始會有所下降,然后出現震盪:loss函數中正則化系數設置有問題,或者loss函數本身有問題。比如,在序列化問題中的label_smoothing設置過大,比如設置為0.9,一般設置為0.1即可(OCR問題中);數據標簽中有錯誤,甚至所有標簽都有一定的錯誤。
在自己訓練新網絡時,可以從0.1開始嘗試,如果loss不下降的意思,那就降低,除以10,用0.01嘗試,一般來說0.01會收斂,不行的話就用0.001. 學習率設置過大,很容易震盪。不過剛剛開始不建議把學習率設置過小,尤其是在訓練的開始階段。在開始階段我們不能把學習率設置的太低否則loss不會收斂。我的做法是逐漸嘗試,從0.1,0.08,0.06,0.05 …逐漸減小直到正常為止。
檢查lable是否有錯,有的時候圖像類別的label設置成1,2,3正確設置應該為0,1,2。
訓練神經網絡的時候,如果不收斂你可以改變一下圖像的大小,很有可能事半功萬倍。
七.下載
yolov3開源代碼:https://github.com/qqwweee/keras-yolo3
本文練手工程GitHub:https://github.com/swliu2016/ai-core/tree/train-core-yolov3
CSDN下載地址:https://download.csdn.net/download/liuwuw/12066793
————————————————
版權聲明:本文為CSDN博主「弈休丶」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/liuwuw/java/article/details/103630996


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM