diff --git a/bob/pad/face/config/oulunpu.py b/bob/pad/face/config/oulunpu.py new file mode 100644 index 0000000000000000000000000000000000000000..69bedd1fe6fa8a1caaf8865c72fa533f7982eb18 --- /dev/null +++ b/bob/pad/face/config/oulunpu.py @@ -0,0 +1,25 @@ +"""The `OULU-NPU`_ Database. +A mobile face presentation attack database with real-world variations database. + +To configure the location of the database on your computer, run:: + + bob config set bob.db.oulunpu.directory /path/to/oulunpu/database + + +If you use this database, please cite the following publication:: + + @INPROCEEDINGS{OULU_NPU_2017, + author = {Boulkenafet, Z. and Komulainen, J. and Li, Lei. and Feng, X. and Hadid, A.}, + keywords = {biometrics, face recognition, anti-spoofing, presentation attack, generalization, colour texture}, + month = May, + title = {{OULU-NPU}: A mobile face presentation attack database with real-world variations}, + journal = {IEEE International Conference on Automatic Face and Gesture Recognition}, + year = {2017}, + } + + +.. include:: links.rst +""" +from bob.pad.face.database import OulunpuPadDatabase + +database = OulunpuPadDatabase() diff --git a/bob/pad/face/database/__init__.py b/bob/pad/face/database/__init__.py index 082597cb446ca2962c26cbf15020d81cea568937..b842785c16887e19c0bc016b5ff249b53ab98a19 100644 --- a/bob/pad/face/database/__init__.py +++ b/bob/pad/face/database/__init__.py @@ -6,6 +6,7 @@ from .maskattack import MaskAttackPadDatabase from .replay_attack import ReplayAttackPadDatabase from .replay_mobile import ReplayMobilePadDatabase from .swan import SwanPadDatabase +from .oulunpu import OulunpuPadDatabase # gets sphinx autodoc done right - don't remove it @@ -32,6 +33,7 @@ __appropriate__( CasiaSurfPadDatabase, CasiaFasdPadDatabase, SwanPadDatabase, + OulunpuPadDatabase, ) __all__ = [_ for _ in dir() if not _.startswith('_')] diff --git a/bob/pad/face/database/oulunpu.py b/bob/pad/face/database/oulunpu.py new file mode 100644 index 0000000000000000000000000000000000000000..a1b56a023ee882fa8c252860648db0f541597a6f --- /dev/null +++ b/bob/pad/face/database/oulunpu.py @@ -0,0 +1,55 @@ +import logging + +from bob.extension import rc +from bob.extension.download import get_file +from bob.pad.base.database import FileListPadDatabase +from bob.pad.face.database import VideoPadSample + +logger = logging.getLogger(__name__) + + +def OulunpuPadDatabase( + protocol="Protocol_1", + selection_style=None, + max_number_of_frames=None, + step_size=None, + annotation_directory=None, + annotation_type=None, + fixed_positions=None, + **kwargs, +): + name = "pad-face-oulunpu-75221078.tar.gz" + dataset_protocols_path = get_file( + name, + [f"http://www.idiap.ch/software/bob/data/bob/bob.pad.face/{name}"], + cache_subdir="protocols", + file_hash="75221078", + ) + + if annotation_directory is None: + name = "annotations-oulunpu-mtcnn-3bee9b6d.tar.xz" + annotation_directory = get_file( + name, + [f"http://www.idiap.ch/software/bob/data/bob/bob.pad.face/{name}"], + cache_subdir="annotations", + file_hash="3bee9b6d", + ) + annotation_type = "eyes-center" + + transformer = VideoPadSample( + original_directory=rc.get("bob.db.oulunpu.directory"), + annotation_directory=annotation_directory, + selection_style=selection_style, + max_number_of_frames=max_number_of_frames, + step_size=step_size, + ) + + database = FileListPadDatabase( + dataset_protocols_path, + protocol, + transformer=transformer, + **kwargs, + ) + database.annotation_type = annotation_type + database.fixed_positions = fixed_positions + return database diff --git a/bob/pad/face/test/test_databases.py b/bob/pad/face/test/test_databases.py index c1162b248d5bbfa42c06b807befa7e0a62dbb068..3c5fba90acc6ebcace6565f44155d21a0fb55af9 100644 --- a/bob/pad/face/test/test_databases.py +++ b/bob/pad/face/test/test_databases.py @@ -39,7 +39,9 @@ def test_replayattack(): sample = database.sort(database.samples())[0] try: - assert sample.annotations["0"] == { + annot = dict(sample.annotations["0"]) + assert annot["leye"][1] > annot["reye"][1], annot + assert annot == { "bottomright": [213, 219], "topleft": [58, 108], "leye": [118, 190], @@ -76,7 +78,9 @@ def test_replaymobile(): sample = database.sort(database.samples())[0] try: - assert sample.annotations["0"] == { + annot = dict(sample.annotations["0"]) + assert annot["leye"][1] > annot["reye"][1], annot + assert annot == { "bottomright": [760, 498], "topleft": [374, 209], "leye": [518, 417], @@ -211,7 +215,9 @@ def test_swan(): sample = database.sort(database.samples())[0] try: - assert dict(sample.annotations["0"]) == { + annot = dict(sample.annotations["0"]) + assert annot["leye"][1] > annot["reye"][1], annot + assert annot == { "bottomright": [849, 564], "leye": [511, 453], "mouthleft": [709, 271], @@ -224,3 +230,61 @@ def test_swan(): assert sample.data[0][0, 0, 0] == 87 except RuntimeError as e: raise SkipTest(e) + + +def test_oulunpu(): + database = bob.bio.base.load_resource( + "oulunpu", + "database", + preferred_package="bob.pad.face", + package_prefix="bob.pad.", + ) + + assert database.protocols() == [ + "Protocol_1", + "Protocol_1_2", + "Protocol_1_3", + "Protocol_2", + "Protocol_3_1", + "Protocol_3_2", + "Protocol_3_3", + "Protocol_3_4", + "Protocol_3_5", + "Protocol_3_6", + "Protocol_4_1", + "Protocol_4_2", + "Protocol_4_3", + "Protocol_4_4", + "Protocol_4_5", + "Protocol_4_6", + ] + assert database.groups() == ["dev", "eval", "train"] + assert len(database.samples(groups=["train", "dev", "eval"])) == 1200 + 900 + 600 + assert len(database.samples(groups=["train", "dev"])) == 1200 + 900 + assert len(database.samples(groups=["train"])) == 1200 + assert ( + len(database.samples(groups=["train", "dev", "eval"], purposes="real")) + == 240 + 180 + 120 + ) + assert ( + len(database.samples(groups=["train", "dev", "eval"], purposes="attack")) + == 960 + 720 + 480 + ) + + sample = database.sort(database.samples())[0] + try: + annot = dict(sample.annotations["0"]) + assert annot["leye"][1] > annot["reye"][1], annot + assert annot == { + "bottomright": [1124, 773], + "leye": [818, 638], + "mouthleft": [1005, 489], + "mouthright": [1000, 634], + "nose": [906, 546], + "reye": [821, 470], + "topleft": [632, 394], + } + assert sample.data.shape == (20, 3, 1920, 1080) + assert sample.data[0][0, 0, 0] == 195 + except RuntimeError as e: + raise SkipTest(e) diff --git a/doc/links.rst b/doc/links.rst index 09f5c2d7d96c5e751167f21500362038c7539c23..5c51ec6cc7d0bb7cb2a4e4034d8220206c431ef6 100644 --- a/doc/links.rst +++ b/doc/links.rst @@ -15,3 +15,4 @@ .. _MIFS: http://www.antitza.com/makeup-datasets.html .. _CELEBA: http://mmlab.ie.cuhk.edu.hk/projects/CelebA.html .. _Swan: https://www.idiap.ch/dataset/swan +.. _oulu-npu: https://sites.google.com/site/oulunpudatabase/ diff --git a/setup.py b/setup.py index 10450c19bf771fcfeab77ac8fdfebb013ac778c9..f14cecf4057cc447451a8941a3699920d5311012 100644 --- a/setup.py +++ b/setup.py @@ -63,6 +63,7 @@ setup( "casiasurf-color = bob.pad.face.config.casiasurf_color:database", "casiasurf = bob.pad.face.config.casiasurf:database", "swan = bob.pad.face.config.swan:database", + "oulunpu = bob.pad.face.config.oulunpu:database", ], # registered configurations: "bob.pad.config": [ @@ -74,6 +75,7 @@ setup( "casiasurf-color = bob.pad.face.config.casiasurf_color", "casiasurf = bob.pad.face.config.casiasurf", "swan = bob.pad.face.config.swan", + "oulunpu = bob.pad.face.config.oulunpu", # LBPs "lbp = bob.pad.face.config.lbp_64", # quality measure