項目不是特別新穎,只是爲了好玩。 Just for fun! 有任何問題還請見諒。
效果
實現過程
首先說一下大概的實現過程:
- 讀取人臉圖像
- 檢測 68 個人臉關鍵點,檢測人臉框
- 讀取貼紙圖像(4 通道 png 圖像)
- 計算左眉毛最左邊的點和右眉毛最右邊的點,通過兩點計算角度,作爲旋轉角度
- 將貼紙圖像旋轉上一步獲取的角度,同時得到旋轉矩陣
- 在貼紙上取一個點作爲參考點(這裏取得是貼紙中鼻子的點),用旋轉矩陣計算出旋轉貼紙中點對應的位置
- 通過人臉框的寬度將貼紙圖像進行尺寸修改,同時計算修改尺寸參考後點的位置
- 將參考點與人臉的鼻子中的一個點對應進行融合得到最終結果
代碼
首先導入依賴庫,定義兩個全局變量,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 中獲取