参考博客https://blog.csdn.net/lilai619/article/details/79695109
系统环境 Ubuntu 16.04
-1.在制作数据时最好先将所有图片重新命名,这样会方便后面数据集的制作,比如这次我演示的是只训练一个杯子(也就是训练一个类),那么我把所有的图片的名字都改为bottle_*.jpg。这可以自己写一个脚本去重命名。制作好后如下图
0.先使用labelimg标注图片数据,把生成的xml文件放在同一个文件夹,比如名为xml
1.在自己喜欢的目录下新建一个文件夹,比如命名为voc_mybottle(因为自己要训练的数据是我的一个杯子)。在这个文件夹下新建一个名为images的文件(一定要是images,原因等下会说)。在新建一个叫labels的文件夹(一定要是labels)。把训练的所有图片放在images的文件夹中。(如第一张图)
2.为了方便我把images labels xml三个文件夹下,然后把下面这个python脚本放在同一个目录下然后执行,labels就会添加所有照片的labels.txt文件,和一个叫img_list.txt的文件,内容是所用图片的路径
#!/usr/bin/env python
# coding: utf-8
# In[3]:
import os
pre_path = os.getcwd()
print(pre_path)
# In[16]:
img_pre_path = pre_path + "/images"
print(img_pre_path)
IMG_txt = "img_list.txt"
img_list = os.listdir(img_pre_path)
with open(IMG_txt, "w") as f:
for index in img_list:
img_path = img_pre_path + '/' + index
print(img_path)
f.write(img_path + '\n')
# In[39]:
import xml.etree.ElementTree as ET
xml_path = pre_path + '/xml'
label_pre_path = pre_path + '/labels'
xml_list = os.listdir(xml_path)
print(xml_path)
print(label_pre_path)
print(xml_list[3])
for index in xml_list:
print(xml_path + '/' + index)
# In[41]:
classes = ["mybottle"]
def convert(size, box):
print(size[0], size[1])
dw = 1.0 / size[0]
dh = 1.0 / 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_xml(in_path, out_path):
in_file = open(in_path)
out_file = open(out_path, '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)
print("w,h", w, h)
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')
for index in xml_list:
xml_file_path = xml_path + '/' + index
# print(index.split('.')[0])
lable_txt_path = pre_path + '/labels/' + index.split('.')[0] + '.txt'
print("xml:" + xml_file_path)
print("label" + lable_txt_path)
convert_xml(xml_file_path, lable_txt_path)
里面的内容是这样的
0 0.571875 0.500925925925926 0.3979166666666667 0.5851851851851853
代表物体的分类编号和两个坐标的xy在图片中的归一化的值
3.执行下面两句指令,使图片分成两份数据,分别为train和test两个数据的图片路径,18是把头18张图片归为train,19以后的图片归为test,这里自行修改
cat img_list.txt | head -n 18 > train_list.txt
cat img_list.txt | tail -n +19 > test_list.txt
~
4.根据官网提示我们要制作一个xxx.data的文件,用来给程序传递参数的。
根据自己的需求建立
比如我的是voc_mybottle.data 内容为
classes = 1 # 训练一个类
train =
valid =
names =
backup = mybottle_backup #自己在darknet下建立的mybottle_backup文件夹,用来放训练的模型
.names文件是用来存放分类的名字,在这里我只有一个mybottle的类,所以只有mybottle这行内容
5.修改关于训练的cfg文件,里面是关于网络的参数
因为这里我们用的是yolov3去训练voc格式数据,所以要修改darknet/cfg/yolov3-voc.cfg文件,为了方便我自己新创了一个mybottle_yolov3-voc.cfg文件。把yolov3-voc.cfg复制过来,改参数。主要改的参数是这些
按照需求改,这里可以去参考其他文章,这里不过多描述。
还有就是改网络结构,因为voc是有20个类,而我现在是只有一个类,所以要把网络调整。每个yolo层都有一个filter=75的参数(3*(num_class + 5)), 这里改成18(3×(1+5))。一共要改3处。
到这里数据就准备好了。
最后一部是下载预权重文件放在darknet文件夹下
wget https://pjreddie.com/media/files/darknet53.conv.74
安装官网训练的例子进行修改运行的参数
cfg/voc.data修改成自己创建的.data文件路径,cfg/yolov3-voc.cfg修改成自己创建的.cfg文件路径。执行就能训练了
运行界面
每默认100步保存一次模型,只保存十次模型,最后一次会被每次更新覆盖。
训练到此结束。
说一下我遇到的坑
在读取照片的时候遇到读取的labels中的文件不在images文件夹的问题。这很奇怪,于是我去找源码,发现一定要把图片的文件夹命名为images才能读取同一个目录下的labels文件夹中的文件
//data.c
void fill_truth_detection(char *path, int num_boxes, float *truth, int classes, int flip, float dx, float dy, float sx, float sy)
{
char labelpath[4096];
find_replace(path, "images", "labels", labelpath);
find_replace(labelpath, "JPEGImages", "labels", labelpath);
find_replace(labelpath, "raw", "labels", labelpath);
find_replace(labelpath, ".jpg", ".txt", labelpath);
find_replace(labelpath, ".png", ".txt", labelpath);
find_replace(labelpath, ".JPG", ".txt", labelpath);
find_replace(labelpath, ".JPEG", ".txt", labelpath);
int count = 0;
box_label *boxes = read_boxes(labelpath, &count);
第二个坑是训练好后,模型能识别物体,可是标签是错的
Loading weights from mybottle_backup/mybottle_yolov3-voc_200.weights...Done!
path/images/bottle_1.jpg: Predicted in 0.028537 seconds.
person: 89%
查了一下源码,发现原来是我用用了官网的测试图片的指令 detect
./darknet detect cfg/yolov3.cfg yolov3.weights data/dog.jpg
// darknet.c
if (0 == strcmp(argv[1], "average")){
average(argc, argv);
} else if (0 == strcmp(argv[1], "yolo")){
run_yolo(argc, argv);
} else if (0 == strcmp(argv[1], "super")){
run_super(argc, argv);
} else if (0 == strcmp(argv[1], "lsd")){
run_lsd(argc, argv);
} else if (0 == strcmp(argv[1], "detector")){
run_detector(argc, argv);
} else if (0 == strcmp(argv[1], "detect")){
float thresh = find_float_arg(argc, argv, "-thresh", .5);
char *filename = (argc > 4) ? argv[4]: 0;
char *outfile = find_char_arg(argc, argv, "-out", 0);
int fullscreen = find_arg(argc, argv, "-fullscreen");
test_detector("cfg/coco.data", argv[2], argv[3], filename, thresh, .5, outfile, fullscreen);
} else if (0 == strcmp(argv[1], "cifar")){
run_cifar(argc, argv);
} else if (0 == strcmp(argv[1], "go")){
run_go(argc, argv);
} else if (0 == strcmp(argv[1], "rnn")){
run_char_rnn(argc, argv);
} else if (0 == strcmp(argv[1], "coco")){
run_coco(argc, argv);
可以看到当使用detect参数时会使用coco.data,所以应该是调用了coco.data的数据,查看coco.data,第一个类果然是person。
于是换回测试的指令使用test参数,(文件路径是自己的路径)
./darknet detector test cfg/coco.data cfg/yolov3.cfg yolov3.weights data/dog.jpg
测试成功
(训练步数少,只是用来测试的,所以效果不好,别介意,哈哈哈哈)
但是这次以后只是用detect参数也回复正常了,不知道是什么原因。如果您知道,恳请赐教。