yolo3--utils.py
###在模型训练时进行数据处理的工具文件,共3个函数
"""Miscellaneous utility functions.""" ###其他实用功能
from functools import reduce ##为了实用reduce函数
from PIL import Image ###PIL(Python Image Library)是python的第三方图像处理库,Image是PIL中的核心类
import numpy as np
from matplotlib.colors import rgb_to_hsv, hsv_to_rgb
###函数1:参数为多个函数名,按照reduce的功能执行,把前一个函数的结果作为下一个函数的输入,直到最后执行完毕
def compose(*funcs):
"""Compose arbitrarily many functions, evaluated left to right.
###组合任意多个函数,从左向右依次求取值
Reference: https://mathieularose.com/function-composition-in-python/
"""
###reduce函数用途,对迭代对象中的元素从左至右两两送入参数1传入的function中进行运算 (参数2是迭代对象,该函数一共就两个函数)
### 下面的reduce函数在这里实现的是每一层网络计算过后参数传递给下一层 ,funcs迭代对象中的元素是函数名称,然后依次从左至右取函数名称送入reduce函数第一个参数所表示的函数中,,
####lambda *a,**kw:g(f(*a,**kw)) ,lambda表达式返回一个函数对象,例如这里*a,**kw是参数,:后面的是参数的计算方式(*a是卷积上一层输出的激活值,**kw是这一层卷积运算的权重矩阵,因为是矩阵所以二维数组,f函数是正常的卷基层运算,g函数是对卷积运算后的激活运算)
#### 这里lambda f,g:lambda *a,**kw:g(f(*a,**kw)) 是将f,g作为参数送入后面(lambda *a,**kw:g(f(*a,*kw)))这个运算中,而后面这个运算是将*a,**kw作为参数送入到g(f(*a,**kw))这个运算中,,,其实就是函数嵌套的另一种表示
# return lambda x: reduce(lambda v, f: f(v), funcs, x)
if funcs:
return reduce(lambda f, g: lambda *a, **kw: g(f(*a, **kw)), funcs)
else:
raise ValueError('Composition of empty sequence not supported.')
####函数2:使用padding方法将所有图片变为一个尺寸的,并且要保证纵横比不变
def letterbox_image(image, size):
'''resize image with unchanged aspect ratio using padding'''
iw, ih = image.size ###原始尺寸
w, h = size ###转换为416*416
scale = min(w/iw, h/ih) ###转换比例 ,nw,nh是转换之后的图像尺寸,416*416下的,其他的空白部分像素填灰色
nw = int(iw*scale)
nh = int(ih*scale)
image = image.resize((nw,nh), Image.BICUBIC) ####BICUBIC是resize的插值方法
new_image = Image.new('RGB', size, (128,128,128)) ###Image.new(mode,size,color=0)
new_image.paste(image, ((w-nw)//2, (h-nh)//2)) ###img.paste(im,box=None,mask=None) 就是将im粘贴到img上box位置处(box=粘贴位置的左上角坐标) //运算符是将除法结果向下取整
return new_image
####函数3:产生随机数
def rand(a=0, b=1):
return np.random.rand()*(b-a) + a
####函数4:获取真实的数据,根据输入的尺寸对原始数据进行缩放处理得到input_shape大小的数据图片,随机进行图片的翻转,标记数据也需要根据比例进行改变
####annotation_line:单条图片信息的列表
def get_random_data(annotation_line, input_shape, random=True, max_boxes=20, jitter=.3, hue=.1, sat=1.5, val=1.5, proc_img=True):
'''random preprocessing for real-time data augmentation'''###实时数据增强的随机预处理
line = annotation_line.split()
image = Image.open(line[0]) ##读取图片
iw, ih = image.size ##原始图片尺寸
h, w = input_shape ##获取
box = np.array([np.array(list(map(int,box.split(',')))) for box in line[1:]]) ###图像中目标框信息每个框包含4个位置大小信息和一个类别信息
######如果不随机变换原始数据,进行下述部分
if not random:
# resize image ###将原始图像转换为416*416的,要做的内容和letterbox_image函数一样
scale = min(w/iw, h/ih)
nw = int(iw*scale)
nh = int(ih*scale)
dx = (w-nw)//2
dy = (h-nh)//2
image_data=0
if proc_img:
image = image.resize((nw,nh), Image.BICUBIC)
new_image = Image.new('RGB', (w,h), (128,128,128))
new_image.paste(image, (dx, dy))
image_data = np.array(new_image)/255.
# correct boxes
box_data = np.zeros((max_boxes,5))
if len(box)>0:
np.random.shuffle(box) ###shuffle函数是将box中的元素进行随机排列
if len(box)>max_boxes: box = box[:max_boxes] ###这里最多只取20个,也就是这里规定只取图像中众多目标框的20个
box[:, [0,2]] = box[:, [0,2]]*scale + dx ###对目标框信息按比例进行缩放和移位,这里处理后的坐标是相对于416*416尺寸的,即input_shape
box[:, [1,3]] = box[:, [1,3]]*scale + dy
box_data[:len(box)] = box
return image_data, box_data ###image_data是resize之后的图像(原始图像的纵横比不能改变),box_data是resize之后的目标边界框信息(注意这里边界框是相对于416*416坐标的)
#####如果是处于数据增强阶段 ,则要随机改变图像尺寸和颜色空间取值范围,执行下述部分,
#####这使用到的数据增强手段有:
# resize image
##1.缩放图片
###随机生成宽高比
new_ar = w/h * rand(1-jitter,1+jitter)/rand(1-jitter,1+jitter)
###随机生成缩放比例
scale = rand(.25, 2)
###计算新图片的尺寸
if new_ar < 1:
nh = int(scale*h)
nw = int(nh*new_ar)
else:
nw = int(scale*w)
nh = int(nw/new_ar)
image = image.resize((nw,nh), Image.BICUBIC)
# place image
###2.平移变换
###随机的把变换后的图像放置在灰色图像上,随机水平位移
dx = int(rand(0, w-nw))
dy = int(rand(0, h-nh))
new_image = Image.new('RGB', (w,h), (128,128,128))
new_image.paste(image, (dx, dy))
image = new_image
# flip image or not
###3.翻转
####是否翻转图像
flip = rand()<.5
if flip: image = image.transpose(Image.FLIP_LEFT_RIGHT)
# distort image
###颜色抖动
####在hsv坐标域中,改变图片的颜色范围,hue值相加,sat和vat相乘
####先由RGB转HSV,再由HSV转RGB,添加若干错误判断,避免范围过大
hue = rand(-hue, hue)
sat = rand(1, sat) if rand()<.5 else 1/rand(1, sat)
val = rand(1, val) if rand()<.5 else 1/rand(1, val)
x = rgb_to_hsv(np.array(image)/255.)
x[..., 0] += hue
x[..., 0][x[..., 0]>1] -= 1
x[..., 0][x[..., 0]<0] += 1
x[..., 1] *= sat
x[..., 2] *= val
x[x>1] = 1
x[x<0] = 0
image_data = hsv_to_rgb(x) # numpy array, 0 to 1
# correct boxes
####在经过缩放和水平变换之后,box的左上角信息也需要有相应的变化#####
####将所有的图片变换,增加至质检框中,并包含若干异常处理,避免变换之后的值过大或过小,去除异常box
####变换图像中目标框的大小位置信息(根据随机变换的信息变换比例)
box_data = np.zeros((max_boxes,5))
if len(box)>0:
np.random.shuffle(box)
##变换所有目标尺寸
box[:, [0,2]] = box[:, [0,2]]*nw/iw + dx
box[:, [1,3]] = box[:, [1,3]]*nh/ih + dy
if flip: box[:, [0,2]] = w - box[:, [2,0]]
box[:, 0:2][box[:, 0:2]<0] = 0
box[:, 2][box[:, 2]>w] = w
box[:, 3][box[:, 3]>h] = h
box_w = box[:, 2] - box[:, 0]
box_h = box[:, 3] - box[:, 1]
box = box[np.logical_and(box_w>1, box_h>1)] # discard invalid box
if len(box)>max_boxes: box = box[:max_boxes]
box_data[:len(box)] = box
return image_data, box_data
######from import 是从模块中导入一个指定的部分到当前命名空间中