如有错误,恳请指出。


在之前使用opencv就介绍使用过一些常用的数据增强的实现方法,见:《数据增强 | 旋转、平移、缩放、错切、HSV增强》,当时介绍了旋转、平移、缩放、错切、HSV增强,但是只是针对了图像的数据增强,并没有涉及到label的变化。

因为,对于分类任务的数据增强来说,只需要对图像进行数据增强就可以了;但是对于目标检测任务来说,图像改变了,也以为着label的位置改变,同样需要作出变换,所以这里我介绍一下目标检测下的数据增强方法。

文章目录

  • 1. 随机旋转、平移、缩放、错切
  • 2. hsv增强
  • 3. 随机翻转(水平与竖直)
  • 4. 完整的数据增强代码展示

1. 随机旋转、平移、缩放、错切

这节来介绍其他的数据正确方式,比如仿射变换还有hsv增强,虽然之前我使用opencv进行了部分尝试,详细见:数据增强 | 旋转、平移、缩放、错切、HSV增强,不过这里还是更加yolov3-spp代码进行补充。

YOLOv3-SPP代码:

# train阶段默认为:
# img:(1472, 1472, 3), targets:(k, 5)
# 旋转角度degrees: 0.0, 平移系数translate: 0.0, 缩放因子scale=0.0, 错切角度shear:0.0
# border=-368
def random_affine(img, targets=(), degrees=10, translate=.1, scale=.1, shear=10, border=0):"""仿射变换 增强torchvision.transforms.RandomAffine(degrees=(-10, 10), translate=(.1, .1), scale=(.9, 1.1), shear=(-10, 10))https://medium.com/uruvideo/dataset-augmentation-with-random-homographies-a8f4b44830d4:param img: img4  [2 x img_size, 2 x img_size, 3]=[1472, 1472, 3]  img_size为我们指定的图片大小:param targets: labels4 [:, cls+x1y1x2y2]=[7, 5]  相对img4的   (x1,y1)左下角  (x2,y2)右上角:param degrees: 旋转角度  0:param translate: 水平或者垂直移动的范围  0:param scale: 放缩尺度因子  0:param shear: 裁剪因子 0:param border: -368  图像每条边需要裁剪的宽度  也可以理解为裁剪后的图像与裁剪前的图像的border:return: img: 经过仿射变换后的图像 img [img_size, img_size, 3]targets=[3, 5] 相对仿射变换后的图像img的target 之所以这里的target少了,是因为仿射变换使得一些target消失或者变得极小了"""# 对图像进行仿射变换# torchvision.transforms.RandomAffine(degrees=(-10, 10), translate=(.1, .1), scale=(.9, 1.1), shear=(-10, 10))# targets = [cls, xyxy]# 最终输出的图像尺寸,等于img4.shape / 2height = img.shape[0] + border * 2width = img.shape[1] + border * 2# Rotation and Scale# 生成旋转以及缩放矩阵R = np.eye(3)  # 生成对角阵a = random.uniform(-degrees, degrees)  # 随机旋转角度s = random.uniform(1 - scale, 1 + scale)  # 随机缩放因子R[:2] = cv2.getRotationMatrix2D(angle=a, center=(img.shape[1] / 2, img.shape[0] / 2), scale=s)# Translation# 生成平移矩阵T = np.eye(3)T[0, 2] = random.uniform(-translate, translate) * img.shape[0] + border  # x translation (pixels)T[1, 2] = random.uniform(-translate, translate) * img.shape[1] + border  # y translation (pixels)# 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)# Combined rotation matrix# 将三个仿射变换矩阵相乘,即可得到最后的仿射变换矩阵M = S @ T @ R  # ORDER IS IMPORTANT HERE!!if (border != 0) or (M != np.eye(3)).any():  # image changed# 进行仿射变化# 最后输出的图像大小为dsize=(width, height)img = cv2.warpAffine(img, M[:2], dsize=(width, height), flags=cv2.INTER_LINEAR, borderValue=(114, 114, 114))# Transform label coordinates# 对图像的label信息进行仿射变换n = len(targets)if n:# warp pointsxy = np.ones((n * 4, 3))# 求出所有目标边界框的四个顶点(x1y1, x1y2, x2y1, x2y2)# x1:1, y1:2, x2:3, y2:4# x1y1:(1,2), x2y2:(3,4), x1y2:(1,4), x2y1:(3,2)xy[:, :2] = targets[:, [1, 2, 3, 4, 1, 4, 3, 2]].reshape(n * 4, 2)  # x1y1, x2y2, x1y2, x2y1# 对四个顶点坐标进行仿射变换,就是与仿射矩阵进行相乘# 对于仿射矩阵中,最后一行是没有用的;所有对于坐标点需要增加一维,矩阵相乘后去除# 这里算是矩阵相乘的一个小trick,比较细节# [4*n, 3] -> [n, 8]xy = (xy @ M.T)[:, :2].reshape(n, 8)# create new boxes# 再求出仿射变换后的所有x坐标与y坐标# 对transform后的bbox进行修正(假设变换后的bbox变成了菱形,此时要修正成矩形)x = xy[:, [0, 2, 4, 6]]  # [n, 4]y = xy[:, [1, 3, 5, 7]]  # [n, 4]# 这里取xy的最小值作为新的边界框的左上角,取xy的最大值最为新的边界框的右下角# 因为随机变换有可能将图像进行旋转,那么边界框也会选择,所以这时候需要对选择的边界框进行修正为不旋转的矩形,而不是菱形◇xy = np.concatenate((x.min(1), y.min(1), x.max(1), y.max(1))).reshape(4, n).T  # [n, 4]# reject warped points outside of image# 对坐标进行裁剪,防止越界,最小值为0,最大值为对于的宽高xy[:, [0, 2]] = xy[:, [0, 2]].clip(0, width)xy[:, [1, 3]] = xy[:, [1, 3]].clip(0, height)w = xy[:, 2] - xy[:, 0]h = xy[:, 3] - xy[:, 1]# 计算调整后的每个box的面积:{ndarray:(9,)}area = w * h# 计算调整前的每个box的面积area0 = (targets[:, 3] - targets[:, 1]) * (targets[:, 4] - targets[:, 2])# 计算每个box的比例ar = np.maximum(w / (h + 1e-16), h / (w + 1e-16))  # aspect ratio# 选取长宽大于4个像素,且调整前后面积比例大于0.2,且比例小于10的boxi = (w > 4) & (h > 4) & (area / (area0 * s + 1e-16) > 0.2) & (ar < 10)# 筛选边界框,所以其实经过仿射变换后的有些标签信息是使用不上的,也就是被忽略掉了targets = targets[i]# 变换后的边界框信息重新赋值targets[:, 1:5] = xy[i]return img, targets# 对图像与标签应用仿射变换
img4, labels4 = random_affine(img4, labels4,                     # 输入图片与边界框信息degrees=self.hyp['degrees'],       # 旋转角度translate=self.hyp['translate'],   # 平移系数scale=self.hyp['scale'],           # 缩放系数shear=self.hyp['shear'],           # 错切角度border=-s // 2)                    # 这里的s是期待输出图片的大小

旋转后需要对边界框坐标进行修正:


对应代码为:

# create new boxes
# 再求出仿射变换后的所有x坐标与y坐标
# 对transform后的bbox进行修正(假设变换后的bbox变成了菱形,此时要修正成矩形)
x = xy[:, [0, 2, 4, 6]]  # [n, 4]
y = xy[:, [1, 3, 5, 7]]  # [n, 4]
# 这里取xy的最小值作为新的边界框的左上角,取xy的最大值最为新的边界框的右下角
# 因为随机变换有可能将图像进行旋转,那么边界框也会选择,所以这时候需要对选择的边界框进行修正为不旋转的矩形,而不是菱形◇
xy = np.concatenate((x.min(1), y.min(1), x.max(1), y.max(1))).reshape(4, n).T  # [n, 4]

最后的debug结果:

labels会丢失部分标签信息,而img的大小仍然是736x736


2. hsv增强

部分参考,见之前的笔记:数据增强 | 旋转、平移、缩放、错切、HSV增强

YOLOv3-SPP代码:

def augment_hsv(img, h_gain=0.5, s_gain=0.5, v_gain=0.5):"""hsv增强  处理图像hsv,不对label进行任何处理:param img: 待处理图片  BGR [736, 736]:param h_gain: h通道色域参数 用于生成新的h通道:param s_gain: h通道色域参数 用于生成新的s通道:param v_gain: h通道色域参数 用于生成新的v通道:return: 返回hsv增强后的图片 img"""# 从-1~1之间随机生成3随机数与三个变量进行相乘r = np.random.uniform(-1, 1, 3) * [h_gain, s_gain, v_gain] + 1  # random gainshue, sat, val = cv2.split(cv2.cvtColor(img, cv2.COLOR_BGR2HSV))dtype = img.dtype  # uint8# 分别针对hue, sat以及val生成对应的Look-Up Table(LUT)查找表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)# 使用cv2.LUT方法利用刚刚针对hue, sat以及val生成的Look-Up Table进行变换img_hsv = cv2.merge((cv2.LUT(hue, lut_hue), cv2.LUT(sat, lut_sat), cv2.LUT(val, lut_val))).astype(dtype)aug_img = cv2.cvtColor(img_hsv, cv2.COLOR_HSV2BGR, dst=img)  # no return needed# 这里源码是没有进行return的,不过我还是觉得return一下比较直观了解return aug_img

3. 随机翻转(水平与竖直)

YOLOv3-SPP代码:

# 平移增强 随机左右翻转 + 随机上下翻转
if self.augment:# 随机左右翻转# random left-right fliplr_flip = True# random.random() 生成一个[0,1]的随机数if lr_flip and random.random() < 0.5:img = np.fliplr(img)  # np.fliplr 将数组在左右方向翻转if nL:labels[:, 1] = 1 - labels[:, 1]  # 1 - x_center  label也要映射# 随机上下翻转# random up-down flipud_flip = Falseif ud_flip and random.random() < 0.5:img = np.flipud(img)  # np.flipud 将数组在上下方向翻转。if nL:labels[:, 2] = 1 - labels[:, 2]  # 1 - y_center  label也要映射

4. 完整的数据增强代码展示

这里我贴上yolov3-spp中所有的__getitem__处理过程,详情见注释。

YOLOv3-SPP代码:

# 自定义数据集
class LoadImagesAndLabels(Dataset):  # for training/testingdef __init__(self,path,   # 指向data/my_train_data.txt路径或data/my_val_data.txt路径# 这里设置的是预处理后输出的图片尺寸# 当为训练集时,设置的是训练过程中(开启多尺度)的最大尺寸# 当为验证集时,设置的是最终使用的网络大小img_size=416,batch_size=16,augment=False,  # 训练集设置为True(augment_hsv),验证集设置为Falsehyp=None,  # 超参数字典,其中包含图像增强会使用到的超参数rect=False,  # 是否使用rectangular trainingcache_images=False,  # 是否缓存图片到内存中single_cls=False, pad=0.0, rank=-1):...# 注意: 开启rect后,mosaic就默认关闭self.mosaic = self.augment and not self.rect...# 自定义处理格式def __getitem__(self, index):"""一般一次性执行batch_size次Args:self: self.img_files: 存放每张照片的地址self.label_files: 存放每张照片的label的地址self.imgs=[None] * n  cache image 恐怕没那么大的显存self.labels: 存放每4张图片的label值 [cls+xywh] xywh都是相对值 cache label并在cache label过程中统计nm, nf, ne, nd等4个变量self.batch: 存放每张图片属于哪个batch  self.shape: 存放每张图片原始的shapeself.n: 总的图片数量     self.hyp  self.img_size数据增强相关变量: self.augment; self.rect; self.mosaicrect=True: 会生成self.batch_shapes 每个batch的所有图片统一输入网络的shapeindex: 传入要index再从datasets中随机抽3张图片进行mosaic增强以及一系列其他的增强,且label同时也要变换Returns:torch.from_numpy(img): 返回一张增强后的图片(tensor格式)labels_out: 这张图片对应的label (class, x, y, w, h) tensor格式self.img_files[index]: 当前这张图片所在的路径地址shapes: train=None  val=(原图hw),(缩放比例),(pad wh) 计算coco map时要用index: 当前这张图片的在self.中的index"""hyp = self.hyp# 训练过程使用mosaic数据增强if self.mosaic:# load mosaicimg, labels = load_mosaic(self, index)shapes = None# 推理阶段使用rect加快推理过程else:# load imageimg, (h0, w0), (h, w) = load_image(self, index)# letterboxshape = self.batch_shapes[self.batch[index]] if self.rect else self.img_size  # final letterboxed shapeimg, ratio, pad = letterbox(img, shape, auto=False, scale_up=self.augment)shapes = (h0, w0), ((h / h0, w / w0), pad)  # for COCO mAP rescaling# load labelslabels = []x = self.labels[index]if x.size > 0:# Normalized xywh to pixel xyxy formatlabels = x.copy()  # label: class, x, y, w, hlabels[:, 1] = ratio[0] * w * (x[:, 1] - x[:, 3] / 2) + pad[0]  # pad widthlabels[:, 2] = ratio[1] * h * (x[:, 2] - x[:, 4] / 2) + pad[1]  # pad heightlabels[:, 3] = ratio[0] * w * (x[:, 1] + x[:, 3] / 2) + pad[0]labels[:, 4] = ratio[1] * h * (x[:, 2] + x[:, 4] / 2) + pad[1]# 是否进行数据增强if self.augment:# 由于mosaic中已经进行了random_affine,所以不需要;没有进行mosaic才需要if not self.mosaic:img, labels = random_affine(img, labels,degrees=hyp["degrees"],translate=hyp["translate"],scale=hyp["scale"],shear=hyp["shear"])# Augment colorspace: hsv数据增强, 这一部分由于没有对标签进行更改,所以不需要对边界框进行处理img = augment_hsv(img, h_gain=hyp["hsv_h"], s_gain=hyp["hsv_s"], v_gain=hyp["hsv_v"])# 在进行仿射变换之后会忽略一些边界框,如果没有边界框信息就可以跳过了,如果有则进行处理nL = len(labels)  # number of labelsif nL:# convert xyxy to xywhlabels[:, 1:5] = xyxy2xywh(labels[:, 1:5])# Normalize coordinates 0-1: 归一化处理labels[:, [2, 4]] /= img.shape[0]  # heightlabels[:, [1, 3]] /= img.shape[1]  # width# 进行随机水平翻转也竖直翻转if self.augment:# random left-right fliplr_flip = True  # 随机水平翻转if lr_flip and random.random() < 0.5:img = np.fliplr(img)if nL:labels[:, 1] = 1 - labels[:, 1]  # 1 - x_center# random up-down flipud_flip = Falseif ud_flip and random.random() < 0.5:img = np.flipud(img)   # 随机竖直翻转if nL:labels[:, 2] = 1 - labels[:, 2]  # 1 - y_center# 判断翻转后是否还有边界框信息, 并进行格式转换labels_out = torch.zeros((nL, 6))  # nL: number of labelsif nL:labels_out[:, 1:] = torch.from_numpy(labels)# Convert BGR to RGB, and HWC to CHW(3x512x512)img = img[:, :, ::-1].transpose(2, 0, 1)img = np.ascontiguousarray(img)   # 内存连续return torch.from_numpy(img), labels_out, self.img_files[index], shapes, index
  • 总结:

所以在yolov3spp中,提供的数据增强方式有mosaic,仿射变换(随机旋转、平移、缩放、错切),hsv色彩增强。但是在yolov3spp中,一般训练过程会是一个mosaic,这会包含了随机旋转、平移、缩放、错切,此时还可以设置是否使用hsv色彩增强以及上下随机翻转。而在推理过程中,就不会使用mosaic,但是可以选择是否使用除mosaic外的其他数据增强方式。


参考资料:

  1. https://www.bilibili.com/video/BV1t54y1C7ra?p=5
  2. https://blog.csdn.net/qq_38253797/article/details/117961285

目标检测的Tricks | 【Trick8】数据增强——随机旋转、平移、缩放、错切、hsv增强相关推荐

  1. 【目标检测】(10) Mosaic 数据增强方法,附Python完整代码

    各位同学好,今天和大家分享一下目标检测算法中常用的图像数据增强方法 Mosaic.先放张图看效果.将四张图片缩放后裁剪拼接在一起,并调整检测框的坐标位置,处理位于图像边缘的检测框.文末有完整代码 1. ...

  2. python实现目标检测voc格式标签数据增强

    文章目录 前言 一.显示图片(可关闭) 二.创建图像变换的类 1.增强数据代码 2.图像加噪声 3.调整图像亮度 4.添加黑色像素块 5.旋转图像 6.图像裁剪 7.平移图像 8.图像镜像 9.图像随 ...

  3. 电气领域相关数据集(目标检测,分类图像数据及负荷预测),电气设备红外测温图像,输电线路图像数据续

    另外一部分见:电气领域相关数据集(目标检测,分类图像数据及负荷预测),输电线路图像数据 1. 变电站烟火检测图像数据集(3600多张,VOC标签) 2. 导线破损检测图像数据集(有拼接增强,VOC标签 ...

  4. 数据增广:旋转,缩放,平移以及错切

    在深度学习(图像领域)中,为了提升训练样本数量数据增广是非常常见的手段.比如: 随机水平翻转 随机色调(H).饱和度(S).明度(V)调整 随机旋转,缩放,平移以及错切 还有近几年常用的mixup,m ...

  5. AI大视觉(二十) | 小目标检测的tricks汇总

    本文来自公众号"AI大道理". 这里既有AI,又有生活大道理,无数渺小的思考填满了一生. 在计算机视觉中,检测小目标是最有挑战的问题之一. 本文汇总了一些有效的策略. 为何小目标 ...

  6. 目标检测领域中的数据不均衡问题综述

    点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 参考:Imbalance Problems in Object Detection paper链接:h ...

  7. ssd目标检测训练自己的数据_目标检测Tensorflow object detection API之训练自己的数据集...

    构建自己的模型之前,推荐先跑一下Tensorflow object detection API的demo JustDoIT:目标检测Tensorflow object detection API​zh ...

  8. 目标检测的Tricks | 【Trick9】nms非极大值抑制处理(包括变体merge-nms、and-nms、soft-nms、diou-nms等介绍)

    如有错误,恳请指出. 用这篇博客记录一下nms,也就是非极大值抑制处理,算是目标检测后处理的一个难点. 在训练阶段是不需要nms处理的,只有在验证或者是测试阶段才需要将预测结果进行非极大值抑制处理,来 ...

  9. 自动驾驶中的激光雷达目标检测的原理和数据特点

    转自:http://www.elecfans.com/d/892365.html (侵删) 安全性是自动驾驶中人们最关注的问题之一. 在算法层面,无人车对周围环境的准确感知是保证安全的基础,因此感知算 ...

最新文章

  1. RxJava zip 使用
  2. 实验四 恶意代码
  3. [html] 你认为写出什么样的html代码才是好代码呢?
  4. react-redux笔记
  5. 电商千万级交易的金手指:分布式事务管理
  6. linux多线程学习(三)——线程属性设置
  7. 数据库快照的工作方式
  8. 常用nginx rewrite重定向-跳转实例
  9. python最大公约数计算。从键盘接收两个整数_python如何求解两数的最大公约数
  10. 《进击的虫师》百度文库下载器
  11. 如何将多个图片合成一个pdf
  12. 深度摄像头:一:深度了解深度摄像头
  13. 在Linux中连接和使用云存储的三种途径
  14. python股票预测_python用线性回归预测股票价格的实现代码
  15. 抢红包案例分析以及代码实现(二)
  16. 是否有标准函数来检查 JavaScript 中的 null、未定义或空白变量?
  17. OpenCV-图像色温
  18. conda 清除已经下载的缓冲包
  19. c35是什么意思_混凝土c25、c30、c35分别代表什么意思?
  20. 【YOLOv5】yolov5训练中遇到的问题解决:BrokenPipeError: [Errno 32] Broken pipe

热门文章

  1. element el-time-picker 时间段动态禁用 禁用时间段
  2. 火遍全球的ChatGPT技术简介与主干网络代码
  3. oracle如果还原库,Oracle 还原库
  4. ProcessingJoy —— 坍缩的立方体【JAVA】
  5. switch(选择)结构
  6. 怀旧服服务器队列位置波动,《魔兽世界》怀旧服刚开服就被挤爆 大量玩家疯狂涌入...
  7. C++编码风格/规范/建议
  8. linux 下 使用 yum安装python3
  9. JavaScript入门(二)
  10. java ajax无刷分页_简单实现Ajax无刷新分页效果