@ -75,6 +75,17 @@ class Resize(object):
@staticmethod
def random_select ( img_scales ) :
""" Randomly select an img_scale from given candidates.
Args :
img_scales ( list [ tuple ] ) : Images scales for selection .
Returns :
( tuple , int ) : Returns a tuple ` ` ( img_scale , scale_dix ) ` ` ,
where ` ` img_scale ` ` is the selected image scale and
` ` scale_idx ` ` is the selected index in the given candidates .
"""
assert mmcv . is_list_of ( img_scales , tuple )
scale_idx = np . random . randint ( len ( img_scales ) )
img_scale = img_scales [ scale_idx ]
@ -82,6 +93,19 @@ class Resize(object):
@staticmethod
def random_sample ( img_scales ) :
""" Randomly sample an img_scale when ``multiscale_mode== ' range ' ``.
Args :
img_scales ( list [ tuple ] ) : Images scale range for sampling .
There must be two tuples in img_scales , which specify the lower
and uper bound of image scales .
Returns :
( tuple , None ) : Returns a tuple ` ` ( img_scale , None ) ` ` , where
` ` img_scale ` ` is sampled scale and None is just a placeholder
to be consistent with : func : ` random_select ` .
"""
assert mmcv . is_list_of ( img_scales , tuple ) and len ( img_scales ) == 2
img_scale_long = [ max ( s ) for s in img_scales ]
img_scale_short = [ min ( s ) for s in img_scales ]
@ -96,6 +120,24 @@ class Resize(object):
@staticmethod
def random_sample_ratio ( img_scale , ratio_range ) :
""" Randomly sample an img_scale when ``ratio_range`` is specified.
A ratio will be randomly sampled from the range specified by
` ` ratio_range ` ` . Then it would be multiplied with ` ` img_scale ` ` to
generate sampled scale .
Args :
img_scale ( tuple ) : Images scale base to multiply with ratio .
ratio_range ( tuple [ float ] ) : The minimum and maximum ratio to scale
the ` ` img_scale ` ` .
Returns :
( tuple , None ) : Returns a tuple ` ` ( scale , None ) ` ` , where
` ` scale ` ` is sampled ratio multiplied with ` ` img_scale ` ` and
None is just a placeholder to be consistent with
: func : ` random_select ` .
"""
assert isinstance ( img_scale , tuple ) and len ( img_scale ) == 2
min_ratio , max_ratio = ratio_range
assert min_ratio < = max_ratio
@ -104,6 +146,23 @@ class Resize(object):
return scale , None
def _random_scale ( self , results ) :
""" Randomly sample an img_scale according to ``ratio_range`` and
` ` multiscale_mode ` ` .
If ` ` ratio_range ` ` is specified , a ratio will be sampled and be
multiplied with ` ` img_scale ` ` .
If multiple scales are specified by ` ` img_scale ` ` , a scale will be
sampled according to ` ` multiscale_mode ` ` .
Otherwise , single scale will be used .
Args :
results ( dict ) : Result dict from : obj : ` dataset ` .
Returns :
dict : Two new keys ' scale` and ' scale_idx ` are added into
` ` results ` ` , which would be used by subsequent pipelines .
"""
if self . ratio_range is not None :
scale , scale_idx = self . random_sample_ratio (
self . img_scale [ 0 ] , self . ratio_range )
@ -120,6 +179,7 @@ class Resize(object):
results [ ' scale_idx ' ] = scale_idx
def _resize_img ( self , results ) :
""" Resize images with ``results[ ' scale ' ]``. """
for key in results . get ( ' img_fields ' , [ ' img ' ] ) :
if self . keep_ratio :
img , scale_factor = mmcv . imrescale (
@ -144,6 +204,7 @@ class Resize(object):
results [ ' keep_ratio ' ] = self . keep_ratio
def _resize_bboxes ( self , results ) :
""" Resize bounding boxes with ``results[ ' scale_factor ' ]``. """
img_shape = results [ ' img_shape ' ]
for key in results . get ( ' bbox_fields ' , [ ] ) :
bboxes = results [ key ] * results [ ' scale_factor ' ]
@ -152,6 +213,7 @@ class Resize(object):
results [ key ] = bboxes
def _resize_masks ( self , results ) :
""" Resize masks with ``results[ ' scale ' ]`` """
for key in results . get ( ' mask_fields ' , [ ] ) :
if results [ key ] is None :
continue
@ -161,6 +223,7 @@ class Resize(object):
results [ key ] = results [ key ] . resize ( results [ ' img_shape ' ] [ : 2 ] )
def _resize_seg ( self , results ) :
""" Resize semantic segmentation map with ``results[ ' scale ' ]``. """
for key in results . get ( ' seg_fields ' , [ ] ) :
if self . keep_ratio :
gt_seg = mmcv . imrescale (
@ -171,6 +234,17 @@ class Resize(object):
results [ ' gt_semantic_seg ' ] = gt_seg
def __call__ ( self , results ) :
""" Call function to resize images, bounding boxes, masks, semantic
segmentation map .
Args :
results ( dict ) : Result dict from loading pipeline .
Returns :
dict : Resized results , ' img_shape ' , ' pad_shape ' , ' scale_factor ' ,
' keep_ratio ' keys are added into result dict .
"""
if ' scale ' not in results :
self . _random_scale ( results )
self . _resize_img ( results )
@ -197,22 +271,31 @@ class RandomFlip(object):
method .
Args :
flip_ratio ( float , optional ) : The flipping probability .
flip_ratio ( float , optional ) : The flipping probability . Default : None .
direction ( str , optional ) : The flipping direction . Options are
' horizontal ' and ' vertical ' . Default : ' horizontal ' .
"""
def __init__ ( self , flip_ratio = 0.5 , direction = ' horizontal ' ) :
assert flip_ratio > = 0 and flip_ratio < = 1
assert direction in [ ' horizontal ' , ' vertical ' ]
def __init__ ( self , flip_ratio = None , direction = ' horizontal ' ) :
self . flip_ratio = flip_ratio
self . direction = direction
if flip_ratio is not None :
assert flip_ratio > = 0 and flip_ratio < = 1
assert direction in [ ' horizontal ' , ' vertical ' ]
def bbox_flip ( self , bboxes , img_shape , direction ) :
""" Flip bboxes horizontally.
Args :
bboxes ( ndarray ) : shape ( . . . , 4 * k )
img_shape ( tuple ) : ( height , width )
bboxes ( numpy . ndarray ) : Bounding boxes , shape ( . . . , 4 * k )
img_shape ( tuple [ int ] ) : Image shape ( height , width )
direction ( str ) : Flip direction . Options are ' horizontal ' ,
' vertical ' .
Returns :
numpy . ndarray : Flipped bounding boxes .
"""
assert bboxes . shape [ - 1 ] % 4 == 0
flipped = bboxes . copy ( )
if direction == ' horizontal ' :
@ -228,6 +311,17 @@ class RandomFlip(object):
return flipped
def __call__ ( self , results ) :
""" Call function to flip bounding boxes, masks, semantic segmentation
maps .
Args :
results ( dict ) : Result dict from loading pipeline .
Returns :
dict : Flipped results , ' flip ' , ' flip_direction ' keys are added into
result dict .
"""
if ' flip ' not in results :
flip = True if np . random . rand ( ) < self . flip_ratio else False
results [ ' flip ' ] = flip
@ -263,6 +357,7 @@ class Pad(object):
There are two padding modes : ( 1 ) pad to a fixed size and ( 2 ) pad to the
minimum size that is divisible by some number .
Added keys are " pad_shape " , " pad_fixed_size " , " pad_size_divisor " ,
Args :
size ( tuple , optional ) : Fixed padding size .
@ -279,6 +374,7 @@ class Pad(object):
assert size is None or size_divisor is None
def _pad_img ( self , results ) :
""" Pad images according to ``self.size``. """
for key in results . get ( ' img_fields ' , [ ' img ' ] ) :
if self . size is not None :
padded_img = mmcv . impad ( results [ key ] , self . size , self . pad_val )
@ -291,15 +387,27 @@ class Pad(object):
results [ ' pad_size_divisor ' ] = self . size_divisor
def _pad_masks ( self , results ) :
""" Pad masks according to ``results[ ' pad_shape ' ]``. """
pad_shape = results [ ' pad_shape ' ] [ : 2 ]
for key in results . get ( ' mask_fields ' , [ ] ) :
results [ key ] = results [ key ] . pad ( pad_shape , pad_val = self . pad_val )
def _pad_seg ( self , results ) :
""" Pad semantic segmentation map according to
` ` results [ ' pad_shape ' ] ` ` . """
for key in results . get ( ' seg_fields ' , [ ] ) :
results [ key ] = mmcv . impad ( results [ key ] , results [ ' pad_shape ' ] [ : 2 ] )
def __call__ ( self , results ) :
""" Call function to pad images, masks, semantic segmentation maps.
Args :
results ( dict ) : Result dict from loading pipeline .
Returns :
dict : Updated result dict .
"""
self . _pad_img ( results )
self . _pad_masks ( results )
self . _pad_seg ( results )
@ -317,6 +425,8 @@ class Pad(object):
class Normalize ( object ) :
""" Normalize the image.
Added key is " img_norm_cfg " .
Args :
mean ( sequence ) : Mean values of 3 channels .
std ( sequence ) : Std values of 3 channels .
@ -330,6 +440,16 @@ class Normalize(object):
self . to_rgb = to_rgb
def __call__ ( self , results ) :
""" Call function to normalize images.
Args :
results ( dict ) : Result dict from loading pipeline .
Returns :
dict : Normalized results , ' img_norm_cfg ' key is added into
result dict .
"""
for key in results . get ( ' img_fields ' , [ ' img ' ] ) :
results [ key ] = mmcv . imnormalize ( results [ key ] , self . mean , self . std ,
self . to_rgb )
@ -374,6 +494,17 @@ class RandomCrop(object):
}
def __call__ ( self , results ) :
""" Call function to randomly crop images, bounding boxes, masks,
semantic segmentation maps .
Args :
results ( dict ) : Result dict from loading pipeline .
Returns :
dict : Randomly cropped results , ' img_shape ' key in result dict is
updated according to crop size .
"""
for key in results . get ( ' img_fields ' , [ ' img ' ] ) :
img = results [ key ]
margin_h = max ( img . shape [ 0 ] - self . crop_size [ 0 ] , 0 )
@ -444,6 +575,15 @@ class SegRescale(object):
self . scale_factor = scale_factor
def __call__ ( self , results ) :
""" Call function to scale the semantic segmentation map
Args :
results ( dict ) : Result dict from loading pipeline .
Returns :
dict : Result dict with semantic segmentation map scaled .
"""
for key in results . get ( ' seg_fields ' , [ ] ) :
if self . scale_factor != 1 :
results [ key ] = mmcv . imrescale (
@ -487,6 +627,15 @@ class PhotoMetricDistortion(object):
self . hue_delta = hue_delta
def __call__ ( self , results ) :
""" Call function to perform photometric distortion on images.
Args :
results ( dict ) : Result dict from loading pipeline .
Returns :
dict : Result dict with images distorted .
"""
if ' img_fields ' in results :
assert results [ ' img_fields ' ] == [ ' img ' ] , \
' Only single img_fields is allowed '
@ -582,6 +731,15 @@ class Expand(object):
self . prob = prob
def __call__ ( self , results ) :
""" Call function to expand images, bounding boxes.
Args :
results ( dict ) : Result dict from loading pipeline .
Returns :
dict : Result dict with images , bounding boxes expanded
"""
if random . uniform ( 0 , 1 ) > self . prob :
return results
@ -661,6 +819,17 @@ class MinIoURandomCrop(object):
}
def __call__ ( self , results ) :
""" Call function to crop images and bounding boxes with minimum IoU
constraint .
Args :
results ( dict ) : Result dict from loading pipeline .
Returns :
dict : Result dict with images and bounding boxes cropped ,
' img_shape ' key is updated .
"""
if ' img_fields ' in results :
assert results [ ' img_fields ' ] == [ ' img ' ] , \
' Only single img_fields is allowed '
@ -751,12 +920,30 @@ class MinIoURandomCrop(object):
@PIPELINES . register_module ( )
class Corrupt ( object ) :
""" Corruption augmentation.
Corruption transforms implemented based on
` imagecorruptions < https : / / github . com / bethgelab / imagecorruptions > ` _ .
Args :
corruption ( str ) : Corruption name .
severity ( int , optional ) : The severity of corruption . Default : 1.
"""
def __init__ ( self , corruption , severity = 1 ) :
self . corruption = corruption
self . severity = severity
def __call__ ( self , results ) :
""" Call function to corrupt image.
Args :
results ( dict ) : Result dict from loading pipeline .
Returns :
dict : Result dict with images corrupted .
"""
if corrupt is None :
raise RuntimeError ( ' imagecorruptions is not installed ' )
if ' img_fields ' in results :
@ -777,6 +964,46 @@ class Corrupt(object):
@PIPELINES . register_module ( )
class Albu ( object ) :
""" Albumentation augmentation.
Adds custom transformations from Albumentations library .
Please , visit ` https : / / albumentations . readthedocs . io `
to get more information .
An example of ` ` transforms ` ` is as followed :
. . code - block : :
[
dict (
type = ' ShiftScaleRotate ' ,
shift_limit = 0.0625 ,
scale_limit = 0.0 ,
rotate_limit = 0 ,
interpolation = 1 ,
p = 0.5 ) ,
dict (
type = ' RandomBrightnessContrast ' ,
brightness_limit = [ 0.1 , 0.3 ] ,
contrast_limit = [ 0.1 , 0.3 ] ,
p = 0.2 ) ,
dict ( type = ' ChannelShuffle ' , p = 0.1 ) ,
dict (
type = ' OneOf ' ,
transforms = [
dict ( type = ' Blur ' , blur_limit = 3 , p = 1.0 ) ,
dict ( type = ' MedianBlur ' , blur_limit = 3 , p = 1.0 )
] ,
p = 0.1 ) ,
]
Args :
transforms ( list [ dict ] ) : A list of albu transformations
bbox_params ( dict ) : Bbox_params for albumentation ` Compose `
keymap ( dict ) : Contains { ' input key ' : ' albumentation-style key ' }
skip_img_without_anno ( bool ) : Whether to skip the image if no ann left
after aug
"""
def __init__ ( self ,
transforms ,
@ -784,17 +1011,6 @@ class Albu(object):
keymap = None ,
update_pad_shape = False ,
skip_img_without_anno = False ) :
"""
Adds custom transformations from Albumentations lib .
Please , visit ` https : / / albumentations . readthedocs . io `
to get more information .
transforms ( list ) : list of albu transformations
bbox_params ( dict ) : bbox_params for albumentation ` Compose `
keymap ( dict ) : contains { ' input key ' : ' albumentation-style key ' }
skip_img_without_anno ( bool ) : whether to skip the image
if no ann left after aug
"""
if Compose is None :
raise RuntimeError ( ' albumentations is not installed ' )
@ -835,6 +1051,7 @@ class Albu(object):
Returns :
obj : The constructed object .
"""
assert isinstance ( cfg , dict ) and ' type ' in cfg
args = cfg . copy ( )
@ -869,6 +1086,7 @@ class Albu(object):
Returns :
dict : new dict .
"""
updated_dict = { }
for k , v in zip ( d . keys ( ) , d . values ( ) ) :
new_k = keymap . get ( k , k )