目标检测中的带或不带标注框的图片离线增强的实现(贴背景、随机旋转、随机色调变换、随机透视变换)
2022/5/30 23:19:58
本文主要是介绍目标检测中的带或不带标注框的图片离线增强的实现(贴背景、随机旋转、随机色调变换、随机透视变换),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
目录
0 前言
1 数据增强的实现
1.1 贴背景
1.2 随机旋转
1.3 随机色调变换
1.4 随机透视变换
1.5 完整代码
2 总结
0 前言
前一段时间在做目标检测任务,由于训练数据较少,需要对已有的数据进行离线增强。
那么什么是数据增强呢?Data Augmentation
,基于有限的数据生成更多等价(同样有效)的数据,丰富训练数据的分布,使通过训练集得到的模型泛化能力更强。
数据增强可以分为两类,离线增强和在线增强。离线增强 : 直接对数据集进行处理,数据的数目会变成增强因子乘以原数据集的数目,这种方法常常用于数据集很小的时候。在线增强 : 这种增强的方法用于,获得 batch 数据之后,然后对这个 batch 的数据进行增强,如旋转、平移、翻折等相应的变化,由于有些数据集不能接受线性级别的增长,这种方法长用于大的数据集,很多机器学习框架已经支持了这种数据增强方式,并且可以使用 GPU 优化计算。
数据增强让有限的数据产生更多的数据,增加训练样本的数量以及多样性(噪声数据),提升模型鲁棒性。神经网络需要大量的参数,许许多多的神经网路的参数都是数以百万计,而使得这些参数可以正确工作则需要大量的数据进行训练,但在很多实际的项目中,我们难以找到充足的数据来完成任务。此外,随机改变训练样本可以降低模型对某些属性的依赖,从而提高模型的泛化能力。
视频讲解地址:【深度学习】【数据增强】【目标检测】带或不带标注框的图片离线增强的实现(贴背景、随机旋转、随机色调变换、随机透视变换)(附源码)_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili
1 数据增强的实现
数据增强主要有仿射变换、透视变换、色调变换等等,对于目标检测任务,一些数据增强方式是不可用的,如裁剪,因为很容易导致目标丢失。我在数据增强中常用贴背景、随机旋转、随机色调变换、随机透视变换四种方法。
以下代码中的标注框均是四点标注的四边形,非水平矩形框。按照左上、右上、右下、左下的顺序排列四点,即顺时针方向,八个坐标点:x0,y0,x1,y1,x2,y2,x3,y3x0,y0,x1,y1,x2,y2,x3,y3。
1.1 贴背景
可以先将标注框内的目标裁剪出来,然后贴到各种各样的背景图上,生成新的数据。
def add_background_randomly(image, background, box_list=[]): """ box_list = [(cls_type_0, rect_0), (cls_type_1, rect_1), ... , (cls_type_n, rect_n)] rect = [x0, y0, x1, y1, x2, y2, x3, y3] left_top = (x0, y0), right_top = (x1, y1), right_bottom = (x2, y2), left_bottom = (x3, y3) """ img_height, img_width = image.shape[:2] bg_height, bg_width = background.shape[:2] # resize image smaller to background # the image accounts for at least two-thirds and not more than four-fifths min_size = min(bg_height, bg_width) // 3 * 2 max_size = min(bg_height, bg_width) // 5 * 4 new_size = random.randint(min_size, max_size) resize_multiple = round(new_size / max(img_height, img_width), 4) # image = image.resize((int(img_width * resize_multiple), int(img_height * resize_multiple)), Image.ANTIALIAS) image = cv2.resize(image, (int(img_width * resize_multiple), int(img_height * resize_multiple))) img_height, img_width = image.shape[:2] # paste the image to the background # height_pos = random.randint((bg_height-img_height)//3, (bg_height-img_height)//3*2) # width_pos = random.randint((bg_width-img_width)//3, (bg_width-img_width)//3*2) height_pos = random.randint(0, (bg_height-img_height)) width_pos = random.randint(0, (bg_width-img_width)) background[height_pos:(height_pos+img_height), width_pos:(width_pos+img_width)] = image img_height, img_width = background.shape[:2] # calculate the boxes after adding background new_box_list = [] for cls_type, rect in box_list: for coor_index in range(len(rect)//2): # resize rect[coor_index*2] = int(rect[coor_index*2] * resize_multiple) # x rect[coor_index*2+1] = int(rect[coor_index*2+1] * resize_multiple) # y # paste rect[coor_index*2] += width_pos # x rect[coor_index*2+1] += height_pos # y # limite rect[coor_index*2] = max(min(rect[coor_index*2], img_width), 0) # x rect[coor_index*2+1] = max(min(rect[coor_index*2+1], img_height), 0)# y box = (cls_type, rect) new_box_list.append(box) image_with_boxes = [background, new_box_list] return image_with_boxes
1.2 随机旋转
对图片进行任意角度的旋转,标注框也随之旋转,旋转后需要外扩图片宽度和高度,避免被裁剪,旋转后的背景可以填充任意颜色。
def rotate_image(image, label_box_list=[], angle=90, color=(0, 0, 0), img_scale=1.0): """ rotate with angle, background filled with color, default black (0, 0, 0) label_box = (cls_type, box) box = [x0, y0, x1, y1, x2, y2, x3, y3] """ # grab the rotation matrix (applying the negative of the angle to rotate clockwise), # then grab the sine and cosine (i.e., the rotation components of the matrix) # if angle < 0, counterclockwise rotation; if angle > 0, clockwise rotation # 1.0 - scale, to adjust the size scale (image scaling parameter), recommended 0.75 height_ori, width_ori = image.shape[:2] x_center_ori, y_center_ori = (width_ori // 2, height_ori // 2) rotation_matrix = cv2.getRotationMatrix2D((x_center_ori, y_center_ori), angle, img_scale) cos = np.abs(rotation_matrix[0, 0]) sin = np.abs(rotation_matrix[0, 1]) # compute the new bounding dimensions of the image width_new = int((height_ori * sin) + (width_ori * cos)) height_new = int((height_ori * cos) + (width_ori * sin)) # adjust the rotation matrix to take into account translation rotation_matrix[0, 2] += (width_new / 2) - x_center_ori rotation_matrix[1, 2] += (height_new / 2) - y_center_ori # perform the actual rotation and return the image # borderValue - color to fill missing background, default black, customizable image_new = cv2.warpAffine(image, rotation_matrix, (width_new, height_new), borderValue=color) # each point coordinates angle = angle / 180 * math.pi box_rot_list = cal_rotate_box(label_box_list, angle, (x_center_ori, y_center_ori), (width_new//2, height_new//2)) box_new_list = [] for cls_type, box_rot in box_rot_list: for index in range(len(box_rot)//2): box_rot[index*2] = int(box_rot[index*2]) box_rot[index*2] = max(min(box_rot[index*2], width_new), 0) box_rot[index*2+1] = int(box_rot[index*2+1]) box_rot[index*2+1] = max(min(box_rot[index*2+1], height_new), 0) box_new_list.append((cls_type, box_rot)) image_with_boxes = [image_new, box_new_list] return image_with_boxes def cal_rotate_box(box_list, angle, ori_center, new_center): # box = [x0, y0, x1, y1, x2, y2, x3, y3] # image_shape - [width, height] box_list_new = [] for (cls_type, box) in box_list: box_new = [] for index in range(len(box)//2): box_new.extend(cal_rotate_coordinate(box[index*2], box[index*2+1], angle, ori_center, new_center)) label_box = (cls_type, box_new) box_list_new.append(label_box) return box_list_new def cal_rotate_coordinate(x_ori, y_ori, angle, ori_center, new_center): # box = [x0, y0, x1, y1, x2, y2, x3, y3] # image_shape - [width, height] x_0 = x_ori - ori_center[0] y_0 = ori_center[1] - y_ori x_new = x_0 * math.cos(angle) - y_0 * math.sin(angle) + new_center[0] y_new = new_center[1] - (y_0 * math.cos(angle) + x_0 * math.sin(angle)) return (x_new, y_new)
1.3 随机色调变换
对图片进行色调变换,包括:亮度、对比度、饱和度、色调。可调节变换的概率。
def hue_change(image): if np.random.rand() < 0.8: image = transforms.ColorJitter(brightness=0.5)(image) if np.random.rand() < 0.2: image = transforms.ColorJitter(contrast=0.2)(image) if np.random.rand() < 0.2: image = transforms.ColorJitter(saturation=0.2)(image) if np.random.rand() < 0.2: image = transforms.ColorJitter(hue=0.2)(image) return image
1.4 随机透视变换
对图片进行任意的透视变换,标注框也随之变换,变换后的背景可以填充任意颜色。
1 def perspective_tranform(image, perspective_rate=0.5, label_box_list=[]): 2 # perspective transform 3 img_height, img_width = image.shape[:2] 4 # points_src = np.float32([[rect[0], rect[1]], [rect[2], rect[3]], [rect[4], rect[5]], [rect[6], rect[7]]]) 5 points_src = np.float32([[0, 0], [img_width-1, 0], [img_width-1, img_height-1], [0, img_height-1]]) 6 max_width = int(img_width * (1.0 + perspective_rate)) 7 max_height = int(img_height * (1.0 + perspective_rate)) 8 min_width = int(img_width * (1.0 - perspective_rate)) 9 min_height = int(img_height * (1.0 + perspective_rate)) 10 delta_width = (max_width - min_width) // 2 11 delta_height = (max_height - min_height) // 2 12 x0 = random.randint(0, delta_width) 13 y0 = random.randint(0, delta_height) 14 x1 = random.randint(delta_width + min_width, max_width) 15 y1 = random.randint(0, delta_height) 16 x2 = random.randint(delta_width + min_width, max_width) 17 y2 = random.randint(delta_height + min_height, max_height) 18 x3 = random.randint(0, delta_width) 19 y3 = random.randint(delta_height + min_height, max_height) 20 points_dst = np.float32([[x0, y0], [x1, y1], [x2, y2], [x3, y3]]) 21 # width_new = max(x0, x1, x2, x3) - min(x0, x1, x2, x3) 22 # height_new = max(y0, y1, y2, y3) - min(y0, y1, y2, y3) 23 M = cv2.getPerspectiveTransform(points_src, points_dst) 24 image_res = cv2.warpPerspective(image, M, (max_width, max_height)) 25 # cut 26 image_new = image_res[min(y0, y1):max(y2, y3), min(x0, x3):max(x1, x2)] 27 28 # labels 29 box_new_list = [] 30 for cls_type, box in label_box_list: 31 # after transformation 32 for index in range(len(box)//2): 33 px = (M[0][0]*box[index*2] + M[0][1]*box[index*2+1] + M[0][2]) / ((M[2][0]*box[index*2] + M[2][1]*box[index*2+1] + M[2][2])) 34 py = (M[1][0]*box[index*2] + M[1][1]*box[index*2+1] + M[1][2]) / ((M[2][0]*box[index*2] + M[2][1]*box[index*2+1] + M[2][2])) 35 box[index*2] = int(px) 36 box[index*2+1] = int(py) 37 # cut 38 box[index*2] -= min(x0, x3) 39 box[index*2+1] -= min(y0, y1) 40 box[index*2] = max(min(box[index*2], image_new.shape[1]), 0) 41 box[index*2+1] = max(min(box[index*2+1], image_new.shape[0]), 0) 42 box_new_list.append((cls_type, box)) 43 44 image_with_boxes = [image_new, box_new_list] 45 46 return image_with_boxes
1.5 完整代码
import os import random from PIL import Image, ImageOps from tqdm import tqdm import torchvision.transforms as transforms import cv2 import numpy as np import math import shutil def add_background_randomly(image, background, box_list=[]): """ box_list = [(cls_type_0, rect_0), (cls_type_1, rect_1), ... , (cls_type_n, rect_n)] rect = [x0, y0, x1, y1, x2, y2, x3, y3] left_top = (x0, y0), right_top = (x1, y1), right_bottom = (x2, y2), left_bottom = (x3, y3) """ img_height, img_width = image.shape[:2] bg_height, bg_width = background.shape[:2] # resize image smaller to background # the image accounts for at least two-thirds and not more than four-fifths min_size = min(bg_height, bg_width) // 3 * 2 max_size = min(bg_height, bg_width) // 5 * 4 new_size = random.randint(min_size, max_size) resize_multiple = round(new_size / max(img_height, img_width), 4) # image = image.resize((int(img_width * resize_multiple), int(img_height * resize_multiple)), Image.ANTIALIAS) image = cv2.resize(image, (int(img_width * resize_multiple), int(img_height * resize_multiple))) img_height, img_width = image.shape[:2] # paste the image to the background # height_pos = random.randint((bg_height-img_height)//3, (bg_height-img_height)//3*2) # width_pos = random.randint((bg_width-img_width)//3, (bg_width-img_width)//3*2) height_pos = random.randint(0, (bg_height-img_height)) width_pos = random.randint(0, (bg_width-img_width)) background[height_pos:(height_pos+img_height), width_pos:(width_pos+img_width)] = image img_height, img_width = background.shape[:2] # calculate the boxes after adding background new_box_list = [] for cls_type, rect in box_list: for coor_index in range(len(rect)//2): # resize rect[coor_index*2] = int(rect[coor_index*2] * resize_multiple) # x rect[coor_index*2+1] = int(rect[coor_index*2+1] * resize_multiple) # y # paste rect[coor_index*2] += width_pos # x rect[coor_index*2+1] += height_pos # y # limite rect[coor_index*2] = max(min(rect[coor_index*2], img_width), 0) # x rect[coor_index*2+1] = max(min(rect[coor_index*2+1], img_height), 0)# y box = (cls_type, rect) new_box_list.append(box) image_with_boxes = [background, new_box_list] return image_with_boxes def rotate_image(image, label_box_list=[], angle=90, color=(0, 0, 0), img_scale=1.0): """ rotate with angle, background filled with color, default black (0, 0, 0) label_box = (cls_type, box) box = [x0, y0, x1, y1, x2, y2, x3, y3] """ # grab the rotation matrix (applying the negative of the angle to rotate clockwise), # then grab the sine and cosine (i.e., the rotation components of the matrix) # if angle < 0, counterclockwise rotation; if angle > 0, clockwise rotation # 1.0 - scale, to adjust the size scale (image scaling parameter), recommended 0.75 height_ori, width_ori = image.shape[:2] x_center_ori, y_center_ori = (width_ori // 2, height_ori // 2) rotation_matrix = cv2.getRotationMatrix2D((x_center_ori, y_center_ori), angle, img_scale) cos = np.abs(rotation_matrix[0, 0]) sin = np.abs(rotation_matrix[0, 1]) # compute the new bounding dimensions of the image width_new = int((height_ori * sin) + (width_ori * cos)) height_new = int((height_ori * cos) + (width_ori * sin)) # adjust the rotation matrix to take into account translation rotation_matrix[0, 2] += (width_new / 2) - x_center_ori rotation_matrix[1, 2] += (height_new / 2) - y_center_ori # perform the actual rotation and return the image # borderValue - color to fill missing background, default black, customizable image_new = cv2.warpAffine(image, rotation_matrix, (width_new, height_new), borderValue=color) # each point coordinates angle = angle / 180 * math.pi box_rot_list = cal_rotate_box(label_box_list, angle, (x_center_ori, y_center_ori), (width_new//2, height_new//2)) box_new_list = [] for cls_type, box_rot in box_rot_list: for index in range(len(box_rot)//2): box_rot[index*2] = int(box_rot[index*2]) box_rot[index*2] = max(min(box_rot[index*2], width_new), 0) box_rot[index*2+1] = int(box_rot[index*2+1]) box_rot[index*2+1] = max(min(box_rot[index*2+1], height_new), 0) box_new_list.append((cls_type, box_rot)) image_with_boxes = [image_new, box_new_list] return image_with_boxes def cal_rotate_box(box_list, angle, ori_center, new_center): # box = [x0, y0, x1, y1, x2, y2, x3, y3] # image_shape - [width, height] box_list_new = [] for (cls_type, box) in box_list: box_new = [] for index in range(len(box)//2): box_new.extend(cal_rotate_coordinate(box[index*2], box[index*2+1], angle, ori_center, new_center)) label_box = (cls_type, box_new) box_list_new.append(label_box) return box_list_new def cal_rotate_coordinate(x_ori, y_ori, angle, ori_center, new_center): # box = [x0, y0, x1, y1, x2, y2, x3, y3] # image_shape - [width, height] x_0 = x_ori - ori_center[0] y_0 = ori_center[1] - y_ori x_new = x_0 * math.cos(angle) - y_0 * math.sin(angle) + new_center[0] y_new = new_center[1] - (y_0 * math.cos(angle) + x_0 * math.sin(angle)) return (x_new, y_new) def hue_change(image): if np.random.rand() < 0.8: image = transforms.ColorJitter(brightness=0.5)(image) if np.random.rand() < 0.2: image = transforms.ColorJitter(contrast=0.2)(image) if np.random.rand() < 0.2: image = transforms.ColorJitter(saturation=0.2)(image) if np.random.rand() < 0.2: image = transforms.ColorJitter(hue=0.2)(image) return image def perspective_tranform(image, perspective_rate=0.5, label_box_list=[]): # perspective transform img_height, img_width = image.shape[:2] # points_src = np.float32([[rect[0], rect[1]], [rect[2], rect[3]], [rect[4], rect[5]], [rect[6], rect[7]]]) points_src = np.float32([[0, 0], [img_width-1, 0], [img_width-1, img_height-1], [0, img_height-1]]) max_width = int(img_width * (1.0 + perspective_rate)) max_height = int(img_height * (1.0 + perspective_rate)) min_width = int(img_width * (1.0 - perspective_rate)) min_height = int(img_height * (1.0 + perspective_rate)) delta_width = (max_width - min_width) // 2 delta_height = (max_height - min_height) // 2 x0 = random.randint(0, delta_width) y0 = random.randint(0, delta_height) x1 = random.randint(delta_width + min_width, max_width) y1 = random.randint(0, delta_height) x2 = random.randint(delta_width + min_width, max_width) y2 = random.randint(delta_height + min_height, max_height) x3 = random.randint(0, delta_width) y3 = random.randint(delta_height + min_height, max_height) points_dst = np.float32([[x0, y0], [x1, y1], [x2, y2], [x3, y3]]) # width_new = max(x0, x1, x2, x3) - min(x0, x1, x2, x3) # height_new = max(y0, y1, y2, y3) - min(y0, y1, y2, y3) M = cv2.getPerspectiveTransform(points_src, points_dst) image_res = cv2.warpPerspective(image, M, (max_width, max_height)) # cut image_new = image_res[min(y0, y1):max(y2, y3), min(x0, x3):max(x1, x2)] # labels box_new_list = [] for cls_type, box in label_box_list: # after transformation for index in range(len(box)//2): px = (M[0][0]*box[index*2] + M[0][1]*box[index*2+1] + M[0][2]) / ((M[2][0]*box[index*2] + M[2][1]*box[index*2+1] + M[2][2])) py = (M[1][0]*box[index*2] + M[1][1]*box[index*2+1] + M[1][2]) / ((M[2][0]*box[index*2] + M[2][1]*box[index*2+1] + M[2][2])) box[index*2] = int(px) box[index*2+1] = int(py) # cut box[index*2] -= min(x0, x3) box[index*2+1] -= min(y0, y1) box[index*2] = max(min(box[index*2], image_new.shape[1]), 0) box[index*2+1] = max(min(box[index*2+1], image_new.shape[0]), 0) box_new_list.append((cls_type, box)) image_with_boxes = [image_new, box_new_list] return image_with_boxes if __name__ == "__main__": # test img_test_path = os.path.join(test_path, file_name) points = np.array([[rect[0],rect[1]], [rect[2],rect[3]], [rect[4],rect[5]], [rect[6],rect[7]]], np.int32) image_rect = cv2.polylines(image_res, pts=[points], isClosed=True, color=(0,0,255), thickness=3) cv2.imwrite(img_test_path, image_res) # print("")
2 总结
如果能采集到足够丰富的数据,可以不用进行数据增强即可训练得到很好的模型。但如果数据量很少,那么数据增强是一个提高模型准确率和泛化能力的很好的方式。
转载;https://blog.csdn.net/sinat_16020825/article/details/116521711
这篇关于目标检测中的带或不带标注框的图片离线增强的实现(贴背景、随机旋转、随机色调变换、随机透视变换)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-27数据结构与算法面试题详解及练习
- 2024-12-27网络请求面试题详解与实战
- 2024-12-27数据结构和算法面试真题详解与实战教程
- 2024-12-27网络请求面试真题解析与实战教程
- 2024-12-27数据结构和算法大厂面试真题详解与实战指南
- 2024-12-27TS大厂面试真题解析与应对策略
- 2024-12-27TS大厂面试真题详解与解析
- 2024-12-27网站安全入门:如何识别和修复漏洞
- 2024-12-27SQL注入基础教程
- 2024-12-27初学者指南:理解和修复跨域漏洞