原理
通过不停地旋转图片,直到dlib能够识别到人脸;通过dlib找到眼角、嘴角、鼻子、下巴特征点,假设鼻子到下巴的直线与双眼直线、嘴角直线垂直,找到脸部角度,结合已旋转角度,计算出最终图像旋转角度。
效果
代码
import dlib
import cv2
import numpy as np
import math
import os
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")
POINTS_NUM_LANDMARK = 68
def rotate_img(image, angle):
# grab the dimensions of the image and then determine the
# center
(h, w) = image.shape[:2]
(cX, cY) = (w // 2, h // 2)
# grab the rotation matrix (applying the negative of the
# angle to rotate clockwise), then grab the sine and cosine
# (i.e., the rotation components of the matrix)
M = cv2.getRotationMatrix2D((cX, cY), -angle, 1.0)
cos = np.abs(M[0, 0])
sin = np.abs(M[0, 1])
# compute the new bounding dimensions of the image
nW = int((h * sin) + (w * cos))
nH = int((h * cos) + (w * sin))
# adjust the rotation matrix to take into account translation
M[0, 2] += (nW / 2) - cX
M[1, 2] += (nH / 2) - cY
# perform the actual rotation and return the image
return cv2.warpAffine(image, M, (nW, nH))
def show(img, ms=0):
""" 显示 """
cv2.imshow('show', img)
h, w = img.shape[:2]
cv2.resizeWindow("show", w, h)
cv2.waitKey(ms)
def _largest_face(dets):
if len(dets) == 1:
return 0
face_areas = [(det.right() - det.left()) * (det.bottom() - det.top()) for det in dets]
largest_area = face_areas[0]
largest_index = 0
for index in range(1, len(dets)):
if face_areas[index] > largest_area:
largest_index = index
largest_area = face_areas[index]
print("largest_face index is {} in {} faces".format(largest_index, len(dets)))
return largest_index
# 从dlib的检测结果抽取姿态估计需要的点坐标
def get_image_points_from_landmark_shape(landmark_shape):
if landmark_shape.num_parts != POINTS_NUM_LANDMARK:
print("ERROR:landmark_shape.num_parts-{}".format(landmark_shape.num_parts))
return -1, None
# 2D image points. If you change the image, you need to change vector
image_points = np.array([
(landmark_shape.part(30).x, landmark_shape.part(30).y), # Nose tip
(landmark_shape.part(8).x, landmark_shape.part(8).y), # Chin
(landmark_shape.part(36).x, landmark_shape.part(36).y), # Left eye left corner
(landmark_shape.part(45).x, landmark_shape.part(45).y), # Right eye right corne
(landmark_shape.part(48).x, landmark_shape.part(48).y), # Left Mouth corner
(landmark_shape.part(54).x, landmark_shape.part(54).y) # Right mouth corner
], dtype="double")
return 0, image_points
# 计算两个点形成的直线角度,注意opencv的Y轴是朝下生长的,采用 y1-y2/x2-x1
# 最终结果在(-180 ~ 180 )
def computerAngle2(x1,y1,x2,y2):
angle = math.atan2((y1 - y2), (x2 - x1))
angle = angle * 180 / math.pi #(-180 ~ 180 )
return angle
# 用dlib检测关键点,返回姿态估计需要的几个点坐标
def detectImage(img,dets,angle):
largest_index = _largest_face(dets)
face_rectangle = dets[largest_index]
landmark_shape = predictor(img, face_rectangle)
return get_image_points_from_landmark_shape(landmark_shape)
def findRotete(image_path):
img = cv2.imread(image_path)
finallyImg = None
finallyAngle = 0
for angle in range(0, 360, 60): # 不停旋转,注意如果每次旋转角度过大,存在所有角度无法识别到人脸的可能
rotateImg = rotate_img(img, angle)
dets = detector(rotateImg, 0)
if (len(dets) > 0):
finallyImg = rotateImg
ret, image_points = detectImage(rotateImg, dets, angle)
if ret != 0:
print('get_image_points failed')
break
for p in image_points:
cv2.circle(rotateImg, (int(p[0]), int(p[1])), 3, (0, 0, 255), -1)
Nose_x = image_points[0][0]
Nose_y = image_points[0][1]
Chin_x = image_points[1][0]
Chin_y = image_points[1][1]
Left_Eye_x = image_points[2][0]
Left_Eye_y = image_points[2][1]
Right_Eye_x = image_points[3][0]
Right_Eye_y = image_points[3][1]
Left_Mouth_x = image_points[4][0]
Left_Mouth_y = image_points[4][1]
Right_Mouth_x = image_points[5][0]
Right_Mouth_y = image_points[5][1]
eyeAngle = computerAngle2(Left_Eye_x, Left_Eye_y, Right_Eye_x, Right_Eye_y)
mouthAngle = computerAngle2(Left_Mouth_x, Left_Mouth_y, Right_Mouth_x, Right_Mouth_y)
nose2chinAngle = computerAngle2(Nose_x, Nose_y, Chin_x, Chin_y)
# 120.8157057517292 120.25643716352927 32.9052429229879
nose2chinAngle += 90 # 假设鼻子到下巴的直线与双眼直线垂直
print(angle, eyeAngle, mouthAngle, nose2chinAngle)
avgAngle = (eyeAngle + mouthAngle + nose2chinAngle) / 3 # 即双眼直线、嘴角直线与鼻子到下巴直线(垂直后)这三条直线的平均角度,后续要将此角度归0,即直线平行于图片
print(avgAngle)
finallyAngle = angle + avgAngle # (angle - (- avgAngle)) avgAngle需要归零,即-avgAngle, 又因为angle 是顺时针旋转角度,avgAngle是逆时针旋转角度,即需要减去(-avgAngle)
if (finallyAngle > 360):
finallyAngle -= 360
if (finallyAngle < - 360):
finallyAngle += 360
print(finallyAngle)
break
show(rotate_img(img, angle), 200)
if (rotateImg is not None):
show(rotateImg, 1000)
finallyImg = rotate_img(img, finallyAngle)
filenames = image_path.split('.')
filename = filenames[0] + "Modify." + filenames[1]
print(filename)
cv2.imwrite(filename, finallyImg)
show(finallyImg, 2000)
dir = "rotateCard"
for s in os.listdir(dir):
image_path = os.path.join(dir, s)
if 'Modify' not in image_path:
findRotete(image_path)
参考来源
opencv人脸检测,旋转处理 - 姜小豆 - 博客园 (cnblogs.com)