Skip to content

Instantly share code, notes, and snippets.

@zalo
Last active August 16, 2022 06:30
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zalo/71fbd5dbfe23cb46406d211b84be9f7e to your computer and use it in GitHub Desktop.
Save zalo/71fbd5dbfe23cb46406d211b84be9f7e to your computer and use it in GitHub Desktop.
This is an example using Adrian Bulat's face_alignment library and Python to draw the head's basis vectors
import numpy as np
import cv2
import face_alignment
# Initialize the face alignment tracker
fa = face_alignment.FaceAlignment(face_alignment.LandmarksType._3D, flip_input=True, device="cuda")
# Start the webcam capture, exit with 'q'
cap = cv2.VideoCapture(0)
while(not (cv2.waitKey(1) & 0xFF == ord('q'))):
ret, frame = cap.read()
if(ret):
# Clear the indices frame
canonical = np.zeros(frame.shape)
# Run the face alignment tracker on the webcam image
imagePoints = fa.get_landmarks_from_image(frame)
if(imagePoints is not None):
imagePoints = imagePoints[0]
# Compute the Mean-Centered-Scaled Points
mean = np.mean(imagePoints, axis=0) # <- This is the unscaled mean
scaled = (imagePoints / np.linalg.norm(imagePoints[42] - imagePoints[39])) * 0.06 # Set the inner eye distance to 60cm (just because)
centered = scaled - np.mean(scaled, axis=0) # <- This is the scaled mean
# Construct a "rotation" matrix (strong simplification, might have shearing)
rotationMatrix = np.empty((3,3))
rotationMatrix[0,:] = (centered[16] - centered[0])/np.linalg.norm(centered[16] - centered[0])
rotationMatrix[1,:] = (centered[8] - centered[27])/np.linalg.norm(centered[8] - centered[27])
rotationMatrix[2,:] = np.cross(rotationMatrix[0, :], rotationMatrix[1, :])
invRot = np.linalg.inv(rotationMatrix)
# Object-space points, these are what you'd run OpenCV's solvePnP() with
objectPoints = centered.dot(invRot)
# Draw the computed data
for i, (imagePoint, objectPoint) in enumerate(zip(imagePoints, objectPoints)):
# Draw the Point Predictions
cv2.circle(frame, (imagePoint[0], imagePoint[1]), 3, (0,255,0))
# Draw the X Axis
cv2.line(frame, tuple(mean[:2].astype(int)),
tuple((mean+(rotationMatrix[0,:] * 100.0))[:2].astype(int)), (0, 0, 255), 3)
# Draw the Y Axis
cv2.line(frame, tuple(mean[:2].astype(int)),
tuple((mean-(rotationMatrix[1,:] * 100.0))[:2].astype(int)), (0, 255, 0), 3)
# Draw the Z Axis
cv2.line(frame, tuple(mean[:2].astype(int)),
tuple((mean+(rotationMatrix[2,:] * 100.0))[:2].astype(int)), (255, 0, 0), 3)
# Draw the indices in Object Space
cv2.putText(canonical, str(i),
((int)((objectPoint[0] * 1000.0) + 320.0),
(int)((objectPoint[1] * 1000.0) + 240.0)),
cv2.FONT_HERSHEY_SIMPLEX, 0.3, (255, 255, 255), 1, cv2.LINE_AA)
cv2.imshow('Webcam View', frame)
cv2.imshow('Canonical View', canonical)
# When everything is done, release the capture
cap.release()
cv2.destroyAllWindows()
@sandhyalaxmiK
Copy link

sandhyalaxmiK commented Feb 4, 2020

Hi. This process is giving good results for video.
I want to calculate pitch, yaw, roll of a face.
So,
I added these lines

   def rotationMatrixToEulerAngles(R) :
       sy = math.sqrt(R[0,0] * R[0,0] +  R[1,0] * R[1,0])
       singular = sy < 1e-6
      if  not singular :
          x = math.atan2(R[2,1] , R[2,2])
           y = math.atan2(-R[2,0], sy)
           z = math.atan2(R[1,0], R[0,0])
     else :
           x = math.atan2(-R[1,2], R[1,1])
           y = math.atan2(-R[2,0], sy)
           z = 0
    return np.array([x, y, z])


 eulerAngles=rotationMatrixToEulerAngles(rotationMatrix)        
 pitch, yaw, roll = [math.radians(_) for _ 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)))
  rotate_degree=[ str(int(pitch)), str(int(yaw)),str(int(roll))]

Your process is giving good results for pitch, roll, yaw.
But, Instead of video, i have taken directory containing different persons in different orientation images and applied above concept. Giving bad results for pitch, roll, yaw values.
Can you tell the process of calculating pitch, roll, yaw angles

Thanks
Sandhya

@leeeeeeo
Copy link

Hi. This process is giving good results for video.
I want to calculate pitch, yaw, roll of a face.
So,
I added these lines

   def rotationMatrixToEulerAngles(R) :
       sy = math.sqrt(R[0,0] * R[0,0] +  R[1,0] * R[1,0])
       singular = sy < 1e-6
      if  not singular :
          x = math.atan2(R[2,1] , R[2,2])
           y = math.atan2(-R[2,0], sy)
           z = math.atan2(R[1,0], R[0,0])
     else :
           x = math.atan2(-R[1,2], R[1,1])
           y = math.atan2(-R[2,0], sy)
           z = 0
    return np.array([x, y, z])


 eulerAngles=rotationMatrixToEulerAngles(rotationMatrix)        
 pitch, yaw, roll = [math.radians(_) for _ 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)))
  rotate_degree=[ str(int(pitch)), str(int(yaw)),str(int(roll))]

Your process is giving good results for pitch, roll, yaw.
But, Instead of video, i have taken directory containing different persons in different orientation images and applied above concept. Giving bad results for pitch, roll, yaw values.
Can you tell the process of calculating pitch, roll, yaw angles

Thanks
Sandhya

Hi Sandhya,
Have you fixed your results? It seems right results to me.

@sandhyalaxmiK
Copy link

sandhyalaxmiK commented Mar 2, 2020

Hi,
Thanks for your reply.
Sorry for late response.

import numpy as np
import cv2
import face_alignment
import math
def rotationMatrixToEulerAngles(R) :
sy = math.sqrt(R[0,0] * R[0,0] + R[1,0] * R[1,0])
singular = sy < 1e-6
if not singular :
x = math.atan2(R[2,1] , R[2,2])
y = math.atan2(-R[2,0], sy)
z = math.atan2(R[1,0], R[0,0])
else :
x = math.atan2(-R[1,2], R[1,1])
y = math.atan2(-R[2,0], sy)
z = 0
return np.array([x, y, z])
#Initialize the face alignment tracker
fa = face_alignment.FaceAlignment(face_alignment.LandmarksType.3D, flip_input=True, device="cpu")
frame=cv2.imread("model9.jpg") #("model9.jpg")
canonical = np.zeros(frame.shape)
#Run the face alignment tracker on the image
#imagePoints = fa.get_landmarks_from_image(frame)
imagePoints = fa.get_landmarks(frame)
if(imagePoints is not None):
imagePoints = imagePoints[0]
# Compute the Mean-Centered-Scaled Points
mean = np.mean(imagePoints, axis=0) # <- This is the unscaled mean
scaled = (imagePoints / np.linalg.norm(imagePoints[42] - imagePoints[39])) * 0.06 # Set the inner eye distance to 60cm (just because)
centered = scaled - np.mean(scaled, axis=0) # <- This is the scaled mean
# Construct a "rotation" matrix (strong simplification, might have shearing)
rotationMatrix = np.empty((3,3))
rotationMatrix[0,:] = (centered[16] - centered[0])/np.linalg.norm(centered[16] - centered[0])
rotationMatrix[1,:] = (centered[8] - centered[27])/np.linalg.norm(centered[8] - centered[27])
rotationMatrix[2,:] = np.cross(rotationMatrix[0, :], rotationMatrix[1, :])
eulerAngles=rotationMatrixToEulerAngles(rotationMatrix)
pitch, yaw, roll = [math.radians(
)*100.0 for _ 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)))
# rotate_degree=[ str(int(pitch)), str(int(roll)),str(int(yaw))]
rotate_degree=[math.floor(pitch),math.floor(roll),math.floor(yaw)]
print ("Rotate_Degree:",rotate_degree)
cv2.imshow('Webcam View', frame)
cv2.imshow('Canonical View', canonical)
cv2.waitKey(0)

With the above code, if i print rotate_degree giving result as Rotate_Degree: ['16', '44', '-20'], where as from https://azure.microsoft.com/en-us/services/cognitive-services/face/#demo link getting values for pitch, roll, yaw as like this
"headPose": {
"pitch": 6.0,
"roll": 24.7,
"yaw": 11.1
}

What should i modify to get the results??

My model9.jpg image is
model9

Thanks in Advance.
Regards,
Sandhyalaxmi Kanna

@yogurt7771
Copy link

Hi,
Thanks for your reply.
Sorry for late response.

import numpy as np
import cv2
import face_alignment
import math
def rotationMatrixToEulerAngles(R) :
sy = math.sqrt(R[0,0] * R[0,0] + R[1,0] * R[1,0])
singular = sy < 1e-6
if not singular :
x = math.atan2(R[2,1] , R[2,2])
y = math.atan2(-R[2,0], sy)
z = math.atan2(R[1,0], R[0,0])
else :
x = math.atan2(-R[1,2], R[1,1])
y = math.atan2(-R[2,0], sy)
z = 0
return np.array([x, y, z])
#Initialize the face alignment tracker
fa = face_alignment.FaceAlignment(face_alignment.LandmarksType.3D, flip_input=True, device="cpu") frame=cv2.imread("model9.jpg") #("model9.jpg") canonical = np.zeros(frame.shape) #Run the face alignment tracker on the image #imagePoints = fa.get_landmarks_from_image(frame) imagePoints = fa.get_landmarks(frame) if(imagePoints is not None): imagePoints = imagePoints[0] # Compute the Mean-Centered-Scaled Points mean = np.mean(imagePoints, axis=0) # <- This is the unscaled mean scaled = (imagePoints / np.linalg.norm(imagePoints[42] - imagePoints[39])) * 0.06 # Set the inner eye distance to 60cm (just because) centered = scaled - np.mean(scaled, axis=0) # <- This is the scaled mean # Construct a "rotation" matrix (strong simplification, might have shearing) rotationMatrix = np.empty((3,3)) rotationMatrix[0,:] = (centered[16] - centered[0])/np.linalg.norm(centered[16] - centered[0]) rotationMatrix[1,:] = (centered[8] - centered[27])/np.linalg.norm(centered[8] - centered[27]) rotationMatrix[2,:] = np.cross(rotationMatrix[0, :], rotationMatrix[1, :]) eulerAngles=rotationMatrixToEulerAngles(rotationMatrix) pitch, yaw, roll = [math.radians()*100.0 for _ 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)))

rotate_degree=[ str(int(pitch)), str(int(roll)),str(int(yaw))]

rotate_degree=[math.floor(pitch),math.floor(roll),math.floor(yaw)]
print ("Rotate_Degree:",rotate_degree)
cv2.imshow('Webcam View', frame)
cv2.imshow('Canonical View', canonical)
cv2.waitKey(0)

With the above code, if i print rotate_degree giving result as Rotate_Degree: ['16', '44', '-20'], where as from https://azure.microsoft.com/en-us/services/cognitive-services/face/#demo link getting values for pitch, roll, yaw as like this "headPose": { "pitch": 6.0, "roll": 24.7, "yaw": 11.1 }
What should i modify to get the results??

My model9.jpg image is
model9

Thanks in Advance.
Regards,
Sandhyalaxmi Kanna

        rotationMatrix = np.empty((3, 3))
        rotationMatrix[0, :] = (centered[16] - centered[0]) / np.linalg.norm(centered[16] - centered[0])
        rotationMatrix[1, :] = (centered[8] - centered[27]) / np.linalg.norm(centered[8] - centered[27])
        rotationMatrix[2, :] = np.cross(rotationMatrix[0, :], rotationMatrix[1, :])

        eulerAngles = rotationMatrixToEulerAngles(rotationMatrix)
        pitch, yaw, roll = eulerAngles
        pitch = np.degrees(np.arcsin(np.sin(pitch)))
        roll = -np.degrees(np.arcsin(np.sin(roll)))
        yaw = -np.degrees(np.arcsin(np.sin(yaw)))

@i-amgeek
Copy link

i-amgeek commented Feb 4, 2021

Thanks @patrickwangqy. It is working very well.

@iperov
Copy link

iperov commented Aug 7, 2021

np.arcsin(np.sin(pitch))

why?

@yogurt7771
Copy link

np.arcsin(np.sin(pitch))

why?

angle range

@iperov
Copy link

iperov commented Aug 9, 2021

you clip it with -90+90 deg? but what if actual YAW > 90 deg ?

@yogurt7771
Copy link

it is impossible

@iperov
Copy link

iperov commented Aug 16, 2021

possible.
12280

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment