summaryrefslogtreecommitdiff
path: root/megapixels/app/processors/face_pose.py
blob: 67ac685d50006064873df005f04af2f1485e8d8a (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
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 FacePoseDLIB:


  dnn_size = (400, 400)

  def __init__(self):
    pass

  def pose(self, landmarks, dim): 
    '''Calculates pose
    '''
    degrees = compute_pose_degrees(landmarks, dim)    
    return degrees


# -----------------------------------------------------------
# utilities
# -----------------------------------------------------------

def compute_pose_degrees(landmarks, dim):
  # computes pose using 6 / 68 points from dlib face landmarks
  # based on learnopencv.com and 
  # https://github.com/jerryhouuu/Face-Yaw-Roll-Pitch-from-Pose-Estimation-using-OpenCV/
  # NB: not as accurate as MTCNN, see @jerryhouuu for ideas
  
  pose_points_idx = (30, 8, 36, 45, 48, 54)
  axis = np.float32([[500,0,0], [0,500,0], [0,0,500]])
  
  # 3D model points.
  model_points = np.array([
      (0.0, 0.0, 0.0),             # Nose tip
      (0.0, -330.0, -65.0),        # Chin
      (-225.0, 170.0, -135.0),     # Left eye left corner
      (225.0, 170.0, -135.0),      # Right eye right corne
      (-150.0, -150.0, -125.0),    # Left Mouth corner
      (150.0, -150.0, -125.0)      # Right mouth corner
  ])
  
  # Assuming no lens distortion
  dist_coeffs = np.zeros((4,1))

  # find 6 pose points
  pose_points = []
  for j, idx in enumerate(pose_points_idx):
    pt = landmarks[idx]
    pose_points.append((pt[0], pt[1]))
  pose_points = np.array(pose_points, dtype='double')  # convert to double
  
  # create camera matrix
  focal_length = dim[0]
  center = (dim[0]/2, dim[1]/2)
  cam_mat = np.array(
      [[focal_length, 0, center[0]],
      [0, focal_length, center[1]],
      [0, 1, 1]], dtype = "double")
  
  # solve PnP for rotation and translation
  (success, rot_vec, tran_vec) = cv.solvePnP(model_points, pose_points, 
                                             cam_mat, dist_coeffs, 
                                             flags=cv.SOLVEPNP_ITERATIVE)

  # project points
  #pts_im, jac = cv.projectPoints(axis, rot_vec, tran_vec, cam_mat, dist_coeffs)
  #pts_model, jac2 = cv.projectPoints(model_points, rot_vec, tran_vec, cam_mat, dist_coeffs)
  rvec_matrix = cv.Rodrigues(rot_vec)[0]
  
  # convert to degrees
  proj_matrix = np.hstack((rvec_matrix, tran_vec))
  eulerAngles = cv.decomposeProjectionMatrix(proj_matrix)[6] 
  pitch, yaw, roll = [math.radians(x) for x in eulerAngles]
  pitch = math.degrees(math.asin(math.sin(pitch)))
  roll = -math.degrees(math.asin(math.sin(roll)))
  yaw = math.degrees(math.asin(math.sin(yaw)))
  degrees = {'pitch': pitch, 'roll': roll, 'yaw': yaw}
  
  # add nose point
  #pt_nose = tuple(landmarks[pose_points_idx[0]])
  return degrees
  #return pts_im, pts_model, degrees, pt_nose


def draw_pose(im, pts_im, pts_model, pt_nose):
  cv.line(im, pt_nose, tuple(pts_im[1].ravel()), (0,255,0), 3) #GREEN
  cv.line(im, pt_nose, tuple(pts_im[0].ravel()), (255,0,), 3) #BLUE
  cv.line(im, pt_nose, tuple(pts_im[2].ravel()), (0,0,255), 3) #RED
  return im


def draw_degrees(im, degrees, color=(0,255,0)):
  for i, item in enumerate(degrees.items()):
    k, v = item
    t = '{}: {:.2f}'.format(k, v)
    origin = (10, 30 + (25 * i))
    cv.putText(im, t, origin, cv.FONT_HERSHEY_SIMPLEX, 0.5, color, thickness=2, lineType=2)