使用 PaddleHub 人臉關鍵點檢測實現貼紙 02 —— 耳朵和鼻子

AI Studio 項目地址

項目不是特別新穎,只是爲了好玩。 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 中獲取

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章