复现车道线分割项目(Lane Segmentation赛事说明在这里),学习数据预处理和数据增强。学习分为Model、Data、Training、Inference、Deployment五个阶段,也就是建模、数据、训练、推断、部署这五个阶段。现在进入的是Data阶段。项目的第一名也就是baseline在这里https://github.com/gujingxiao/Lane-Segmentation-Solution-For-BaiduAI-Autonomous-Driving-Competition
一、数据预处理
1.1 数据的说明
对于竞赛数据集:
使用Gray_Label里面的灰度标签;Labels_Fixed是RGB标签,只是灰度和RGB的一个映射关系,Gray_Label用于训练,Label_Fixed方便查看。使用Road02-04里面的数据,数据里面的lable不要使用,那些label是无效的。
数据增强肯定可以增加准确率,但是在项目开始时,不建议使用。应先建立baseline,尽快是模型能够跑起来,再想办法提高精度。
1.2 观察数据
注意观察数据。观察数据的分布、数据不平衡问题、异常、错误等。发现问题,要用数据清洗的办法解决。什么情况下进行数据清洗?与任务无关的、不能训练模型的、不包含目标特征的图片,剪裁掉无用的部分。由于GPU的限制,在服务器上建立训练用的张量NCHW里面的N一般选择2。
要注意维度的转换, NHWC维度转换为NCHW维度。利用张量的transpose方法转换维度。
数据不平衡会有什么问题?如果二分类类别不平衡,A分类99%,B分类1%,那么模型学不到东西。因为给一个图片就标记A就可以获得99%的正确率。
数据的data和label对应可以用文件名匹配的办法,文件名是时间戳命名的。观察数据集,每个图片的大小H×W=1710×3384。图片分辨率很大,不能全部加载,否则会把内存耗尽。面对这样的数据,在内存小、GPU性能差时怎么办?可以减小batch_size,resize降低图像分辨率,对图片剪裁crop。
1.3 label的处理
训练的时候,label要使用trainId。因为trainId是0-8连续的9个分类,方便做成one-hot向量label。
提交结果时,将推断映射到trainId对应的任意一个id就行。
注意:ignoreInEval列,ignoreInEval==True表示忽略,表示这个数据集里面就没有这个分类,因此不必要关注。直接将这类trainId变成背景类void。
注意:trainId==4的这个类别,ignoreInEval都是True,说明整个这个类别在数据集里面都没有。那么,就把这个类别忽略掉,后面的递进补上,5变成4,6变成5……8变成7。这样最终,实际只有8个分类。
1.4 生成CSV文件
前面提到data和label对应,用程序处理一下,生成CSV格式文件。CSV文件有两列,一列data的绝对路径,一列label的绝对路径。这个可以用pandas库来处理。列举一下pandas库的用法。
个人比较喜欢第一种用法,columns指定csv文件中有几列,这里是image和label两列。data是一个列表类型,每个元素都是一个item数据。这样就可以想写几条就写j
import pandas as pd
df = pd.DataFrame(data=abspaths, # csv文件数据,每个元素是一条数据
columns=['image', 'label']) # 两列 image、label
# 生成csv文件
save = pd.DataFrame({'image':image_list, 'label':label_list})
save_shuffle = shuffle(save)
save_shuffle.to_csv('../data_list/train.csv', index=False)
# 读取csv文件
data_dir = './data_list/train.csv'
train_list = pd.read_csv(data_dir)
1.5 推断
推断的方式可以用模型融合。baseline里面的模型融合就是三个模型的推断做平均值,用平均值作为推断。
1.6 其他内容
补充一些知识。语义分割常见的数据集:PASCAL VOC 2012、cityscapes等;Lane Segmentation的数据集是阿波罗数据集的一部分。阿波罗数据集的论文地址https://arxiv.org/abs/1803.06184,论文题目The ApolloScape Open Dataset for Autonomous Driving and its Application
语义分割的Label比较特殊,是和原图大小一样的图。或者label比原图要小,因为只对其中一部分感兴趣。要有自己的工具,可以参考COCO API,地址https://github.com/cocodataset/cocoapi
二、数据增强
- 数据增强的目的是:提升特征的鲁棒性与模型的泛化性。
- 一般来讲,数据增强是可以提升模型性能,但是并不是所有方法都成立,要考虑性价比。
- 数据增强与数据集和任务存在一定关联性。(色彩增强对分类问题有效、翻转等对目标检测有效)
复现的常用的数据增强的代码在这里https://github.com/Ascetics/Pytorch-SegToolbox/blob/master/utils/augment.py
2.1 数据增强——基于pixel
- 亮度。图像均值反应。也就是改变了图像的均值。
- 对比度。均值不变的情况下,缩放差别。也就是图像均值不变,各色彩的方差增大。
- 颜色饱和度。RGB转向HSV,再增加饱和度。
代码示例
import torch
import torchvision.transforms.functional as TF
import matplotlib.pyplot as plt
import numpy as np
import random
import os
from PIL import Image, ImageFilter
from utils.tools import get_proj_root
class PairAdjustColor(object):
def __init__(self, factors=(0.3, 2.)):
super(PairAdjustColor, self).__init__()
self.factors = factors
pass
def __call__(self, image, label=None):
"""
调整亮度、对比度、饱和度
只调整image,不调整label
:param image: [H,W,C] PIL Image RGB 0~255
:param label: [H,W] PIL Image trainId
:return: [H,W,C] PIL Image RGB 0~255, [H,W] PIL Image trainId
"""
brightness_factor = random.uniform(*self.factors)
contrast_factor = random.uniform(*self.factors)
saturation_factor = random.uniform(*self.factors)
image = TF.adjust_brightness(image, brightness_factor)
image = TF.adjust_contrast(image, contrast_factor)
image = TF.adjust_saturation(image, saturation_factor)
return image, label
pass
2.2 数据增强——基于位置变换
- 水平或垂直翻转
- 平移
- 旋转
- 缩放
- 剪裁
以水平或垂直翻转为例
class PairRandomHFlip(object):
def __init__(self):
super(PairRandomHFlip, self).__init__()
pass
def __call__(self, image, label=None):
"""
图像随机左右翻转
:param image: [H,W,C] PIL Image RGB
:param label: [H,W] PIL Image trainId
:return: [H,W,C] PIL Image RGB, [H,W] PIL Image trainId
"""
if random.uniform(0, 1) < 0.5: # 50%的概率会翻转
image = TF.hflip(image) # 左右翻转
if label is not None:
label = TF.hflip(label)
return image, label
pass
class PairRandomVFlip(object):
def __init__(self):
super(PairRandomVFlip, self).__init__()
pass
def __call__(self, image, label=None):
"""
图像随机上下翻转
:param image: [H,W,C] PIL Image RGB
:param label: [H,W] PIL Image trainId
:return: [H,W,C] PIL Image RGB, [H,W] PIL Image trainId
"""
if random.uniform(0, 1) < 0.5: # 50%的概率会翻转
image = TF.vflip(image) # 上下翻转
if label is not None:
label = TF.vflip(label)
return image, label
pass
注意缩放的时候,image用bilinear差值,label只能用nearest插值。这是因为label中分类是从0,1,2……n_class-1的,插值会产生新的不存在的label。比如,图像的label是555777,bilinear插值变成5556677777,这里面66就是新的在原有label里面不存在的,用nearest差值就可以不会有这个问题。
2.3 数据增强——其他
- Mixup方法,两个目标融合成一个目标
- Cutout随机丢弃图像中的一部分,就相当于针对遮挡问题的增强。在图像中随机找到一个部分,像素全部置0。
- AutoArgument
以Cutout为例
class PairRandomFixErase(object):
def __init__(self, mask_size=64, value=0):
"""
按照固定大小,随机遮挡图像中的某一块方形区域
:param mask_size: 被遮挡的区域大小,默认64x64
:param value: 被遮挡的部分用value值填充
"""
super(PairRandomFixErase, self).__init__()
self.mask_size = mask_size
self.value = value
pass
def __call__(self, image, label=None):
"""
按照固定大小,随机遮挡图像中的某一块方形区域
:param image: [C,H,W] tensor,必须是tensor
:param label: [H,W] tensor,必须是tensor
:return: [C,H,W] tensor, [H,W] tensor
"""
_, h, w = image.shape
top = random.randint(0, h - self.mask_size) # 随机到遮挡部分的top
left = random.randint(0, w - self.mask_size) # 随机到遮挡部分的left
if random.uniform(0, 1) < 0.5: # 随机遮挡
image = TF.erase(image, top, left, self.mask_size, self.mask_size,
v=self.value, inplace=True)
return image, label
pass
三、常用的图像增强库
可以考虑使用已经成熟的图像增强库,比如iaa、Albumentations等。