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