Pytorch-yolov3项目使用总结

前言

本文主要的参考资料和github项目如下:

Could not find the Qt platform plugin windows错误解决方法:

https://blog.csdn.net/DonetRen/article/details/106437538

pytorchyolov3训练自己数据集:

https://www.cnblogs.com/pprp/p/10863496.html

https://blog.csdn.net/qq_38587510/article/details/106019905

https://blog.csdn.net/qq_39056987/article/details/104327638

github项目:

labelImg:https://github.com/tzutalin/labelImg

yolov3:https://github.com/ultralytics/yolov3(master是`yolov5`,archive是`yolov3`)

labelImg

安装PyQt5lxml就可以运行了,运行可能会需要配置环境变量

具体参考文章:https://blog.csdn.net/DonetRen/article/details/106437538

数据标注的方式和数据集分割的方式(划分为训练集、验证集和测试集)与keras-yolov3项目是一样的,但是数据格式转换的操作有所不同,pytorch-yolov3项目中没有keras项目中的voc_annotation.py用来转换数据格式,需要使用以下代码来完成操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# -*- coding: utf-8 -*-
"""
Created on Tue Oct 2 11:42:13 2018
此脚本VOC格式数据集构造完毕
需要说明的是:如果打算使用coco评价标准,需要继续构造符合 darknet格式的数据集(coco),构造coco中json格式
如果要求不高,只需要VOC格式即可,使用作者写的mAP计算程序即可
需要修改的地方:
1. sets中替换为自己的数据集
2. classes中替换为自己的类别
3. 将本文件放到VOC2007同级目录
4. 直接开始运行
"""

from xml.etree import ElementTree
import pickle
import os
from os import listdir, getcwd
from os.path import join


sets = [('2007', 'train'), ('2007', 'val'), ('2007', 'test')] # 替换为自己的数据集
# classes = ["head", "eye", "nose"] # 修改为自己的类别
classes = ["target"] # 因为仅有一个类别,所以给自己的类别命名为target


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


def convert_annotation(_year, _image_id):
in_file = open(f'VOC{_year}/Annotations/{_image_id}.xml') # 将数据集放于当前目录下
out_file = open(f'VOC{_year}/labels/{_image_id}.txt', 'w')
# 读取xml文件
tree = ElementTree.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)
xml_box = obj.find('bndbox')
b = (float(xml_box.find('xmin').text),
float(xml_box.find('xmax').text),
float(xml_box.find('ymin').text),
float(xml_box.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(f'VOC{year}/labels/'):
os.makedirs(f'VOC{year}/labels/')
image_ids = open(f'VOC{year}/ImageSets/Main/{image_set}.txt').read().strip().split()
list_file = open(f'{year}_{image_set}.txt', 'w')
for image_id in image_ids:
# 这里的文件路径前面做了修改
list_file.write(f'data/VOCdevkit/VOC{year}/JPEGImages/{image_id}.jpg\n')
convert_annotation(year, image_id)
list_file.close()


# os.system("cat 2007_train.txt 2007_val.txt > train.txt") #修改为自己的数据集用作训练

list_file.write(f'data/VOCdevkit/VOC{year}/JPEGImages/{image_id}.jpg\n')

这里对写入的文件路径做了调整,前面加了data/VOCdevkit,这样就可以直接把VOCdevkit整个项目复制到pytorch-yolov3项目的data目录下。

使用以上脚本生成的

MiniConda

实际上就是可以创建虚拟环境和包管理,但是不得不说使用conda安装包很麻烦,使用国内镜像源下载很多包依然很慢很慢,而且想换源还要不断的改配置,相比较pip是真的好用,使用哪个镜像直接一个-i就可以,而且安装包快的飞起,这次算是对miniconda的一次尝试吧,后面如果再做项目,没有特殊的环境需求,我可能还是倾向于使用统一环境,使用pip来安装包。

补充一个使用pycharm配置conda虚拟环境的方式。

Pytorch YoloV3

apex

apex混合精度加速,无显卡不能加速。若想要使用,需要单独从github拉相关的项目。

修改配置文件

data目录下新建*.data文件,文件内容和说明如下:

1
2
3
4
5
6
classes = 1 # 改成你的数据集的类别个数
train = data/VOCdevkit/VOCdevkit/2007_train.txt # 通过voc_label.py文件生成的txt文件
valid = data/VOCdevkit/VOCdevkit/2007_test.txt # 通过voc_label.py文件生成的txt文件
names = data/yidun.names # 记录类别
backup = backup/ # 记录checkpoint存放位置
eval = coco # 选择map计算方式

backup=backup/eval=coco这两项根据自身实际情况添加即可:

backup=backup/ 指的是会在backup文件夹下存放训练得到的checkpoint;

eval=coco指的是在测试和评判模型时采用coco数据集中的评判标准,倘若加了这一项,还需要对之前生成的.xml文件做转换,将其由VOC格式转换为coco格式的json文件。

特别注意:文件真正的内容不要包含后面的注释,否则运行时会报错

修改yolov3.cfg文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[convolutional]
size=1
stride=1
pad=1
filters=18 # 修改为3*(classes+5)
activation=linear


[yolo]
mask = 0,1,2
anchors = 10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326
classes=1 # 修改为自己的类别数
num=9
jitter=.3
ignore_thresh = .7
truth_thresh = 1
random=1

只需要更改每个[yolo]层前边卷积层的filter个数和[yolo]层的classes数量即可,也就是上面代码注释部分标注的内容,一共三组6处地方需要修改;同样地,实际内容要去掉注释,否则会报错。

在Windows下运行该项目,可能会有多个地方出现打开文件时编码不正确的情况,因为Windows下默认编码为gbk,所以要么将文件编码改为gbk,要么在打开文件的地方设置encoding='utf-8'

预训练权重文件

我们在训练数据的时候,一般使用预训练权重,即yolov3.weights,但是因为其是darket版的权重文件,需要通过一定的方式转换成pytorch版的;另外,除了最初的yolov3模型,还有在此基础上改写的tiny模型和spp模型。

这几个预训练权重文件(Darket版)在weights目录下的download_yolov3_weights.sh文件中给出了说明

文件权重模型转换:

1
2
3
4
5
6
7
8
9
$ git clone https://github.com/ultralytics/yolov3 && cd yolov3

# convert darknet cfg/weights to pytorch model
$ python3 -c "from models import *; convert('cfg/yolov3-spp.cfg', 'weights/yolov3-spp.weights')"
Success: converted 'weights/yolov3-spp.weights' to 'weights/yolov3-spp.pt'

# convert cfg/pytorch model to darknet weights
$ python3 -c "from models import *; convert('cfg/yolov3-spp.cfg', 'weights/yolov3-spp.pt')"
Success: converted 'weights/yolov3-spp.pt' to 'weights/yolov3-spp.weights'

实际上就是使用models.py脚本中的convert方法来转化,并且支持双向转换。不过需要搭配cfg文件夹下的.cfg文件来完成转换,会生成对应名称的.pt文件,反之亦然。

入口train.py

这个yolov3.pt文件在train.py的main函数的weights配置中使用,如果我们是使用命令行运行,那就在命令行中指定--weights参数就好了,如果是在类似pycharm的软件中直接执行,可以直接修改这个值,类似需要修改的还有--cfg, --data等。

命令行运行示例:

python train.py --data data/coco.data --cfg cfg/yolov3.cfg --weights yolov3.pt

另外,按照我这里训练数据存放的形式,还需要在修改datasets.py文件中的这个地方:

replace('images', 'labels') – > replace('JPEGImages', 'labels')(共两处)

因为这里在读取标签时,是直接将文件路径中的images替换为labels,后缀由jpg改为txt,而我并没有把图片放到指定的images文件下,所以这里需要改一下。

当一些必要的调整做完之后,如果不着急进度,我的建议是可以从train.py开始逐步调试,搞清楚整个项目运行的流程,具体细节一开始不用搞的很清楚,只要大概知道哪些方法都是做什么用的就行了,而且,因为一些特殊原因,可能会遇到一些报错,基本一直调试下去,就可以把所有的错误都自己独立的解决掉。

比如上面遇到的编码问题、weights参数报错(pt文件不存在)、修改配置文件不能加注释、改VOC数据生成路径、修改datasets.py等问题,均是在调试过程中发现并解决的。

模型加载

上一次研究yolov3一直不清楚yolov3.cfg这个文件的作用,只是单纯的按照要求修改文件,这次研究了下,发现这个就是yolov3模型的核心配置文件,就是从这个文件加载的神经网络模型,经过对比里面的内容发现与网络结构图一一对应:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[convolutional]
batch_normalize=1
filters=32
size=3
stride=1
pad=1
activation=leaky

# Downsample

[convolutional]
batch_normalize=1
filters=64
size=3
stride=2
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=32
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=64
size=3
stride=1
pad=1
activation=leaky

比如这里截取的部分,对应着Darknet53最开始的地方。

相应的还有yolov3-spp.cfgyolov3-tiny.cfg,对应着稍有不同的模型,使用的时候,要搭配yolov3-spp.weightsyolov3-tiny.weights使用,不过要先转换为.pt文件才能通过pytorch使用。

训练过程

训练过程的输出(上面是输出的代码):

1
s = ('%10s' * 2 + '%10.3g' * 6) % ('%g/%g' % (epoch, epochs - 1), mem, *mloss, len(targets), img_size)
1
2
Epoch   gpu_mem      GIoU       obj       cls     total   targets  img_size
11/299 0G 3.54 1.44 0 4.97 6 480: 100%|██████████| 16/16 [04:12<00:00, 15.77s/it]

Epoch:当前轮次

gpu_mem:推测大概是GPU占的内存吧,如果没有GPU,那就是0

GIoU:一种类似IoU(交并比)的算法,使用其作为目标损失函数;

obj:objectness(置信度);

cls:classification

*/16表示共16个批次,当前训练到第几批,因为这里总共244张图片,batch_size=16,所以总共16个batch,最后一个batch的size只有4。

验证过程的输出:

1
2
Class    Images   Targets         P         R   mAP@0.5        F1: 100%|██████████| 3/3 [00:24<00:00,  8.18s/it]
all 48 48 0.911 0.917 0.892 0.914

Class

Images:表示有多少张图片

Targets:表示有多少个要检测的目标

P:Precision准确率

R:Recall召回率

mAPMean Average Precision 的缩写,即均值平均精度。
作为 object dection 中衡量检测精度的指标。
计算公式为: 所有类别的平均精度求和除以所有类别。

F1

模型保存

训练的过程中,会在weights文件夹下生成best.ptlast.pt两个权重文件,last.pt随着训练轮次不断更新,best.pt则保留截止当前训练过程中最好的权重。

测试

测试使用detect.py脚本,与train.py类似,运行时通过很多命令行参数来配置,如果是直接在pycharm中运行,那么直接修改相应的参数值就好了,比如这几个参数

1
2
3
4
parser.add_argument('--cfg', type=str, default='cfg/yolov3.cfg', help='*.cfg path')
parser.add_argument('--names', type=str, default='data/yidun.names', help='*.names path')
parser.add_argument('--weights', type=str, default='weights/best.pt', help='weights path')
parser.add_argument('--source', type=str, default='data/samples', help='source') # input file/folder, 0 for webcam

cfgnames这两个和train.py保持一致,weights这里改成我们训练过程中保存下来的最好的模型权重;

source这里需要注意,这里默认的是读取data/samples目录下的图片来测试,后面的注释还写了如果设置为0,他会测试视频;因为我上面说了我是直接把数据集都放在了data/VOCdevkit下,samples里没有图片,如果要测试的话,我还需要把测试集的图片复制过去,所以我想是不是可以把这里的source值改成我的测试目录,所以就改成了data/VOCdevkit/2007_test.txt(这个文件里保存了所有测试图片的路径,但是不是实际的测试图片)我也不确定这个source这样设置是否可行,所以我就去调试了,调试发现

1
2
3
4
5
6
7
8
9
webcam = source == '0' or source.startswith('rtsp') or source.startswith('http') or source.endswith('.txt')

if webcam:
view_img = True
torch.backends.cudnn.benchmark = True # set True to speed up constant image size inference
dataset = LoadStreams(source, img_size=imgsz)
else:
save_img = True
dataset = LoadImages(source, img_size=imgsz)

因为我这里source设置的值满足后缀是.txt,所以webcam=True,然后接下来走了视频测试的流程;

然后我就把webcam主动设置为False,让程序去走图片检测的流程,最终发现source的值要求该路径下必须是实打实的图片,所以只好把测试图片全部放到samples路径下,经过下面简单的代码移动就可以了:

1
2
3
4
5
6
7
8
with open(r'data/VOCdevkit/2007_test.txt', 'r', encoding='utf-8') as fr:
images = fr.readlines()
for i, image in enumerate(images):
with open(image.strip(), 'rb') as f:
image_content = f.read()
fw = open(f'data/samples/test{i+1}.jpg', 'wb')
fw.write(image_content)
fw.close()

预测完成之后,会在output文件夹下,生成检测结果——带有检测框的图片,直接打开查看框是否准确即可。

项目实践过程:

1、切分图片并从CAD导出数据集

2、标注数据

3、训练数据,无效果–>怀疑是尺寸问题

4、resize图片和标注好的xml文件

5、利用k-means算法重新生成anchor box的大小,有初步的效果。