本文主要是介绍DataWhale AI夏令营 2024大运河杯-数据开发应用创新赛-task3,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
DataWhale AI夏令营 2024大运河杯-数据开发应用创新赛
- 数据增强
- 数据收集
- 打标签
多的不说少的不唠,之前说过初赛基本就是比谁的数据好了,因为原始数据的质量太低了想跑到0.25都很难所以需要使用一些数据增强策略以及收集一些新的数据集。
数据增强
计算机视觉中有一个应用比较广泛的包,来进行数据增强transforms,其中包含了一些图像增强的常见策略比如调整大小、裁剪、归一化、随机反转等等。如果大家想从事计算机视觉方面的工作可以去了解一下,这里我不过多赘述。
这里提供一份使用transforms进行数据增强的代码。
# -*- coding: utf-8 -*-
"""
Created on 2023-04-01 9:08
@author: Fan yi ming
Func: 对于目标检测的数据增强[YOLO](特点是数据增强后标签也要更改)
review:常用的数据增强方式;1.翻转:左右和上下翻转,随机翻转2.随机裁剪,图像缩放3.改变色调4.添加噪声
注意: boxes的标签和坐标一个是int,一个是float,存放的时候要注意处理方式。
参考:https://github.com/REN-HT/Data-Augmentation/blob/main/data_augmentation.py
"""
import torch
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFileImageFile.LOAD_TRUNCATED_IMAGES = True
from torchvision import transforms
import numpy as np
import matplotlib.pyplot as plt
import os
import randomrandom.seed(0)class DataAugmentationOnDetection:def __init__(self):super(DataAugmentationOnDetection, self).__init__()# 以下的几个参数类型中,image的类型全部如下类型# 参数类型: image:Image.open(path)def resize_keep_ratio(self, image, boxes, target_size):"""参数类型: image:Image.open(path), boxes:Tensor, target_size:int功能:将图像缩放到size尺寸,调整相应的boxes,同时保持长宽比(最长的边是target size"""old_size = image.size[0:2] # 原始图像大小# 取最小的缩放比例ratio = min(float(target_size) / (old_size[i]) for i in range(len(old_size))) # 计算原始图像宽高与目标图像大小的比例,并取其中的较小值new_size = tuple([int(i * ratio) for i in old_size]) # 根据上边求得的比例计算在保持比例前提下得到的图像大小# boxes 不用变化,因为是等比例变化return image.resize(new_size, Image.BILINEAR), boxesdef resizeDown_keep_ratio(self, image, boxes, target_size):""" 与上面的函数功能类似,但它只降低图片的尺寸,不会扩大图片尺寸"""old_size = image.size[0:2] # 原始图像大小# 取最小的缩放比例ratio = min(float(target_size) / (old_size[i]) for i in range(len(old_size))) # 计算原始图像宽高与目标图像大小的比例,并取其中的较小值ratio = min(ratio, 1)new_size = tuple([int(i * ratio) for i in old_size]) # 根据上边求得的比例计算在保持比例前提下得到的图像大小# boxes 不用变化,因为是等比例变化return image.resize(new_size, Image.BILINEAR), boxesdef resize(self, img, boxes, size):# ---------------------------------------------------------# 类型为 img=Image.open(path),boxes:Tensor,size:int# 功能为:将图像长和宽缩放到指定值size,并且相应调整boxes# ---------------------------------------------------------return img.resize((size, size), Image.BILINEAR), boxesdef random_flip_horizon(self, img, boxes, h_rate=1):# -------------------------------------# 随机水平翻转# -------------------------------------if np.random.random() < h_rate:transform = transforms.RandomHorizontalFlip(p=1)img = transform(img)if len(boxes) > 0:x = 1 - boxes[:, 1]boxes[:, 1] = xreturn img, boxesdef random_flip_vertical(self, img, boxes, v_rate=1):# 随机垂直翻转if np.random.random() < v_rate:transform = transforms.RandomVerticalFlip(p=1)img = transform(img)if len(boxes) > 0:y = 1 - boxes[:, 2]boxes[:, 2] = yreturn img, boxesdef center_crop(self, img, boxes, target_size=None):# -------------------------------------# 中心裁剪 ,裁剪成 (size, size) 的正方形, 仅限图形,w,h# 这里用比例是很难算的,转成x1,y1, x2, y2格式来计算# -------------------------------------w, h = img.sizesize = min(w, h)if len(boxes) > 0:# 转换到xyxy格式label = boxes[:, 0].reshape([-1, 1])x_, y_, w_, h_ = boxes[:, 1], boxes[:, 2], boxes[:, 3], boxes[:, 4]x1 = (w * x_ - 0.5 * w * w_).reshape([-1, 1])y1 = (h * y_ - 0.5 * h * h_).reshape([-1, 1])x2 = (w * x_ + 0.5 * w * w_).reshape([-1, 1])y2 = (h * y_ + 0.5 * h * h_).reshape([-1, 1])boxes_xyxy = torch.cat([x1, y1, x2, y2], dim=1)# 边框转换if w > h:boxes_xyxy[:, [0, 2]] = boxes_xyxy[:, [0, 2]] - (w - h) / 2else:boxes_xyxy[:, [1, 3]] = boxes_xyxy[:, [1, 3]] - (h - w) / 2in_boundary = [i for i in range(boxes_xyxy.shape[0])]for i in range(boxes_xyxy.shape[0]):# 判断x是否超出界限if (boxes_xyxy[i, 0] < 0 and boxes_xyxy[i, 2] < 0) or (boxes_xyxy[i, 0] > size and boxes_xyxy[i, 2] > size):in_boundary.remove(i)# 判断y是否超出界限elif (boxes_xyxy[i, 1] < 0 and boxes_xyxy[i, 3] < 0) or (boxes_xyxy[i, 1] > size and boxes_xyxy[i, 3] > size):in_boundary.append(i)boxes_xyxy = boxes_xyxy[in_boundary]boxes = boxes_xyxy.clamp(min=0, max=size).reshape([-1, 4]) # 压缩到固定范围label = label[in_boundary]# 转换到YOLO格式x1, y1, x2, y2 = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3]xc = ((x1 + x2) / (2 * size)).reshape([-1, 1])yc = ((y1 + y2) / (2 * size)).reshape([-1, 1])wc = ((x2 - x1) / size).reshape([-1, 1])hc = ((y2 - y1) / size).reshape([-1, 1])boxes = torch.cat([xc, yc, wc, hc], dim=1)# 图像转换transform = transforms.CenterCrop(size)img = transform(img)if target_size:img = img.resize((target_size, target_size), Image.BILINEAR)if len(boxes) > 0:return img, torch.cat([label.reshape([-1, 1]), boxes], dim=1)else:return img, boxes# ------------------------------------------------------# 以下img皆为Tensor类型# ------------------------------------------------------def random_bright(self, img, u=120, p=1):# -------------------------------------# 随机亮度变换# -------------------------------------if np.random.random() < p:alpha = np.random.uniform(-u, u) / 255img += alphaimg = img.clamp(min=0.0, max=1.0)return imgdef random_contrast(self, img, lower=0.5, upper=1.5, p=1):# -------------------------------------# 随机增强对比度# -------------------------------------if np.random.random() < p:alpha = np.random.uniform(lower, upper)img *= alphaimg = img.clamp(min=0, max=1.0)return imgdef random_saturation(self, img, lower=0.5, upper=1.5, p=1):# 随机饱和度变换,针对彩色三通道图像,中间通道乘以一个值if np.random.random() < p:alpha = np.random.uniform(lower, upper)img[1] = img[1] * alphaimg[1] = img[1].clamp(min=0, max=1.0)return imgdef add_gasuss_noise(self, img, mean=0, std=0.1):noise = torch.normal(mean, std, img.shape)img += noiseimg = img.clamp(min=0, max=1.0)return imgdef add_salt_noise(self, img):noise = torch.rand(img.shape)alpha = np.random.random() / 5 + 0.7img[noise[:, :, :] > alpha] = 1.0return imgdef add_pepper_noise(self, img):noise = torch.rand(img.shape)alpha = np.random.random() / 5 + 0.7img[noise[:, :, :] > alpha] = 0return imgdef plot_pics(img, boxes):# 显示图像和候选框,img是Image.Open()类型, boxes是Tensor类型plt.imshow(img)label_colors = [(213, 110, 89)]w, h = img.sizefor i in range(boxes.shape[0]):box = boxes[i, 1:]xc, yc, wc, hc = boxx = w * xc - 0.5 * w * wcy = h * yc - 0.5 * h * hcbox_w, box_h = w * wc, h * hcplt.gca().add_patch(plt.Rectangle(xy=(x, y), width=box_w, height=box_h,edgecolor=[c / 255 for c in label_colors[0]],fill=False, linewidth=2))plt.show()def get_image_list(image_path):# 根据图片文件,查找所有图片并返回列表files_list = []for root, sub_dirs, files in os.walk(image_path):for special_file in files:special_file = special_file[0: len(special_file)]files_list.append(special_file)return files_listdef get_label_file(label_path, image_name):# 根据图片信息,查找对应的labelfname = os.path.join(label_path, image_name[0: len(image_name) - 4] + ".txt")data2 = []if not os.path.exists(fname):return data2if os.path.getsize(fname) == 0:return data2else:with open(fname, 'r', encoding='utf-8') as infile:# 读取并转换标签for line in infile:data_line = line.strip("\n").split()data2.append([float(i) for i in data_line])return data2def save_Yolo(img, boxes, save_path, prefix, image_name):# img: 需要时Image类型的数据, prefix 前缀# 将结果保存到save path指示的路径中if not os.path.exists(save_path) or \not os.path.exists(os.path.join(save_path, "images")):os.makedirs(os.path.join(save_path, "images"))os.makedirs(os.path.join(save_path, "labels"))try:img.save(os.path.join(save_path, "images", prefix + image_name))with open(os.path.join(save_path, "labels", prefix + image_name[0:len(image_name) - 4] + ".txt"), 'w',encoding="utf-8") as f:if len(boxes) > 0: # 判断是否为空# 写入新的label到文件中for data in boxes:str_in = ""for i, a in enumerate(data):if i == 0:str_in += str(int(a))else:str_in += " " + str(float(a))f.write(str_in + '\n')except:print("ERROR: ", image_name, " is bad.")def runAugumentation(image_path, label_path, save_path):image_list = get_image_list(image_path)for image_name in image_list:print("dealing: " + image_name)img = Image.open(os.path.join(image_path, image_name))boxes = get_label_file(label_path, image_name)boxes = torch.tensor(boxes)# 下面是执行的数据增强功能,可自行选择# Image类型的参数DAD = DataAugmentationOnDetection()""" 尺寸变换 """# 缩小尺寸# t_img, t_boxes = DAD.resizeDown_keep_ratio(img, boxes, 1024)# save_Yolo(t_img, boxes, save_path, prefix="rs_", image_name=image_name)# 水平旋转t_img, t_boxes = DAD.random_flip_horizon(img, boxes.clone())save_Yolo(t_img, t_boxes, save_path, prefix="fh_", image_name=image_name)# 竖直旋转t_img, t_boxes = DAD.random_flip_vertical(img, boxes.clone())save_Yolo(t_img, t_boxes, save_path, prefix="fv_", image_name=image_name)# center_cropt_img, t_boxes = DAD.center_crop(img, boxes.clone(), 1024)save_Yolo(t_img, t_boxes, save_path, prefix="cc_", image_name=image_name)""" 图像变换,用tensor类型"""to_tensor = transforms.ToTensor()to_image = transforms.ToPILImage()img = to_tensor(img)# random_brightt_img, t_boxes = DAD.random_bright(img.clone()), boxessave_Yolo(to_image(t_img), boxes, save_path, prefix="rb_", image_name=image_name)# random_contrast 对比度变化t_img, t_boxes = DAD.random_contrast(img.clone()), boxessave_Yolo(to_image(t_img), boxes, save_path, prefix="rc_", image_name=image_name)# random_saturation 饱和度变化t_img, t_boxes = DAD.random_saturation(img.clone()), boxessave_Yolo(to_image(t_img), boxes, save_path, prefix="rs_", image_name=image_name)# 高斯噪声t_img, t_boxes = DAD.add_gasuss_noise(img.clone()), boxessave_Yolo(to_image(t_img), boxes, save_path, prefix="gn_", image_name=image_name)# add_salt_noiset_img, t_boxes = DAD.add_salt_noise(img.clone()), boxessave_Yolo(to_image(t_img), boxes, save_path, prefix="sn_", image_name=image_name)# add_pepper_noiset_img, t_boxes = DAD.add_pepper_noise(img.clone()), boxessave_Yolo(to_image(t_img), boxes, save_path, prefix="pn_", image_name=image_name)print("end: " + image_name)if __name__ == '__main__':# 图像和标签文件夹image_path = "./yolo-dataset-lwb/val"label_path = "./yolo-dataset-lwb/val_txt"save_path = "./yolo-dataset-lwb/save_val" # 结果保存位置路径,可以是一个不存在的文件夹# 运行runAugumentation(image_path, label_path, save_path)
因为YOLOV8为我们集成好了很多的数据增强策略了已经。
以下是YOLOV8在训练中已经使用的一些策略,大家也可以调参使用。
参数 | 范围 | 功能 |
---|---|---|
hsv_h | 默认0.015,可调范围是0.0~1.0 | 调整图像的色调,引入颜色可变性。 |
hsv_s | 默认0.7,可调范围是0.0~1.0 | 调整图像的饱和度。 |
translate | 默认0.1,可调范围是0.0~1.0 | 平移一小部分图像。 |
scale | 默认0.5,可调范围是大于等于0.0都可以 | 缩放图像。 |
fliplr | 默认为0.5,可调范围是0.0~1.0 | 以指定的概率将图像从左向右翻转,左右镜像。 |
mosaic | 默认为1.0,可调范围是0.0~1.0 | 将四个训练图像组合为一个。 |
auto_augment | 默认为randaugment,可调范围是(randaugment、autoaugment和augmix) | 面向分类任务,自动应用预定义的增强策略。 |
erasing | 默认为0.4,可调范围是0.0~0.9 | 在分类训练过程中随机擦除图像的一部分。 |
crop_fraction | 默认为1.0,可调范围是0.1~1.0 | 将分类图像裁剪到其大小的一小部分。 |
以下是YOLOV8未使用的一些增强策略
参数 | 范围 | 功能 |
---|---|---|
degrees | 范围是-180~+180 | 在指定的度数范围内随机旋转图像。 |
shear | 范围是-180~+180 | 以指定的角度剪切图像。 |
perspective | 范围是0.0~0.001 | 将随机透视变换应用于图像。 |
flipud | 范围是0.0~1.0 | 以指定的概率将图像倒置。 |
bgr | 范围是0.0~1.0 | 以指定的概率将图像通道从RGB翻转到BGR。 |
mixup | 范围是0.0~1.0 | 混合两个图像及其标签,创建合成图像。 |
copy_paste | 范围是0.0~1.0 | 将对象从一个图像中复制并粘贴到另一个图像上。 |
这里是一篇关于YOLOV8各个参数功能详细介绍的大佬原文添加链接描述建议像详细了解参数功能的可以去看一下。
参数的使用方法如下(直接在train中使用即可):
import os
os.environ["CUDA_VISIBLE_DEVICES"] = "0"import warnings
warnings.filterwarnings('ignore')from ultralytics import YOLO
model = YOLO("yolov8n.pt")
results = model.train(data="yolo-dataset/yolo.yaml", epochs=2, imgsz=1080, batch=16, copy_paste=0.2, mixup=0.5)
数据收集
然后大家可以观察一下官方大大给出的数据集,其实大多视频都是同一个场景或者标签有错误,所以如果想卷的话还是要自己动手收集数据打标签,这里我也给大家推荐几个数据集。
这里是一个飞桨提供的一个垃圾桶的数据集大家可以去找一些适合本次大赛的图片添加到训练集中垃圾桶溢满数据集。
这个是极市在2022年的一个比赛的数据集,里面的图片看例图和本次本赛还是很贴近的,但是这个目前极市官方是不公开数据的,不过这个比赛当视很火,应该可以在网上找到资源机动车违停识别。
打标签
感觉写到上面有点水笔记,再加个打标签吧,虽然能参加比赛的佬基本都会。
首先先创建一个打标签的环境
conda create -n label python=3.8
激活环境
conda activate label
安装labelme
pip install labelme
等待安装完成使用在终端输入labelme启动labelme
labelme
启动后界面如下
点击打开目录选择你数据集所在的文件夹即可,接着选择‘文件-更改输出路径’,这里建议图片和标签不要放在一起,因为labelme标注后的数据不是yolo的格式需要进一步的转化才能使用。
这里提供一个转标签的python文件
# # trans_labelme_to_yolo.py
import cv2
import os
import json
import shutil
import numpy as np
from pathlib import Path
from glob import glob# id2cls = {0: 'clothing'}
# cls2id = {'clothing': 0}
id2cls = {0: '非机动车违停', 1: '机动车违停', 2: '垃圾桶满溢', 3:'违法经营'}
cls2id = {'非机动车违停': 0, '机动车违停': 1, '垃圾桶满溢': 2, '违法经营': 3}# 支持中文路径
def cv_imread(filePath):cv_img = cv2.imdecode(np.fromfile(filePath, dtype=np.uint8), flags=cv2.IMREAD_COLOR)return cv_imgdef labelme2yolo_single(img_path, label_file):anno = json.load(open(label_file, "r", encoding="utf-8"))shapes = anno['shapes']w0, h0 = anno['imageWidth'], anno['imageHeight']image_path = os.path.basename(img_path + anno['imagePath'])labels = []for s in shapes:pts = s['points']x1, y1 = pts[0]x2, y2 = pts[1]x = (x1 + x2) / 2 / w0y = (y1 + y2) / 2 / h0w = abs(x2 - x1) / w0h = abs(y2 - y1) / h0cid = cls2id[s['label']]labels.append([cid, x, y, w, h])return np.array(labels), image_pathdef labelme2yolo(img_path, labelme_label_dir, save_dir='res/'):labelme_label_dir = str(Path(labelme_label_dir)) + '/'save_dir = str(Path(save_dir))yolo_label_dir = save_dir + '/'if not os.path.exists(yolo_label_dir):os.makedirs(yolo_label_dir)json_files = glob(labelme_label_dir + '*.json')for ijf, jf in enumerate(json_files):print(ijf + 1, '/', len(json_files), jf)filename = os.path.basename(jf).rsplit('.', 1)[0]labels, image_path = labelme2yolo_single(img_path, jf)if len(labels) > 0:# 在这里我们对np.savetxt的调用做了修改np.savetxt(yolo_label_dir + filename + '.txt', labels, fmt='%d %0.16f %0.16f %0.16f %0.16f')# shutil.copy(labelme_label_dir + image_path, yolo_image_dir + image_path)print('Completed!')if __name__ == '__main__':img_path = './yolo-dataset-lwb/val' # 数据集图片的路径json_dir = './yolo-dataset-lwb/val_json' # json标签的路径save_dir = './yolo-dataset-lwb/val_txt' # 保存的txt标签的路径labelme2yolo(img_path, json_dir, save_dir)
这篇关于DataWhale AI夏令营 2024大运河杯-数据开发应用创新赛-task3的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!