yolov4模型训练
yolov4 模型训练
发现网上关于yolo模型的训练资料很少 所以在这里总结一下各种的坑 和 如何训练自己的数据集
和一些关于yolov4 参数的解释 方便快速的玩起来yolov4
关于为什么用yolov4而不用yolov5: yolov5的官方框架是pytorch 而我为了将模型部署到树莓派上而尽量少的装环境 所以选择了 基于darknet的yolov4 这样训练出来的模型就可以直接的被opencv的dnn模块读取
关于数据集
拖了快半年了 终于写完了
这里采用VOC的格式来组织数据结构
| --- Annotations 存放VOC数据集的标签标注格式 .xml
| -- train.txt
VOC--------------| --- ImageSets--Main--| train 和 val 的编号
| -- val.txt
| --- JPEGImages 存放训练图片
| --- labels 用来存放图片的标签 yolo类型标签
| --- TESTImages 测试图片(val和test的区别): val 训练时的测试集 test完成后自己用来验证的图片
1. 数据集文件夹生成
点击查看代码
import os
import time
'''
该程序用生成 VOC数据集文件夹结构
结构如下:
| --- Annotations 存放voc格式标签
| -- train.txt
数据集名称 -----| --- ImageSets--Main--| train 和 val 的编号
| -- val.txt
| --- JPEGImages 存放训练图片
| --- labels 用来存放图片的标签 yolo类型标签
| --- TESTImages 测试图片(val和test的区别): val 训练时的测试集 test完成后自己用来验证的图片
'''
# 本数据集名称
FNAME = "testData"
# 本数据集生成的位置
FPATH = "../"
def mkFolder(path,fname):
floderName = fname # 生成主文件夹的名称,比如今年是2021,则主文件夹的名称为VOC2021
# path = os.path.normpath(path)
# 主文件夹路径
floderPath = os.path.join(path, floderName)
if not os.path.exists(floderPath):
# 创建文件夹
os.makedirs(floderPath)
print(floderPath)
# 子文件夹名称
subfloderName = ['Annotations', 'ImageSets', 'JPEGImages', 'labels', 'TESTImages'] # 生成的子文件夹名称
for name in subfloderName:
# 子文件夹路径
subfloderPath = os.path.join(path, floderName, name)
print(subfloderPath)
if not os.path.exists(subfloderPath):
# 不存在就创建
os.makedirs(subfloderPath)
# 如果是图片文件夹 就再创建Main
# Main内用来存放 train.txt 和 val.txt 用来写路径 训练图片 和 验证图片的路径
if name == 'ImageSets':
secSubFolderName = 'Main'
secSubFolderPath = os.path.join(path, floderName, name, secSubFolderName)
print(secSubFolderPath)
if not os.path.exists(secSubFolderPath):
os.makedirs(secSubFolderPath)
if __name__ == '__main__':
mkFolder(FPATH,FNAME)
2. 将待训练图片移入JPEGImages文件夹
3. 对图片进行改名
点击查看代码
import os
'''
本程序用于图形名称修改
'''
# 图片存放的路径
PATH = r"D:\yolov4\VOC\lj\JPEGImages"
# 遍历更改文件名
num = 1
for file in os.listdir(PATH):
os.rename(os.path.join(PATH,file),os.path.join(PATH,str(num))+".jpg")
num = num + 1
4. 对图片取标签
需要用到 labelImg
生成的xml文件 保存到 Annotations文件夹内
5. 对图片和标签划分数据集与验证集(如果验证集不拿来训练和测试集是一样的)
会生成 两个文件.txt 在ImageSets/Main在内
分别存储两个数据集的图片名称
点击查看代码
import os
'''
本程序用于分割数据集 和 训练集
'''
if __name__ == '__main__':
source_folder = r'D:/yolov4/VOC/lj/Annotations' # 图片源目录
train = r'../TestData/ImageSets/Main/train.txt' # train.txt文件路径
val = r'../TestData/ImageSets/Main/val.txt' # val.txt文件路径
# 拿到dir内的文件名
file_list = os.listdir(source_folder)
file_num = 0
with open(train, mode='a', encoding='utf-8') as train_file:
with open(val, mode='a', encoding='utf-8') as val_file:
for file_obj in file_list:
file_path = os.path.join(source_folder, file_obj)
# 拿到路径和后缀
file_name, file_extend = os.path.splitext(file_obj)
file_num = file_num + 1
if file_num % 4 == 0: # 每隔4张选取一张验证集
# 可自由选择训练集与测试集比例如果想设为9:1则把4改成9
val_file.write(file_name + '\n')
else:
train_file.write(file_name + '\n')
6. 生成yolov4可以识别的标签 .xml --> .txt放入 labels文件夹内
并且根据 ImageSets生成 路径训练集和测试集 图片的路径文件
还生成.names文件
点击查看代码
import xml.etree.ElementTree as ET
import os
'''
本程序将voc的mxl文标签转换为yolo的txt标签 存储在labels文件内
并创建xx_train.txt,xx_val.txt 将图片名称转为图片路径存储
'''
# 生成标签存储路径
LABELPATH = r'D:\yolov4\VOC\testData\labels'
# 图片名称文件 路径
IMGSETPATH = r'../testData/ImageSets/Main/%s.txt'
# 图片路径文件 的保存路径
SAVEPATH = r'../testData/testData_%s.txt'
# 这里是XML文件的路径
XMLPATH = r'../testData/Annotations/%s.xml'
# 这里是图片路径
IMGPATH= 'D:/yolov4/VOC/testData/JPEGImages/%s.jpg\n'
# 这里写入 标签的名称 标定时候的名称 转换成txt时候 会被消失 所以需要 .name文件
classes = ["lj"]
# 不需要配置
sets = ['train', 'val'] ##这里要与Main中的txt文件一致
# 位置转换函数
def convert(size, box):
dw = 1. / (size[0])
dh = 1. / (size[1])
x = (box[0] + box[1]) / 2.0 - 1
y = (box[2] + box[3]) / 2.0 - 1
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)
# image_id图片的名称 xml转换成yolo文件
def convert_annotation(image_id):
# 输入 文件xxx.xml 表示某张图片的xml标签
in_file = open(XMLPATH % image_id, encoding="utf-8")
# 输出 文件txt yolo标签 名称和图片一样
out_file = open(LABELPATH + "\%s.txt" % image_id, 'w', encoding="utf-8")
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')
# 循环 分别转换 train 和 val
for image_set in sets:
# 如果不存在 labels创建labels
if not os.path.exists(LABELPATH):
os.makedirs(LABELPATH)
# 读取图片名称 train.txt里面放的是图片的名称
image_ids = open(IMGSETPATH % image_set,encoding='utf-8').read().strip('\n').split('\n')
# 写入到 year_train.txt or year_test.txt
# train和val的路径文件放在VOC文件夹内
list_file = open(SAVEPATH % image_set, 'w')
# 循环获取名称
for image_id in image_ids:
# 在year_train和year_val内写入图片的路径
list_file.write(IMGPATH%image_id)
convert_annotation(image_id)
list_file.close()
7. 创建.data文件 这个文件里面记录了我们所有需要的文件的路径
点击查看代码
# .data 文件的保存路径
DATASAVEPATH = r"D:\yolov4\VOC\testData\mydata.data"
CLASSNUM = 1
TRAINPATH = r"D:\yolov4\VOC\testData\testData_train.txt"
VALIDPATH = r"D:\yolov4\VOC\testData\testData_val.txt"
NAMEPATH = r"D:\yolov4\VOC\testData\name.names"
BACKPATH = r"D:\yolov4\VOC\testData\mymodel"
with open(file=DATASAVEPATH,mode="wt",encoding='utf-8') as f:
f.write("classes = {}\n".format(CLASSNUM))
f.write("train =" + TRAINPATH + '\n')
f.write("valid = " + VALIDPATH + '\n')
f.write("names = " + NAMEPATH + '\n')
f.write("backup = "+ BACKPATH + '\n')
8. 修改cfg文件(模型配置文件)
这是yolov4.cfg 其他的看着改
把第三行batch改为batch=64
把subdivisions那一行改为 subdivisions=16
将max_batch更改为(数据集标签种类数(classes)*2000 但不小于训练的图片数量以及不小于6000)
将第20的steps改为max_batch的0.8倍和0.9倍
把位于8-9行设为width=416 height=416 或者其他32的倍数:
将classes=80 改为你的类别数 (有三个地方,969行,1056行,1143行)
改正[filters=255] 为 filters=(classes + 5)x3 (位置为查找yolo,每个yolo前的[convolutional]里,注意只修改最接近yolo的那个filters需要修改,一共应该有三处)
如果使用 [Gaussian_yolo] 层,修改 filters=(classes + 9)x3 (位置为CRRL+F查找Gaussian_yolo,每个Gaussian_yolo前的[convolutional]里,注意只修改最接近Gaussian_yolo的那个filters需要修改,一共应该有三处)
————————————————
版权声明:本文为CSDN博主「雪回」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Mintary/article/details/112511251
9.训练模型
darknet.exe detector train D:\yolov4\VOC\testData\mydata.data D:\yolov4\VOC\testData\mymodel\my_yolo_tiny.cfg D:\yolov4\VOC\testData\mymodel\yolov4-tiny.conv.29
.data .cfg .conv.29
10. 利用opencv加载模型
点击查看代码
import numpy as np
import time
import cv2
'''
模型测试程序
NAMEPATH 标签路径
MODELPATH 模型路径
WEIGHTPATH 权值路径
'''
NAMEPATH = "../../I_train_model/my_voc.names"
MODELPATH = '../../I_train_model/my_yolo_tiny.cfg'
WEIGHTPATH = '../../I_train_model/my_yolo_tiny_last.weights'
LABELS = open(NAMEPATH).read().strip().split("\n")
np.random.seed(666)
COLORS = np.random.randint(0, 255, size=(len(LABELS), 3), dtype="uint8")
# 导入 YOLO 配置和权重文件并加载网络:
net = cv2.dnn.readNetFromDarknet(MODELPATH, WEIGHTPATH)
# 获取 YOLO 未连接的输出图层
layer = net.getUnconnectedOutLayersNames()
cap = cv2.VideoCapture(0)
while cap.isOpened():
_,image = cap.read()
# image = cv2.imread('I_train_model/img.png')
# 获取图片尺寸
(H, W) = image.shape[:2]
# 从输入图像构造一个 blob,然后执行 YOLO 对象检测器的前向传递,给我们边界盒和相关概率
blob = cv2.dnn.blobFromImage(image, 1/255.0, (320, 320),
swapRB=True, crop=False)
net.setInput(blob)
start = time.time()
# 前向传递,获得信息
layerOutputs = net.forward(layer)
# 用于得出检测时间
end = time.time()
print("YOLO took {:.6f} seconds".format(end - start))
boxes = []
confidences = []
classIDs = []
# 循环提取每个输出层
for output in layerOutputs:
# 循环提取每个框
for detection in output:
# 提取当前目标的类 ID 和置信度
scores = detection[5:]
classID = np.argmax(scores)
confidence = scores[classID]
# 通过确保检测概率大于最小概率来过滤弱预测
if confidence > 0.5:
# 将边界框坐标相对于图像的大小进行缩放,YOLO 返回的是边界框的中心(x, y)坐标,
# 后面是边界框的宽度和高度
box = detection[0:4] * np.array([W, H, W, H])
(centerX, centerY, width, height) = box.astype("int")
# 转换出边框左上角坐标
x = int(centerX - (width / 2))
y = int(centerY - (height / 2))
# 更新边界框坐标、置信度和类 id 的列表
boxes.append([x, y, int(width), int(height)])
confidences.append(float(confidence))
classIDs.append(classID)
# 非最大值抑制,确定唯一边框
idxs = cv2.dnn.NMSBoxes(boxes, confidences, 0.5, 0.3)
# 确定每个对象至少有一个框存在
if len(idxs) > 0:
# 循环画出保存的边框
for i in idxs.flatten():
# 提取坐标和宽度
(x, y) = (boxes[i][0], boxes[i][1])
(w, h) = (boxes[i][2], boxes[i][3])
# 画出边框和标签
color = [int(c) for c in COLORS[classIDs[i]]]
cv2.rectangle(image, (x, y), (x + w, y + h), color, 1, lineType=cv2.LINE_AA)
text = "{}: {:.4f}".format(LABELS[classIDs[i]], confidences[i])
cv2.putText(image, text, (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX,
0.5, color, 1, lineType=cv2.LINE_AA)
cv2.imshow("Tag", image)
cmd = cv2.waitKey(1)
if cmd == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
cfg参数
[net]
# Testing # 测试时,batch和subdivisions设置为1,否则可能出错。
#batch=1 # 大一些可以减小训练震荡及训练时NAN的出现。
#subdivisions=1 # 必须为为8的倍数,显存吃紧可以设成32或64。
# Training
batch=64 # 训练过程中将64张图一次性加载进内存,前向传播后将64张图的loss累加求平均,再一次性后向传播更新权重。
subdivisions=16 # 一个batch分16次完成前向传播,即每次计算4张。
width=608 # 网络输入的宽。
height=608 # 网络输入的高。
channels=3 # 网络输入的通道数。
momentum=0.949 # 动量梯度下降优化方法中的动量参数,更新的时候在一定程度上保留之前更新的方向。
decay=0.0005 # 权重衰减正则项,用于防止过拟合。
angle=0 # 数据增强参数,通过旋转角度来生成更多训练样本。
saturation = 1.5 # 数据增强参数,通过调整饱和度来生成更多训练样本。
exposure = 1.5 # 数据增强参数,通过调整曝光量来生成更多训练样本。
hue=.1 # 数据增强参数,通过调整色调来生成更多训练样本。
learning_rate=0.001 # 学习率。
burn_in=1000 # 在迭代次数小于burn_in时,学习率的更新为一种方式,大于burn_in时,采用policy的更新方式。
max_batches = 500500 #训练迭代次数,跑完一个batch为一次,一般为类别数*2000,训练样本少或train from scratch可适当增加。
policy=steps # 学习率调整的策略。
steps=400000,450000 # 动态调整学习率,steps可以取max_batches的0.8~0.9。
scales=.1,.1 # 迭代到steps(1)次时,学习率衰减十倍,steps(2)次时,学习率又会在前一个学习率的基础上衰减十倍。
#cutmix=1 # cutmix数据增强,将一部分区域cut掉但不填充0像素而是随机填充训练集中的其他数据的区域像素值,分类结果按一定的比例分配。
mosaic=1 # 马赛克数据增强,取四张图,随机缩放、随机裁剪、随机排布的方式拼接,详见上述代码分析。

浙公网安备 33010602011771号