赞
踩
参考:https://github.com/AlexeyAB/darknet
记录以下流程和错误,之后再用到,查询起来也快些。
翻译了部分参照文档,并按照流程走的~
提前工作有:
1.克隆了darknet代码;
2.数据已经标注完毕。且生成了对应的标签数据集
标注工具使用了labelme,生成的是json文件,需要转换为txt格式的。
将labelme标记的.json
数据,转为yolov4训练用的.txt
数据。
图片和json文件保存在obj文件夹里。
python代码:
from os import getcwd import numpy as np import os import json import glob wd = getcwd() "labelme标注的json数据集转为keras yolo的txt训练集" classes = ["car","pedestrian","truck","bus"] #修改为待检测的类别名 image_ids = glob.glob(r"obj/*.jpg") #jpg和json文件都在文件夹obj/里 print(image_ids) def convert_annotation(image_id): jsonfile=open('%s.json' % (image_id)) in_file = json.load(jsonfile) #print(in_file) height=in_file["imageHeight"] width=in_file["imageWidth"] size=[width,height] list_file = open('%s.txt'%(image_id.split('.')[0]), 'w') for i in range(0,len(in_file["shapes"])): object=in_file["shapes"][i] cls=object["label"] points=object["points"] dw = 1./(size[0]) dh = 1./(size[1]) min_x=min_y= np.inf max_x = max_y = 0 for x, y in points: min_x = min(min_x, x) min_y = min(min_y, y) max_x = max(max_x, x) max_y = max(max_y, y) x=(min_x+max_x)/2.0 print(x) y=(min_y+max_y)/2.0 print(y) w=max_x-min_x h=max_y-min_y x = x*dw w = w*dw y = y*dh h = h*dh if cls not in classes: print("cls not in classes") continue cls_id = classes.index(cls) b = (x, y, w, h) list_file.write(str(cls_id)+" "+" ".join([str(a) for a in b]) ) list_file.write('\n') list_file.close() jsonfile.close() for image_id in image_ids: # list_file.write('%s.jpg' % (image_id.split('.')[0])) convert_annotation(image_id.split('.')[0])
该脚本程序会为每个
.jpg
文件生成一个对应的.txt
文件,而且都在obj目录下。一张图片,对应一个txt标注文件,它们同名但不同后缀。
得到图片和对应的txt标注文件之后,接下来需要生成train.txt和val.txt。
这两个txt文件保存图片的路径(可以是绝对路径,也可以是相对路径,相对路径是相对于darknet主目录而言)。本人使用了相对路径。
训练集和验证集的划分,按照一定的比例,在脚本gene_trainval.py中,比例使用变量ratio控制。
gene_trainval.py代码:
# 检验.txt 对应的.jpg或.jpeg或.png文件是否存在、是否可读(此处调用了cv2库) # 按照训练集和验证机的比例,生成train.txt和val.txt import os import random import cv2 # 默认img和txt在同一个文件夹 path='obj' # 训练集:测试集 = 0.85:0.15 ratio=0.85 def check(img_path): # 判断文件是否可读 image=cv2.imread(img_path) if image is None: return False else: return True def main(): filenames=os.listdir(path) # txt目标注释文件列表 annoList=[] for file in filenames: # 文件名 name=file.split('.')[0] # 文件后缀 suffix=file.split('.')[-1] # 是图片 if suffix in ['jpg','jpeg','png']: # 如果没有安装python版cv2,且确定图片无损坏,则注释此行、import cv2、check()即可。 assert check(path + '/' + file)==True, '%s is unreadable'%(file) # 图片对应的txt文件 anno= name + '.txt' if os.path.isfile(path + '/'+ anno): annoList.append(file) else: print('%s without annotation file' %(file) ) # 乱序 random.shuffle(annoList) length=len(annoList) # 训练集 f=open('./train.txt','w') for anno in annoList[:int(ratio*length)]: f.write('./data/' + path + '/' + anno + '\n') f.close() # 测试集 f=open('./val.txt','w') for anno in annoList[int(ratio*length):]: f.write('./data/' + path + '/' + anno + '\n') f.close() if __name__=='__main__': main() print('generate trainval.txt success')
现在,我们得到了obj文件夹,里面有图片和txt标注文件,得到了train.txt和val.txt文件,它们保存图片的路径。
当前目录结构如下:
darknet/
data/
obj/
train.txt
test.txt
gene_tainval.py
.data
文件在data
目录里创建.data文件,tree
:
darknet/
data/
obj.names # 物体类别名称
obj.data # 该文件夹保存数据集信息
obj/ # 存放图片以及标签信息
train.txt # 存放训练集地址
test.txt # 存放检测集地址
以下分别解释data/
下5个文件(夹)的作用:
obj.name
保存要检测物体的名称,一行写一个例如:
car
truck
obj.data
保存有五类信息:检测类别数量,训练集,验证集,类别名称,保存权重的文件夹,比如:
classes= 2 # 目标物体的类别数量
train= ./data/train.txt # 训练数据集的路径
valid= ./data/test.txt # 验证数据集的路径
names= ./data/obj.names # 目标物体的类别名称
backup = ./backup # 保存训练权重文件
文件夹obj/
存放数据集图片和每张图片的标签信息,即:
001.jpg
001.txt
002.jpg
002.txt
其中,每个.txt
是对应.jpg
的标签文件,其包含的信息有:
<object-class> <x_center> <y_center> <width> <height>
本人是将训练集和验证集的图片,都放在了obj文件夹里。
每个
.jpg
文件,都有同名但.txt
的文件相对应,且在obj文件夹里。001.txt文件中一行对应001.jpg中一个物体信息:类别 中心点x坐标 中心点y坐标 宽 高 ,可能有多行。
坐标计算方式如下:
<x_center> = <absolute_x> / <image_width> 即bounding box中心x实际坐标 / 图片实际宽度
<y_center> = <absolute_y> / <image_height> 即bounding box中心y实际坐标/图片实际高度
<width> = <absolute_width> / <image_width> 即bbox宽度/图片实际宽度
<height> = <absolute_width> / <image_width> 即bbox高度 /图片实际高度
train.txt
保存所有训练集图片的相对地址,如:
./data/obj/img001.jpg
./data/obj/img002.jpg
./data/obj/img003.jpg
test.txt
保存所有验证集图片的相对地址,如:
./data/obj/img11.jpg
./data/obj/img21.jpg
./sdata/obj/img31.jpg
.cfg
文件cfg文件保存网络结构,darknet是实现该网络结构的一种框架,同pytorch框架一样,只是前者比较小众(c++语言写的)。
复制cfg
文件夹下yolov4.cfg
为yolov4-obg.cfg
(此处名字随意,所在文件夹也任意,但训练或测试,的时候,cfg文件的路径,是命令的参数之一)
修改yolov4-obj.cfg
中的内容:
# step1: 修改batch和subdivisions L2: batch=64 # 原来就是64,根据gpu自己选择,但必须是2的倍数 L3: subdivisions=16 # 根据自己的gpu选择,如果out of memory,则32或64,依次倍增。或者64,32,...,1,依次倍减batch值 # step2: 修改图片的尺寸 L7: width=608 # 这边我没有修改,该值必须被32整除。 L8: height=608 L19: max_batches=500500 #该值最少是classes*2000 L21:steps=400000,450000 #该值是 max_batches的80% 和 90% # step3: 修改classes(每个yolo层都需要修改一次,一共三次) L968: classes=2 # 和obj.data文件中classes值相同,=2表示只识别两类物体 L1056: classes=2 L1144: classes=2 # step4: 修改每个yolo相邻的上一个convolution层的filter,一共3个 L961: filters=21 # 因为我预测两类物体:21 = 3*(5+2),具体看下文解释 L1049: filters=21 L1137: filters=21
L2,L3表示第2行,第3行。
filters=21 是3*(5+2)=21得来的。
如果一共检测4类物体,比如:car,pedestrian,truck,bus,则filters的值为3*(5+4)=27。
若无GPU,则不做任何修改,否则一些参数应修改为:
GPU=1: 表示在训练的时候使用CUDA进行加速训练(CUDA应该在 /usr/local/cuda文件夹下),若无,
CUDNN=1: 表示在训练的过程中使用CUDNN v5-v7进行加速(cuDNN应该在 /usr/local/cudnn文件夹下)
CUDNN_half=0: 为Tensor Cores (在Titan V / Tesla V100 / DGX-2等)上进行加速训练和推理。
OPENCV=1: 编译OpenCV 4.x/3.x/2.4.x等。OpenCV可以读取视频或者图片。
#L69 将NVCC=nvcc改为你自己的nvcc的路径
NVCC=/usr/local/cuda/bin/nvcc
注意L109和L121以下的3行:看一下cuda的默认路径,是否和自己的一致,不一致就需要修改。有的人是NVCC=/usr/local/cuda9.0/bin/nvcc,则需要修改。
以上就是开始训练前的准备。
下载预训练权值yolov4.conv.137
,放在darknet/文件夹下。
执行命令
./darknet detector train data/obj.data cfg/yolov4-obj.cfg yolov4.conv.137 -map
这这里,如果opencv和cudnn没有装错的话,就开启正常训练了。如果batch和subdivision参数设置合理的话,基本不会出现内存不足而中途退出的情况。
启动训练一段时间之后,如果出现“out of memory,核心已转储” ,则调整yolov4-obj.cfg文件中的batch值(每次调小2倍),或subdivision值(每次调大2倍)。
然后,在backup文件夹内,找到最后的权重文件(.weights后缀)的文件名,比如:last.weights
,然后使用该参数替换上述训练命令中的weights路径参数,从中断处继续训练。示例:
./darknet detector train ./data/obj.data ./cfg/yolov4-obj.cfg ./backup/last.weights -map
迭代一定的次数后,会自动将训练的权重文件保存在backup文件夹下,且会以迭代次数作为名字。注意mAP图的变化,找到最优值所在的迭代次数,对应的权重文件,就是最适合的。
1.指定GPU:
上述训练命令,如果没有指定GPU,则默认是0号卡。可以在命令后面加参数i-
指定。比如:
#如果想要指定具体的gpu进行训练,使用-i来指定,比如我想使用索引为2的gpu进行训练:
./darknet detector train data/obj.data cfg/yolov4-obj.cfg yolov4.conv.137 -i 2 -map
2.训练时不显示mAP和loss变化图:使用参数-dont_show
./darknet detector train data/obj.data cfg/yolo-obj.cfg yolov4.conv.137 -dont_show
3.在远程网页端,显示mAP&Loss图:
./darknet detector train data/obj.data cfg/yolo-obj.cfg yolov4.conv.137 -dont_show -mjpeg_port 8090 -map
然后使用浏览器打开链接:http://ip-address:8090
训练过程中,darknet自动保存权值文件到backup/
backup文件夹下的yolo-obj_last.weights文件会每隔1000个iterations保存一次,新的会替代旧的
darknet总的训练步长可以在yolov4-obj.cfg文件中修改max_batches的大小(默认max_batches = 500500)
-map表示可视化显示mAP&Loss 图表,每4Epochs
训练完成后,在backup/查看结果文件:yolo-obj_final.weights
每100次迭代,比如2000次迭代后,由于外因,你停止了检测,之后可以使用:
./darknet detector train data/obj.data yolo-obj.cfg backup/yolo-obj_2000.weights
来继续训练。当avg-average loss (error)值达到最低时,你也可以提前停止训练。
关于提前终止训练,官网是这样说的:
如果你在9000 iterations停止训练,可能最好的模型在7000, 8000, 9000之间,不确定会不会产生过拟合,因此你需要找到提前停止训练的点(Early Stopping Point),如图所示:
(官网的图)在Early Stopping Point处,权值文件是最佳的。
如果需要测试权重文件的mAP,则:
准备验证集。
你需要在 obj.data中将valid = data/test.txt 修改成 valid = data/valid.txt (valid.txt的格式和train.txt一样),如果你没有验证集,你可以直接将train.txt的内容直接复制到valid.txt文件中。
测试候选权重的mAP。
如果你是在9000 iterations停止训练的,你就需要对之前保存的权重进行测试:
测试mAP的命令如下:
step1.测试7000 iterations的mAP:
./darknet detector map data/obj.data cfg/yolov4-obj.cfg backup/yolo-obj_7000.weights
step2: 测试8000 iterations的mAP
./darknet detector map data/obj.data cfg/yolov4-obj.cfg backup/yolo-obj_8000.weights
step3: 测试9000 iterations的mAP
./darknet detector map data/obj.data cfg/yolov4-obj.cfg backup/yolo-obj_9000.weights
选择mAP值或者IOU值最高的权重文件,该文件就是最优的训练权重。
这样就可以显示训练时的mAP,根据训练过程图来选择最合适的权重了。
训练好权重之后,我们可以使用权重进行检测,在终端中输入下面的命令检测图片:
./darknet detector test ./data/obj.data ./cfg/yolo-obj.cfg ./backup/yolo-obj_8000.weights ./data/test.jpg
检测视频:
# 只显示FPS,不显示图像
./darknet detector demo ./data/obj.data ./cfg/yolov4-obj.cfg ./backup/yolov4-obj_9000.weights ./demo3.mp4 -dont_show -out_filename res.mp4
# 显示FPS,显示图像,保存检测视频结果
./darknet detector demo data/obj.data cfg/yolov4-obj.cfg backup/yolov4-obj_9000.weights data/demo3.mp4 -ext_output -out_filename res.mp4
AlexeyAB/darknet官网代码中有自带两个脚本:darknet_video.py和darknet_images.py,它们调用darknet编译生成的lib库(位于darknet主目录下)分别进行视频检测和图像检测。通过该脚本,可以进行相关的后处理。为了生成libdarknet,需设置Makefile
文件第7行lIBSO=1
,从新编译。
修改:
- 脚本中的parse()函数:添加
.data
文件路径、.cfg
文件路径、.weights
文件路径。- args参数
相关:通过标注文件中的坐标信息,画出物体的标注框
import os import cv2 obj_path='./image' # image + txt out_path='./out' # after drow box,save to ./out fileList=os.listdir(obj_path) total=len(fileList)//2 def bbox2points(x,y,w,h): # center point to corner point xmin = int(round(x - (w / 2))) xmax = int(round(x + (w / 2))) ymin = int(round(y - (h / 2))) ymax = int(round(y + (h / 2))) return xmin, ymin, xmax, ymax def draw_box(img,txt): f=open(obj_path + '/' + txt) image=cv2.imread(obj_path + '/'+ img) output=image.copy() height,width=image.shape[0],image.shape[1] for line in f.readlines(): _,x,y,w,h=line.split(' ') x,y,w,h=float(x),float(y),float(w),float(h) x=x*width;y=y*height w=w*width;h=h*height xmin,ymin,xmax,ymax=bbox2points(x,y,w,h) cv2.rectangle(output, (xmin, ymin),(xmax, ymax), (0,255,0), 1) cv2.imwrite(out_path + '/' + img,output) def main(): fail_list=[] for i,file in enumerate(fileList): print('[%d/%d]' % (i,total) ) suffix=file.split('.')[-1] if suffix in ['jpg','jpeg','png']: txt=file.split('.')[0] + '.txt' if os.path.isfile(obj_path + '/'+ txt): draw_box(file,txt) print(file) else: print('%s is not find' % (txt) ) fail_list.append(txt) if len(fail_list)==0: print('all success') else: print('nof found txt') for i in fail_list: print(i) if __name__=='__main__': main()
[1] https://github.com/AlexeyAB/darknet
以上。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。