目录
3、生成2007_train.txt和2007_val.txt等以及label文件夹
10.1 删除2007_train.txt 和 2007_val.txt的文件
1、下载darknet代码
darknet yolov3 官网:https://pjreddie.com/darknet/yolo/
1.1 下载代码
git clone https://github.com/pjreddie/darknet
1.2 编译代码
cd darknet
1.2.1 修改Makefile文件
1)如果使用GPU,GPU=1;否则使用CPU,CPU=0;
2)如果使用cudnn加速库,CUDNN=1;否则为0;
3)如果使用opencv,将opencv=1;否则,opencv=0;
(注:本机将cudnn设为1时,执行官网的测试demo出现问题,如下所示,自行车没有找到。因此,而设定cudnn=0就可以。)
1.2.2 编译
在darknet下,编译代码make
make
1.2.3 下载权重
wget https://pjreddie.com/media/files/darknet53.conv.74
下载权重文件,后面训练时使用。
在darknet目录下新建weights目录,并将darknet53.conv.74移动到weights目录下。
(建议:访问 https://pjreddie.com/darknet/yolo/,测试一下代码是否可行)
2、准备训练数据集
按照voc的格式准备数据,如果全部是jpg的格式的图片,直接跳过注意事项即可:
注意:最好统一图片的格式,本文默认图片为jpg的格式;如果是其他格式,使用voc_label.py生成2007_train.txt,包含图片的完整的路径信息,都是以jpg格式生成。这样训练的时候,找不到jpg格式的图片,会造成“Cannot load image”.
情形1)全部图片都为同一格式,比如:png或者bmp时,直接修改 voc_label.py 倒数第3行的后缀名称即可:
情形2)图片中的后缀格式不统一,既有png/jpg/bmp等2种以上图片时,见文章最后补充10。
2.1 标注数据
使用LabelImg软件(可自行百度下载),对训练集图片进行标注,一张图片对应一个xml文件。
2.2 存放训练集图片和xml文件
新建文件夹 VOCdevkit, 在VOCdevkit目录下新建文件夹VOC2007,在VOC2007目录下新建3个目录,分别为
Annotations、ImageSets、JPEGImages。最后在ImageSets下新建Main目录。目录结构如下:
VOCdevkit
-------------VOC2007
------------Annotations (存放标注的xml文件)
------------ImageSets
----------------Main (存放训练集、测试集的文件名的txt文件:train,val,test,trainval)
------------JPEGImages (存放训练集的图片)
2.3 划分数据集
将训练集图片进行划分,train、val、test和trainval集。将以下代码保存为train_val_test.py,并将放置在VOC2007目录下执行。
import os
import random
# 验证集和测试集占0.2
trainval_percent = 0.2
# 训练集占0.8
train_percent = 0.8
xmlfilepath = 'Annotations'
txtsavepath = 'ImageSets/Main'
total_xml = os.listdir(xmlfilepath)
num = len(total_xml)
list = range(num)
tv = int(num * trainval_percent) # 0.2 * num
tr = int(tv * train_percent) # 0.2 * 0.8 * num
trainval = random.sample(list, tv)
train = random.sample(trainval, tr)
ftrainval = open('ImageSets/Main/trainval.txt', 'w')
ftest = open('ImageSets/Main/test.txt', 'w')
ftrain = open('ImageSets/Main/train.txt', 'w')
fval = open('ImageSets/Main/val.txt', 'w')
for i in list:
name = total_xml[i][:-4] + '\n'
if i in trainval:
ftrainval.write(name) # trainval:0.2 * num
if i in train:
ftest.write(name) # test: 0.16 * num
else:
fval.write(name) # val: 0.04 * num
else:
ftrain.write(name) # train: 0.8 * num
ftrainval.close()
ftrain.close()
fval.close()
ftest.close()
在JPEGImages/Main 生成4个txt文本,train.txt, trainval.txt, val.txt, test.txt。
其中,train:0.8比例
trainval: 0.2比例
test: 0.2*0.8=0.16比例
val: 0.2-0.16=0.04比例
test和val组成trainval。
(实际比例可自行修改代码中的参数进行修改,
train比例:train_percent
trainval比例: trainval_percent
但val的比例始终:train_percent * trainval_percent
test的比例:trainval_percent - val的比例)
这些txt的文件中,实际存放了图片的文件名,但不包括后缀名。
crazing_10是文件名,一行对应一张图片。
3、生成2007_train.txt和2007_val.txt等以及label文件夹
3.1 拷贝数据集
将上一步的制作好的VOCdevkit文件夹拷贝到darknet的目录下。
3.2 生成所需的文件
复制以下代码命名为voc_label.py,移动darknet的目录。(此文件实际为darknet/script/voc_label.py, 最后两行注释掉)
import xml.etree.ElementTree as ET
import pickle
import os
from os import listdir, getcwd
from os.path import join
sets=[('2007', 'train'), ['2007', 'val']]
# 修改为自己的类名
classes = ["crazing", "inclusion", "patches", "pitted_surface","rolled-in_scale", "scratches"]
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')
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()
与darknet/script/voc_label.py相比,除了注释掉最后两行以外,还需要根据自己的训练集修改如下两处。
修改自己的 classes列表:
运行代码:
python voc_label.py
此时会生成2个txt文件和一个labels文件夹
1)2个txt:2007_train.txt, 2007_val.txt 在darknet目录下;
txt文件中存放图片的完整路径信息
2)label文件夹在 VOCdevkit/VOC2007目录下;
生成label文件夹,并在VOCdevkit/VOC2007/labels的文件中,存放图片文件名命名的txt文本。
文本信息为类别索引和标准化后坐标位置信息。
labels文件夹下存放以图片名命名的txt文本标签信息
以crazing_10.txt为例:
第一个字符 0,表示类别信息;后面4位是位置坐标信息
通过解析xml文件获得实际的类名和坐标信息,并将类名用数字表示,同时将坐标信息标准化。
原始坐标信息是左上角和右下角,通过convert转换为中间坐标点信息和宽、高,数值范围在【0-1】之间。
4、修改data/voc.names
自己的实际类名信息,一行一个类名
(注意:与voc_label.py中classes顺序保持一致)
5、修改cfg/voc.data文件
classes :训练集类别总数
train:训练集路径
valid:验证集路径
names: data/voc.names 文件
backup: 备份文件夹,训练后的权重信息或者断点保存信息在此文件夹下。
(记得:在darknet下创建目录 backup)
6、修改cfg/yolov3-voc.py
共有3处需要修改 ,搜索 “yolo” 主要集中 yolo附近。需要修改 filters 和 classes 的参数。
filters = 3 * (classes数目 + 5)
classes = 自己数据集类别数目
1)第一处:大概在601行
2)第二处:大概在689行
3)第三处:大概在769行
如果显存太小,将random=0,关闭多尺度训练。
4) 最后将训练模型开启。
7、训练
1) 训练
./darknet detector train cfg/voc.data cfg/yolov3-voc.cfg weights/darknet53.conv.74
(Ctrl + c 中止训练, Ctrl + z 暂停训练, 暂停回来按住fg )
多GPU训练
./darknet detector train cfg/voc.data cfg/yolov3-voc.cfg darknet53.conv.74 -gpus 0,1,2,3
2)如果使用ssh连接的服务器的话,可以使用如下命令,关闭ssh服务器会在后头继续运行:
nohup ./darknet detector train cfg/voc.data cfg/yolov3-voc.cfg weights/darknet53.conv.74 &
说明:
1)nohup 指不间断、不挂断地运行,no hang up的缩写。如果运行一个程序,在退出时想继续运行,可以使用nohup。
2)& 是后台运行的意思,
3)nohup 命令 &,就是不挂断的后台持续运行程序
(也可以使用tee进行,但是不能关闭ssh窗口)
./darknet detector train cfg/voc.data cfg/yolov3-voc.cfg darknet53.conv.74 2>&1 | tee visualization/train_yolov3.log
3)训练的次数可以通过 cfg/yolov3-voc.cfg 文件中的max_batches修改,默认是50200。还有一些其他参数可以在此修改。暂时没有深入了解。
训练结束之后,会生成train.log日志文件,可以查看训练情况。
4)模型的文件保存在backup文件中:
5)中断后继续训练
中断后,程序会在backup目录下,自动保存上一次权重信息:yolov3-voc.backup,重新训练时导入即可。
多GPU时加上参数: -gpus 0, 1, 2, 3
./darknet detector train cfg/voc.data cfg/yolov3-voc.cfg backup/yolov3-voc.backup -gpus 0,1
8、测试
将cfg/yolov3-voc.cfg中的测试模式打开:
测试:
./darknet detector test cfg/voc.data cfg/yolov3-voc.cfg backup/yolov3-voc_final.weights
输入需要测试的图片路径即可
(backup的权重都可以进行测试)
9、批量测试
上述图片只能对图片进行单张测试,现在需要对一个文件夹下的文件进行批量测试。
9.1 准备批量测试的图片
将需要批量测试的图片放到test目录下,将test目录放到darknet目录下;
执行: (/data/lxh/darknet/test 修改成自己的目录)
ls -R /data/lxh/darknet/test/* > test.txt
其中test.txt,存放测试集的图片的绝对路径。
9.2 修改 example/detector.c文件
1) 用下面代码替换 void test_detector(), 有3处需要修改成自己的路径。
void test_detector(char *datacfg, char *cfgfile, char *weightfile, char *filename, float thresh, float hier_thresh, char *outfile, int fullscreen)
{
list *options = read_data_cfg(datacfg);
char *name_list = option_find_str(options, "names", "data/names.list");
char **names = get_labels(name_list);
image **alphabet = load_alphabet();
network *net = load_network(cfgfile, weightfile, 0);
set_batch_network(net, 1);
srand(2222222);
double time;
char buff[256];
char *input = buff;
float nms=.45;
int i=0;
while(1){
if(filename){
strncpy(input, filename, 256);
image im = load_image_color(input,0,0);
image sized = letterbox_image(im, net->w, net->h);
//image sized = resize_image(im, net->w, net->h);
//image sized2 = resize_max(im, net->w);
//image sized = crop_image(sized2, -((net->w - sized2.w)/2), -((net->h - sized2.h)/2), net->w, net->h);
//resize_network(net, sized.w, sized.h);
layer l = net->layers[net->n-1];
float *X = sized.data;
time=what_time_is_it_now();
network_predict(net, X);
printf("%s: Predicted in %f seconds.\n", input, what_time_is_it_now()-time);
int nboxes = 0;
detection *dets = get_network_boxes(net, im.w, im.h, thresh, hier_thresh, 0, 1, &nboxes);
//printf("%d\n", nboxes);
//if (nms) do_nms_obj(boxes, probs, l.w*l.h*l.n, l.classes, nms);
if (nms) do_nms_sort(dets, nboxes, l.classes, nms);
draw_detections(im, dets, nboxes, thresh, names, alphabet, l.classes);
free_detections(dets, nboxes);
if(outfile)
{
save_image(im, outfile);
}
else{
save_image(im, "predictions");
#ifdef OPENCV
cvNamedWindow("predictions", CV_WINDOW_NORMAL);
if(fullscreen){
cvSetWindowProperty("predictions", CV_WND_PROP_FULLSCREEN, CV_WINDOW_FULLSCREEN);
}
show_image(im, "predictions");
cvWaitKey(0);
cvDestroyAllWindows();
#endif
}
free_image(im);
free_image(sized);
if (filename) break;
}
else {
printf("Enter Image Path: ");
fflush(stdout);
input = fgets(input, 256, stdin);
if(!input) return;
strtok(input, "\n");
list *plist = get_paths(input);
char **paths = (char **)list_to_array(plist);
printf("Start Testing!\n");
int m = plist->size;
if(access("/data/lxh/darknet/data/out",0)==-1)//"/data/lxh/darknet/data"修改成自己的路径
{
if (mkdir("/data/lxh/darknet/data/out",0777))//"/data/lxh/darknet/data"修改成自己的路径
{
printf("creat file bag failed!!!");
}
}
for(i = 0; i < m; ++i){
char *path = paths[i];
image im = load_image_color(path,0,0);
image sized = letterbox_image(im, net->w, net->h);
//image sized = resize_image(im, net->w, net->h);
//image sized2 = resize_max(im, net->w);
//image sized = crop_image(sized2, -((net->w - sized2.w)/2), -((net->h - sized2.h)/2), net->w, net->h);
//resize_network(net, sized.w, sized.h);
layer l = net->layers[net->n-1];
float *X = sized.data;
time=what_time_is_it_now();
network_predict(net, X);
printf("Try Very Hard:");
printf("%s: Predicted in %f seconds.\n", path, what_time_is_it_now()-time);
int nboxes = 0;
detection *dets = get_network_boxes(net, im.w, im.h, thresh, hier_thresh, 0, 1, &nboxes);
//printf("%d\n", nboxes);
//if (nms) do_nms_obj(boxes, probs, l.w*l.h*l.n, l.classes, nms);
if (nms) do_nms_sort(dets, nboxes, l.classes, nms);
draw_detections(im, dets, nboxes, thresh, names, alphabet, l.classes);
free_detections(dets, nboxes);
if(outfile){
save_image(im, outfile);
}
else{
char b[2048];
sprintf(b,"/data/lxh/darknet/data/out/%s",GetFilename(path)); //"/data/lxh/darknet/data"修改成自己的路径
save_image(im, b);
printf("save %s successfully!\n",GetFilename(path));
#ifdef OPENCV
cvNamedWindow("predictions", CV_WINDOW_NORMAL);
if(fullscreen){
cvSetWindowProperty("predictions", CV_WND_PROP_FULLSCREEN, CV_WINDOW_FULLSCREEN);
}
show_image(im, "predictions");
cvWaitKey(0);
cvDestroyAllWindows();
#endif
}
free_image(im);
free_image(sized);
if (filename) break;
}
}
}
}
2)在detector.c的前面添加如下函数*GetFilename(char *p),注意注释内容
(这是识别之后保存文件名的长度,如果你的文件名大于6个字符,根据具体需要改)
#include "darknet.h"
#include <sys/stat.h>
#include<stdio.h>
#include<time.h>
#include<sys/types.h>
static int coco_ids[] = {1,2,3,4,5,6,7,8,9,10,11,13,14,15,16,17,18,19,20,21,22,23,24,25,27,28,31,32,33,34,35,36,37,38,39,40,41,42,43,44,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,67,70,72,73,74,75,76,77,78,79,80,81,82,84,85,86,87,88,89,90};
char *GetFilename(char *p)
{
static char name[20]={""};
char *q = strrchr(p,'/') + 1;
strncpy(name,q,6);//注意后面的6,如果你的测试集的图片的名字字符(不包括后缀)是其他长度,请改为你需要的长度(官方的默认的长度是6)
return name;
}
3) 编译
make
9.3 执行批量测试的命令
./darknet detector test cfg/voc.data cfg/yolov3-voc.cfg backup/yolov3-voc_final.weights
1)执行如下所示:
2)输入批量测试的绝对路径信息文本: /data/lxh/darknet/test.txt
(也可以使用 cfg/voc.data的文件里的valid后面的路径)
最后会在darknet/data/out/目录下生成批量测试图片。
10.补充
补充的问题针对第2步提到的情形2)图片格式不统一问题
10.1 删除2007_train.txt 和 2007_val.txt的文件
首先按照上述步骤,运行到3.2,修改3.2生成的2007_train.txt 和 2007_val.txt的文件
10.2 运行rename_picpath.py
重新生成txt的文件,后续接着第4步运行即可。
python rename_picpath.py
rename_picpath.py代码如下:
# rename_picpath.py
import os
'''
处理图片后缀不是jpg,导致训练过程中 Cannot load image的问题
'''
# 保存训练图片的完整的路径列表信息
train_file = open('2007_train.txt', "w")
trainval_file = open("2007_trainval.txt", "w")
wd = os.getcwd()
# 训练集
train_list = open("VOCdevkit/VOC2007/ImageSets/Main/train.txt").read().strip().split()
for file in os.listdir("VOCdevkit/VOC2007/JPEGImages/"):
if file.split('.')[0] in train_list:
train_file.write("{}/VOCdevkit/VOC2007/JPEGImages/{}\n".format(wd, file))
# 验证集
trainval_list = open("VOCdevkit/VOC2007/ImageSets/Main/trainval.txt").read().strip().split()
for file2 in os.listdir("VOCdevkit/VOC2007/JPEGImages"):
if file2.split('.')[0] in trainval_list:
trainval_file.write("{}/VOCdevkit/VOC2007/JPEGImages/{}\n".format(wd, file2))
train_file.close()
trainval_file.close()
注意:此处生成的txt文本信息,会在第5步中使用。