在大多数情况下,我们在为模型创建训练数据集时无需担心注释格式。 COCO API
为我们提供了一系列的api函数,方便我们获取任务的图像数据和目标标签。
COCO数据集有一个名为pycocotools
的 python API,供用户轻松加载和使用COCO数据集进行检测、分割和其他cv任务。 以下是基于 pycocotools
实现COCO检测数据集的PaddleViT实现,并用于训练和验证。
CocoDataset
ClassCocoDataset
类由 paddle.io.Dataset
类实现, 并需要两个函数 __getitem__
与 __len__
, 即:
class CocoDetection(paddle.io.Dataset):
def __init__(self, image_folder, anno_file, transforms, return_mask):
super().__init__()
...
def __getitem__(self, idx):
...
def __len__(self):
...
__init__
method
在类的初始化方法中:
from pycocotools.coco import COCO
...
class CocoDataset():
def __init__(self):
super().__init__()
# step1
self.coco = COCO(anno_file)
# step2
ids = list(sorted(self.coco.imgs.keys()))
self.ids = self._remove_images_without_annotations(ids)
# step3
self._transforms = transforms
# step4
self.prepare = ConvertCocoPolysToMasks(return_masks)
self.root = img_folder
__getitem__
method
__getitem__
方法将索引作为输入,并输出包含单张图像及其目标标签的(image, target)
对。在coco检测中,这个目标是一个类似于以下形式的 dict
:
target = {'image_id': image_id, 'annotations': target}
image_id
是在coco注释中相同的图像id.
target
是键值对的字典,例如 bbox
和 mask
. (英文版单词拼写错误)
__getitem__
方法定义:
def __getitem__(self, idx):
image_id = self.ids[idx]
image = self._load_image(image_id)
target = self._load_target(image_id)
target = {'image_id': image_id, 'annotations': target}
image, target = self.prepare(image, target)
if self._transform is not None:
image, target = self._transform(image, target)
return image, target
__len__
method
返回数据集中的样本数,与ids
长度相同:
def __len__(self):
return len(self.ids)
_load_image
, _load_target
methods
PIL.Image
和 COCO API
用于根据给定索引获取图像数据和原始目标标签.
def _load_image(self, idx):
""" Return PIL Image (RGB) according to COCO image id"""
path = self.coco.loadImgs(idx)[0]['file_name']
return Image.open(os.path.join(self.root, path)).convert('RGB')
def _load_target(self, idx):
""" Return image annos according to COCO image id"""
return self.coco.loadAnns(self.coco.getAnnIds(idx))
ConvertCocoPolysToMasks
Class该类定义了以图像和标签为输入并输出图像数组和处理后的标签。
专门对于目标标签的处理:
iscrowd=1
的图像;[x1, y1, x2, y2]
中的包围框转换为numpy数组类型,然后根据包围框裁剪图像;target
字典中。Transforms
Module在转换模块(transforms.py
)中定义了多种数据压缩方法。 定义我们自己的模块而不是使用paddle视觉转换的原因是,每个数据变换都必须应用于图像数据集其目标标签,例如bbox和掩码。假设在训练期间对图像数据应用类随机裁剪操作,则该图像中的bbox必需应用相同的裁剪。
Validation transforms
DETR 的验证转换具有以下操作:
RandomResize()
: 将图像和标签调整为具有相同比例的特定大小。ToTensor()
: 将图像数据转换为 paddle.Tensor
Normalize()
: 均值$-mean$和$/std$Training transforms
DETR的训练转换具有以下操作:
RandomHorizontalFlip()
随机水平翻转数据。RandomSelect()
随机选择两个子操作之一: (1) 一个单个 RandomResize
步骤; (2) 一个 三步骤操作: RandomReize
, RandomSizeCrop
, 以及 RandomResize
ToTensor()
: 将图像数据转换为 paddle.Tensor
Normalize()
: 图像数据标准化, $-mean$ 和 $/std$RandomHorizontalFlip()
此变换需要初始化参数中的概率用来控制是否应用反转的随机性。
class RandomHorizontalFlip():
def __init__(self, p=0.5):
self.p = p
def __call__(self, image, target):
if random.random() < self.p:
return hflip(image, target)
return image, target
hflip
方法定义了图像和目标(包含包围框和盐吗的真实标注值的字典)的水平翻转操作。
RandomSelect()
RandomSelect()
有一个prob值控制选择它的两个子操作之一的随机性。
class RandomSelect():
""" Random select one the transforms to apply with probablity p"""
def __init__(self, transforms1, transforms2, p=0.5):
self.transforms1 = transforms1
self.transforms2 = transforms2
self.p = p
def __call__(self, image, target):
if random.random() > self.p:
return self.transforms1(image, target)
return self.transforms2(image, target)
两个转换操作在DETR训练中使用:
RandomResize()
RandomResize()
+ RandomSizeCrop()
+ RandomResize()
RandomResize()
RandomResize
有两个参数:sizes
和 max_size
. 该方法随机选择sizes
中的一个值作为图像短边的目标尺寸,同时保持图像的比例不变。但是,如果图像的长边大于max_size
(当使用所选尺寸作为短边时),则将图像的长边设置为max_size
,而较短的尺寸需要重新计算以保持图像长宽比例不变。
必须在bbox和掩码使用相同的尺寸调整操作。 通过乘以高度和宽度的比例可以转换包围框。可以通过插值和二值化来转换掩码以获得缩放掩码(如果 values > 0.5则设置为1,否则设置为0)。
RandomSizeCrop()
RandomSizeCrop
将min_size
和max_size
作为输入,然后将裁减图像中的随机区域作为输出。输出区域的尺寸为 [randint(min_size, max_size), randint(min_size, max_size)]
.
RandomSizeCrop
分为三个步骤实现:
min_size
, max_size
和原始图像尺寸,生成随机图像宽度和图像高度。[top, left, height, width]
表示.具体来说,我们实现了一个crop
方法,其输入
(1)在[top, left, height, width]
中的裁剪区域,
(2) 原始图像 以及 (3) 目标标签,然后返回裁剪后的图像和裁剪后的标签。
(请注意,在裁剪之后,原始包围框或者掩码也会被裁剪,甚至在裁剪后的图像中看不到,因此,我们必须从目标标签中消除那些无效的框和掩吗。)
ToTensor()
ToTensor
将图像数据从PIL.Image转换为paddle.Tensor, 返回图像张量和相应的标签,通过以下方式可以实现:
import paddle.vision.transforms as T
class ToTensor:
def __call__(self, image, target):
return T.to_tensor(image), target
Normalize()
在 Normalize
方法中, 除了数据归一化(-mean & /std), 我们还将包围框从 [x0, y0, x1, y1]
归一化为 [cx, cy, w, h]
, 根据图像尺寸归一化为相对坐标. 实现方式如下:
class Normalize():
def __init__(self, mean, std):
self.mean = mean
self.std = std
def __call__(self, image, target=None):
# -mean, / std
image = T.functional.normalize(image, mean=self.mean, std=self.std)
if target is None:
return image, None
target = target.copy()
# from xyxy -> cxcywh -> relative coords
h, w = image.shape[-2:]
if 'boxes' in target and target['boxes'].shape[0] != 0:
boxes = target['boxes']
boxes = box_xyxy_to_cxcywh_numpy(boxes)
boxes = boxes / np.array([w, h, w, h], dtype='float32')
target['boxes'] = boxes
return image, target