summaryrefslogtreecommitdiff
path: root/megapixels/app/processors/face_extractor.py
blob: f618cd363f2cc3cce521a4fde904c0468caa9c95 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import os
from os.path import join
from pathlib import Path

import cv2 as cv
import numpy as np
import dlib
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

def similarity(self, query_enc, known_enc):
    return np.linalg.norm(query_enc - known_enc, axis=1)

def flatten(vec):
    '''Converts N-D vector into a flattened list for CSV
    :param points: (list) a feature vector as list of floats
    :returns dict item for each point (eg {'d1':0.28442156, 'd1': 0.1868632})
    '''
    vec_flat = {}
    for idx, val in enumerate(vec, 1):
      vec_flat[f'd{idx}'] = val
    return vec_flat



class Extractor:

  n_dim = None  # override

  def __init__(self):
    self.log = logger_utils.Logger.getLogger()

  def flatten(self, vec):
    '''Converts N-D vector into a flattened list for CSV
    :param points: (list) a feature vector as list of floats
    :returns dict item for each point (eg {'d1':0.28442156, 'd1': 0.1868632})
    '''
    vec_flat = {}
    for idx, val in enumerate(vec, 1):
      vec_flat[f'd{idx}'] = val
    return vec_flat

  def to_str(self, vec):
    return ','.join([str(x) for x in vec])

  def unflatten_df(self, df):
    # convert from
    return [df[f'd{i}'] for i in range(1,257)]


class ExtractorVGG(Extractor):

  # https://github.com/ox-vgg/vgg_face2
  # Uses OpenCV DNN to extract feature vector for VGG Face 2 models
  n_dim = 256
  dnn_dim = (224,224)
  dnn_mean = (91.4953, 103.8827, 131.0912)

  def __init__(self):
    super().__init__()
    fp_model = '/data_store_hdd/apps/megapixels/models/caffe/vgg_face2/resnet50_256_caffe/resnet50_256.caffemodel'
    fp_prototxt = '/data_store_hdd/apps/megapixels/models/caffe/vgg_face2/resnet50_256_caffe/resnet50_256.prototxt'
    self.dnn = cv.dnn.readNetFromCaffe(fp_prototxt, fp_model)
    self.feat_layer = self.dnn.getLayerNames()[-2]

  def extract_jitter(self, im, bbox_norm):
    '''(experimental) Extracts feature vector for face crop
    :param im:
    :param bbox_norm: (BBox) normalized
    :param padding: (float) percent to extend ROI
    :param jitters: not used here
    :returns (list) of (float)'''
    dim = im.shape[:2][::-1]
    num_jitters = cfg.DEFAULT_NUM_JITTERS
    padding = cfg.DEFAULT_FACE_PADDING_VGG_FACE2
    pad_adj = .00875 * padding  # percentage of padding to vary
    paddings = np.linspace(padding - pad_adj, padding + pad_adj, num=num_jitters)
    jitter_amt = cfg.DEFAULT_JITTER_AMT
    vecs = []
    for i in range(num_jitters):
      bbox_norm_jit = bbox_norm.jitter(jitter_amt)  # jitters w, h, center
      bbox_ext = bbox_norm_jit.expand(paddings[i])
      #bbox_ext = bbox_norm.expand(paddings[i])
      x1,y1,x2,y2 = bbox_ext.to_dim(dim).to_xyxy()
      im_crop = im[y1:y2, x1:x2]
      # According to VGG, model trained using Bilinear interpolation (INTER_LINEAR)
      im_crop = cv.resize(im_crop, self.dnn_dim, interpolation=cv.INTER_LINEAR)
      blob = cv.dnn.blobFromImage(im_crop, 1.0, self.dnn_dim, self.dnn_mean)
      self.dnn.setInput(blob)
      vec = np.array(self.dnn.forward(self.feat_layer)[0])
      vec_norm = vec/np.linalg.norm(vec)  # normalize
      vecs.append(vec_norm)
    vec_norm = np.mean(np.array(vecs), axis=0)
    return vec_norm

  def extract(self, im, bbox_norm):
    '''Extracts feature vector for face crop
    :param im:
    :param bbox_norm: (BBox) normalized
    :param padding: (float) percent to extend ROI
    :param jitters: not used here
    :returns (list) of (float)'''
    padding = cfg.DEFAULT_FACE_PADDING_VGG_FACE2
    bbox_ext = bbox_norm.expand(padding)
    dim = im.shape[:2][::-1]
    x1,y1,x2,y2 = bbox_ext.to_dim(dim).to_xyxy()
    im = im[y1:y2, x1:x2]
    # According to VGG, model trained using Bilinear interpolation (INTER_LINEAR)
    im = cv.resize(im, self.dnn_dim, interpolation=cv.INTER_LINEAR)
    blob = cv.dnn.blobFromImage(im, 1.0, self.dnn_dim, self.dnn_mean)
    self.dnn.setInput(blob)
    vec = np.array(self.dnn.forward(self.feat_layer)[0])
    vec_norm = vec/np.linalg.norm(vec)  # normalize
    return vec_norm


class ExtractorDLIB(Extractor):

  # https://github.com/davisking/dlib/blob/master/python_examples/face_recognition.py
  # facerec.compute_face_descriptor(img, shape, 100, 0.25)
  # padding=opt_padding not yet implemented in dlib===19.16 but merged in master
  n_dim = 128
  process_width = 100

  def __init__(self, gpu=0, jitters=cfg.DLIB_FACEREC_JITTERS):
    super().__init__()
    self.num_jitters = cfg.DLIB_FACEREC_JITTERS
    # set and swap GPU visibility
    if gpu > -1:
      cuda_visible_devices = os.getenv('CUDA_VISIBLE_DEVICES', '')
      os.environ['CUDA_VISIBLE_DEVICES'] = str(gpu)
    self.predictor = dlib.shape_predictor(cfg.DIR_MODELS_DLIB_5PT)
    self.facerec = dlib.face_recognition_model_v1(cfg.DIR_MODELS_DLIB_FACEREC_RESNET)
    # unset and swap GPU visibility
    if gpu > -1:
      os.environ['CUDA_VISIBLE_DEVICES'] = cuda_visible_devices  # reset GPU env


  def extract(self, im, bbox_norm):
    '''Converts image and bbox into 128d vector
    :param im: (numpy.ndarray) BGR image
    :param bbox_norm: (BBox) normalized
    '''
    # scale the image so the face is always 100x100 pixels
    dim = im.shape[:2][::-1]
    bbox_dim = bbox_norm.to_dim(dim)
    scale = self.process_width / bbox_dim.width
    cv.resize(im, None, fx=scale, fy=scale, interpolation=cv.INTER_LANCZOS4) 
    bbox_dim_dlib = bbox_dim.to_dlib()
    face_shape = self.predictor(im, bbox_dim_dlib)
    # this is only in dlib version 19.6++?
    # vec = self.facerec.compute_face_descriptor(im, face_shape, self.num_jitters, self.padding)
    # vectors are already normalized
    vec = self.facerec.compute_face_descriptor(im, face_shape, self.num_jitters)
    return vec