summaryrefslogtreecommitdiff
path: root/megapixels/app/processors
diff options
context:
space:
mode:
Diffstat (limited to 'megapixels/app/processors')
-rw-r--r--megapixels/app/processors/face_age.py28
-rw-r--r--megapixels/app/processors/face_age_gender.py116
-rw-r--r--megapixels/app/processors/face_gender.py28
3 files changed, 116 insertions, 56 deletions
diff --git a/megapixels/app/processors/face_age.py b/megapixels/app/processors/face_age.py
deleted file mode 100644
index 35174628..00000000
--- a/megapixels/app/processors/face_age.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import os
-from os.path import join
-from pathlib import Path
-import math
-
-import cv2 as cv
-import numpy as np
-import imutils
-
-from app.utils import im_utils, logger_utils
-from app.models.bbox import BBox
-from app.settings import app_cfg as cfg
-from app.settings import types
-
-
-
-class FaceAge:
-
- # Estimates face age
-
- def __init__(self, gpu=0):
- self.log = logger_utils.Logger.getLogger()
- pass
-
-
- def age(self, im, bbox_dim):
- self.log.warn('not yet implemented')
- return 0.0 \ No newline at end of file
diff --git a/megapixels/app/processors/face_age_gender.py b/megapixels/app/processors/face_age_gender.py
new file mode 100644
index 00000000..95efa8fc
--- /dev/null
+++ b/megapixels/app/processors/face_age_gender.py
@@ -0,0 +1,116 @@
+'''
+Uses models from: https://data.vision.ee.ethz.ch/cvl/rrothe/imdb-wiki/
+
+@article{Rothe-IJCV-2016,
+ author = {Rasmus Rothe and Radu Timofte and Luc Van Gool},
+ title = {Deep expectation of real and apparent age from a single image without facial landmarks},
+ journal = {International Journal of Computer Vision (IJCV)},
+ year = {2016},
+ month = {July},
+}
+'''
+
+import os
+from os.path import join
+from pathlib import Path
+import math
+
+import cv2 as cv
+import numpy as np
+import imutils
+
+from app.utils import im_utils, logger_utils
+from app.models.bbox import BBox
+from app.settings import app_cfg as cfg
+from app.settings import types
+
+
+
+class _FaceAgeGender:
+ '''Predicts age from face crop
+ https://data.vision.ee.ethz.ch/cvl/rrothe/imdb-wiki/
+ '''
+
+ dnn_size = (224,224)
+ dnn_mean = (104.0, 177.0, 123.0)
+ ages = np.arange(0, 101).reshape(101, 1)
+
+ def __init__(self, fp_prototxt, fp_model):
+ self.log = logger_utils.Logger.getLogger()
+ self.net = cv.dnn.readNetFromCaffe(fp_prototxt, fp_model)
+
+ def _preprocess(self, im, bbox_dim):
+ # isolate face ROI, expand bbox by 40% according to authors
+ # https://data.vision.ee.ethz.ch/cvl/rrothe/imdb-wiki/
+ dim = im.shape[:2][::-1]
+ bbox_dim_exp = bbox_dim.expand_dim( int(0.4*bbox_dim.width), dim)
+ roi = bbox_dim_exp.to_xyxy()
+ im_face_crop = im[roi[1]:roi[3], roi[0]:roi[2]] # isolate face roi
+
+ # resize for blob
+ im_resized = cv.resize(im_face_crop, self.dnn_size)
+ blob = cv.dnn.blobFromImage(im_resized, 1.0, self.dnn_size, self.dnn_mean)
+ return blob
+
+class FaceGender(_FaceAgeGender):
+
+ # use "apparent" age models
+ fp_model = join(cfg.DIR_MODELS_CAFFE, 'gender', 'gender.caffemodel')
+ fp_prototxt = join(cfg.DIR_MODELS_CAFFE, 'gender', 'gender.prototxt')
+
+ def __init__(self):
+ super().__init__(self.fp_prototxt, self.fp_model)
+
+ def predict(self, im, bbox_dim):
+ '''Predicts gender from face crop
+ :param im: (numpy.ndarray) BGR image
+ :param bbox_dim: (BBox) dimensioned
+ :returns (dict) with scores for male and female
+ '''
+ im_blob = self._preprocess(im, bbox_dim)
+ self.net.setInput(im_blob)
+ preds = self.net.forward()[0]
+ return {'f': preds[0], 'm': preds[1]}
+
+class FaceAgeApparent(_FaceAgeGender):
+
+ # use "apparent" age models
+ fp_model = join(cfg.DIR_MODELS_CAFFE, 'age_apparent', 'dex_chalearn_iccv2015.caffemodel')
+ fp_prototxt = join(cfg.DIR_MODELS_CAFFE, 'age_apparent', 'age.prototxt')
+
+ def __init__(self):
+ super().__init__(self.fp_prototxt, self.fp_model)
+
+ def predict(self, im, bbox_dim):
+ '''Predicts apparent age from face crop
+ :param im: (numpy.ndarray) BGR image
+ :param bbox_dim: (BBox) dimensioned
+ :returns (float) predicted age
+ '''
+ im_blob = self._preprocess(im, bbox_dim)
+ self.net.setInput(im_blob)
+ preds = self.net.forward()[0]
+ age = preds.dot(self.ages).flatten()[0]
+ return age
+
+
+class FaceAgeReal(_FaceAgeGender):
+
+ # use "real" age models
+ fp_model = join(cfg.DIR_MODELS_CAFFE, 'age_real', 'dex_imdb_wiki.caffemodel')
+ fp_prototxt = join(cfg.DIR_MODELS_CAFFE, 'age_real', 'age.prototxt')
+
+ def __init__(self):
+ super().__init__(self.fp_prototxt, self.fp_model)
+
+ def predict(self, im, bbox_dim):
+ '''Predicts apparent age from face crop
+ :param im: (numpy.ndarray) BGR image
+ :param bbox_dim: (BBox) dimensioned
+ :returns (float) predicted age
+ '''
+ im_blob = self._preprocess(im, bbox_dim)
+ self.net.setInput(im_blob)
+ preds = self.net.forward()[0]
+ age = preds.dot(self.ages).flatten()[0]
+ return age \ No newline at end of file
diff --git a/megapixels/app/processors/face_gender.py b/megapixels/app/processors/face_gender.py
deleted file mode 100644
index ea64b828..00000000
--- a/megapixels/app/processors/face_gender.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import os
-from os.path import join
-from pathlib import Path
-import math
-
-import cv2 as cv
-import numpy as np
-import imutils
-
-from app.utils import im_utils, logger_utils
-from app.models.bbox import BBox
-from app.settings import app_cfg as cfg
-from app.settings import types
-
-
-
-class FaceGender:
-
- # Estimates face age
-
- def __init__(self, gpu=0):
- self.log = logger_utils.Logger.getLogger()
- pass
-
-
- def gender(self, im, bbox_dim):
- self.log.warn('not yet implemented')
- return 0.0 \ No newline at end of file