Hub人脸关键点检测实现耳朵和鼻子贴纸
busyboxs 发布于2020-04 浏览:2558 回复:3
0
收藏
最后编辑于2022-04

AI Studio 项目地址: https://aistudio.baidu.com/aistudio/projectdetail/402824

项目不是特别新颖,只是为了好玩。 Just for fun! 有任何问题还请见谅。

效果

实现过程

首先说一下大概的实现过程:

  1. 读取人脸图像
  2. 检测 68 个人脸关键点,检测人脸框
  3. 读取贴纸图像(4 通道 png 图像)
  4. 计算左眉毛最左边的点和右眉毛最右边的点,通过两点计算角度,作为旋转角度
  5. 将贴纸图像旋转上一步获取的角度,同时得到旋转矩阵
  6. 在贴纸上去一个点作为参考点(这里取得是贴纸中鼻子的点),用旋转矩阵计算出旋转贴纸中点对应的位置
  7. 通过人脸框的宽度将贴纸图像进行尺寸修改,同时计算修改尺寸参考后点的位置
  8. 将参考点与人脸的鼻子中的一个点对应进行融合得到最终结果

代码

首先导入依赖库,定义两个全局变量,LABELS 用于表示人脸的每个部分,COLORS 为了画关键点用于区分

import paddlehub as hub
from random import randrange
import math
import numpy as np
import cv2

def get_random_color():
    return randrange(0, 255, 1), randrange(10, 255, 1), randrange(10, 255, 1)


LABELS = ['chin', 'left_eyebrow', 'right_eyebrow', 'nose_bridge',
          'nose_tip', 'left_eye', 'right_eye', 'top_lip', 'bottom_lip']
COLORS = [get_random_color() for _ in LABELS]

以下函数通过调用 Paddlehub 的接口实现人脸关键点的检测,并返回 68 个人脸关键点数据

def get_landmarks(img):
    module = hub.Module(name="face_landmark_localization")
    result = module.keypoint_detection(images=[img])
    landmarks = result[0]['data'][0]
    return landmarks

 以下函数通过调用 Paddlehub 的接口实现人脸边框的检测,返回人脸框左上角的点坐标和边框的宽和高

def get_face_rectangle(img):
    face_detector = hub.Module(name="ultra_light_fast_generic_face_detector_1mb_320")
    result = face_detector.face_detection(images=[img])
    x1 = int(result[0]['data'][0]['left'])
    y1 = int(result[0]['data'][0]['top'])
    x2 = int(result[0]['data'][0]['right'])
    y2 = int(result[0]['data'][0]['bottom'])
    return x1, y1, x2 - x1, y2 - y1

为了方便使用,以下代码将 68 个人脸关键点分成了人脸的几个部分

def face_landmarks(face_image, location_of_face=None):
    landmarks = get_landmarks(face_image)
    landmarks_as_tuples = [[(int(p[0]), int(p[1])) for p in landmarks]]
    return [{
        "chin": points[0:17],
        "left_eyebrow": points[17:22],
        "right_eyebrow": points[22:27],
        "nose_bridge": points[27:31],
        "nose_tip": points[31:36],
        "left_eye": points[36:42],
        "right_eye": points[42:48],
        "top_lip": points[48:55] + [points[64]] + [points[63]] + [points[62]] + [points[61]] + [points[60]],
        "bottom_lip": points[54:60] + [points[48]] + [points[60]] +
                      [points[67]] + [points[66]] + [points[65]] + [points[64]]
    } for points in landmarks_as_tuples]

以下代码用于计算两个点之间的角度,在这里,我们主要使用左眉毛最左的点和右眉毛最右的点来计算

def calculate_angle(point1, point2):
    x1, x2, y1, y2 = point1[0], point2[0], point1[1], point2[1]
    return 180 / math.pi * math.atan((float(y2 - y1)) / (x2 - x1))

下面函数将图像旋转一定的角度,并返回旋转后的图像和对应的旋转矩阵,旋转矩阵用于计算参考点在旋转图像中位置

 

def rotate_bound(image, angle):
    (h, w) = image.shape[:2]
    (cX, cY) = (w / 2, h / 2)

    M = cv2.getRotationMatrix2D((cX, cY), -angle, 1.0)
    cos = np.abs(M[0, 0])
    sin = np.abs(M[0, 1])

    nW = int((h * sin) + (w * cos))
    nH = int((h * cos) + (w * sin))

    M[0, 2] += (nW / 2) - cX
    M[1, 2] += (nH / 2) - cY

    return cv2.warpAffine(image, M, (nW, nH)), M

 下面的代码就是具体的贴贴纸的代码,这里有两种类型的贴纸,但是具体样式是一样的,所以公用一个代码。 其中参考点需要根据具体的贴纸图像进行设置(我这里使用的是 windows 画图工具来查看的点坐标信息)

def add_sticker_ear_and_nose(img, sticker_name):
    stickers = {'cat': 'cat', 'mouse': 'mouse'}
    nose_center = {'cat': [180, 400], 'mouse': [208, 313]}
    sticker_img = f'stickers/{stickers[sticker_name]}.png'
    sticker = cv2.imread(sticker_img, -1)
    landmarks = face_landmarks(img)
    angle = calculate_angle(landmarks[0]['left_eyebrow'][0], landmarks[0]['right_eyebrow'][-1])
    nose_tip_center = nose_center[sticker_name]  # nose center of sticker
    rotated, M = rotate_bound(sticker, angle)
    tip_center_rotate = np.dot(M, np.array([[nose_tip_center[0]], [nose_tip_center[1]], [1]]))
    sticker_h, sticker_w,  _ = rotated.shape
    x, y, w, h = get_face_rectangle(img)
    dv = w / sticker_w
    distance_x, distance_y = int(tip_center_rotate[0] * dv), int(tip_center_rotate[1] * dv)
    rotated = cv2.resize(rotated, (0, 0), fx=dv, fy=dv)
    sticker_h, sticker_w, _ = rotated.shape
    y_top_left = landmarks[0]['nose_tip'][2][1] - distance_y
    x_top_left = landmarks[0]['nose_tip'][2][0] - distance_x
    start = 0
    if y_top_left < 0:
        sticker_h = sticker_h + y_top_left
        start = -y_top_left
        y_top_left = 0

    for chanel in range(3):
        img[y_top_left:y_top_left + sticker_h, x_top_left:x_top_left + sticker_w, chanel] = \
            rotated[start:, :, chanel] * (rotated[start:, :, 3] / 255.0) + \
            img[y_top_left:y_top_left + sticker_h, x_top_left:x_top_left + sticker_w, chanel] \
            * (1.0 - rotated[start:, :, 3] / 255.0)

    return img

 最后就是执行函数,贴上贴纸,具体效果见文章开头。

image_file = 'face/img1.jpeg'
image = cv2.imread(image_file)
image = add_sticker_ear_and_nose(image, 'cat')
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
# cv2.imwrite('result/002.png', image)
plt.figure()
plt.imshow(image) 
plt.axis('off') 
plt.show()

 

image_file = 'face/img1.jpeg'
image = cv2.imread(image_file)
image = add_sticker_ear_and_nose(image, 'mouse')
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
# cv2.imwrite('result/002.png', image)
plt.figure()
plt.imshow(image) 
plt.axis('off') 
plt.show()

完整代码可以在我的 github 中获取 : https://github.com/busyboxs/BaiduAIAPI/tree/master/sticker_cat

 

收藏
点赞
0
个赞
共3条回复 最后由用户已被禁言回复于2022-04
#4189******30回复于2020-04
#3 189******30回复
结果是"立体"的,贴纸的效果参考了人脸的角度。这也是为什么监测点有那么多吧。不错,不错
展开

原来是用cv旋转的。

0
#3189******30回复于2020-04

结果是"立体"的,贴纸的效果参考了人脸的角度。这也是为什么监测点有那么多吧。不错,不错

0
#2189******30回复于2020-04

这些关键点是paddlehub生成的。那些贴着是有标注的信息吧。

0
TOP
切换版块