win10下用yolov3訓練WiderFace資料集來實現人臉檢測(TensorFlow版本,darkface作為測試集)
阿新 • • 發佈:2020-12-21
技術標籤:人臉檢測
資料集準備工作
- 訓練集 Wider Face格式轉換
下載人臉資料集wider face,解壓到同一個資料夾下
在同一個目錄下,新建convert.py檔案(把下面程式放入)執行程式得到影象和其對應的xml檔案。
# -*- coding: utf-8 -*-
import shutil
import random
import os
import string
from skimage import io
headstr = """\
<annotation>
<folder>VOC2012</folder>
<filename>%06d.jpg</filename>
<source>
<database>My Database</database>
<annotation>PASCAL VOC2012</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...')
執行後得到以下的檔案(訓練會重新打亂,所以只需要管Annotations,JPEGImages兩個資料夾):
Annotations:存放影象對應的xml
ImageSets:包含train.txt ,val.txt(分別包含對應的影象名字)
JPEGImages:存放影象
- 測試集Dark Face格式轉換
(若只用WilderFace作為訓練集,測試集,忽略這一步)
下載夜間人臉資料集Dark Face,我使用的是它的訓練集6000張影象作為測試。
執行下面的程式得到影象和對應的xml檔案。
# -*- coding: utf-8 -*-
import os,shutil
import cv2
from lxml.etree import Element, SubElement, tostring
def txt_xml(img_path,img_name,txt_path,img_txt,xml_path,img_xml):
clas = []
img=cv2.imread(os.path.join(img_path,img_name))
imh, imw = img.shape[0:2]
txt_img=os.path.join(txt_path,img_txt)
with open(txt_img,"r") as f:
next(f)
for line in f.readlines():
line = line.strip('\n')
list = line.split(" ")
print(list)
clas.append(list)
node_root = Element('annotation')
node_folder = SubElement(node_root, 'folder')
node_folder.text = '1'
node_filename = SubElement(node_root, 'filename')
#影象名稱
node_filename.text = img_name
node_size = SubElement(node_root, 'size')
node_width = SubElement(node_size, 'width')
node_width.text = str(imw)
node_height = SubElement(node_size, 'height')
node_height.text = str(imh)
node_depth = SubElement(node_size, 'depth')
node_depth.text = '3'
for i in range(len(clas)):
node_object = SubElement(node_root, 'object')
node_name = SubElement(node_object, 'name')
node_name.text = "face"
node_pose=SubElement(node_object, 'pose')
node_pose.text="Unspecified"
node_truncated=SubElement(node_object, 'truncated')
node_truncated.text="truncated"
node_difficult = SubElement(node_object, 'difficult')
node_difficult.text = '0'
node_bndbox = SubElement(node_object, 'bndbox')
node_xmin = SubElement(node_bndbox, 'xmin')
node_xmin.text = str(clas[i][0])
node_ymin = SubElement(node_bndbox, 'ymin')
node_ymin.text = str(clas[i][1])
node_xmax = SubElement(node_bndbox, 'xmax')
node_xmax.text = str(clas[i][2])
node_ymax = SubElement(node_bndbox, 'ymax')
node_ymax.text = str(clas[i][3])
xml = tostring(node_root, pretty_print=True) # 格式化顯示,該換行的換行
img_newxml = os.path.join(xml_path, img_xml)
file_object = open(img_newxml, 'wb')
file_object.write(xml)
file_object.close()
if __name__ == "__main__":
#影象資料夾所在位置
img_path = r"E:\BaiduNetdiskDownload\DarkFace_Train_new\images"
#標註資料夾所在位置
txt_path=r"E:\BaiduNetdiskDownload\DarkFace_Train_new\label"
#txt轉化成xml格式後存放的資料夾
xml_path=r"E:\BaiduNetdiskDownload\DarkFace_Train_new\xml"
for img_name in os.listdir(img_path):
print(img_name)
img_xml=img_name.split(".")[0]+".xml"
img_txt=img_name.split(".")[0]+".txt"
txt_xml(img_path, img_name, txt_path, img_txt,xml_path, img_xml)
yolov3訓練
- 下載訓練程式碼和預訓練模型,並搭建好環境
專案程式碼:tensorflow-serving-yolov3
基於vgg的coco預訓練模型百度雲地址,密碼:vw9x - 把預訓練模型解壓到專案的checkpoint資料夾下,然後執行convert_weight.py,得到yolov3_coco_demo.ckpt模型
- 修改vis.names裡的內容,只新增face這一個類別
- 把tensorflow-serving-yolov3/VOC2007/資料夾下的Annotations,JPEGImages兩個資料夾換成第一步生成的wilderface的資料夾,接著修改split.py的60行內容
fi = open('./data/classes/voc.names', 'r')
修改為
fi = open('./data/classes/vis.names', 'r')
執行split.py,根目錄下得到以下三個檔案:
- 修改core/config.py的以下內容:
__C.TRAIN.BATCH_SIZE = 2#根據自己電腦情況自行修改
__C.YOLO.CLASSES = "./data/classes/vis.names"
__C.YOLO.ORIGINAL_WEIGHT = "./checkpoint/yolov3_coco_demo.ckpt"
__C.YOLO.DEMO_WEIGHT = "./checkpoint/yolov3_coco_demo.ckpt"
__C.TRAIN.FISRT_STAGE_EPOCHS = 20#根據自己需要自行修改
__C.TRAIN.SECOND_STAGE_EPOCHS = 30
__C.TRAIN.INITIAL_WEIGHT = "./checkpoint/yolov3_coco_demo.ckpt"
- 執行train.py進行訓練
- 執行convert_weight.py儲存模型
評估階段
- 我用的是darkface的訓練集全部作為測試,複製voc2007資料夾並重命名為DARK_face,並把Annotations,JPEGImages兩個資料夾替換成darkface的xml和影象(這一步就是為生成YOLOv3所需要格式做準備)
- 修改split.py的內容
# -*- coding:utf-8 -*-
# split.py
from __future__ import division
import xml.etree.ElementTree as ET
import random
import os
def base_txt():
saveBasePath = r"./DARK_face/ImageSets" # txt檔案儲存目錄
total_xml = os.listdir(r'./DARK_face/Annotations') # 獲取標註檔案(file_name.xml)
# 劃分資料集為(訓練,驗證,測試集 = 49%,20%,30%)
val_percent =0 # 可以自己修改
test_percent = 1
trainval_percent = 0
# print(trainval_percent)
tv = int(len(total_xml) * trainval_percent)
#tr = int(len(total_xml) * train_percent)
ta = int(tv * val_percent)
tr = int(tv -ta)
tt = int(len(total_xml) * test_percent)
# 打亂訓練檔案(洗牌)
trainval = random.sample(range(len(total_xml)), tv)
train = random.sample(trainval, tr)
print("訓練集圖片數量:", tr)
print("驗證集圖片數量:", ta)
print("測試集圖片數量:", tt)
# with open('/tmp/VOC2007/split.txt', 'w', encoding='utf-8') as f:
# f.write(str(val_percent))
ftrainval = open(os.path.join(saveBasePath, 'Main/trainval.txt'), 'w')
ftest = open(os.path.join(saveBasePath, 'Main/test.txt'), 'w')
ftrain = open(os.path.join(saveBasePath, 'Main/train.txt'), 'w')
fval = open(os.path.join(saveBasePath, 'Main/val.txt'), 'w')
for i in range(len(total_xml)): # 遍歷所有 file_name.xml 檔案
name = total_xml[i][:-4] + '\n' # 獲取 file_name
if i in trainval:
ftrainval.write(name)
if i in train:
ftrain.write(name)
else:
fval.write(name)
else:
ftest.write(name)
ftrainval.close()
ftrain.close()
fval.close()
ftest.close()
base_txt()
fi = open('./data/classes/vis.names', 'r') # 按資料夾裡面的檔案修改好
txt = fi.readlines()
voc_class = []
for w in txt:
w = w.replace('\n', '')
voc_class.append(w)
print('資料集裡面的類別:', voc_class)
classes = voc_class
def convert_annotation(year, image_id, list_file):
in_file = open('./DARK_%s/Annotations/%s.xml'%(year, image_id))
tree=ET.parse(in_file)
root = tree.getroot()
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 = (int(xmlbox.find('xmin').text), int(xmlbox.find('ymin').text), int(xmlbox.find('xmax').text), int(xmlbox.find('ymax').text))
list_file.write(" " + ",".join([str(a) for a in b]) + ',' + str(cls_id))
wd = '.'
# sets=[ ('2007', 'train'), ('2007', 'val'), ('2007', 'test')]
sets=[ ('face', 'test')]
# wd = getcwd()
for year, image_set in sets:
image_ids = open('./DARK_%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/DARK_%s/JPEGImages/%s.png'%(wd, year, image_id))
convert_annotation(year, image_id, list_file)
list_file.write('\n')
list_file.close()
執行split.py,得到face_test.txt檔案(內容如下)
格式: image_path x_min, y_min, x_max, y_max, class_id x_min, y_min ,…, class_id
- 評估資料集,修改config.py
__C.TEST.ANNOT_PATH = "./face_test.txt"
你之前訓練儲存的模型
__C.TEST.WEIGHT_FILE = "./checkpoint/yolov3_train_loss=14.6886.ckpt-50"
- 執行evaluate.py,在data/detection/下檢視評估的結果
- 計算mAP,在終端執行
cd mAP
python main.py -na
ps:訓練自己的資料集
1.先把vis.names檔案內容修改為自己所需要的類別。
2.把自己資料集轉換成影象+xml形式,替換掉專案中的voc2007資料夾對應的部分。
3.根據自己需要修改split.py裡面的內容,劃分資料集。
4.修改config.py內容,執行train.py訓練。
5.執行evaluate.py評估結果。
6.執行mAP資料夾下的 main.py計算mAP。