You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

296 lines
12 KiB

# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import traceback
import random
try:
from collections.abc import Sequence
except Exception:
from collections import Sequence
import numpy as np
from paddle.fluid.dataloader.collate import default_collate_fn
from .operators import Transform, Resize, ResizeByShort, _Permute, interp_dict
from .box_utils import jaccard_overlap
from paddlers.utils import logging
class BatchCompose(Transform):
def __init__(self, batch_transforms=None, collate_batch=True):
super(BatchCompose, self).__init__()
self.batch_transforms = batch_transforms
self.collate_batch = collate_batch
def __call__(self, samples):
if self.batch_transforms is not None:
for op in self.batch_transforms:
try:
samples = op(samples)
except Exception as e:
stack_info = traceback.format_exc()
logging.warning("fail to map batch transform [{}] "
"with error: {} and stack:\n{}".format(
op, e, str(stack_info)))
raise e
samples = _Permute()(samples)
extra_key = ['h', 'w', 'flipped']
for k in extra_key:
for sample in samples:
if k in sample:
sample.pop(k)
if self.collate_batch:
batch_data = default_collate_fn(samples)
else:
batch_data = {}
for k in samples[0].keys():
tmp_data = []
for i in range(len(samples)):
tmp_data.append(samples[i][k])
if not 'gt_' in k and not 'is_crowd' in k and not 'difficult' in k:
# This if assumes that all elements in tmp_data has the same type.
if len(tmp_data) == 0 or not isinstance(tmp_data[0],
(str, bytes)):
tmp_data = np.stack(tmp_data, axis=0)
batch_data[k] = tmp_data
return batch_data
class BatchRandomResize(Transform):
"""
Resize a batch of inputs to random sizes.
Attention: If `interp` is 'RANDOM', the interpolation method will be chosen randomly.
Args:
target_sizes (list[int] | list[list|tuple] | tuple[list|tuple]):
Multiple target sizes, each of which should be an int or list/tuple of length 2.
interp (str, optional): Interpolation method for resizing image(s). One of
{'NEAREST', 'LINEAR', 'CUBIC', 'AREA', 'LANCZOS4', 'RANDOM'}.
Defaults to 'LINEAR'.
Raises:
TypeError: Invalid type of `target_size`.
ValueError: Invalid interpolation method.
See Also:
RandomResize: Resize input to random sizes.
"""
def __init__(self, target_sizes, interp='NEAREST'):
super(BatchRandomResize, self).__init__()
if not (interp == "RANDOM" or interp in interp_dict):
raise ValueError("interp should be one of {}".format(
interp_dict.keys()))
self.interp = interp
assert isinstance(target_sizes, list), \
"target_size must be a list."
for i, item in enumerate(target_sizes):
if isinstance(item, int):
target_sizes[i] = (item, item)
self.target_size = target_sizes
def __call__(self, samples):
height, width = random.choice(self.target_size)
resizer = Resize((height, width), interp=self.interp)
samples = resizer(samples)
return samples
class BatchRandomResizeByShort(Transform):
"""
Resize a batch of inputs to random sizes while keeping the aspect ratio.
Attention: If `interp` is 'RANDOM', the interpolation method will be chosen randomly.
Args:
short_sizes (list[int] | tuple[int]): Target sizes of the shorter side of
the image(s).
max_size (int, optional): Upper bound of longer side of the image(s).
If `max_size` is -1, no upper bound will be applied. Defaults to -1.
interp (str, optional): Interpolation method for resizing image(s). One of
{'NEAREST', 'LINEAR', 'CUBIC', 'AREA', 'LANCZOS4', 'RANDOM'}.
Defaults to 'LINEAR'.
Raises:
TypeError: Invalid type of `target_size`.
ValueError: Invalid interpolation method.
See Also:
RandomResizeByShort: Resize input to random sizes while keeping the aspect
ratio.
"""
def __init__(self, short_sizes, max_size=-1, interp='NEAREST'):
super(BatchRandomResizeByShort, self).__init__()
if not (interp == "RANDOM" or interp in interp_dict):
raise ValueError("interp should be one of {}".format(
interp_dict.keys()))
self.interp = interp
assert isinstance(short_sizes, list), \
"short_sizes must be a list."
self.short_sizes = short_sizes
self.max_size = max_size
def __call__(self, samples):
short_size = random.choice(self.short_sizes)
resizer = ResizeByShort(
short_size=short_size, max_size=self.max_size, interp=self.interp)
samples = resizer(samples)
return samples
class _BatchPad(Transform):
def __init__(self, pad_to_stride=0):
super(_BatchPad, self).__init__()
self.pad_to_stride = pad_to_stride
def __call__(self, samples):
coarsest_stride = self.pad_to_stride
max_shape = np.array([data['image'].shape for data in samples]).max(
axis=0)
if coarsest_stride > 0:
max_shape[0] = int(
np.ceil(max_shape[0] / coarsest_stride) * coarsest_stride)
max_shape[1] = int(
np.ceil(max_shape[1] / coarsest_stride) * coarsest_stride)
for data in samples:
im = data['image']
im_h, im_w, im_c = im.shape[:]
padding_im = np.zeros(
(max_shape[0], max_shape[1], im_c), dtype=np.float32)
padding_im[:im_h, :im_w, :] = im
data['image'] = padding_im
return samples
class _Gt2YoloTarget(Transform):
"""
Generate YOLOv3 targets by groud truth data, this operator is only used in
fine grained YOLOv3 loss mode.
"""
def __init__(self,
anchors,
anchor_masks,
downsample_ratios,
num_classes=80,
iou_thresh=1.):
super(_Gt2YoloTarget, self).__init__()
self.anchors = anchors
self.anchor_masks = anchor_masks
self.downsample_ratios = downsample_ratios
self.num_classes = num_classes
self.iou_thresh = iou_thresh
def __call__(self, samples, context=None):
assert len(self.anchor_masks) == len(self.downsample_ratios), \
"anchor_masks', and 'downsample_ratios' should have same length."
h, w = samples[0]['image'].shape[:2]
an_hw = np.array(self.anchors) / np.array([[w, h]])
for sample in samples:
gt_bbox = sample['gt_bbox']
gt_class = sample['gt_class']
if 'gt_score' not in sample:
sample['gt_score'] = np.ones(
(gt_bbox.shape[0], 1), dtype=np.float32)
gt_score = sample['gt_score']
for i, (
mask, downsample_ratio
) in enumerate(zip(self.anchor_masks, self.downsample_ratios)):
grid_h = int(h / downsample_ratio)
grid_w = int(w / downsample_ratio)
target = np.zeros(
(len(mask), 6 + self.num_classes, grid_h, grid_w),
dtype=np.float32)
for b in range(gt_bbox.shape[0]):
gx, gy, gw, gh = gt_bbox[b, :]
cls = gt_class[b]
score = gt_score[b]
if gw <= 0. or gh <= 0. or score <= 0.:
continue
# find best match anchor index
best_iou = 0.
best_idx = -1
for an_idx in range(an_hw.shape[0]):
iou = jaccard_overlap(
[0., 0., gw, gh],
[0., 0., an_hw[an_idx, 0], an_hw[an_idx, 1]])
if iou > best_iou:
best_iou = iou
best_idx = an_idx
gi = int(gx * grid_w)
gj = int(gy * grid_h)
# gtbox should be regresed in this layes if best match
# anchor index in anchor mask of this layer
if best_idx in mask:
best_n = mask.index(best_idx)
# x, y, w, h, scale
target[best_n, 0, gj, gi] = gx * grid_w - gi
target[best_n, 1, gj, gi] = gy * grid_h - gj
target[best_n, 2, gj, gi] = np.log(
gw * w / self.anchors[best_idx][0])
target[best_n, 3, gj, gi] = np.log(
gh * h / self.anchors[best_idx][1])
target[best_n, 4, gj, gi] = 2.0 - gw * gh
# objectness record gt_score
target[best_n, 5, gj, gi] = score
# classification
target[best_n, 6 + cls, gj, gi] = 1.
# For non-matched anchors, calculate the target if the iou
# between anchor and gt is larger than iou_thresh
if self.iou_thresh < 1:
for idx, mask_i in enumerate(mask):
if mask_i == best_idx: continue
iou = jaccard_overlap(
[0., 0., gw, gh],
[0., 0., an_hw[mask_i, 0], an_hw[mask_i, 1]])
if iou > self.iou_thresh and target[idx, 5, gj,
gi] == 0.:
# x, y, w, h, scale
target[idx, 0, gj, gi] = gx * grid_w - gi
target[idx, 1, gj, gi] = gy * grid_h - gj
target[idx, 2, gj, gi] = np.log(
gw * w / self.anchors[mask_i][0])
target[idx, 3, gj, gi] = np.log(
gh * h / self.anchors[mask_i][1])
target[idx, 4, gj, gi] = 2.0 - gw * gh
# objectness record gt_score
target[idx, 5, gj, gi] = score
# classification
target[idx, 5 + cls, gj, gi] = 1.
sample['target{}'.format(i)] = target
# remove useless gt_class and gt_score after target calculated
sample.pop('gt_class')
sample.pop('gt_score')
return samples