随着圣诞节的到来,节日气氛越来越浓。街上随处可见挂着装饰品的圣诞树,不少小伙伴头上也戴着一顶红牛角圣诞帽。
往年这个时候,很多P图软件都会推出给头像加圣诞帽的功能,甚至一年大家@微信官方都可以给自己的微信头像加圣诞帽。
作为一个学习技术的人,写一个程序来实现这个功能,做一个软件,当然是一件很酷的事情。
今天给大家分享一下如何使用创建一个自动为头像添加圣诞帽的软件。
如果不想看实现,可以跳到文末获取软件。
事情
我们为头像添加圣诞帽的总体思路如下:
基于以上思路,我们为这个软件做的关键词是:
实施
看完以上的思路帽子后面没有调节器,相信大家脑海中对制作这个软件的过程有了一个大致的框架。我们的实现主要分为图片制作、GUI界面、打包三个部分。
准备
首先列出本次软件制作过程中需要用到的一些包模块:
pip – 系统模块 pip
提醒:安装dlib的时候会有很多坑和困难。一般安装的时候需要上网搜索并报错,才能找到解决办法。确保您在安装一次后不想再次尝试。
为图像制作准备圣诞帽
我们需要准备一张圣诞帽图片,最好是png格式的,因为我们可以直接使用alpha通道作为png图片的遮罩。如果是jpg图片,需要先转换成png格式的图片。注意这里的转换不只是改变后缀名,那是行不通的。
我们使用的圣诞帽如下:
为了能够对RGB通道头像图片进行操作,我们需要将圣诞帽帽图片分离为RGB通道图片和alpha通道图片:
r,g,b,a = cv2.split(hat_img)
rgb_hat = cv2.merge((r,g,b))
cv2.imwrite("hat_alpha.jpg",a)
分离后得到的alpha通道图像如下:
人脸识别
我从我的百宝箱里选了一张萌萌哒的图片作为程序的测试图。
大家注意了,因为我们需要做人脸识别,然后自动添加帽子,所以你选择的图片必须是真人的正面照片,否则人脸识别不出来,不知道在哪里添加圣诞帽。
下面我们使用dlib的 face检测器进行人脸检测,使用dlib提供的模型提取人脸的5个关键点。代码如下:
# dlib人脸关键点检测器
predictor_path = "shape_predictor_5_face_landmarks.dat"
predictor = dlib.shape_predictor(predictor_path)
# dlib正脸检测器
detector = dlib.get_frontal_face_detector()
# 正脸检测
dets = detector(img, 1)
# 如果检测到人脸
if len(dets)>0:
for d in dets:
x,y,w,h = d.left(),d.top(), d.right()-d.left(), d.bottom()-d.top()
# x,y,w,h = faceRect
cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2,8,0)
# 关键点检测,5个关键点
shape = predictor(img, d)
for point in shape.parts():
cv2.circle(img,(point.x,point.y),3,color=(0,255,0))
cv2.imshow("image",img)
cv2.waitKey()
打印图片效果如下:
这张照片是不是很眼熟?网上很多人脸识别图片都是这样的。
调整帽子的大小
我们选取两个眼角的点,找到中心作为帽子所在x方向的参考坐标。
然后我们根据人脸检测检测到的人脸大小来调整帽子的大小,使帽子的大小合适。
看到这里,你应该明白,我们头像图片中的人的脸越积极,我们做出的效果就越好。
# 选取左右眼眼角的点
point1 = shape.part(0)
point2 = shape.part(2)
# 求两点中心
eyes_center = ((point1.x+point2.x)//2,(point1.y+point2.y)//2)
# 根据人脸大小调整帽子大小
factor = 1.5
resized_hat_h = int(round(rgb_hat.shape[0]*w/rgb_hat.shape[1]*factor))
resized_hat_w = int(round(rgb_hat.shape[1]*w/rgb_hat.shape[1]*factor))
if resized_hat_h > y:
resized_hat_h = y-1
# 根据人脸大小调整帽子大小
resized_hat = cv2.resize(rgb_hat,(resized_hat_w,resized_hat_h))
帽子区域处理
我们首先使用帽子的 alpha 通道作为遮罩:
mask = cv2.resize(a,(resized_hat_w,resized_hat_h))
mask_inv = cv2.bitwise_not(mask)
接下来,从人像图中移除需要添加帽子的区域:
# 帽子相对与人脸框上线的偏移量
dh = 0
dw = 0
# 原图ROI
# bg_roi = img[y+dh-resized_hat_h:y+dh, x+dw:x+dw+resized_hat_w]
bg_roi = img[y+dh-resized_hat_h:y+dh,(eyes_center[0]-resized_hat_w//3):(eyes_center[0]+resized_hat_w//3*2)]
# 原图ROI中提取放帽子的区域
bg_roi = bg_roi.astype(float)
mask_inv = cv2.merge((mask_inv,mask_inv,mask_inv))
alpha = mask_inv.astype(float)/255
# 相乘之前保证两者大小一致(可能会由于四舍五入原因不一致)
alpha = cv2.resize(alpha,(bg_roi.shape[1],bg_roi.shape[0]))
# print("alpha size: ",alpha.shape)
# print("bg_roi size: ",bg_roi.shape)
bg = cv2.multiply(alpha, bg_roi)
bg = bg.astype('uint8')
提取出来的效果图如下:
接下来,我们提取圣诞帽的区域:
hat = cv2.bitwise_and(resized_hat,resized_hat,mask = mask)
提取出来的效果图如下:
封顶
图像处理的最后一步是,就是把提取出来的圣诞帽区域和图像中的提取区域相加,然后放到原始图像中。这里需要注意的是,添加前两者的大小要一致。
# 相加之前保证两者大小一致(可能会由于四舍五入原因不一致)
hat = cv2.resize(hat,(bg_roi.shape[1],bg_roi.shape[0]))
# 两个ROI区域相加
add_hat = cv2.add(bg,hat)
# cv2.imshow("add_hat",add_hat)
# 把添加好帽子的区域放回原图
img[y+dh-resized_hat_h:y+dh,(eyes_center[0]-resized_hat_w//3):(eyes_center[0]+resized_hat_w//3*2)] = add_hat
最后我们得到的效果图如下:
图形界面
我们先看效果图:
然后看看这部分的实现代码:
import PySimpleGUI as sg
import os.path
import cv2
file_list_column = [
[sg.Submit('生成', key='Go', size=(15, 1)), sg.Cancel('退出', key='Cancel', size=(15, 1))],
[
sg.Text("图片位置(选择文件夹)"),
sg.In(size=(25, 1), enable_events=True, key="-FOLDER-"),
sg.FolderBrowse('浏览'),
],
[
sg.Listbox(
values=[], enable_events=True, size=(40, 20), key="-FILE LIST-"
)
]
]
image_viewer_column = [
[sg.Text("从左边图片列表中选择一张图片:")],
[sg.Image(key="-IMAGE-")]
]
layout = [
[
sg.Column(file_list_column),
sg.VSeperator(),
sg.Column(image_viewer_column),
]
]
window = sg.Window("人像添加圣诞帽软件", layout)
filename = ''
while True:
event, values = window.read()
if event == "Cancel" or event == sg.WIN_CLOSED:
break
if event == "-FOLDER-":
folder = values["-FOLDER-"]
try:
file_list = os.listdir(folder)
except:
file_list = []
fnames = [
f
for f in file_list
if os.path.isfile(os.path.join(folder, f))
and f.lower().endswith((".jpg", ".png"))
]
window["-FILE LIST-"].update(fnames)
elif event == "-FILE LIST-":
try:
filename = os.path.join(values["-FOLDER-"], values["-FILE LIST-"][0])
if filename.endswith('.jpg'):
im = cv2.imread(filename)
cv2.imwrite(filename.replace('jpg', 'png'), im)
window["-IMAGE-"].update(filename=filename.replace('jpg', 'png'))
except Exception as e:
print(e)
elif event == "Go":
try:
# output = add_hat(filename)
# 展示效果
# cv2.imshow("output",output)
# cv2.waitKey(0)
# cv2.imwrite("output.png",output)
# print(output)
window["-IMAGE-"].update(filename='output.png')
except:
print('OMG!添加失败了!')
cv2.destroyAllWindows()
这里我选择框架来做,比较简单。界面分为左右两部分,左边是两个按钮(OK和)加上一个文件夹选择器,加上一个图片文件列表;右边是图片展示框。
选择左侧的文件夹后,下面会列出文件夹中包含.png和.jpg的图片列表。点击图片列表中的图片,右侧会显示您选择的图片。这张选中的图片是我们稍后需要添加圣诞帽的那张。
这里需要注意的是,图片显示默认只支持png格式,所以我在显示的时候做了判断。如果是jpg格式的图片,我会用cv2转成png格式。 ,然后显示它。
至此,我们的关键步骤就完成了。下一步是整合我们的两部分代码。其实也很简单。当用户在GUI界面点击“生成”按钮时,后台会接收到图片的路径,并传递给我们的图片处理函数。处理后图像保存在文件夹中,并更新GUI界面。右图显示。
最终运行效果:
打包软件
我们仍然使用熟悉的模块来打包软件,代码打包成可执行的exe格式。
首先下载我们需要的模块包:
点
然后在命令行输入打包命令:
.py
这个.py是我们写的程序。
打包比较费时间,耐心等待即可。打包完成后,会在我们的代码目录下生成三个文件夹:
我们只需要关注 dist. dist文件夹下面是一个子文件夹,然后进去就可以找到我们的.exe文件了。由于我们的程序运行有两个依赖文件,我们的圣诞帽图像和我们的人脸识别训练集,所以我们需要将这两个文件放到 EXE 文件所在的文件夹中。
双击 .exe 文件现在可以正常工作了。
总结
本文从一个实际需求出发帽子后面没有调节器,讲解一个头像加圣诞帽软件的诞生过程。与之前的一些小应用相比,涉及的知识点较多,可能会有些复杂。部分知识点限于篇幅不做详细讲解,可以私下补充。
暂无评论内容