原理
通过不停地旋转图片,直到dlib能够识别到人脸;通过dlib找到眼角、嘴角、鼻子、下巴特征点,假设鼻子到下巴的直线与双眼直线、嘴角直线垂直,找到脸部角度,结合已旋转角度,计算出最终图像旋转角度。
效果
代码
import dlibimport cv2import numpy as npimport mathimport osdetector = dlib.get_frontal_face_detector()predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")POINTS_NUM_LANDMARK = 68def 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)
dlib【正面人脸检测】【特征点检测】【旋转】_dlib检测正脸_ya鸡给给的博客-CSDN博客
使用opencv和dlib进行人脸姿态估计(python)_dlib 人脸角度_yuanlulu的博客-CSDN博客