Python实战:基于OpenCV实现Snapchat风格美颜挂件
本文介绍如何使用Python和OpenCV库实现类似Snapchat的增强现实美颜挂件。通过级联分类器检测人脸、鼻子及眼部特征,结合Alpha通道处理蒙版,实现胡子与眼镜挂件的自动贴合与融合,支持单张图像及连续视频帧处理。
基于 Snapchat 的增强现实
胡子挂件融合
在第一个项目中,我们将尝试在检测到的人脸上叠加一个小胡子特效。实现方式既可以是处理来自摄像头的连续视频帧,也可以是对单张测试图像进行处理。在实际讲解程序的关键步骤之前,先展示应用程序预期输出的结果图像:

项目的第一步是识别图像中的人脸。如上图所示,程序使用青色矩形框标出检测到的人脸位置及大小;随后遍历所有检测到的人脸区域,在这些区域内进一步搜索鼻子,图中用粉红色矩形表示检测到的鼻子位置;在成功检测到鼻子后,程序会根据已计算的鼻子位置和尺寸,调整用于覆盖“胡子”挂件的区域范围,蓝色矩形即表示计算得出的胡须覆盖区域。若处理对象为连续视频帧,则在完成当前帧所有检测到的人脸处理后,程序将继续分析下一帧。
根据上述流程,程序需首先检测图像中的人脸和鼻子。为此,我们创建了两个分类器,分别用于人脸检测和鼻子检测:
# 用于人脸和鼻子检测的级联分类器
face_cascade = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")
nose_cascade = cv2.CascadeClassifier("haarcascade_mcs_nose.xml")
分类器创建完成后,下一步是使用 cv2.detectMultiScale() 函数来检测图像中的目标对象。cv2.detectMultiScale() 函数能够检测输入灰度图像中不同尺寸的对象,并以矩形列表的形式返回检测结果。例如,在检测人脸时使用的代码如下:
faces = face_cascade.detectMultiScale(gray, 1.3, 5)
接下来,程序遍历所有检测到的人脸,并在其中尝试检测鼻子:
# 遍历检测到的人脸
for (x, y, w, h) in faces:
# 根据检测到的面大小创建感兴趣区域(ROI)
roi_gray = gray[y:y + h, x:x + w]
roi_color = frame[y:y + h, x:x + w]
# 在检测到的人脸内检测鼻子
noses = nose_cascade.detectMultiScale(roi_gray)
在检测到鼻子后,程序遍历所有鼻子检测结果,并计算用于覆盖“胡子”挂件的区域。在过滤掉错误的鼻子位置后,将“胡子”挂件依据先前计算的区域覆盖到图像上:
for (nx, ny, nw, nh) in noses:
# 计算将被“胡子”挂件覆盖的区域坐标
x1 = int(nx - nw / 2)
x2 = int(nx + nw / 2 + nw)
y1 = int(ny + nh / 2 + nh / 8)
y2 = int(ny + nh + nh / 4 + nh / 6)
if x1 < 0 or x2 < 0 or x2 > w or y2 > h:
continue
# 计算将被“胡子”挂件覆盖的区域尺寸
img_moustache_res_width = int(x2 - x1)
img_moustache_res_height = int(y2 - y1)
# 调整掩膜大小,使其与放置“胡子”挂件的区域相等
mask = cv2.resize(img_moustache_mask, (img_moustache_res_width, img_moustache_res_height))
# 翻转掩膜
mask_inv = cv2.bitwise_not(mask)
# 将“胡子”挂件调整为所需区域
img = cv2.resize(img_moustache, (img_moustache_res_width, img_moustache_res_height))
# 获取原始图像的ROI
roi = roi_color[y1:y2, x1:x2]
# 创建ROI背景和ROI前景
roi_bakground = cv2.bitwise_and(roi, roi, mask=mask_inv)
roi_foreground = cv2.bitwise_and(img, img, mask=mask)
# 获取结果
res = cv2.add(roi_bakground, roi_foreground)
# 将res置于原始图像中
roi_color[y1:y2, x1:x2] = res
break
上述程序的关键在于处理 img_mustache_mask 图像。该图像是利用待融合“胡子”图像的 Alpha 通道生成的,借助此图像,程序可以仅绘制叠加图像的前景部分。基于融合图像的 alpha 通道生成的“胡子”蒙版如下:
img_moustache = cv2.imread('moustache.png', -1)
img_moustache_mask = img_moustache[:, :, 3]
cv2.imshow("img moustache mask", img_moustache_mask)
最终获得的胡子蒙版效果如下所示:

增强现实融合“胡子”挂件后的最终结果如下所示:

此处不再单独讲解视频帧的处理过程,因为其逻辑与单张图像的处理方法一致。在后续的完整代码中,将提供处理连续视频帧以实现增强现实的完整代码。
完整代码
import cv2
# 用于人脸和鼻子检测的级联分类器
face_cascade = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")
nose_cascade = cv2.CascadeClassifier("haarcascade_mcs_nose.xml")
# 加载胡子图像
img_moustache = cv2.imread('moustache.png', -1)
# 创建胡子蒙版
img_moustache_mask = img_moustache[:, :, 3]
# 将胡子图像转换为 BGR 图像
img_moustache = img_moustache[:, :, 0:3]
# 创建 VideoCapture 对象
video_capture = cv2.VideoCapture(0)
while True:
# 从 VideoCapture 对象捕获帧
ret, frame = video_capture.read()
# 将 frame 转换为灰度图像
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 检测人脸
faces = face_cascade.detectMultiScale(gray, 1.3, 5)
# 迭代检测到的人脸
for (x, y, w, h) in faces:
# 根据检测到的人脸大小创建ROI
roi_gray = gray[y:y + h, x:x + w]
roi_color = frame[y:y + h, x:x + w]
# 在检测到的人脸中检测鼻子
noses = nose_cascade.detectMultiScale(roi_gray)
for (nx, ny, nw, nh) in noses:
# 计算将放置 “胡子” 挂件的坐标
x1 = int(nx - nw / 2)
x2 = int(nx + nw / 2 + nw)
y1 = int(ny + nh / 2 + nh / 8)
y2 = int(ny + nh + nh / 4 + nh / 6)
if x1 < 0 or x2 < 0 or x2 > w or y2 > h:
continue
# 计算“胡子”挂件区域的尺寸
img_moustache_res_width = int(x2 - x1)
img_moustache_res_height = int(y2 - y1)
# 根据挂件区域缩放“胡子”蒙版
mask = cv2.resize(img_moustache_mask, (img_moustache_res_width, img_moustache_res_height))
# 翻转蒙版
mask_inv = cv2.bitwise_not(mask)
# 缩放“胡子”挂件
img = cv2.resize(img_moustache, (img_moustache_res_width, img_moustache_res_height))
# 从原始图像中获取ROI
roi = roi_color[y1:y2, x1:x2]
# 创建ROI前景和背景
roi_bakground = cv2.bitwise_and(roi, roi, mask=mask_inv)
roi_foreground = cv2.bitwise_and(img, img, mask=mask)
# roi_bakground 与 roi_foreground 加和获取结果
res = cv2.add(roi_bakground, roi_foreground)
roi_color[y1:y2, x1:x2] = res
break
# 显示结果
cv2.imshow('Snapchat-based OpenCV moustache overlay', frame)
# 按下 “q” 键退出
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 释放资源
video_capture.release()
cv2.destroyAllWindows()
眼镜挂件融合
在这一实战程序中,我们将学习如何在检测到的人脸眼部区域融合眼镜挂件。首先通过下图查看程序的预期结果:

同样,为了实现眼镜挂件的融合,首先需要利用眼睛检测器来识别图像中的眼睛:
eyepair_cascade= cv2.CascadeClassifier("haarcascade_mcs_eyepair_big.xml")
上图中,青色矩形表示检测到的人脸在图像中的位置和大小;粉红色矩形表示图像中检测到的眼睛;黄色矩形则表示眼镜将被覆盖的位置,该位置是根据眼睛所在区域的位置和大小计算得出的。
同时,为融合的眼镜挂件图像增加一定的透明度,以使其看起来更加逼真。眼镜图像的蒙版如下所示:

融合后的结果图像如下图所示:

完整代码
基本代码结构与上一个示例相同,因此不再赘述。需要注意的是“眼镜”挂件融合区域的计算逻辑。
import cv2
face_cascade = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")
eyepair_cascade = cv2.CascadeClassifier("haarcascade_mcs_eyepair_big.xml")
img_glasses = cv2.imread('glasses.png', -1)
img_glasses_mask = img_glasses[:, :, 3]
img_glasses = img_glasses[:, :, 0:3]
video_capture = cv2.VideoCapture(0)
while True:
ret, frame = video_capture.read()
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 检测人脸
faces = face_cascade.detectMultiScale(gray, 1.3, 5)
for (x, y, w, h) in faces:
roi_gray = gray[y:y + h, x:x + w]
roi_color = frame[y:y + h, x:x + w]
# 在检测到的人脸中检测眼睛
eyepairs = eyepair_cascade.detectMultiScale(roi_gray)
for (ex, ey, ew, eh) in eyepairs:
# 计算“眼睛”挂件放置的坐标
x1 = int(ex - ew / 10)
x2 = int((ex + ew) + ew / 10)
y1 = int(ey)
y2 = int(ey + eh + eh / 2)
if x1 < 0 or x2 < 0 or x2 > w or y2 > h:
continue
# 计算“眼睛”挂件放置区域大小
img_glasses_res_width = int(x2 - x1)
img_glasses_res_height = int(y2 - y1)
mask = cv2.resize(img_glasses_mask, (img_glasses_res_width, img_glasses_res_height))
mask_inv = cv2.bitwise_not(mask)
img = cv2.resize(img_glasses, (img_glasses_res_width, img_glasses_res_height))
roi = roi_color[y1:y2, x1:x2]
roi_bakground = cv2.bitwise_and(roi, roi, mask=mask_inv)
roi_foreground = cv2.bitwise_and(img, img, mask=mask)
res = cv2.add(roi_bakground, roi_foreground):
roi_color[y1:y2, x1:x2] = res
break
# 显示结果画面
cv2.imshow('Snapchat-based OpenCV glasses filter', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
video_capture.release()
cv2.destroyAllWindows()
以上就是Python实战之基于OpenCV的美颜挂件制作的详细内容,更多关于Python OpenCV的内容请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













