win10 下的YOLOv3 訓練 wider_face 數據集檢測人臉

一、數據集下載html

(1)wider_face 數據集網址爲 http://shuoyang1213.me/WIDERFACE/index.htmlshell

  

  下載以上幾項文件(這裏推薦 google Drive 百度雲在沒有會員的狀況下,下載太慢)express

(2)將文件解壓到各自獨立的文件夾網絡

  

  

二、數據集簡介app

  WIDER FACE 數據集是一我的臉檢測基準(benchmark)數據集,圖片選取自 WIDER(Web Image Dataset for Event Recognition) 數據集。圖片數 32,203 張,人臉數 393,703 個,在大小(scale)、位置(pose)、遮擋(occlusion)等不一樣形式中,人臉是高度變換的。WIDER FACE 數據集是基於61個事件類別,每一個事件類別,隨機選取訓練40%、驗證10%、測試50%。訓練和測試含有邊框(bounding box)真值(ground truth),而驗證不含。dom

  這裏主要使用訓練集和驗證集,他們對應的標籤文件分別爲 wider_face_split/wider_face_train_bbx_gt.txt  和  wider_face_split/wider_face_val_bbx_gt.txt ide

  在 wider_face_train_bbx_gt.txt  文件中  學習

  數據以下所示:測試

0--Parade/0_Parade_marchingband_1_849.jpg
1
449 330 122 149 0 0 0 0 0 0

  第一行表明圖片路徑ui

  第二行是圖片中目標個數(人臉個數)

  第三行是具體的圖片中人臉標註的相關參數(具體含義能夠在 readme.txt 中看到)

     從左到右的含義分別是 x1, y1, w, h, blur, expression, illumination, invalid, occlusion, pose

  (1)x1, y1, w, h, 分別表明 左下點座標 及寬長

  (2)blur:模糊程度,0——> 清晰 ,1——> 通常模糊 , 2——> 嚴重模糊

  (3)expression: 表情 0——> 正常 , 1——> 誇張

  (4)illumination:光源(應該是曝光程度)0——> 正常 , 1——>極度

  (5)occlusion:遮擋  0——> 沒有遮擋 , 1——> 部分遮擋 , 2——> 嚴重遮擋

  (6)pose: 姿式 0——> 正常姿式 , 1——非正常姿式

  (7)invalid: 無效圖片 0——否, 1——> 是  

三、數據集轉換

  YOLO v3 須要的 標籤格式爲

0 0.498046875 0.292057761732852 0.119140625 0.1075812274368231  #type  x y w h

 從左到右的含義分別爲 目標類型 (這裏只有一種類型,因此都是0 ) 目標框中心點的(x,y)座標  目標框的寬度和高度   (這裏的數據都是單位數據 即 x—— 中心點實際x / 圖片寬度 , y—— 中心點實際y / 圖片高度)

 這裏能夠直接把 wider_face 標籤轉成 yolo 標籤,也能夠先轉成 voc 格式標籤再轉成 yolo 標籤。考慮到官方有將VOC 格式轉成 yolo 格式的代碼 voc_label.py 因而先轉成 VOC 格式 的標註

(1)轉成VOC 格式

# -*- coding: utf-8 -*-

import shutil
import random
import os
import string
from skimage import io

headstr = """\
<annotation>
    <folder>VOC2007</folder>
    <filename>%06d.jpg</filename>
    <source>
        <database>My Database</database>
        <annotation>PASCAL VOC2007</annotation>
        <image>flickr</image>
        <flickrid>NULL</flickrid>
    </source>
    <owner>
        <flickrid>NULL</flickrid>
        <name>company</name>
    </owner>
    <size>
        <width>%d</width>
        <height>%d</height>
        <depth>%d</depth>
    </size>
    <segmented>0</segmented>
"""
objstr = """\
    <object>
        <name>%s</name>
        <pose>Unspecified</pose>
        <truncated>0</truncated>
        <difficult>0</difficult>
        <bndbox>
            <xmin>%d</xmin>
            <ymin>%d</ymin>
            <xmax>%d</xmax>
            <ymax>%d</ymax>
        </bndbox>
    </object>
"""

tailstr = '''\
</annotation>
'''




def writexml(idx, head, bbxes, tail):
    filename = ("Annotations/%06d.xml" % (idx))
    f = open(filename, "w")
    f.write(head)
    for bbx in bbxes:
        f.write(objstr % ('face', bbx[0], bbx[1], bbx[0] + bbx[2], bbx[1] + bbx[3]))
    f.write(tail)
    f.close()


def clear_dir():
    if shutil.os.path.exists(('Annotations')):
        shutil.rmtree(('Annotations'))
    if shutil.os.path.exists(('ImageSets')):
        shutil.rmtree(('ImageSets'))
    if shutil.os.path.exists(('JPEGImages')):
        shutil.rmtree(('JPEGImages'))

    shutil.os.mkdir(('Annotations'))
    shutil.os.makedirs(('ImageSets/Main'))
    shutil.os.mkdir(('JPEGImages'))


def excute_datasets(idx, datatype):

    f = open(('ImageSets/Main/' + datatype + '.txt'), 'a')
    f_bbx = open(('wider_face_split/wider_face_' + datatype + '_bbx_gt.txt'), 'r')

    while True:
        filename = f_bbx.readline().strip('\n')

        if not filename:
            break


        im = io.imread(('WIDER_' + datatype + '/images/' + filename))
        head = headstr % (idx, im.shape[1], im.shape[0], im.shape[2])
        nums = f_bbx.readline().strip('\n')
        bbxes = []
        if nums=='0':
            bbx_info= f_bbx.readline()
            continue
        for ind in range(int(nums)):
            bbx_info = f_bbx.readline().strip(' \n').split(' ')
            bbx = [int(bbx_info[i]) for i in range(len(bbx_info))]
            # x1, y1, w, h, blur, expression, illumination, invalid, occlusion, pose
            if bbx[7] == 0:
                bbxes.append(bbx)
        writexml(idx, head, bbxes, tailstr)
        shutil.copyfile(('WIDER_' + datatype + '/images/' + filename), ('JPEGImages/%06d.jpg' % (idx)))
        f.write('%06d\n' % (idx))
        idx += 1
    f.close()
    f_bbx.close()
    return idx


if __name__ == '__main__':
    clear_dir()
    idx = 1
    idx = excute_datasets(idx, 'train')
    idx = excute_datasets(idx, 'val')
    print('Complete...')
wider_face 轉成VOC

  目錄格式爲

  

  (2)VOC 格式轉成 yolo 須要的格式

  將 上述步驟生成的 三個文件夾 即 Annotations ImageSets JPEGImages 放到以前編譯好的 \darknet-master\build\darknet\x64\data\voc\VOCdevkit\VOCface 目錄中 

   將voc_label.py 放入  \darknet-master\build\darknet\x64\data\voc\ 目錄下 

  打開 voc_label.py 文件 

  將7 、8左右的代碼改爲以下所示: 

# sets=[('2012', 'train'), ('2012', 'val'), ('2007', 'train'), ('2007', 'val'), ('2007_test', 'test')]#
sets=[('face', 'train'), ('face', 'val')]#
#
# classes = ["aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"]

classes = ["face"]
import xml.etree.ElementTree as ET
import pickle
import os
from os import listdir, getcwd
from os.path import join

# sets=[('2012', 'train'), ('2012', 'val'), ('2007', 'train'), ('2007', 'val'), ('2007_test', 'test')]#
sets=[('face', 'train'), ('face', 'val')]#
#
# classes = ["aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"]

classes = ["face"]


def convert(size, box):
    dw = 1./size[0]
    dh = 1./size[1]
    x = (box[0] + box[1])/2.0
    y = (box[2] + box[3])/2.0
    w = box[1] - box[0]
    h = box[3] - box[2]
    x = x*dw
    w = w*dw
    y = y*dh
    h = h*dh
    return (x,y,w,h)

def convert_annotation(year, image_id):
    in_file = open('VOCdevkit/VOC%s/Annotations/%s.xml'%(year, image_id))
    out_file = open('VOCdevkit/VOC%s/labels/%s.txt'%(year, image_id), 'w')
    tree=ET.parse(in_file)
    root = tree.getroot()
    size = root.find('size')
    w = int(size.find('width').text)
    h = int(size.find('height').text)

    for obj in root.iter('object'):
        difficult = obj.find('difficult').text
        cls = obj.find('name').text
        if cls not in classes or int(difficult) == 1:
            continue
        cls_id = classes.index(cls)
        xmlbox = obj.find('bndbox')
        b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text), float(xmlbox.find('ymax').text))
        bb = convert((w,h), b)
        out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n')



if __name__=='__main__':
    wd = getcwd()
    for year, image_set in sets:
        if not os.path.exists('VOCdevkit/VOC%s/labels/' % (year)):
            os.makedirs('VOCdevkit/VOC%s/labels/' % (year))
        image_ids = open('VOCdevkit/VOC%s/ImageSets/Main/%s.txt' % (year, image_set)).read().strip().split()
        list_file = open('%s_%s.txt' % (year, image_set), 'w')
        for image_id in image_ids:
            list_file.write('%s/VOCdevkit/VOC%s/JPEGImages/%s.jpg\n' % (wd, year, image_id))
            convert_annotation(year, image_id)
        list_file.close()
VOC_label.py

  運行 voc_label.py 結束後 將會在 voc 目錄下生成 face_train.txt 和 face_val.txt

  至此前期數據準備工做完成。

四、修改配置文件

(1)配置 cfg 文件

  將 darknet-master\build\darknet\x64\cfg\yolov3.cfg  文件 複製一份 並重命名爲 yolov3-obj.cfg 

  打開 yolov3-obj.cfg 將 第三行第四行註釋掉 將第七行和第八行註釋取消

  將 batch 設爲 batch=64   (第6行)

  將 subdivisions 設爲 subdivisions=8 (第7行)

  若是顯卡內存較小(即後面運行時報 out of memory 的錯時) 能夠 將 batch 改爲 32 16 8 等 (保證 batch 是 subdivisions  的整數倍),同時取消多尺度訓練 即 設置 random = 0 ( 第 61五、70一、788 行 )

   將 max_batches 改成 max_batches = 2000 (第20行)max_batches 的數量爲檢測的目標數 * 2000 

  將 steps 改成 steps=1600,1800 (第22行)steps =max_batches *0.8 ,0.9

  將 classes 改成 classes =1  (第 610 、69六、783 行)

  將 filters 改成 filters =18 (只改三個 yolo 層的上一層的 filters 即 第 60三、689 、776 行 )

 (2)配置 obj.data 和 obj.names 文件

   能夠 複製 voc.data 和obj.names 文件並重命名,也能夠本身新建兩個文件

  obj.data 文件中 內容爲

classes= 1
train  = data/voc/face_train.txt
valid  = data/voc/face_val.txt
#difficult = data/difficult_2007_test.txt
names = data/obj.names
backup = backup/

  obj.names 的內容爲 face (只有這一行)

face

(3)配置 \darknet-master\Makefile 文件 (在有 GPU 和 CUDNN 的狀況下)

  將第 1 行 GPU=0 改爲 GPU=1

  將第 2 行 CUDNN=0 改爲 CUDNN=1

  將第 58 行 改成 NVCC=C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v10.0/bin (本身的cuda 安裝目錄)

  將 88 —— 108 行的內容改爲以下所示 (即 將對應目錄 改爲)

ifeq ($(GPU), 1)
COMMON+= -DGPU -I/C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v10.0/include
CFLAGS+= -DGPU
ifeq ($(OS),Darwin) #MAC
LDFLAGS+= -L/usr/local/cuda/lib -lcuda -lcudart -lcublas -lcurand
else
LDFLAGS+= -L/C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v10.0/lib/x64 -lcuda -lcudart -lcublas -lcurand
endif
endif

ifeq ($(CUDNN), 1)
COMMON+= -DCUDNN
ifeq ($(OS),Darwin) #MAC
CFLAGS+= -DCUDNN -I/usr/local/cuda/include
LDFLAGS+= -L/usr/local/cuda/lib -lcudnn
else
CFLAGS+= -DCUDNN -IC:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v10.0/include
LDFLAGS+= -L/C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v10.0/lib/x64 -lcudnn
endif
endif

(4)下載 預訓練文件 https://pjreddie.com/media/files/darknet53.conv.74  放到 \darknet-master\build\darknet\x64 目錄中

五、開始訓練 

  在  \darknet-master\build\darknet\x64 目錄下打開 powershell 

  運行命令 ./darknet.exe detector train data/obj.data cfg/yolov3-obj.cfg darknet53.conv.74  開始訓練

  若是報 CUDA Error: out of memory 

   則 將 batch 改爲 32 16 8 等 (保證 batch 是 subdivisions  的整數倍),同時取消多尺度訓練 即 設置 random = 0 ( 第 61五、70一、788 行 ) (我是都改爲 8 才能夠)

六、訓練過程當中的 輸出參數解釋

   

  表示全部訓練圖片中的一個批次(batch),批次大小的劃分根據在cfg/yolov3-obj.cfg中設定的, 批次大小的劃分根據咱們在 .cfg 文件中設置的subdivisions參數。在我使用的 .cfg 文件中 batch = 8 ,subdivision = 8,因此在訓練輸出中,訓練迭代包含了8組(8組 Region 82, Region 94, Region 106),每組又包含了1張圖片,跟設定的batch和subdivision的值一致。( 也就是說每輪迭代會從全部訓練集裏隨機抽取 batch = 8 個樣本參與訓練,全部這些 batch 個樣本又被均分爲 subdivision = 8 次送入網絡參與訓練,以減輕內存佔用的壓力)

 

  (1) Region 82 ,Region 94 , Region 106 表明三個 訓練尺度 82 爲最大尺度 用來預測較小目標, 106 爲最小尺度  用來預測較大目標,94  爲 中間尺度  在每一個尺度 中的數據 會出現大量的 nan 數據 是正常現象,只有迭代的 avg loss 出現 nan 值才說明訓練出錯。

  (2)Avg IOU:表示在當前subdivision內的圖片的平均IOU,表明預測的矩形框和真實目標的交併比 越接近1 越好

  (3)Class:標註物體分類的正確率, 指望該值趨近於1;

  (4)Obj:越接近 1 越好

  (5)No Obj:愈來愈小,但不爲 0

  (6).5R:以IOU=0.5爲閾值時候的recall; recall = 檢出的正樣本/實際的正樣本

  (7).75R: 以IOU=0.75爲閾值時候的recall; recall = 檢出的正樣本/實際的正樣本

  (8)count:count後的值表示全部的當前subdivision圖片(本例中一張)中包含正樣本的圖片的數量。

  (9)最後一行

    11:指當前訓練的迭代次數

    640.579651:整體的 loss

    647.46337 avg loss :平均的loss  在這個數字到達 0.05-3 之間 能夠中止訓練(當該數字 變化趨於平穩,波動不大時中止 )

    0.00000 rate: 表明當前的學習率,在.cfg文件中定義了它的初始值和調整策略。剛開始出現的值頗有可能時 0 是正常狀況

       3.38700 seconds:當前批次的訓練時間

    88 images:表明已參與訓練的圖片的數量

七、訓練完成與測試

 本次訓練用的是破顯卡(750 ti),訓練不到兩小時就我就停下了 avg loss 3.8 左右,測試下訓練效果

 將cfg 文件的 batch  和 subdivisions  換成 1 

 打開 powershell

 輸入命令 ./darknet.exe detector test data/obj.data cfg/yolov3-obj.cfg  backup/yolov3-obj_last.weights -i 0 -thresh 0.25 

 放幾張從網上隨便找的照片,測試結果。初步結果還能夠

 

相關文章
相關標籤/搜索