自动瘦脸需要两步走,首先人脸关键点检测,第二步做指定区域内的变换
人脸检测,有开源库dlib可以直接使用 我的环境是win10 + python3.6,首先安装dlib.whl(https://pan.baidu.com/s/1-sJ0_YfNhEwAeKblVhUBdw 提取码:27w9),然后下载模型shape_predictor_68_face_landmarks.dat(https://pan.baidu.com/s/1088jBKPSdPsVpRrJk0PgMA 提取码: g7i1) 下载到本地后保存即可。使用dlib的预训练好的模型整体效果还不错,不过对于歪着脸的和带有黑色镜框的图片效果不是太理想,黑色镜框的眼镜对眉毛的定位有误导,歪着脸对脸部轮廓的检测有干扰。
瘦脸算法,局部变形的理论部分主要参考图像处理算法之瘦脸及放大眼睛这篇文章,认真读一下公式并不难的。然后对照公式写代码即可,因为找到了已经写好的代码 我就没从0开始了,直接拿过来用了 感谢瘦脸实现的作者,不过 这个代码直接用还是有问题的,他只做了固定脸两侧的调整,有些照片会出现很怪的效果,我做了调整,从关键点的3到16每隔两个点做一次变形,即从左颧骨到下巴再到右颧骨依次做调整,这样的结果会平滑许多。但是,这个代码的效率很低,如需工程中使用还需要进一步的优化。
import dlib
import cv2
import numpy as np
import math
predictor_path = "G:\\software\\python_tools\\Dlib_FaceDec\\shape_predictor_68_face_landmarks.dat"
# 使用dlib自带的frontal_face_detector作为我们的特征提取器
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor(predictor_path)
def landmark_dec_dlib_fun(img_src):
img_gray = cv2.cvtColor(img_src, cv2.COLOR_BGR2GRAY)
land_marks = []
rects = detector(img_gray, 0)
for i in range(len(rects)):
land_marks_node = np.matrix([[p.x, p.y] for p in predictor(img_gray, rects[i]).parts()])
land_marks.append(land_marks_node)
return land_marks
'''
方法: Interactive Image Warping 局部平移算法
'''
def localTranslationWarp(srcImg, startX, startY, endX, endY, radius):
ddradius = float(radius * radius)
copyImg = np.zeros(srcImg.shape, np.uint8)
copyImg = srcImg.copy()
# 计算公式中的|m-c|^2
ddmc = (endX - startX) * (endX - startX) + (endY - startY) * (endY - startY)
H, W, C = srcImg.shape
for i in range(W):
for j in range(H):
# 计算该点是否在形变圆的范围之内
# 优化,第一步,直接判断是会在(startX,startY)的矩阵框中
if math.fabs(i - startX) > radius and math.fabs(j - startY) > radius:
continue
distance = (i - startX) * (i - startX) + (j - startY) * (j - startY)
if (distance < ddradius):
# 计算出(i,j)坐标的原坐标
# 计算公式中右边平方号里的部分
ratio = (ddradius - distance) / (ddradius - distance + ddmc)
ratio = ratio * ratio
# 映射原位置
UX = i - ratio * (endX - startX)
UY = j - ratio * (endY - startY)
# 根据双线性插值法得到UX,UY的值
value = BilinearInsert(srcImg, UX, UY)
# 改变当前 i ,j的值
copyImg[j, i] = value
return copyImg
# 双线性插值法
def BilinearInsert(src, ux, uy):
w, h, c = src.shape
if c == 3:
x1 = int(ux)
x2 = x1 + 1
y1 = int(uy)
y2 = y1 + 1
part1 = src[y1, x1].astype(np.float) * (float(x2) - ux) * (float(y2) - uy)
part2 = src[y1, x2].astype(np.float) * (ux - float(x1)) * (float(y2) - uy)
part3 = src[y2, x1].astype(np.float) * (float(x2) - ux) * (uy - float(y1))
part4 = src[y2, x2].astype(np.float) * (ux - float(x1)) * (uy - float(y1))
insertValue = part1 + part2 + part3 + part4
return insertValue.astype(np.int8)
def face_thin_auto(src):
landmarks = landmark_dec_dlib_fun(src)
# 如果未检测到人脸关键点,就不进行瘦脸
if len(landmarks) == 0:
return
thin_image = src
landmarks_node = landmarks[0]
endPt = landmarks_node[16]
for index in range(3, 14, 2):
start_landmark = landmarks_node[index]
end_landmark = landmarks_node[index + 2]
r = math.sqrt((start_landmark[0, 0] - end_landmark[0, 0]) * (start_landmark[0, 0] - end_landmark[0, 0]) +
(start_landmark[0, 1] - end_landmark[0, 1]) * (start_landmark[0, 1] - end_landmark[0, 1]))
thin_image = localTranslationWarp(thin_image, start_landmark[0, 0], start_landmark[0, 1], endPt[0, 0], endPt[0, 1], r)
# 显示
# cv2.imshow('thin', thin_image)
cv2.imwrite(r'e:\thin.jpg', thin_image)
def main():
src = cv2.imread(r'e:\1.bmp')
# cv2.imshow('src', src)
face_thin_auto(src)
cv2.waitKey(0)
if __name__ == '__main__':
main()