| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415 |
- # ------------------------------------------------------------
- # Data preprocessor for SSD
- # ------------------------------------------------------------
- import cv2
- import numpy as np
- import torch
- from numpy import random
- # ------------------------- Augmentations -------------------------
- class Compose(object):
- """Composes several augmentations together.
- Args:
- transforms (List[Transform]): list of transforms to compose.
- Example:
- >>> augmentations.Compose([
- >>> transforms.CenterCrop(10),
- >>> transforms.ToTensor(),
- >>> ])
- """
- def __init__(self, transforms):
- self.transforms = transforms
- def __call__(self, img, boxes=None, labels=None):
- for t in self.transforms:
- img, boxes, labels = t(img, boxes, labels)
- return img, boxes, labels
- ## Convert Image to float type
- class ConvertFromInts(object):
- def __call__(self, image, boxes=None, labels=None):
- return image.astype(np.float32), boxes, labels
- ## Convert color format
- class ConvertColor(object):
- def __init__(self, current='BGR', transform='HSV'):
- self.transform = transform
- self.current = current
- def __call__(self, image, boxes=None, labels=None):
- if self.current == 'BGR' and self.transform == 'HSV':
- image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
- elif self.current == 'HSV' and self.transform == 'BGR':
- image = cv2.cvtColor(image, cv2.COLOR_HSV2BGR)
- else:
- raise NotImplementedError
- return image, boxes, labels
- ## Resize image
- class Resize(object):
- def __init__(self, img_size=640):
- self.img_size = img_size
- def __call__(self, image, boxes=None, labels=None):
- orig_h, orig_w = image.shape[:2]
- # resize
- image = cv2.resize(image, (self.img_size, self.img_size)).astype(np.float32)
- img_h, img_w = image.shape[:2]
- # rescale bbox
- if boxes is not None:
- boxes[..., [0, 2]] = boxes[..., [0, 2]] / orig_w * img_w
- boxes[..., [1, 3]] = boxes[..., [1, 3]] / orig_h * img_h
- return image, boxes, labels
- ## Random Saturation
- class RandomSaturation(object):
- def __init__(self, lower=0.5, upper=1.5):
- self.lower = lower
- self.upper = upper
- assert self.upper >= self.lower, "contrast upper must be >= lower."
- assert self.lower >= 0, "contrast lower must be non-negative."
- def __call__(self, image, boxes=None, labels=None):
- if random.randint(2):
- image[:, :, 1] *= random.uniform(self.lower, self.upper)
- return image, boxes, labels
- ## Random Hue
- class RandomHue(object):
- def __init__(self, delta=18.0):
- assert delta >= 0.0 and delta <= 360.0
- self.delta = delta
- def __call__(self, image, boxes=None, labels=None):
- if random.randint(2):
- image[:, :, 0] += random.uniform(-self.delta, self.delta)
- image[:, :, 0][image[:, :, 0] > 360.0] -= 360.0
- image[:, :, 0][image[:, :, 0] < 0.0] += 360.0
- return image, boxes, labels
- ## Random Lighting noise
- class RandomLightingNoise(object):
- def __init__(self):
- self.perms = ((0, 1, 2), (0, 2, 1),
- (1, 0, 2), (1, 2, 0),
- (2, 0, 1), (2, 1, 0))
- def __call__(self, image, boxes=None, labels=None):
- if random.randint(2):
- swap = self.perms[random.randint(len(self.perms))]
- shuffle = SwapChannels(swap) # shuffle channels
- image = shuffle(image)
- return image, boxes, labels
- ## Random Contrast
- class RandomContrast(object):
- def __init__(self, lower=0.5, upper=1.5):
- self.lower = lower
- self.upper = upper
- assert self.upper >= self.lower, "contrast upper must be >= lower."
- assert self.lower >= 0, "contrast lower must be non-negative."
- # expects float image
- def __call__(self, image, boxes=None, labels=None):
- if random.randint(2):
- alpha = random.uniform(self.lower, self.upper)
- image *= alpha
- return image, boxes, labels
- ## Random Brightness
- class RandomBrightness(object):
- def __init__(self, delta=32):
- assert delta >= 0.0
- assert delta <= 255.0
- self.delta = delta
- def __call__(self, image, boxes=None, labels=None):
- if random.randint(2):
- delta = random.uniform(-self.delta, self.delta)
- image += delta
- return image, boxes, labels
- ## Random SampleCrop
- class RandomSampleCrop(object):
- """Crop
- Arguments:
- img (Image): the image being input during training
- boxes (Tensor): the original bounding boxes in pt form
- labels (Tensor): the class labels for each bbox
- mode (float tuple): the min and max jaccard overlaps
- Return:
- (img, boxes, classes)
- img (Image): the cropped image
- boxes (Tensor): the adjusted bounding boxes in pt form
- labels (Tensor): the class labels for each bbox
- """
- def __init__(self):
- self.sample_options = (
- # using entire original input image
- None,
- # sample a patch s.t. MIN jaccard w/ obj in .1,.3,.4,.7,.9
- (0.1, None),
- (0.3, None),
- (0.7, None),
- (0.9, None),
- # randomly sample a patch
- (None, None),
- )
- def intersect(self, box_a, box_b):
- max_xy = np.minimum(box_a[:, 2:], box_b[2:])
- min_xy = np.maximum(box_a[:, :2], box_b[:2])
- inter = np.clip((max_xy - min_xy), a_min=0, a_max=np.inf)
- return inter[:, 0] * inter[:, 1]
- def compute_iou(self, box_a, box_b):
- inter = self.intersect(box_a, box_b)
- area_a = ((box_a[:, 2]-box_a[:, 0]) *
- (box_a[:, 3]-box_a[:, 1])) # [A,B]
- area_b = ((box_b[2]-box_b[0]) *
- (box_b[3]-box_b[1])) # [A,B]
- union = area_a + area_b - inter
- return inter / union # [A,B]
- def __call__(self, image, boxes=None, labels=None):
- height, width, _ = image.shape
- # check
- if len(boxes) == 0:
- return image, boxes, labels
- while True:
- # randomly choose a mode
- sample_id = np.random.randint(len(self.sample_options))
- mode = self.sample_options[sample_id]
- if mode is None:
- return image, boxes, labels
- min_iou, max_iou = mode
- if min_iou is None:
- min_iou = float('-inf')
- if max_iou is None:
- max_iou = float('inf')
- # max trails (50)
- for _ in range(50):
- current_image = image
- w = random.uniform(0.3 * width, width)
- h = random.uniform(0.3 * height, height)
- # aspect ratio constraint b/t .5 & 2
- if h / w < 0.5 or h / w > 2:
- continue
- left = random.uniform(width - w)
- top = random.uniform(height - h)
- # convert to integer rect x1,y1,x2,y2
- rect = np.array([int(left), int(top), int(left+w), int(top+h)])
- # calculate IoU (jaccard overlap) b/t the cropped and gt boxes
- overlap = self.compute_iou(boxes, rect)
- # is min and max overlap constraint satisfied? if not try again
- if overlap.min() < min_iou and max_iou < overlap.max():
- continue
- # cut the crop from the image
- current_image = current_image[rect[1]:rect[3], rect[0]:rect[2],
- :]
- # keep overlap with gt box IF center in sampled patch
- centers = (boxes[:, :2] + boxes[:, 2:]) / 2.0
- # mask in all gt boxes that above and to the left of centers
- m1 = (rect[0] < centers[:, 0]) * (rect[1] < centers[:, 1])
- # mask in all gt boxes that under and to the right of centers
- m2 = (rect[2] > centers[:, 0]) * (rect[3] > centers[:, 1])
- # mask in that both m1 and m2 are true
- mask = m1 * m2
- # have any valid boxes? try again if not
- if not mask.any():
- continue
- # take only matching gt boxes
- current_boxes = boxes[mask, :].copy()
- # take only matching gt labels
- current_labels = labels[mask]
- # should we use the box left and top corner or the crop's
- current_boxes[:, :2] = np.maximum(current_boxes[:, :2],
- rect[:2])
- # adjust to crop (by substracting crop's left,top)
- current_boxes[:, :2] -= rect[:2]
- current_boxes[:, 2:] = np.minimum(current_boxes[:, 2:],
- rect[2:])
- # adjust to crop (by substracting crop's left,top)
- current_boxes[:, 2:] -= rect[:2]
- return current_image, current_boxes, current_labels
- ## Random scaling
- class Expand(object):
- def __call__(self, image, boxes, labels):
- if random.randint(2):
- return image, boxes, labels
- height, width, depth = image.shape
- ratio = random.uniform(1, 4)
- left = random.uniform(0, width*ratio - width)
- top = random.uniform(0, height*ratio - height)
- expand_image = np.zeros(
- (int(height*ratio), int(width*ratio), depth),
- dtype=image.dtype)
- expand_image[int(top):int(top + height),
- int(left):int(left + width)] = image
- image = expand_image
- boxes = boxes.copy()
- boxes[:, :2] += (int(left), int(top))
- boxes[:, 2:] += (int(left), int(top))
- return image, boxes, labels
- ## Random HFlip
- class RandomHorizontalFlip(object):
- def __call__(self, image, boxes, classes):
- _, width, _ = image.shape
- if random.randint(2):
- image = image[:, ::-1]
- boxes = boxes.copy()
- boxes[:, 0::2] = width - boxes[:, 2::-2]
- return image, boxes, classes
- ## Random swap channels
- class SwapChannels(object):
- """Transforms a tensorized image by swapping the channels in the order
- specified in the swap tuple.
- Args:
- swaps (int triple): final order of channels
- eg: (2, 1, 0)
- """
- def __init__(self, swaps):
- self.swaps = swaps
- def __call__(self, image):
- """
- Args:
- image (Tensor): image tensor to be transformed
- Return:
- a tensor with channels swapped according to swap
- """
- # if torch.is_tensor(image):
- # image = image.data.cpu().numpy()
- # else:
- # image = np.array(image)
- image = image[:, :, self.swaps]
- return image
- ## Random color jitter
- class PhotometricDistort(object):
- def __init__(self):
- self.pd = [
- RandomContrast(),
- ConvertColor(transform='HSV'),
- RandomSaturation(),
- RandomHue(),
- ConvertColor(current='HSV', transform='BGR'),
- RandomContrast()
- ]
- self.rand_brightness = RandomBrightness()
- def __call__(self, image, boxes, labels):
- im = image.copy()
- im, boxes, labels = self.rand_brightness(im, boxes, labels)
- if random.randint(2):
- distort = Compose(self.pd[:-1])
- else:
- distort = Compose(self.pd[1:])
- im, boxes, labels = distort(im, boxes, labels)
- return im, boxes, labels
- # ------------------------- Preprocessers -------------------------
- ## SSD-style Augmentation
- class SSDAugmentation(object):
- def __init__(self, img_size=640):
- self.img_size = img_size
- self.pixel_mean = [0., 0., 0.]
- self.pixel_std = [255., 255., 255.]
- self.color_format = 'bgr'
- self.augment = Compose([
- ConvertFromInts(), # 将int类型转换为float32类型
- PhotometricDistort(), # 图像颜色增强
- Expand(), # 扩充增强
- RandomSampleCrop(), # 随机剪裁
- RandomHorizontalFlip(), # 随机水平翻转
- Resize(self.img_size) # resize操作
- ])
- def __call__(self, image, target, mosaic=False):
- orig_h, orig_w = image.shape[:2]
- ratio = [self.img_size / orig_w, self.img_size / orig_h]
- # augment
- boxes = target['boxes'].copy()
- labels = target['labels'].copy()
- image, boxes, labels = self.augment(image, boxes, labels)
- # to tensor
- img_tensor = torch.from_numpy(image).permute(2, 0, 1).contiguous().float()
- target['boxes'] = torch.from_numpy(boxes).float()
- target['labels'] = torch.from_numpy(labels).float()
- # normalize image
- img_tensor /= 255.
- return img_tensor, target, ratio
-
- ## SSD-style valTransform
- class SSDBaseTransform(object):
- def __init__(self, img_size):
- self.img_size = img_size
- self.pixel_mean = [0., 0., 0.]
- self.pixel_std = [255., 255., 255.]
- self.color_format = 'bgr'
- def __call__(self, image, target=None, mosaic=False):
- # resize
- orig_h, orig_w = image.shape[:2]
- ratio = [self.img_size / orig_w, self.img_size / orig_h]
- image = cv2.resize(image, (self.img_size, self.img_size)).astype(np.float32)
-
- # scale targets
- if target is not None:
- boxes = target['boxes'].copy()
- labels = target['labels'].copy()
- img_h, img_w = image.shape[:2]
- boxes[..., [0, 2]] = boxes[..., [0, 2]] / orig_w * img_w
- boxes[..., [1, 3]] = boxes[..., [1, 3]] / orig_h * img_h
- target['boxes'] = boxes
-
- # to tensor
- img_tensor = torch.from_numpy(image).permute(2, 0, 1).contiguous().float()
- if target is not None:
- target['boxes'] = torch.from_numpy(boxes).float()
- target['labels'] = torch.from_numpy(labels).float()
-
- # normalize image
- img_tensor /= 255.
- return img_tensor, target, ratio
|