diff --git a/bob/pad/face/database/replay_mobile.py b/bob/pad/face/database/replay_mobile.py index b34146e76a8edf7f2a4a2fa3237bfce77eaec1a7..8e2547c37256d04d11603ca36ea0ca5b7ab2f942 100644 --- a/bob/pad/face/database/replay_mobile.py +++ b/bob/pad/face/database/replay_mobile.py @@ -108,7 +108,7 @@ class ReplayMobilePadFile(VideoPadFile): Video data stored in the FrameContainer, see ``bob.bio.video.utils.FrameContainer`` for further details. """ - + directory = directory or self.original_directory video_data_array = self.f.load( directory=directory, extension=extension) diff --git a/bob/pad/face/preprocessor/Patch.py b/bob/pad/face/preprocessor/Patch.py new file mode 100644 index 0000000000000000000000000000000000000000..5bbbd935ba1d0e0e54a80d7194b6218e4e2ec5ca --- /dev/null +++ b/bob/pad/face/preprocessor/Patch.py @@ -0,0 +1,77 @@ +from ..utils import extract_patches +from bob.bio.base.preprocessor import Preprocessor +from bob.bio.video import FrameContainer +from bob.bio.video.preprocessor import Wrapper +from collections import OrderedDict + + +class ImagePatches(Preprocessor): + """Extracts patches of images and returns it in a FrameContainer. You need + to wrap the further blocks (extractor and algorithm) that come after this + in bob.bio.video wrappers. + """ + + def __init__(self, block_size, block_overlap=(0, 0), n_random_patches=None, + **kwargs): + super(ImagePatches, self).__init__(**kwargs) + self.block_size = block_size + self.block_overlap = block_overlap + self.n_random_patches = n_random_patches + + def __call__(self, image, annotations=None): + fc = FrameContainer() + + patches = extract_patches(image, self.block_size, self.block_overlap, + self.n_random_patches) + for i, patch in enumerate(patches): + fc.add(i, patch) + + if not len(fc): + return None + + return fc + + +class VideoPatches(Wrapper): + """Extracts patches of images from video containers and returns it in a + FrameContainer. + """ + + def __init__(self, block_size, block_overlap=(0, 0), n_random_patches=None, + normalizer=None, + **kwargs): + super(VideoPatches, self).__init__(**kwargs) + self.block_size = block_size + self.block_overlap = block_overlap + self.n_random_patches = n_random_patches + self.normalizer = normalizer + + def __call__(self, frames, annotations=None): + fc = FrameContainer() + + if self.normalizer is not None: + annotations = OrderedDict(self.normalizer(annotations)) + + for index, frame, _ in frames: + + # if annotations are given, and if particular frame annotations are + # not missing we take them: + annots = annotations[index] if annotations is not None and \ + index in annotations else None + + # preprocess image (by default: detect a face) + preprocessed = self.preprocessor(frame, annots) + if preprocessed is None: + continue + + # extract patches + patches = extract_patches( + preprocessed, self.block_size, self.block_overlap, + self.n_random_patches) + for i, patch in enumerate(patches): + fc.add('{}_{}'.format(index, i), patch) + + if not len(fc): + return None + + return fc diff --git a/bob/pad/face/preprocessor/__init__.py b/bob/pad/face/preprocessor/__init__.py index 9c061901c9379473af0815eba039e973b5b42829..036786965d73601e5d2adc860cacc7e57891864a 100644 --- a/bob/pad/face/preprocessor/__init__.py +++ b/bob/pad/face/preprocessor/__init__.py @@ -8,6 +8,8 @@ from .LiPulseExtraction import LiPulseExtraction from .Chrom import Chrom from .SSR import SSR from .PPGSecure import PPGSecure +from .Patch import ImagePatches, VideoPatches + def __appropriate__(*args): """Says object was actually declared here, and not in the import module. @@ -37,5 +39,7 @@ __appropriate__( PPGSecure, VideoFaceCropAlignBlockPatch, BlockPatch, + ImagePatches, + VideoPatches, ) __all__ = [_ for _ in dir() if not _.startswith('_')] diff --git a/bob/pad/face/utils/__init__.py b/bob/pad/face/utils/__init__.py index 5ef8eb5e5a50c922c1c12a7d955a00fa24e6c331..2a16d37923d62d018f86441141f69fdbb60f7600 100644 --- a/bob/pad/face/utils/__init__.py +++ b/bob/pad/face/utils/__init__.py @@ -1,7 +1,8 @@ from .load_utils import ( - frames, number_of_frames, yield_faces, scale_face, blocks, - bbx_cropper, min_face_size_normalizer, color_augmentation, - blocks_generator, the_giant_video_loader) + frames, number_of_frames, yield_faces, scale_face, blocks, bbx_cropper, + min_face_size_normalizer, color_augmentation, blocks_generator, + the_giant_video_loader, random_sample, random_patches, extract_patches +) # gets sphinx autodoc done right - don't remove it __all__ = [_ for _ in dir() if not _.startswith('_')] diff --git a/bob/pad/face/utils/load_utils.py b/bob/pad/face/utils/load_utils.py index e37ece1f13c8d06d0abbddb4a89bcacfdb700358..4bddd8830bfbe3a5fe92f957656fd6b4f095f2aa 100644 --- a/bob/pad/face/utils/load_utils.py +++ b/bob/pad/face/utils/load_utils.py @@ -237,16 +237,40 @@ def color_augmentation(image, channels=('rgb',)): return numpy.concatenate(final_image, axis=0) -def _random_sample(A, size): +def random_sample(A, size): + """Randomly selects ``size`` samples from the array ``A``""" return A[numpy.random.choice(A.shape[0], size, replace=False), ...] +def random_patches(image, block_size, n_random_patches=1): + """Extracts N random patches of block_size from an image""" + h, w = image.shape[-2:] + bh, bw = block_size + if h < block_size[0] or w < block_size[1]: + raise ValueError("block_size must be smaller than image shape") + hl = numpy.random.randint(0, h - bh, size=n_random_patches) + wl = numpy.random.randint(0, w - bw, size=n_random_patches) + for ch, cw in zip(hl, wl): + yield image[..., ch:ch + bh, cw:cw + bw] + + +def extract_patches(image, block_size, block_overlap=(0, 0), + n_random_patches=None): + """Yields either all patches from an image or N random patches.""" + if n_random_patches is None: + return blocks_generator(image, block_size, block_overlap) + else: + return random_patches( + image, block_size, n_random_patches=n_random_patches) + + def the_giant_video_loader(paddb, padfile, region='whole', scaling_factor=None, cropper=None, normalizer=None, patches=False, block_size=(96, 96), block_overlap=(0, 0), random_patches_per_frame=None, augment=None, - multiple_bonafide_patches=1, keep_pa_samples=None): + multiple_bonafide_patches=1, keep_pa_samples=None, + keep_bf_samples=None): """Loads a video pad file frame by frame and optionally applies transformations. @@ -279,6 +303,8 @@ def the_giant_video_loader(paddb, padfile, Will use more random patches for bonafide samples keep_pa_samples : float If given, will drop some PA samples. + keep_bf_samples : float + If given, will drop some BF samples. Returns ------- @@ -312,7 +338,7 @@ def the_giant_video_loader(paddb, padfile, random_patches_per_frame *= multiple_bonafide_patches generator = ( patch for frame in generator - for patch in _random_sample( + for patch in random_sample( blocks(frame, block_size, block_overlap), random_patches_per_frame)) @@ -323,4 +349,8 @@ def the_giant_video_loader(paddb, padfile, generator = (frame for frame in generator if random.random() < keep_pa_samples) + if keep_bf_samples is not None and padfile.attack_type is None: + generator = (frame for frame in generator + if random.random() < keep_bf_samples) + return generator