😎😎😎物体检测-系列教程 总目录
有任何问题欢迎在下面留言
本篇文章的代码运行界面均在Pycharm中进行
本篇文章配套的代码资源已经上传
点我下载源码
6、LoadImagesAndLabels类的数据加载器
__getitem__函数,并不是在一开始就会去执行这个函数,而是在程序执行训练的时候,实际上要读一张图像到网络中去传的时候才能去执行这个函数
6.1 马赛克数据增强
def __getitem__(self, index):
index = self.indices[index]
hyp = self.hyp
mosaic = self.mosaic and random.random() < hyp['mosaic']
if mosaic:
img, labels = load_mosaic(self, index)
shapes = None
if random.random() < hyp['mixup']:
img2, labels2 = load_mosaic(self, random.randint(0, self.n - 1))
r = np.random.beta(8.0, 8.0)
img = (img * r + img2 * (1 - r)).astype(np.uint8)
labels = np.concatenate((labels, labels2), 0)
else:
img, (h0, w0), (h, w) = load_image(self, index)
shape = self.batch_shapes[self.batch[index]] if self.rect else self.img_size
img, ratio, pad = letterbox(img, shape, auto=False, scaleup=self.augment)
shapes = (h0, w0), ((h / h0, w / w0), pad)
labels = self.labels[index].copy()
if labels.size:
labels[:, 1:] = xywhn2xyxy(labels[:, 1:], ratio[0] * w, ratio[1] * h, padw=pad[0], padh=pad[1])
- 数据加载器函数,传入索引
- index ,重新获取实际的图像索引
- hyp ,超参数字典
- mosaic ,判断是否进行Mosaic数据增强。如果self.mosaic为True且随机数小于hyp字典中的mosaic参数,则进行Mosaic增强
- 如果进行马赛克数据增强
- img, labels,调用
load_mosaic函数
加载并处理Mosaic图像及其标签 - shapes ,将shapes变量设置为None,因为在Mosaic模式下,图像形状会在load_mosaic函数内部处理
- 随机决定是否对当前图像应用MixUp增强
- img2, labels2,随机选择另一张图像进行MixUp增强
- r,生成MixUp的混合比率,使用Beta分布
- img ,按比率混合两张图像
- labels ,合并两组标签
- 如果不使用Mosaic增强
- img, (h0, w0), (h, w),调用load_image函数加载单张图像及其尺寸信息
- shape ,根据是否进行矩形训练来确定图像的目标尺寸
- img, ratio, pad,使用
letterbox函数
将图像调整到指定尺寸,包括pad填充 - shape ,存储原始和调整后的图像尺寸及填充信息,用于后续计算
- labels ,复制当前图像的标签,防止原始数据被修改
- 如果存在标签,则进行坐标转换和归一化处理
- 将归一化的xywh标签转换为xyxy格式,并考虑图像缩放和填充
6.2 MixUp数据增强
if self.augment:
if not mosaic:
img, labels = random_perspective(img, labels,
degrees=hyp['degrees'],
translate=hyp['translate'],
scale=hyp['scale'],
shear=hyp['shear'],
perspective=hyp['perspective'])
augment_hsv(img, hgain=hyp['hsv_h'], sgain=hyp['hsv_s'], vgain=hyp['hsv_v'])
nL = len(labels)
if nL:
labels[:, 1:5] = xyxy2xywh(labels[:, 1:5])
labels[:, [2, 4]] /= img.shape[0]
labels[:, [1, 3]] /= img.shape[1]
if self.augment:
if random.random() < hyp['flipud']:
img = np.flipud(img)
if nL:
labels[:, 2] = 1 - labels[:, 2]
if random.random() < hyp['fliplr']:
img = np.fliplr(img)
if nL:
labels[:, 1] = 1 - labels[:, 1]
labels_out = torch.zeros((nL, 6))
if nL:
labels_out[:, 1:] = torch.from_numpy(labels)
img = img[:, :, ::-1].transpose(2, 0, 1)
img = np.ascontiguousarray(img)
return torch.from_numpy(img), labels_out, self.img_files[index], shapes
- 如果启用了数据增强:
- 如果没有使用Mosaic增强,则对单张图像应用随机透视变换
- img, labels,使用
random_perspective函数
应用随机透视变换和其他空间变换 - 使用
augment_hsv函数
,对图像进行HSV颜色空间的增强,通过调整色调(H)、饱和度(S)和亮度(V)来改变图像的颜色属性 - nL,当前图像的标签数量
- 如果存在标签(nL大于0),则执行以下坐标转换:
- 将标签从xyxy格式(左上角和右下角坐标)转换为xywh格式(中心点坐标加宽高)
- 将标签的高度坐标归一化到0-1范围内,通过图像的高度进行除法操作
- 将标签的宽度坐标归一化到0-1范围内,通过图像的宽度进行除法操作
- 如果启用了数据增强且当前不是使用Mosaic数据增强,则检查是否应用其他增强方法
- 随机决定是否进行上下翻转增强
- img ,对图像进行上下翻转
- 如果存在标签,更新标签的位置,以匹配翻转后的图像
- 调整标签的y坐标,以适应图像的上下翻转
- 随机决定是否进行左右翻转增强
- 对图像进行左右翻转
- 如果存在标签,更新标签的位置,以匹配翻转后的图像
- 调整标签的x坐标,以适应图像的左右翻转
- labels_out ,初始化一个全0的PyTorch张量labels_out,用于存储转换后的标签数据
- 如果存在标签:
- 将NumPy数组labels转换为PyTorch张量,并赋值给labels_out
- img ,将图像从BGR格式转换为RGB格式,并调整通道顺序以适配PyTorch的输入格式(OpenCV读进来的图像是BGR)
- img ,确保图像数据在内存中是连续的,这对于PyTorch加载图像数据是必要的
- 返回处理后的图像数据、标签、当前图像的文件路径、图像的原始和调整后的尺寸信息
6.3 letterbox函数
def letterbox(img, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True, stride=32):
# Resize and pad image while meeting stride-multiple constraints
shape = img.shape[:2] # current shape [height, width]
if isinstance(new_shape, int):
new_shape = (new_shape, new_shape)
# Scale ratio (new / old)
r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
if not scaleup: # only scale down, do not scale up (for better test mAP)
r = min(r, 1.0)
# Compute padding
ratio = r, r # width, height ratios
new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] # wh padding
if auto: # minimum rectangle
dw, dh = np.mod(dw, stride), np.mod(dh, stride) # wh padding
elif scaleFill: # stretch
dw, dh = 0.0, 0.0
new_unpad = (new_shape[1], new_shape[0])
ratio = new_shape[1] / shape[1], new_shape[0] / shape[0] # width, height ratios
dw /= 2 # divide padding into 2 sides
dh /= 2
if shape[::-1] != new_unpad: # resize
img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)
top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) # add border
return img, ratio, (dw, dh)
6.4 random_perspective函数
def random_perspective(img, targets=(), segments=(), degrees=10, translate=.1, scale=.1, shear=10, perspective=0.0,
border=(0, 0)):
# torchvision.transforms.RandomAffine(degrees=(-10, 10), translate=(.1, .1), scale=(.9, 1.1), shear=(-10, 10))
# targets = [cls, xyxy]
height = img.shape[0] + border[0] * 2 # shape(h,w,c)
width = img.shape[1] + border[1] * 2
# Center
C = np.eye(3)
C[0, 2] = -img.shape[1] / 2 # x translation (pixels)
C[1, 2] = -img.shape[0] / 2 # y translation (pixels)
# Perspective
P = np.eye(3)
P[2, 0] = random.uniform(-perspective, perspective) # x perspective (about y)
P[2, 1] = random.uniform(-perspective, perspective) # y perspective (about x)
# Rotation and Scale
R = np.eye(3)
a = random.uniform(-degrees, degrees)
# a += random.choice([-180, -90, 0, 90]) # add 90deg rotations to small rotations
s = random.uniform(1 - scale, 1 + scale)
# s = 2 ** random.uniform(-scale, scale)
R[:2] = cv2.getRotationMatrix2D(angle=a, center=(0, 0), scale=s)
# Shear
S = np.eye(3)
S[0, 1] = math.tan(random.uniform(-shear, shear) * math.pi / 180) # x shear (deg)
S[1, 0] = math.tan(random.uniform(-shear, shear) * math.pi / 180) # y shear (deg)
# Translation
T = np.eye(3)
T[0, 2] = random.uniform(0.5 - translate, 0.5 + translate) * width # x translation (pixels)
T[1, 2] = random.uniform(0.5 - translate, 0.5 + translate) * height # y translation (pixels)
# Combined rotation matrix
M = T @ S @ R @ P @ C # order of operations (right to left) is IMPORTANT
if (border[0] != 0) or (border[1] != 0) or (M != np.eye(3)).any(): # image changed
if perspective:
img = cv2.warpPerspective(img, M, dsize=(width, height), borderValue=(114, 114, 114))
else: # affine
img = cv2.warpAffine(img, M[:2], dsize=(width, height), borderValue=(114, 114, 114))
# Visualize
# import matplotlib.pyplot as plt
# ax = plt.subplots(1, 2, figsize=(12, 6))[1].ravel()
# ax[0].imshow(img[:, :, ::-1]) # base
# ax[1].imshow(img2[:, :, ::-1]) # warped
# Transform label coordinates
n = len(targets)
if n:
use_segments = any(x.any() for x in segments)
new = np.zeros((n, 4))
if use_segments: # warp segments
segments = resample_segments(segments) # upsample
for i, segment in enumerate(segments):
xy = np.ones((len(segment), 3))
xy[:, :2] = segment
xy = xy @ M.T # transform
xy = xy[:, :2] / xy[:, 2:3] if perspective else xy[:, :2] # perspective rescale or affine
# clip
new[i] = segment2box(xy, width, height)
else: # warp boxes
xy = np.ones((n * 4, 3))
xy[:, :2] = targets[:, [1, 2, 3, 4, 1, 4, 3, 2]].reshape(n * 4, 2) # x1y1, x2y2, x1y2, x2y1
xy = xy @ M.T # transform
xy = (xy[:, :2] / xy[:, 2:3] if perspective else xy[:, :2]).reshape(n, 8) # perspective rescale or affine
# create new boxes
x = xy[:, [0, 2, 4, 6]]
y = xy[:, [1, 3, 5, 7]]
new = np.concatenate((x.min(1), y.min(1), x.max(1), y.max(1))).reshape(4, n).T
# clip
new[:, [0, 2]] = new[:, [0, 2]].clip(0, width)
new[:, [1, 3]] = new[:, [1, 3]].clip(0, height)
# filter candidates
i = box_candidates(box1=targets[:, 1:5].T * s, box2=new.T, area_thr=0.01 if use_segments else 0.10)
targets = targets[i]
targets[:, 1:5] = new[i]
return img, targets
6.5 augment_hsv函数
def augment_hsv(img, hgain=0.5, sgain=0.5, vgain=0.5):
r = np.random.uniform(-1, 1, 3) * [hgain, sgain, vgain] + 1 # random gains
hue, sat, val = cv2.split(cv2.cvtColor(img, cv2.COLOR_BGR2HSV))
dtype = img.dtype # uint8
x = np.arange(0, 256, dtype=np.int16)
lut_hue = ((x * r[0]) % 180).astype(dtype)
lut_sat = np.clip(x * r[1], 0, 255).astype(dtype)
lut_val = np.clip(x * r[2], 0, 255).astype(dtype)
img_hsv = cv2.merge((cv2.LUT(hue, lut_hue), cv2.LUT(sat, lut_sat), cv2.LUT(val, lut_val))).astype(dtype)
cv2.cvtColor(img_hsv, cv2.COLOR_HSV2BGR, dst=img) # no return needed