不用PS,一键生成熊猫头表情包(二)

摘要

技术栈如下:

代码演示:python
面部关键点识别:dlib
图像处理:pillow,opencv

环境的配置,可自行查找。尤其是dlib的安装,具体安装过程我也忘记了。

说明:代码演示不是完整的,因为掺杂着业务代码,关键步骤的代码都会贴出来。
在这里插入图片描述

人脸关键点

(图片来源网络,如有侵权请联系删除)

抠出人脸关键信息,使用的是dlib人脸68点检测。

在这里插入图片描述
演示人脸,使用杨洋的图片(不要问为什么,因为这张图片处理效果好😂)
在这里插入图片描述

关键点选择

这里并没有选用全部的人脸关键点,目的是缩小范围,方便后续往模板上贴图,选用点如下:
在这里插入图片描述

import dlib
import cv2 as cv

img = cv.imread('读入图片')
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')
res = detector(gray, 1)

shape = predictor(img, res[0])
face = shape.parts()

points = []
point1 = [int((face[0].x + face[17].x) / 2), int((face[0].y + face[17].y) / 2)]
point2 = [int((face[1].x + face[36].x) / 2), int((face[1].y + face[36].y) / 2)]
point3 = [int((face[5].x + face[48].x) / 2), int((face[5].y + face[48].y) / 2)]
point4 = [int((face[6].x + face[59].x) / 2), int((face[6].y + face[59].y) / 2)]
point5 = [int((face[7].x + face[58].x) / 2), int((face[7].y + face[58].y) / 2)]
point6 = [int((face[8].x + face[57].x) / 2), int((face[8].y + face[57].y) / 2)]
point7 = [int((face[9].x + face[56].x) / 2), int((face[9].y + face[56].y) / 2)]
point8 = [int((face[10].x + face[55].x) / 2), int((face[10].y + face[55].y) / 2)]
point9 = [int((face[11].x + face[54].x) / 2), int((face[11].y + face[54].y) / 2)]
point10 = [int((face[15].x + face[45].x) / 2), int((face[15].y + face[45].y) / 2)]
point11 = [int((face[16].x + face[26].x) / 2), int((face[16].y + face[26].y) / 2)]
for pos in face[17:27]:
    points.append([pos.x, pos.y - half])
points.append(point11)
points.append(point10)
。。。。。。

68点人脸关键点检测模型:
链接:https://pan.baidu.com/s/1_kBo6zoYacZdeqgISbkUew
提取码:2sok

脸部提取

关键点拿到后,把人脸提取出来。

import numpy as np

face_pos = np.array(points, np.int32)
mask = np.zeros(img.shape, np.uint8)
mask = cv.polylines(mask, [face_pos], True, (255, 255, 255))
mask = cv.fillPoly(mask, [face_pos], (255, 255, 255))
mask = cv.bitwise_and(mask, img)

脸部旋转

旋转人脸,使人脸与水平线垂直。

通过计算鼻子27和30两个点连线与水平线之间的夹角,来旋转人脸,示意图如下:
在这里插入图片描述

def cv2pil(img):
    # opencv对象转换为pil对象
    return Image.fromarray(cv.cvtColor(img, cv.COLOR_RGB2BGR))

def cal_ang(point_1, point_2, point_3):
    # 三点计算夹角,返回角度对应各点的角度
    a = math.sqrt((point_2[0]-point_3[0])*(point_2[0]-point_3[0])+(point_2[1]-point_3[1])*(point_2[1] - point_3[1]))
    b = math.sqrt((point_1[0]-point_3[0])*(point_1[0]-point_3[0])+(point_1[1]-point_3[1])*(point_1[1] - point_3[1]))
    c = math.sqrt((point_1[0]-point_2[0])*(point_1[0]-point_2[0])+(point_1[1]-point_2[1])*(point_1[1]-point_2[1]))
    A = math.degrees(math.acos((a*a-b*b-c*c)/(-2*b*c)))
    B = math.degrees(math.acos((b*b-a*a-c*c)/(-2*a*c)))
    C = math.degrees(math.acos((c*c-a*a-b*b)/(-2*a*b)))
    return math.ceil(A), math.ceil(B), math.ceil(C)

mask = cv2pil(mask)
angle = cal_ang((face[30].x, face[30].y), (face[30].x + 10, face[30].y), (face[27].x, face[27].y))
ang = (90 + angle[0]) if face[30].y < face[27].y else (90 - angle[0])
mask = mask.rotate(ang, expand=True)

图像转为png格式

将图片转换为png图片。该步骤的目的是去除背景,方便后续贴图。如果不去除背景,那么贴图的时候是一个矩形,边角可能超出模板的线条,有违和感。

def pil2cv(img):
    # pil对象转换为opencv对象
    return cv.cvtColor(np.asarray(img), cv.COLOR_RGB2BGR)

mask = pil2cv(mask)
gray = cv.cvtColor(mask, cv.COLOR_BGR2GRAY)
binary = cv.threshold(gray, 1, 255, cv.THRESH_BINARY)[1]
contours = cv.findContours(binary, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)[1]
max_con = sorted(contours, key=cv.contourArea, reverse=True)[0]
x, y, w, h = cv.boundingRect(max_con)
face = mask[y:(y + h), x:(x + w)]
face = cv.cvtColor(face, cv.COLOR_BGR2GRAY)
face = cv.cvtColor(face, cv.COLOR_GRAY2BGR)

face = cv2pil(face)
f_gray = face.convert('L')
face = face.convert('RGBA')
f_data = face.getdata()
g_data = f_gray.getdata()

new_data = []
for i in range(g_data.size[0] * g_data.size[1]):
    if g_data[i] == 0:
        new_data.append((f_data[i][0], f_data[i][1], f_data[i][2], 0))
    else:
        new_data.append((f_data[i][0], f_data[i][1], f_data[i][2], 255))
face.putdata(new_data)

处理结果如下:
在这里插入图片描述

调色

表情包就是要仅保留面部的眉毛、眼睛、鼻子、嘴巴等关键部位的线条信息。这里通过增加对比度和亮度,剔除不需要的面部信息。

from PIL import  ImageEnhance

contrast = ImageEnhance.Contrast(face)
face = contrast.enhance(3.6)
bright = ImageEnhance.Brightness(face)
face = bright.enhance(1.2)

鉴于每张图片的亮度、对比度不同,所以3.6和1.2这两个参数只能处理部分图片,而对于过曝或曝光不足的图片,则需要动态调整参数(可以作为后续的优化点)。

经过该步骤处理后:
在这里插入图片描述

贴图

模板需要提前准备好以下参数:

  • 人脸贴图位置、宽高(x, y, w, h)
  • 人脸需要的旋转角度(angle)

在这里插入图片描述

e_x, e_y, e_w, e_h, e_angle = (0, 0, 0, 0, 0)   # 模板需要准备的参数

face = face.rotate(e_angle, expand=True)
fw, fh = face.size
template = Image.open('模板图片')
face = face.resize((int(fw / (fh / e_h)), e_h), Image.ANTIALIAS)
fw, fh = face.size
face = face.crop((int((fw - e_w) / 2), 0, int((fw - e_w) / 2) + e_w, fh))
template.paste(face, (e_x, e_y, e_x + e_w, e_y + e_h), mask=face.split()[-1])

在这里插入图片描述




更多功能,或者想体验一下,可以扫下方二维码:


在这里插入图片描述


主业前端程序猿一枚。图片处理方面,作为业余爱好。如有错误,请各位大佬轻喷,谢谢!!😂

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晴天晒小麦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值