From 9ad9020dac7122f6180e13e896d1093f66cdd018 Mon Sep 17 00:00:00 2001 From: Tiago Freitas Pereira <tiagofrepereira@gmail.com> Date: Mon, 31 May 2021 19:08:04 +0200 Subject: [PATCH] Updated pytorch models and opencv based models --- .../baseline/{pytorch_pipe_v1.py => afffe.py} | 24 ++-- .../{opencv_pipe.py => vgg16_oxford.py} | 34 ++--- bob/bio/face/embeddings/opencv.py | 117 ++++++++++++++++++ bob/bio/face/embeddings/pytorch.py | 99 +++++++++++++++ bob/bio/face/test/test_baselines.py | 14 ++- setup.py | 22 ++-- 6 files changed, 246 insertions(+), 64 deletions(-) rename bob/bio/face/config/baseline/{pytorch_pipe_v1.py => afffe.py} (75%) rename bob/bio/face/config/baseline/{opencv_pipe.py => vgg16_oxford.py} (61%) create mode 100644 bob/bio/face/embeddings/opencv.py create mode 100644 bob/bio/face/embeddings/pytorch.py diff --git a/bob/bio/face/config/baseline/pytorch_pipe_v1.py b/bob/bio/face/config/baseline/afffe.py similarity index 75% rename from bob/bio/face/config/baseline/pytorch_pipe_v1.py rename to bob/bio/face/config/baseline/afffe.py index 843e5381..68d4964c 100644 --- a/bob/bio/face/config/baseline/pytorch_pipe_v1.py +++ b/bob/bio/face/config/baseline/afffe.py @@ -1,15 +1,14 @@ import bob.bio.base from bob.bio.face.preprocessor import FaceCrop -from bob.bio.face.extractor import PyTorchLoadedModel -from bob.bio.base.algorithm import Distance -from bob.bio.base.pipelines.vanilla_biometrics.legacy import BioAlgorithmLegacy +from bob.bio.face.embeddings.pytorch import AFFFE_2021 +from bob.pipelines import wrap import scipy.spatial from bob.bio.base.pipelines.vanilla_biometrics import Distance - from sklearn.pipeline import make_pipeline from bob.pipelines import wrap from bob.bio.base.pipelines.vanilla_biometrics import VanillaBiometricsPipeline + memory_demanding = False if "database" in locals(): annotation_type = database.annotation_type @@ -22,16 +21,14 @@ else: annotation_type = None fixed_positions = None - -cropped_positions = {"leye": (49, 72), "reye": (49, 38)} - cropped_positions = {"leye": (110, 144), "reye": (110, 96)} preprocessor_transformer = FaceCrop( cropped_image_size=(224, 224), - cropped_positions={"leye": (110, 144), "reye": (110, 96)}, + cropped_positions=cropped_positions, color_channel="rgb", fixed_positions=fixed_positions, + allow_upside_down_normalized_faces=True, ) transform_extra_arguments = ( @@ -40,20 +37,13 @@ transform_extra_arguments = ( else (("annotations", "annotations"),) ) -transform_extra_arguments = ( - None - if (cropped_positions is None or fixed_positions is not None) - else (("annotations", "annotations"),) -) - - -extractor_transformer = PyTorchLoadedModel() +extractor_transformer = AFFFE_2021() +# Algorithm algorithm = Distance( distance_function=scipy.spatial.distance.cosine, is_distance_function=True ) - # Chain the Transformers together transformer = make_pipeline( wrap( diff --git a/bob/bio/face/config/baseline/opencv_pipe.py b/bob/bio/face/config/baseline/vgg16_oxford.py similarity index 61% rename from bob/bio/face/config/baseline/opencv_pipe.py rename to bob/bio/face/config/baseline/vgg16_oxford.py index 166c73b8..dd513cf8 100644 --- a/bob/bio/face/config/baseline/opencv_pipe.py +++ b/bob/bio/face/config/baseline/vgg16_oxford.py @@ -1,18 +1,14 @@ import bob.bio.base from bob.bio.face.preprocessor import FaceCrop -from bob.bio.base.transformers.preprocessor import PreprocessorTransformer -from bob.bio.face.extractor import OpenCVModel -from bob.bio.base.extractor import Extractor -from bob.bio.base.transformers import ExtractorTransformer -from bob.bio.base.algorithm import Distance -from bob.bio.base.pipelines.vanilla_biometrics.legacy import BioAlgorithmLegacy +from bob.bio.face.embeddings.opencv import VGG16_Oxford +from bob.pipelines import wrap import scipy.spatial from bob.bio.base.pipelines.vanilla_biometrics import Distance + from sklearn.pipeline import make_pipeline from bob.pipelines import wrap from bob.bio.base.pipelines.vanilla_biometrics import VanillaBiometricsPipeline - memory_demanding = False if "database" in locals(): annotation_type = database.annotation_type @@ -25,23 +21,13 @@ else: annotation_type = None fixed_positions = None - -cropped_positions = {"leye": (98, 144), "reye": (98, 76)} -# Preprocessor +cropped_positions = {"leye": (100, 140), "reye": (100, 95)} preprocessor_transformer = FaceCrop( cropped_image_size=(224, 224), - cropped_positions={"leye": (98, 144), "reye": (98, 76)}, - color_channel="rgb", - fixed_positions=fixed_positions, -) - -cropped_positions = {"leye": (98, 144), "reye": (98, 76)} -# Preprocessor -preprocessor_transformer = FaceCrop( - cropped_image_size=(224, 224), - cropped_positions={"leye": (98, 144), "reye": (98, 76)}, + cropped_positions=cropped_positions, color_channel="rgb", fixed_positions=fixed_positions, + allow_upside_down_normalized_faces=True, ) transform_extra_arguments = ( @@ -52,10 +38,7 @@ transform_extra_arguments = ( # Extractor -weights = None # PATH/TO/WEIGHTS -config = None # PATH/TO/CONFIG - -extractor_transformer = OpenCVModel(weights=weights, config=config) +extractor_transformer = VGG16_Oxford() # Algorithm @@ -63,9 +46,6 @@ algorithm = Distance( distance_function=scipy.spatial.distance.cosine, is_distance_function=True ) -## Creation of the pipeline - - # Chain the Transformers together transformer = make_pipeline( wrap( diff --git a/bob/bio/face/embeddings/opencv.py b/bob/bio/face/embeddings/opencv.py new file mode 100644 index 00000000..0535b6d4 --- /dev/null +++ b/bob/bio/face/embeddings/opencv.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python +# vim: set fileencoding=utf-8 : +# Yu Linghu & Xinyi Zhang <yu.linghu@uzh.ch, xinyi.zhang@uzh.ch> +# Tiago de Freitas Pereira <tiago.pereira@idiap.ch> + +import bob.bio.base +import numpy as np +from sklearn.base import TransformerMixin, BaseEstimator +from sklearn.utils import check_array +import os +from bob.extension.download import get_file + + +class OpenCVTransformer(TransformerMixin, BaseEstimator): + """ + Base Transformer using the OpenCV interface. + + + .. note:: + This class supports Caffe ``.caffemodel``, Tensorflow ``.pb``, Torch ``.t7`` ``.net``, Darknet ``.weights``, DLDT ``.bin``, and ONNX ``.onnx`` + + + Parameters + ---------- + + checkpoint_path: str + Path containing the checkpoint + + config: + Path containing some configuration file (e.g. .json, .prototxt) + """ + + def __init__(self, checkpoint_path=None, config=None, **kwargs): + super().__init__(**kwargs) + self.checkpoint_path = checkpoint_path + self.config = config + self.model = None + + def _load_model(self): + import cv2 + + net = cv2.dnn.readNet(self.checkpoint_path, self.config) + self.model = net + + def transform(self, X): + """__call__(image) -> feature + + Extracts the features from the given image. + + **Parameters:** + + X : 2D :py:class:`numpy.ndarray` (floats) + The image to extract the features from. + + **Returns:** + + feature : 2D or 3D :py:class:`numpy.ndarray` (floats) + The list of features extracted from the image. + """ + + import cv2 + + if self.model is None: + self._load_model() + + import ipdb + + ipdb.set_trace() + + img = np.array(X) + img = img / 255 + + self.model.setInput(img) + + return self.model.forward() + + def __getstate__(self): + # Handling unpicklable objects + + d = self.__dict__.copy() + d["model"] = None + return d + + def _more_tags(self): + return {"stateless": True, "requires_fit": False} + + +class VGG16_Oxford(OpenCVTransformer): + """ + Original VGG16 model from the paper: https://www.robots.ox.ac.uk/~vgg/publications/2015/Parkhi15/parkhi15.pdf + + """ + + def __init__(self): + urls = [ + "https://www.robots.ox.ac.uk/~vgg/software/vgg_face/src/vgg_face_caffe.tar.gz", + "http://bobconda.lab.idiap.ch/public-upload/data/bob/bob.bio.face/master/caffe/vgg_face_caffe.tar.gz", + ] + + filename = get_file( + "vgg_face_caffe.tar.gz", + urls, + cache_subdir="data/caffe/vgg_face_caffe", + file_hash="ee707ac6e890bc148cb155adeaad12be", + extract=True, + ) + path = os.path.dirname(filename) + config = os.path.join(path, "vgg_face_caffe", "VGG_FACE_deploy.prototxt") + checkpoint_path = os.path.join(path, "vgg_face_caffe", "VGG_FACE.caffemodel") + + super(VGG16_Oxford, self).__init__(checkpoint_path, config) + + def _load_model(self): + import cv2 + + net = cv2.dnn.readNet(self.checkpoint_path, self.config) + self.model = net diff --git a/bob/bio/face/embeddings/pytorch.py b/bob/bio/face/embeddings/pytorch.py new file mode 100644 index 00000000..d091a581 --- /dev/null +++ b/bob/bio/face/embeddings/pytorch.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python +# vim: set fileencoding=utf-8 : +# Yu Linghu & Xinyi Zhang <yu.linghu@uzh.ch, xinyi.zhang@uzh.ch> +# Tiago de Freitas Pereira <tiago.pereira@idiap.ch> + +from sklearn.base import TransformerMixin, BaseEstimator +from sklearn.utils import check_array +import numpy as np +import imp +import os +from bob.extension.download import get_file + + +class PyTorchModel(TransformerMixin, BaseEstimator): + """ + """ + + def __init__(self, checkpoint_path=None, config=None, **kwargs): + super().__init__(**kwargs) + self.checkpoint_path = checkpoint_path + self.config = config + self.model = None + + def transform(self, X): + """__call__(image) -> feature + + Extracts the features from the given image. + + **Parameters:** + + image : 2D :py:class:`numpy.ndarray` (floats) + The image to extract the features from. + + **Returns:** + + feature : 2D or 3D :py:class:`numpy.ndarray` (floats) + The list of features extracted from the image. + """ + import torch + + if self.model is None: + self._load_model() + X = check_array(X, allow_nd=True) + X = torch.Tensor(X) + X = X / 255 + + return self.model(X).detach().numpy() + + def __getstate__(self): + # Handling unpicklable objects + + d = self.__dict__.copy() + d["model"] = None + return d + + def _more_tags(self): + return {"stateless": True, "requires_fit": False} + + +class AFFFE_2021(PyTorchModel): + """ + AFFFE from https://www.idiap.ch/software/bob/data/bob/bob.bio.face/master/pytorch/AFFFE-42a53f19.tar.gz + + """ + + def __init__(self): + + urls = [ + "https://www.idiap.ch/software/bob/data/bob/bob.bio.face/master/pytorch/AFFFE-42a53f19.tar.gz", + "http://www.idiap.ch/software/bob/data/bob/bob.bio.face/master/pytorch/AFFFE-42a53f19.tar.gz", + ] + + filename = get_file( + "AFFFE-42a53f19.tar.gz", + urls, + cache_subdir="data/pytorch/AFFFE-42a53f19.tar.gz", + file_hash="1358bbcda62cb59b85b2418ef1f81e9b", + extract=True, + ) + path = os.path.dirname(filename) + config = os.path.join(path, "AFFFE.py") + checkpoint_path = os.path.join(path, "AFFFE.pth") + + super(AFFFE_2021, self).__init__(checkpoint_path, config) + + def _load_model(self): + + import torch + + MainModel = imp.load_source("MainModel", self.config) + network = torch.load(self.checkpoint_path) + network.eval() + + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + + network.to(device) + + self.model = network + diff --git a/bob/bio/face/test/test_baselines.py b/bob/bio/face/test/test_baselines.py index 40c5b8bf..bfa360eb 100644 --- a/bob/bio/face/test/test_baselines.py +++ b/bob/bio/face/test/test_baselines.py @@ -66,6 +66,7 @@ def run_baseline(baseline, samples_for_training=[], target_scores=None): # Regular pipeline pipeline = load_resource(baseline, "pipeline") scores = pipeline(samples_for_training, biometric_references, probes) + assert len(scores) == 1 assert len(scores[0]) == 1 @@ -174,11 +175,14 @@ def test_opencv_pipe(): @pytest.mark.slow @is_library_available("torch") -def test_pytorch_pipe_v1(): - run_baseline("pytorch-pipe-v1", target_scores=None) +def test_afffe(): + run_baseline("afffe", target_scores=-0.7397219061544165) @pytest.mark.slow -@is_library_available("torch") -def test_pytorch_pipe_v2(): - run_baseline("pytorch-pipe-v2", target_scores=None) +@is_library_available("cv2") +def test_vgg16_oxford(): + import ipdb + + ipdb.set_trace() + run_baseline("vgg16-oxford", target_scores=None) diff --git a/setup.py b/setup.py index be478a7d..6efb33dd 100644 --- a/setup.py +++ b/setup.py @@ -136,12 +136,9 @@ setup( "gabor-graph = bob.bio.face.config.baseline.gabor_graph:transformer", "lgbphs = bob.bio.face.config.baseline.lgbphs:transformer", "dummy = bob.bio.face.config.baseline.dummy:transformer", - "mxnet-pipe = bob.bio.face.config.baseline.mxnet_pipe:transformer", "mxnet-tinyface = bob.bio.face.config.baseline.mxnet_tinyface:transformer", - "pytorch-pipe-v1 = bob.bio.face.config.baseline.pytorch_pipe_v1:transformer", - "pytorch-pipe-v2 = bob.bio.face.config.baseline.pytorch_pipe_v2:transformer", - "tf-pipe = bob.bio.face.config.baseline.tf_pipe:transformer", - "opencv-pipe = bob.bio.face.config.baseline.opencv_pipe:transformer", + "afffe = bob.bio.face.config.baseline.afffe:transformer", + "vgg16-oxford = bob.bio.face.config.baseline.vgg16_oxford:transformer", ], # baselines "bob.bio.pipeline": [ @@ -158,12 +155,9 @@ setup( "resnet50-msceleb-arcface-2021 = bob.bio.face.config.baseline.resnet50_msceleb_arcface_2021:pipeline", "resnet50-vgg2-arcface-2021 = bob.bio.face.config.baseline.resnet50_vgg2_arcface_2021:pipeline", "mobilenetv2-msceleb-arcface-2021 = bob.bio.face.config.baseline.mobilenetv2_msceleb_arcface_2021", - "mxnet-pipe = bob.bio.face.config.baseline.mxnet_pipe:pipeline", "mxnet-tinyface = bob.bio.face.config.baseline.mxnet_tinyface:pipeline", - "pytorch-pipe-v1 = bob.bio.face.config.baseline.pytorch_pipe_v1:pipeline", - "pytorch-pipe-v2 = bob.bio.face.config.baseline.pytorch_pipe_v2:pipeline", - "tf-pipe = bob.bio.face.config.baseline.tf_pipe:pipeline", - "opencv-pipe = bob.bio.face.config.baseline.opencv_pipe:pipeline", + "afffe = bob.bio.face.config.baseline.afffe:pipeline", + "vgg16-oxford = bob.bio.face.config.baseline.vgg16_oxford:pipeline", ], "bob.bio.config": [ "facenet-sanderberg = bob.bio.face.config.baseline.facenet_sanderberg", @@ -177,10 +171,8 @@ setup( "lda = bob.bio.face.config.baseline.lda", "mxnet-pipe = bob.bio.face.config.baseline.mxnet_pipe", "mxnet-tinyface = bob.bio.face.config.baseline.mxnet_tinyface", - "pytorch-pipe-v1 = bob.bio.face.config.baseline.pytorch_pipe_v1", - "pytorch-pipe-v2 = bob.bio.face.config.baseline.pytorch_pipe_v2", - "tf-pipe = bob.bio.face.config.baseline.tf_pipe", - "opencv-pipe = bob.bio.face.config.baseline.opencv_pipe", + "afffe = bob.bio.face.config.baseline.afffe", + "vgg16-oxford = bob.bio.face.config.baseline.vgg16_oxford", "arface = bob.bio.face.config.database.arface", "atnt = bob.bio.face.config.database.atnt", "gbu = bob.bio.face.config.database.gbu", @@ -219,4 +211,4 @@ setup( "Programming Language :: Python", "Topic :: Scientific/Engineering :: Artificial Intelligence", ], -) \ No newline at end of file +) -- GitLab