给图片中的人脸添加特效(帽子)
人脸特效流程和原理拆解
1数据准备
首先要准备帽子和人脸照片,加载到项目的目录中
2人脸检测
首先调用OpenCV中的库函数对照片进行人脸检测,得到人脸矩形的各参数值,从而可以得到帽子要加在什么位置上去,由于人脸照片和帽子照片的大小位置的不同,所以当我们要将两个照片进行拼接的时候就得取人脸的上部分即额头的那一部分(如下图)
之后按照这个尺寸重新规划帽子的尺寸。
3灰度化帽子
因为我们要想得到两张照片拼接的样子,这就必须得用到OpenCV的另一个对图像处理的函数按像素相或的操作,所以我们需要准备出两张照片:1彩色帽子,黑色背景,2黑色帽子人额头和彩色背景
所以我们可以通过灰度二值化来减小问题的规模,因为RGB在这里是没有用的,保留反而会增加问题的复杂度,所以我们将帽子图片变成灰白的。
4阈值化帽子
灰度化操作之后就要将帽子变成黑白两色类似于0和1(255),全0为黑。我们不妨规定当灰度值大于10认定为是白色。
5取反帽子
顾名思义,白色帽子黑色背景的照片变成黑色帽子白色背景,为什么要这样做呢,别急看下一步。
6将黑帽子和白背景与人脸像素相与
利用OpenCV的按像素与操作将上一步得到的图片和之前的展示的人额头图片相与即可得到下幅图
7将白帽子黑背景与彩色帽子相与
将之前步骤获得的白色帽子黑背景的图片与相同比例的彩色帽子照片相与即可得到下面的图
有些伙伴可能会说这张图和一开始准备的帽子图有什么区别吗,唯一的区别就是这张图的尺寸是和人的额头那一种截取的照片是完美匹配的。
8将上两步的结果相或
这一步也能解决上一步中小伙伴存在的困惑,只有两张图尺寸完全一样才可以相或,最终的图片效果为下图所示
之后将这一部分图像直接在相应的位置赋值给原来的人脸图片即可得到最终的样式图。
视频特效制作
这里跟上面的主要区别就是从对图片的处理转移到视频的处理,方式很简单,就是把视频里的某一些等间隔的帧进行取出进行类似于图片处理的操作即可,这里直接上代码,注释也标得很清楚。
# -*- coding:utf-8 -*-
"""
日期:2021年10月24日
"""
import cv2 # 图像处理的opencv库
import math # 数学运算库
import dlib # 加载dlib算法模块
import argparse # 加载解析模块
def hat_face(opt):
# 加载dlib自带的人脸检测模型
detector = dlib.get_frontal_face_detector()
# 读取帽子样式的图片
img_hat = cv2.imread(opt.hat_path)
# 使用opencv中的VideoCapture函数,读取视频
cap = cv2.VideoCapture(opt.video_path)
# 初始化定义frame_id,便于后面跳帧
frame_id = 0
# 判断cap是否读取成功
while cap.isOpened():
# 因为视频采集,每秒可能会采集N帧图片,因此使用read函数,逐帧读取图片
# ret 返回True或Fasle,表示是否读取到图片
# frame 返回读取的图片信息
ret, frame = cap.read()
# 如果ok为false,则采集结束
if not ret:
# 打印输出,提示信息
print("Camera cap over!")
continue
# frame_id加1,便于跳帧
frame_id += 1
# 如果frame_id除以10,不等于0,则不断循环,只有等于0时,才进行到下面的显示步骤,这样可以达到跳帧的效果
# 因为在算法处理中,比如一秒有25帧图像,为了提升项目速度,没有必要对每一帧都进行算法处理
# 注意:这里的10可以自行设置,如觉得跳帧太慢,可以设置大一些,比如15
if not int(frame_id) % 10 == 0: continue
# 对读取的原始图像,进行人脸检测
faceRects = detector(frame)
# 如果len(faces)>0,则说明图片上存在人脸
if len(faceRects) > 0:
# 图片中可能存在多张人脸,依次遍历输出每张人脸的位置信息,并在特定位置,添加兔子帽子特效
for box_info in faceRects:
# dlib检测出的人脸位置信息,为rectangle类型,所以可以通过left()、top()、right()、bottom()提取人脸框左上角的x0,y0,右下角的x1,y1。
x0, y0, width_face, height_face = box_info.left(), box_info.top(), box_info.right() - box_info.left(), box_info.bottom() - box_info.top()
# 获得帽子图片的高height_hat,宽width_hat,便于后面控制帽子显示的大小
height_hat, width_hat = img_hat.shape[0], img_hat.shape[1]
# 将帽子的高度变成,适合检测到的脸型的大小
imgComposeSizeH = int(height_hat / width_hat * width_face)
# 因为帽子在脸部上方,当脸部靠近视频顶部,为了保证帽子能显示出来,帽子的高度默认为脸部到视频上方的距离
if imgComposeSizeH > (y0 - 20):
imgComposeSizeH = (y0 - 20)
# 将帽子的图片归一化到适合的宽和高大小,宽度为人脸的宽度,高度为上面调整后的帽子高度
imgComposeSize = cv2.resize(img_hat, (width_face, imgComposeSizeH), interpolation=cv2.INTER_NEAREST)
# 为了实现在人脸上方的额头,添加兔子帽子特效,只需要将兔子帽子贴到额头处即可
# 注意:y0是人脸检测框左上方顶点的y坐标,y0-20,则差不多定位到额头处,再减去imgComposeSizeH,调整后兔子帽子的高度
# 即从这个位置开始贴合兔子帽子,可以实现人脸帽子特效
top = (y0 - 20 - imgComposeSizeH)
# 当人脸太靠近图片上方,通过前一行的计算,有可能为负数,所以当出现这样的情况时,top设置为0
if top <= 0:
top = 0
# 获得调整尺寸后,帽子新的高height_hat_new,新的宽width_hat_new
height_hat_new, width_hat_new = imgComposeSize.shape[0], imgComposeSize.shape[1]
# 将额头上方,对应的帽子区域截取出来
# 注意:通常从大图中抠取小图的方式,比如待截取小图的坐标是[x0,y0,x1,y1],则使用img[y0:y1,x0:x1即可]即可。
small_img_hat = frame[top:top + height_hat_new, x0:x0 + width_hat_new]
# 如需查看,上一行的效果,可将cv2.imwrite前面的#取消,运行后,保存到本目录查看
# cv2.imwrite("small_img_hat.jpg",small_img_hat)
# 将帽子图像,从RGB彩色图像转换为灰度图像
small_img_hat_gray = cv2.cvtColor(imgComposeSize, cv2.COLOR_RGB2GRAY)
# 如需查看,上一行的效果,可将cv2.imwrite前面的#取消,运行后,保存到本目录查看
# cv2.imwrite("small_img_hat_gray.jpg", small_img_hat_gray)
# 阈值化处理,将10<像素<255区间的图像抠出来
# 因为背景为黑色,所以阈值设置10到255,即可将帽子区域提取出来
ret, mask_hat = cv2.threshold(small_img_hat_gray, 10, 255, cv2.THRESH_BINARY)
# 如需查看,上一行的效果,可将cv2.imwrite前面的#取消,运行后,保存到本目录查看
# cv2.imwrite("mask_hat.jpg", mask_hat)
# 使用取反操作,将帽子的图像从黑色0变为白色255,将白色255变成黑色0
mask_hat_inv = cv2.bitwise_not(mask_hat)
# 如需查看,上一行的效果,可将cv2.imwrite前面的#取消,运行后,保存到本目录查看
# cv2.imwrite("mask_hat_inv.jpg", mask_hat_inv)
# 使用按位与操作,将抠出的帽子区域和取反后的帽子图像相加
img1_bg = cv2.bitwise_and(small_img_hat, small_img_hat, mask=mask_hat_inv)
# 如需查看,上一行的效果,可将cv2.imwrite前面的#取消,运行后,保存到本目录查看
# cv2.imwrite("img1_bg.jpg", img1_bg)
# 使用按位与操作,将像素值在[10,255]之间的帽子RGB区域抠取出来
img2_fg = cv2.bitwise_and(imgComposeSize, imgComposeSize, mask=mask_hat)
# 如需查看,上一行的效果,可将cv2.imwrite前面的#取消,运行后,保存到本目录查看
# cv2.imwrite("img2_fg.jpg", img2_fg)
# 将img1_bg和img2_fg相加,等于戴了帽子的区域图像
dst = cv2.add(img1_bg, img2_fg)
# 如需查看,上一行的效果,可将cv2.imwrite前面的#取消,运行后,保存到本目录查看
# cv2.imwrite("dst.jpg", dst)
# 将带了帽子的局部区域,贴合到原始图像上
frame[top:top + height_hat_new, x0:x0 + width_hat_new] = dst
# 如需查看,上一行的效果,可将cv2.imwrite前面的#取消,运行后,保存到本目录查看
# cv2.imwrite("img.jpg", img)
# 很多时候,图片的长宽太大,屏幕显示不全
# 为了便于查看图片,可以将图片的大小,缩放到某一尺寸,比如(800,600),即宽800像素,高600像素
img = cv2.resize(frame, (800, 600))
# 显示图片
cv2.imshow("image", img)
# 显示图片停顿的时间,如果是0,则一直显示。如果是100,则显示100ms
cv2.waitKey(10)
if __name__ == '__main__':
# 新建一个解析器
parser = argparse.ArgumentParser()
# 为解析器添加选项,比如video_path,添加视频地址。(在default后面,添加需要读取的视频路径)
# 注意:如果需要抓取本电脑的摄像头采集的实时视频,可以设置default=0(注意不是default='0')
# 注意:不同的视频,宽高大小可能不同,大家可以根据自己的视频比例,调试前面的的cv2.resize中的数值,修改显示窗口的大小。
parser.add_argument('--video_path', default='video.mov', help='path of cap video')
# 为解析器添加选项,比如hat_path,添加帽子图片的地址,可以更换其他样式的帽子尝试。(在default后面,添加需要读取帽子图片路径)
parser.add_argument('--hat_path', default="maozi.png", help='path of hat image')
# 解析选项的参数
opt = parser.parse_args()
# 调用hat_face函数,对视频中的人脸添加帽子的特效
hat_face(opt)
下面附上某一帧的样式图
心得体会
又是本周最后一作,又是在熄灯之后结束,时间过得真快啊,我们人生只有一次,如何在有限的人生中选择正确的适合我们自己的事并且努力将它们做出自己的风格,留下属于自己的痕迹,这始终是需要我们思考的。规划和执行力始终是这个时代最重要的一par.
Goodbye, my dear friend!
Best wishes!
Hope everything work out!
salute!!!
关注我,点赞投币和收藏,AI学习和生活不迷路!