Skip to content
This repository has been archived by the owner on Jul 2, 2021. It is now read-only.

Add random_erasing #558

Open
wants to merge 30 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
82e1b06
add random_erasing
akitotakeki Apr 8, 2018
0eebc97
add test
akitotakeki Apr 8, 2018
8dd6a42
fix test
akitotakeki Apr 8, 2018
5033569
Merge branch 'pr/432' into akitotakeki-add-random-erasing
akitotakeki Apr 8, 2018
f7e6390
add trial option
akitotakeki Apr 8, 2018
5aa696c
use _sample_parameters in random_sized_crop
akitotakeki Apr 8, 2018
dfb1712
fix doc
akitotakeki Apr 8, 2018
f0dc4e9
fix mean
akitotakeki Apr 8, 2018
4220892
use numpy.ndarray for mean
akitotakeki Apr 8, 2018
58037c3
fix mean
akitotakeki Apr 8, 2018
c5dd0e5
fix import
akitotakeki Apr 8, 2018
b527229
fix mean value in test
akitotakeki Apr 8, 2018
6612b8b
fix import
akitotakeki Apr 8, 2018
9f2d327
fix aspect range in test
akitotakeki Apr 8, 2018
9269d96
fix test
akitotakeki Apr 8, 2018
8fe9235
use random_sized_crop
akitotakeki Apr 11, 2018
15a1610
fix doc and add scale option
akitotakeki Apr 11, 2018
6024db8
fix test
akitotakeki Apr 11, 2018
01b6672
Merge branch 'master' into akitotakeki-add-random-erasing
akitotakeki Apr 11, 2018
39b93d8
fix params
akitotakeki Apr 11, 2018
e170fa5
Merge branch 'akitotakeki-add-random-erasing' of https://github.com/a…
akitotakeki Apr 11, 2018
78cb847
fix prob
akitotakeki Nov 1, 2018
6bf2ef6
reuse _sample_parameters
akitotakeki Nov 1, 2018
1ee30f1
Revert "reuse _sample_parameters"
akitotakeki Nov 1, 2018
d5efc32
use img.copy()
akitotakeki Nov 1, 2018
26b0f0e
change from fixed_value to fill
akitotakeki Nov 2, 2018
2f4fc64
fix the range of pixel
akitotakeki Nov 3, 2018
4dfd64b
Merge remote-tracking branch 'upstream/master' into akitotakeki-add-r…
akitotakeki Jan 3, 2019
c76eb27
Merge branch 'akitotakeki-add-random-erasing' of https://github.com/a…
akitotakeki Jan 3, 2019
4033e52
rm scale option
akitotakeki Jan 3, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions chainercv/transforms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
from chainercv.transforms.image.flip import flip # NOQA
from chainercv.transforms.image.pca_lighting import pca_lighting # NOQA
from chainercv.transforms.image.random_crop import random_crop # NOQA
from chainercv.transforms.image.random_erasing import random_erasing # NOQA
from chainercv.transforms.image.random_expand import random_expand # NOQA
from chainercv.transforms.image.random_flip import random_flip # NOQA
from chainercv.transforms.image.random_rotate import random_rotate # NOQA
from chainercv.transforms.image.random_sized_crop import random_sized_crop # NOQA
from chainercv.transforms.image.resize import resize # NOQA
from chainercv.transforms.image.resize_contain import resize_contain # NOQA
from chainercv.transforms.image.scale import scale # NOQA
Expand Down
101 changes: 101 additions & 0 deletions chainercv/transforms/image/random_erasing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from __future__ import division

import math
import numpy as np
import random

from random_sized_crop import _sample_parameters


def random_erasing(img, prob=0.5,
scale_ratio_range=(0.02, 0.4),
aspect_ratio_range=(0.3, 1 / 0.3),
mean=[0.4914, 0.4822, 0.4465],
return_param=False, copy=False):
"""Select a rectangle region in an image and erase its pixels with mean values.

The size :math:`(H_{erase}, W_{erase})` and the left top coordinate
:math:`(y_{start}, x_{start})` of the region are calculated as follows:

+ :math:`H_{erase} = \\lfloor{\\sqrt{s \\times H \\times W \
\\times a}}\\rfloor`
+ :math:`W_{erase} = \\lfloor{\\sqrt{s \\times H \\times W \
\\div a}}\\rfloor`
+ :math:`y_{start} \\sim Uniform\\{0, H - H_{erase}\\}`
+ :math:`x_{start} \\sim Uniform\\{0, W - W_{erase}\\}`
+ :math:`s \\sim Uniform(s_1, s_2)`
+ :math:`b \\sim Uniform(a_1, a_2)` and \
:math:`a = b` or :math:`a = \\frac{1}{b}` in 50/50 probability.

Here, :math:`s_1, s_2` are the two floats in
:obj:`scale_ratio_interval` and :math:`a_1, a_2` are the two floats
in :obj:`aspect_ratio_interval`.
Also, :math:`H` and :math:`W` are the height and the width of the image.
Note that :math:`s \\approx \\frac{H_{erase} \\times
W_{erase}}{H \\times W}` and
:math:`a \\approx \\frac{H_{erase}}{W_{erase}}`.
The approximations come from flooring floats to integers.

.. note::

When it fails to sample a valid scale and aspect ratio for a hundred
times, it picks values in a non-uniform way.
If this happens, the selected scale ratio can be smaller
than :obj:`scale_ratio_interval[0]`.

Args:
img (~numpy.ndarray): An image array. This is in CHW format.
prob (float): Erasing probability.
scale_ratio_interval (tuple of two floats): Determines
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the arguments, scale_ratio_range is used.

the distribution from which a scale ratio is sampled.
aspect_ratio_interval (tuple of two floats): Determines
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the arguments, aspect_ratio_range is used.

the distribution from which an aspect ratio is sampled.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mean is not described.

return_param (bool): Returns parameters if :obj:`True`.

Returns:
~numpy.ndarray or (~numpy.ndarray, dict):

If :obj:`return_param = False`,
returns only the cropped image.

If :obj:`return_param = True`,
returns a tuple of erased image and :obj:`param`.
:obj:`param` is a dictionary of intermediate parameters whose
contents are listed below with key, value-type and the description
of the value.

* **y_slice** (*slice*): A slice used to erase a region in the input
image. The relation below holds together with :obj:`x_slice`.
* **x_slice** (*slice*): Similar to :obj:`y_slice`.
* **scale_ratio** (float): :math:`s` in the description (see above).
* **aspect_ratio** (float): :math:`a` in the description.

"""
if random.randint(0, 1) > prob:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

randint(0, 1) returns 0 or 1. When 0 < prob < 1, this condition means 50% regardless of prob.

_, H, W = img.shape
scale_ratio, aspect_ratio =\
_sample_parameters(
(H, W), scale_ratio_range, aspect_ratio_range, trial=100)

H_crop = int(math.floor(np.sqrt(scale_ratio * H * W * aspect_ratio)))
W_crop = int(math.floor(np.sqrt(scale_ratio * H * W / aspect_ratio)))
y_start = random.randint(0, H - H_crop)
x_start = random.randint(0, W - W_crop)
y_slice = slice(y_start, y_start + H_crop)
x_slice = slice(x_start, x_start + W_crop)

img[:, y_slice, x_slice] = mean
else:
y_slice = None
x_slice = None
scale_ratio = None
aspect_ratio = None

if copy:
img = img.copy()
if return_param:
params = {'y_slice': y_slice, 'x_slice': x_slice,
'scale_ratio': scale_ratio, 'aspect_ratio': aspect_ratio}
return img, params
else:
return img
127 changes: 127 additions & 0 deletions chainercv/transforms/image/random_sized_crop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
from __future__ import division

import math
import numpy as np
import random


def random_sized_crop(img,
scale_ratio_interval=(0.08, 1),
aspect_ratio_interval=(3 / 4, 4 / 3),
return_param=False, copy=False):
"""Crop an image to random size and aspect ratio.

The size :math:`(H_{crop}, W_{crop})` and the left top coordinate
:math:`(y_{start}, x_{start})` of the crop are calculated as follows:

+ :math:`H_{crop} = \\lfloor{\\sqrt{s \\times H \\times W \
\\times a}}\\rfloor`
+ :math:`W_{crop} = \\lfloor{\\sqrt{s \\times H \\times W \
\\div a}}\\rfloor`
+ :math:`y_{start} \\sim Uniform\\{0, H - H_{crop}\\}`
+ :math:`x_{start} \\sim Uniform\\{0, W - W_{crop}\\}`
+ :math:`s \\sim Uniform(s_1, s_2)`
+ :math:`b \\sim Uniform(a_1, a_2)` and \
:math:`a = b` or :math:`a = \\frac{1}{b}` in 50/50 probability.

Here, :math:`s_1, s_2` are the two floats in
:obj:`scale_ratio_interval` and :math:`a_1, a_2` are the two floats
in :obj:`aspect_ratio_interval`.
Also, :math:`H` and :math:`W` are the height and the width of the image.
Note that :math:`s \\approx \\frac{H_{crop} \\times W_{crop}}{H \\times W}`
and :math:`a \\approx \\frac{H_{crop}}{W_{crop}}`.
The approximations come from flooring floats to integers.

.. note::

When it fails to sample a valid scale and aspect ratio for ten
times, it picks values in a non-uniform way.
If this happens, the selected scale ratio can be smaller
than :obj:`scale_ratio_interval[0]`.

Args:
img (~numpy.ndarray): An image array. This is in CHW format.
scale_ratio_interval (tuple of two floats): Determines
the distribution from which a scale ratio is sampled.
The default values are selected so that the area of the crop is
8~100% of the original image. This is the default
setting used to train ResNets in Torch style.
aspect_ratio_interval (tuple of two floats): Determines
the distribution from which an aspect ratio is sampled.
The default values are
:math:`\\frac{3}{4}` and :math:`\\frac{4}{3}`, which
are also the default setting to train ResNets in Torch style.
return_param (bool): Returns parameters if :obj:`True`.

Returns:
~numpy.ndarray or (~numpy.ndarray, dict):

If :obj:`return_param = False`,
returns only the cropped image.

If :obj:`return_param = True`,
returns a tuple of cropped image and :obj:`param`.
:obj:`param` is a dictionary of intermediate parameters whose
contents are listed below with key, value-type and the description
of the value.

* **y_slice** (*slice*): A slice used to crop the input image.\
The relation below holds together with :obj:`x_slice`.
* **x_slice** (*slice*): Similar to :obj:`y_slice`.

.. code::

out_img = img[:, y_slice, x_slice]

* **scale_ratio** (float): :math:`s` in the description (see above).
* **aspect_ratio** (float): :math:`a` in the description.

"""
_, H, W = img.shape
scale_ratio, aspect_ratio =\
_sample_parameters(
(H, W), scale_ratio_interval, aspect_ratio_interval)

H_crop = int(math.floor(np.sqrt(scale_ratio * H * W * aspect_ratio)))
W_crop = int(math.floor(np.sqrt(scale_ratio * H * W / aspect_ratio)))
y_start = random.randint(0, H - H_crop)
x_start = random.randint(0, W - W_crop)
y_slice = slice(y_start, y_start + H_crop)
x_slice = slice(x_start, x_start + W_crop)

img = img[:, y_slice, x_slice]

if copy:
img = img.copy()
if return_param:
params = {'y_slice': y_slice, 'x_slice': x_slice,
'scale_ratio': scale_ratio, 'aspect_ratio': aspect_ratio}
return img, params
else:
return img


def _sample_parameters(size, scale_ratio_interval, aspect_ratio_interval,
trial=10):
H, W = size
for _ in range(trial):
aspect_ratio = random.uniform(
aspect_ratio_interval[0], aspect_ratio_interval[1])
if random.uniform(0, 1) < 0.5:
aspect_ratio = 1 / aspect_ratio
# This is determined so that relationships "H - H_crop >= 0" and
# "W - W_crop >= 0" are always satisfied.
scale_ratio_max = min((scale_ratio_interval[1],
H / (W * aspect_ratio),
(aspect_ratio * W) / H))

scale_ratio = random.uniform(
scale_ratio_interval[0], scale_ratio_interval[1])
if scale_ratio_interval[0] <= scale_ratio <= scale_ratio_max:
return scale_ratio, aspect_ratio

# This scale_ratio is outside the given interval when
# scale_ratio_max < scale_ratio_interval[0].
scale_ratio = random.uniform(
min((scale_ratio_interval[0], scale_ratio_max)), scale_ratio_max)
return scale_ratio, aspect_ratio
8 changes: 8 additions & 0 deletions docs/source/reference/transforms.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ random_crop
~~~~~~~~~~~
.. autofunction:: random_crop

random_erasing
~~~~~~~~~~~~~
.. autofunction:: random_erasing

random_expand
~~~~~~~~~~~~~
.. autofunction:: random_expand
Expand All @@ -35,6 +39,10 @@ random_rotate
~~~~~~~~~~~~~
.. autofunction:: random_rotate

random_sized_crop
~~~~~~~~~~~~~~~~~
.. autofunction:: random_sized_crop

resize
~~~~~~
.. autofunction:: resize
Expand Down
44 changes: 44 additions & 0 deletions tests/transforms_tests/image_tests/test_random_erasing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from __future__ import division

import unittest

import numpy as np

from chainer import testing
from chainercv.transforms import random_erasing


@testing.parameterize(
{'H': 256, 'W': 256},
{'H': 129, 'W': 352},
{'H': 352, 'W': 129},
{'H': 35, 'W': 500},
)
class TestRandomErasing(unittest.TestCase):

def test_random_sized_crop(self):
img = np.random.uniform(size=(3, self.H, self.W))
prob = 0.5
scale_ratio_interval = (0.02, 4)
aspect_ratio_interval = (0.3, 1 / 0.3)
mean = [0.4914, 0.4822, 0.4465]
out, params = random_erasing(img, prob, scale_ratio_interval,
aspect_ratio_interval, mean,
return_param=True)

scale_ratio = params['scale_ratio']
aspect_ratio = params['aspect_ratio']

self.assertTrue(
(aspect_ratio_interval[0] <= aspect_ratio) and
(aspect_ratio <= aspect_ratio_interval[1]))
self.assertTrue(
scale_ratio <= scale_ratio_interval[1])
scale_ratio_max = min((scale_ratio_interval[1],
self.H / (self.W * aspect_ratio),
(aspect_ratio * self.W) / self.H))
self.assertTrue(
min((scale_ratio_max, scale_ratio_interval[0])) <= scale_ratio)


testing.run_module(__name__, __file__)
54 changes: 54 additions & 0 deletions tests/transforms_tests/image_tests/test_random_sized_crop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from __future__ import division

import unittest

import math
import numpy as np

from chainer import testing
from chainercv.transforms import random_sized_crop


@testing.parameterize(
{'H': 256, 'W': 256},
{'H': 129, 'W': 352},
{'H': 352, 'W': 129},
{'H': 35, 'W': 500},
)
class TestRandomSizedCrop(unittest.TestCase):

def test_random_sized_crop(self):
img = np.random.uniform(size=(3, self.H, self.W))
scale_ratio_interval = (0.08, 1)
aspect_ratio_interval = (3 / 4, 4 / 3)
out, params = random_sized_crop(img, scale_ratio_interval,
aspect_ratio_interval,
return_param=True)

expected = img[:, params['y_slice'], params['x_slice']]
np.testing.assert_equal(out, expected)

_, H_crop, W_crop = out.shape
scale_ratio = params['scale_ratio']
aspect_ratio = params['aspect_ratio']
area = scale_ratio * self.H * self.W
expected_H_crop = int(math.floor(
np.sqrt(area * aspect_ratio)))
expected_W_crop = int(math.floor(
np.sqrt(area / aspect_ratio)))
self.assertEqual(H_crop, expected_H_crop)
self.assertEqual(W_crop, expected_W_crop)

self.assertTrue(
(aspect_ratio_interval[0] <= aspect_ratio) and
(aspect_ratio <= aspect_ratio_interval[1]))
self.assertTrue(
scale_ratio <= scale_ratio_interval[1])
scale_ratio_max = min((scale_ratio_interval[1],
self.H / (self.W * aspect_ratio),
(aspect_ratio * self.W) / self.H))
self.assertTrue(
min((scale_ratio_max, scale_ratio_interval[0])) <= scale_ratio)


testing.run_module(__name__, __file__)