本篇博客写的基于Ubuntu系统下的yolov3训练配置过程,基于windows的可以参考AlexeyAB的Github博客地址: (AlexeyAB的Github)。当然,原作者Pjreddie的网页也有ubuntu配置过程可以参考:原作者pjreddie
首先,你需要下载训练数据集(或者自己的数据集),以Pascal-Voc数据集格式为例:
通过官网给出的voc_label.py生成对应的标签txt文件(训练自己数据集的时候注意修改一下路径)
脚本voc_label.py下载地址:https://pjreddie.com/media/files/voc_label.py
如果是使用Pascal-VOC进行生成txt的话,与voc_label.py同目录下面会有相对应的2007_train.txt 2007_val.txt 2007_test.txt 2012_train.txt 2012_val.txt
运行下面命令:
cat 2007_train.txt 2007_val.txt 2012_*.txt > train.txt
(如果你自己的数据集可以生成对应的txt文件,只需要改下voc_label.py中的文件路径即可)
YOLO9000通过Kmeans聚类来预先估计anchor的大小,那么我们可以通过对Pascal-VOC数据集进行聚类来预先估计一下数据的anchor大小。该部分参考github上:Kmeans聚类anchor-boxes链接。
但是,相对该链接的example.py部分修改如下:
import glob
import xml.etree.ElementTree as ET
import numpy as np
from kmeans import kmeans, avg_iou
ROOT_ANNOTATIONS_PATH = "/.../VOCdevkit/" # your root path
SUB_FILE = ['VOC2007', 'VOC2012']
CLUSTERS = 9
def load_dataset(path):
dataset = []
for i in range(len(SUB_FILE)):
concrete_path = path + SUB_FILE[i] + '/Annotations'
for xml_file in glob.glob("{}/*xml".format(path)):
tree = ET.parse(xml_file)
height = int(tree.findtext("./size/height"))
width = int(tree.findtext("./size/width"))
for obj in tree.iter("object"):
xmin = np.float64(obj.findtext("bndbox/xmin")) / width
ymin = np.float64(obj.findtext("bndbox/ymin")) / height
xmax = np.float64(obj.findtext("bndbox/xmax")) / width
ymax = np.float64(obj.findtext("bndbox/ymax")) / height
if xmax == xmin or ymax == ymin:
print(xml_file) # print the failed xml_file
dataset.append([xmax - xmin, ymax - ymin])
return np.array(dataset)
data = load_dataset(ROOT_ANNOTATIONS_PATH)
out = kmeans(data, k=CLUSTERS)
print("Accuracy: {:.2f}%".format(avg_iou(data, out) * 100))
print("Boxes:\n {}-{}".format(out[:, 0]*416, out[:, 1]*416))
ratios = np.around(out[:, 0] / out[:, 1], decimals=2).tolist()
print("Ratios:\n {}".format(sorted(ratios)))
简单解释一下example.py的相关参数设置:ROOT_ANNOTATIONS_PATH 为根目录,举例Pascal-VOC数据集下路径为/VOCdevkit/。其次:SUB_FILE为VOC2007与VOC2012文件夹,通过迭代循环查找它们下面的Annotations的xml文件。CLUSTERS为聚类次数,可以根据具体情况来进行设置。
当然,上述kmeans-anchor-boxes链接还有一个主要的kmeans.py函数,简单贴一下其脚本:
import numpy as np
def iou(box, clusters):
"""
Calculates the Intersection over Union (IoU) between a box and k clusters.
:param box: tuple or array, shifted to the origin (i. e. width and height)
:param clusters: numpy array of shape (k, 2) where k is the number of clusters
:return: numpy array of shape (k, 0) where k is the number of clusters
"""
x = np.minimum(clusters[:, 0], box[0])
y = np.minimum(clusters[:, 1], box[1])
if np.count_nonzero(x == 0) > 0 or np.count_nonzero(y == 0) > 0:
raise ValueError("Box has no area")
intersection = x * y
box_area = box[0] * box[1]
cluster_area = clusters[:, 0] * clusters[:, 1]
iou_ = intersection / (box_area + cluster_area - intersection)
return iou_
def avg_iou(boxes, clusters):
"""
Calculates the average Intersection over Union (IoU) between a numpy array of boxes and k clusters.
:param boxes: numpy array of shape (r, 2), where r is the number of rows
:param clusters: numpy array of shape (k, 2) where k is the number of clusters
:return: average IoU as a single float
"""
return np.mean([np.max(iou(boxes[i], clusters)) for i in range(boxes.shape[0])])
def translate_boxes(boxes):
"""
Translates all the boxes to the origin.
:param boxes: numpy array of shape (r, 4)
:return: numpy array of shape (r, 2)
"""
new_boxes = boxes.copy()
for row in range(new_boxes.shape[0]):
new_boxes[row][2] = np.abs(new_boxes[row][2] - new_boxes[row][0])
new_boxes[row][3] = np.abs(new_boxes[row][3] - new_boxes[row][1])
return np.delete(new_boxes, [0, 1], axis=1)
def kmeans(boxes, k, dist=np.median):
"""
Calculates k-means clustering with the Intersection over Union (IoU) metric.
:param boxes: numpy array of shape (r, 2), where r is the number of rows
:param k: number of clusters
:param dist: distance function
:return: numpy array of shape (k, 2)
"""
rows = boxes.shape[0]
distances = np.empty((rows, k))
last_clusters = np.zeros((rows,))
np.random.seed()
# the Forgy method will fail if the whole array contains the same rows
clusters = boxes[np.random.choice(rows, k, replace=False)]
while True:
for row in range(rows):
distances[row] = 1 - iou(boxes[row], clusters)
nearest_clusters = np.argmin(distances, axis=1)
if (last_clusters == nearest_clusters).all():
break
for cluster in range(k):
clusters[cluster] = dist(boxes[nearest_clusters == cluster], axis=0)
last_clusters = nearest_clusters
return clusters
假设聚类出来Boxes输出为:[12 24 53 76 19 87 93 32 65]-[32 43 66 112 57 132 176 198 312]。那么对应的anchors为:[12 32] [24 43]…以此类推,将其修改cfg文件夹下面对应的yolov3-voc.cfg文件中的anchors即可,每次进行聚类出来的anchor-box可能不一致,这是由于kmeans随机初始化聚类,具体哪个anchor效果好,只能靠多次尝试一下了。
去原作者github官网下载darknet的的最新压缩包后进行解压缩。
解压缩之后:cd darknet-master
注意:先别急着进行make
如果你需要使用GPU、CUDNN、OpenCV等进行训练的话,那么你首先打开makefile文件,在前面几个对应的位置将0修改为1,如果你使用CPU进行训练,那么请你直接跳过这一步。
修改过后,可以进行make命令。
下面就是设置你的训练相关参数配置了:
1 首先修改darknet文件下面的cfg文件夹下面的voc.data
classes = 20 # 需要修改成为你的数据集类别数
train = /train.txt # 前文说到的生成train.txt路径
val = /2007_test.txt # 前文说到的生成test路径
names = data/voc.names # 你自己数据集的类别名称
backup= backup # 存储训练模型路径文件夹
首先你修改你一共有多少个类别数,train.txt与2007_test.txt路径设置,voc.names则是你检测的目标类别名称,backup则是你存储训练模型的路径。
当然,此时的你还不能进行训练,需要修改一下参数:
./cfg/voc.names 文件下面加入你自己训练数据集的类别。
./cfg/yolov3-voc.cfg 这个文件打开,是yolov3的网络配置文件,首先你需要将前面几行的Train参数注释取消,Test部分注释。同时,搜索文件中出现classes 的变量,修改为你对应的训练类别数目,同时上面的filter = (classes + 5) * 3
整个yolov3网络中修改classes总共三个地方,同时对应的filter也是三个地方。
当然,你还可以调节batch_size,学习率等一些训练参数。
下载yolov3 训练的预训练权重:darknet53.conv.74 链接
好了,设置完成后可以进行训练:运行下面命令
./darknet detector train cfg/voc.data cfg/yolov3-voc.cfg darknet53.conv.74 -gpus 0,1,2,3
gpus可以根据自己需要设置,没有的话cpu不需要设置
训练结束后,你会在backup文件夹下面看到yolov3-voc_final.weights
运行命令:
./darknet detector valid ./cfg/voc.data ./cfg/yolov3-voc.cfg ./backup/yolov3-voc_final.weights -gpus=0,1
这里如果你不写 -out 算法会在results文件夹下面默认生成对应的类别标签。
然后你将results文件夹下面的文件comp4_det_test_xxx.txt改为对应的类别xxx.txt
例如:results生成里面存在comp4_det_test_dog.txt 改为 dog.txt
OK, 然后你可以运行mAp_voc2007.py(文末百度网盘下载)进行在测试集上面验证yolov3训练的模型效果。其中mAp_voc2007.py里面的main函数文件夹需要你修改一下classes名字,生成的results路径,xmlpath与imgpath路径即可。
(将你的yolov3-voc_final.weights名称改为yolov3.weights 名称修改任意,只要对应下面的命令模型名称一致即可)
./darknet detector train cfg/voc.data cfg/yolov3-voc.cfg yolov3.weights -gpus=0,1 (-clear 如果加上clear表示重头开始训练)
模型微调示例:
1、添加clear
./darknet detector train cfg/voc.data cfg/yolov3-voc.cfg ./backup/yolov3.weights -gpus=0,1 -clear
2、不添加clear进行第一个模型基础上微调
修改yolov3-voc.cfg中的max_batches与steps参数
./darknet detector train cfg/voc.data cfg/yolov3-voc.cfg ./backup/yolov3.weights -gpus=0,1
测试图片:
首先测试阶段需要将yolov3-voc.cfg文件中的Train模块注释,Test注释取消。
./darknet detector test ./cfg/voc.data ./cfg/yolov3-voc.cfg ./backup/yolov3-voc_final.weights data/xxx.jpg
测试视频demo:
./darknet detector demo ./cfg/voc.data ./cfg/yolov3-voc.cfg ./backup/yolov3-voc_final.weights
此处这里的voc.data 与yolov3-voc.cfg 是你训练自己的数据集类别,在环境配置阶段应该已经完成。
1、 使用yolov3训练时候出现:Can’t open label file, xxxx
这种一般就是你的文件路径设置不对, 查看一下voc.data中训练集与测试集路径问题。
还有一种情况就是,你的图片文件后缀名随机的包含多个.jpg等情况,darknet中代码使用find_replace()进行替换文件的.jpg 与 .txt 这样会导致问题。所以你可以查看一下是不是数据集命名的问题。
例如:你的图片名称为 qaijf.jpg!.png87594.jpg 这种的其实就会出现问题,因为utils.c 文件中find_replace()函数会替换图片名称中出现.jpg的情况(.png .tif等一样会被替换)。
推荐解决方法:
如果你的标签数据已经标注好了,建议使用Generate_Random_image_xml.py(文末百度网盘下载)进行图像数据与标签数据重命名,这里重命名只是改变为数字组成的字符串。因为如果你随机生成字母+数字的话,如果子串里面存在raw将会自动替换为labels,建议不要随机生成字符串。
2 如果你在运行脚本mAp_voc2007.py情况下出现cPickle相关问题
一般是python2.x与python3.x系列下面的pickle没有对应上。如果你使用python2.x版本,那么不需要改动。如果你使用python3.x系列的话,可能需要将cPickle改为pickle,对应位置函数cPickle前缀改为pickle。
3 如果你修改darknet函数里面的c代码,记得要重新make一下。
后续发现其它的训练过程问题,会添加至本博文中。之后,会添加基于windows训练的环境配置过程。推荐看下AlexeyAB的github会有详细的说明。最后,感谢原作者提供的训练说明!如果本文介绍有错误,还请批评指正!
文中py文件下载地址:
链接:https://pan.baidu.com/s/1ugtfpUlhp2v63dXHqSox1A
提取码:rjcs
https://pjreddie.com/darknet/yolo/