YoloV3笔记(二):
文章目录
最近对YoloV3进行了一段学习,在此记录下学习笔记。
(注:此文主要讲解对数据的处理与代码的实现。)
首先,分享学习资源:
YOLO-V3硬核讲解(第三部分-YOLO-V3数据制作+代码实现)_哔哩哔哩_bilibili
直接上代码讲解:
1.XML文件处理 Data_solve.py
首先,做为小白的我这次通过学习才知道了困扰我好久的.XML文件怎么来的,这里教学视频中推荐了标注精灵这个软件,可对图片进行标注,建立对应的XML文件,这里给出学习资源:
深度学习数据标注(告别labelme和labelimg)_哔哩哔哩_bilibili
还不懂XML文件怎么创建的可观看以上视频。
# 获取图像标签等信息
import math
import xml.etree.ElementTree as et
import os
class_num = {
'person':0,
'horse':1,
'bicycle':2
}
# 定义一个字典用于键值对对应
xml_dir = 'Data/image_voc' # XML文件的地址
xml_filenames = os.listdir(xml_dir) # 获取文件夹中所有XML文件路径
with open('data.text','a') as f:
for xml_filename in xml_filenames:
xml_filename_path = os.path.join(xml_dir,xml_filename) # 遍历拼接所有XML文件的路径
# 以下元素树形式
"""
这里简单提提元素树:
找到根节点后,则可以进行各种操作
查找子节点:
递归查找所有子节点:obj.iter('Name')。
非递归查找所有子节点:obj.findall('Name')
"""
tree = et.parse(xml_filename_path) #用于读取XML文件内容
root = tree.getroot() # 获取根节点
filename = root.find('filename')
names = root.findall('object/name')
boxs = root.findall('object/bndbox')
# 找到XML文件下所有的name和box
data = []
data.append(filename.text)
for name,box in zip(names,boxs):
cls = class_num[name.text] #种类 取下标
# 计算中心点偏移量
cx,cy,w,h = math.floor((int(box[2].text)-int(box[0].text))/2),math.floor((int(box[3].text)-int(box[1].text))/2),int(box[2].text)-int(box[0].text),int(box[3].text)-int(box[1].text)
data.append(cls) # 类别数 可字典查询
data.append(cx)
data.append(cy)
data.append(w)
data.append(h)
# 将算出来的数据存储在data中
_str = ''
for i in data:
_str = _str + ' ' +str(i)
# 将处理后的数据按’ ‘分割开以后,写入data.txt文件中
f.write(_str+'\n')
f.close()
代码完成后效果如下:
2.对图片格式进行处理,防止缩放图片改变 utils.py
因为直接将480*364的图片进行缩放会使图像发生变化
所以采取取图像最大边做正方形,将图片“贴入”其中,未黏贴位置采用灰度补充,实现等比缩放
from PIL import Image
from config import *
def make_416_Image(image_path):
img = Image.open(image_path) #读取文件
w,h = img.size[0],img.size[1] #取宽高
print(w,h)
temp = max(w,h) #找最长边
mask = Image.new(mode = 'RGB',size = (temp,temp),color = (0,0,0)) # 最大边做灰度图
mask.paste(img) #贴图
return mask
if __name__ == '__main__':
mask = make_416_Image('Data/images/000017.jpg')
mask.show()
3.定义三个框(13×13,26×26,52×52) config.py
DATA_WIDTH = 416 #宽
DATA_HEIGHT = 416 #高
CLASS_NUM = 3 #种类数
antors = {
13:[[168,302],[27,221],[336,284]],
26:[[129,209],[85,413],[44,42]],
52:[[129,209],[85,413],[44,42]]
}
# kmeans的方法 分出9个框 将差值不同的数据分类
# 13*13 26*26 52*52 各有三个框
ANTORS_AREA = {
13:[x*y for x,y in antors[13]],
26:[x*y for x,y in antors[26]],
52:[x*y for x,y in antors[52]]
}
print(ANTORS_AREA[13])
用Kmeans的方法归类数据(不断计算更新中心点的距离,计算IOU)。(网上很多讲解,可以去b站取取经)
IOU = 交集/并集,越大说明差异越小。
简单来说,Kmeans(就是把挨得近的数据点划分到一起)进行的是如下操作,:
1.定义有多少类(簇)
2.将每一个类的类心随机定义在一个点上
3.将每个点与距离其最近的类关联起来
4.对每个簇找到的所有点取中心点
5.更新簇的中心
6.重复去上操作,直到簇心变化不大。
比如这里说有一个点A(2.5,5.0),它身边有两个簇心(2.5,0) (0,5.0),因为A距离(2.5,0)这个簇心的距离比较近,所以A归(2.5,0)这一类。
在Yolov3中,kmeans操作就是不断更新anchor框的中心点,直到得到最适合我们数据的anchor框,即得到最可能的框对应的类别是什么。
4.Dataset.py文件(主要数据处理)
本代码以三分类为例(人、马、自行车),以三分类为例那需要多少的通道呢?
答案:(5+3)*3 = 24
其中,什么是5+3?
5分别代表:置信度C,X、Y、H、W的偏移量 3代表三分类
那什么又是*3呢?
正如我们所知,YoloV3以三个框(大13×13、中26×26、小52×52)识别图片,每个框中都具备(5+3)即上述那么多元素。
在我看来,yolov3检测分为两步:
1、确定检测对象位置(偏移量)
2、对检测对象类别
# 数据处理
import math
import numpy as np
import torch
from torch.utils.data import Dataset
from PIL import Image
import os
from utils import *
from torchvision import transforms
from config import *
tf = transforms.Compose([
transforms.ToTensor() #归一化
])
def one_hot(cls_num,i):
rst = np.zeros(cls_num) # 生成用0填充的数组的,共有cls_num列
rst[i] = 1
return rst
# 独热编码处理类别数
class YoloDataset(Dataset):
def __init__(self):
f = open('data.text','r') #以读的方式打开data.text
self.Dataset = f.readlines()
def __len__(self):
return len(self.Dataset)
def __getitem__(self, index):
data = self.Dataset[index].strip() #注意 这里.strip()用于去除data.text文件中第一列的空格,否则读取时会报错
Temp_data = data.split() #按空格拆分数据
_boxes = np.array([float(x) for x in Temp_data[1:]]) # 取数组方便切割
boxes = np.split(_boxes,len(_boxes)//5) #每隔5个数据切割一次
img = make_416_Image(os.path.join('Data/images', Temp_data[0]))
# 看utils.py文件讲解
w,h = img.size
case = 416/w #用于偏差计算
img = img.resize((416,416)) #改变图片格式为416*416
img_data = tf(img) #归一化
labels = {} #定义字典
for feature_size,_antors in antors.items():
labels[feature_size] = np.zeros((feature_size,feature_size,3,5+CLASS_NUM))
# 在一个尺度下画框
for box in boxes:
cls,cx,cy,w,h = box
cx,cy,w,h = cx*case,cy*case,w*case,h*case # 缩放
_x,x_index = math.modf(cx*feature_size/DATA_WIDTH)
_y,y_index = math.modf(cy*feature_size/DATA_WIDTH)
# 计算偏移量和格子数
for i,antor in enumerate(_antors):
area = w*h
iou = min(area,ANTORS_AREA[feature_size][i]/max(area,ANTORS_AREA[feature_size][i]))
"""
置信度计算 锚框的交集除以并集 feature_size定义第几个框4
min为交集 max为并集
"""
p_w, p_h = w / (antor[0]), h / (antor[1])
# W H的偏移量
labels[feature_size][int(x_index),int(y_index),i] = np.array([iou,_x,_y,np.log(p_w),np.log(p_h),*one_hot(CLASS_NUM,int(cls))])
# 取对数保证数据都为正值 *代表变量为可变参数,不定长
return labels[13],labels[26],labels[52],img_data
if __name__ == '__main__':
Dataset = YoloDataset()
其中,偏移量的计算:
偏移量:x/(416/13) x/(416/26) x/(416/52)