最后编辑于2022-04
AI Studio 项目地址: https://aistudio.baidu.com/aistudio/projectdetail/402824
项目不是特别新颖,只是为了好玩。 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 中获取 : https://github.com/busyboxs/BaiduAIAPI/tree/master/sticker_cat
收藏
点赞
0
个赞
请登录后评论
TOP
切换版块
原来是用cv旋转的。
结果是"立体"的,贴纸的效果参考了人脸的角度。这也是为什么监测点有那么多吧。不错,不错
这些关键点是paddlehub生成的。那些贴着是有标注的信息吧。