昨天就想把这个弄出来,结果白天太懒,晚上出去玩,今天眼睁睁地看着有人发了类似文章。但是,嘛,都弄了一半了,就弄完吧,实现方法也有些差别。
从前天起,朋友圈陆陆续续开始出现“给我一顶圣诞帽@微信官方”的信息,自己也被涮了一把。
当时想了半天,微信官方肯定是先把用户的头像提取出来,之后用计算机视觉技术进行脸部识别,找出脸部的坐标,并且判断角度,之后加上帽子。是的,就是这样 !不由感叹,现在腾讯真牛啊,这么快就把这些都用上了。
结果,发现一切都特么是假的...
不过既然有想法了,那不妨自己弄一弄呗,作为调包小能手,只要用现成的库就好了。
实现想法
首先先说说想法,先把整个过程分成几步,这样子每个部分写好不同的函数就好了。
- 最开始我们要做的当然是找到脸,并且得到脸部的坐标。
- 然后根据脸部的坐标,调整帽子的大小和位置,加入图片。
- 加入不同种类的帽子,完工!
什么都别说,先把人给我找出来!
最初想到脸部检测,当然是各种深度学习CNN什么的,但是结果发现太麻烦了。自己训练的话需要比较久,用已有模型也比较大,跑起来麻烦。
之后突然看到OpenCV直接有脸部检测模块,就拿来直接用吧。懒人第一原则,能不动手就不动手,信奉拿来主义。
OpenCV 有两种常用的脸部检测器,一个是Haar 分类器,一个是LBP 分类器,两个都不是深度学习脸部检测器。
Haar的话,主要是利用人脸的长相特征,然后用一定形状的滤镜来检测这种特征。比如比较有特点的眼睛部分。
而LBP的话,将图片分为很多小的区域,然后用一定方法进行编码,最后产生一个特征向量。过程比较复杂,有兴趣自己仔细看看。
这两个都需要事先在许多图片上进行训练,幸运的是OpenCV已经提供了一些训练好的模型。那么这两个哪个更好呢,一般来说Haar的准确率更高,而LBP的速度更快。
来敲代码吧,首先召唤各种小帮手们。
import cv2 # 计算机视觉库
import matplotlib.pyplot as plt # 画图
import numpy as np
import random
from os import listdir
from PIL import Image, ImageDraw # 图像处理
%matplotlib inline
脸部识别函数代码如下。
def face_detection(path, method='haar'):
"""
Face detection funciton.
Input: Photo path, and detection methods (Haar or LBP)
Output: The coordinates of faces
"""
# Load Photos
photo = cv2.imread(path)
# Face detector can only use gray scale img
gray_photo = cv2.cvtColor(photo, cv2.COLOR_BGR2GRAY)
首先读入图片,并且转换成灰度图片。因为模型是在灰度图片上训练的。
# Load Classifier, we detect both frontal faces and profile faces
if method.lower() == 'haar':
front_detector = cv2.CascadeClassifier('models/haarcascade_frontalface_alt.xml')
side_detector = cv2.CascadeClassifier('models/haarcascade_profileface.xml')
elif method.lower() == 'lbp':
front_detector = cv2.CascadeClassifier('models/lbpcascade_frontalface_improved.xml')
side_detector = cv2.CascadeClassifier('models/lbpcascade_profileface.xml')
else:
print('No such method! Only provide haar and lbp now.')
然后加载训练好的正脸和侧脸识别器(看不到脸,就没有帽子XD)。
# Detect Faces
faces = front_detector.detectMultiScale(gray_photo, scaleFactor=1.1, minNeighbors=5)
side_faces = side_detector.detectMultiScale(gray_photo, scaleFactor=1.1, minNeighbors=5)
if len(faces) < 1:
faces = side_faces
elif len(side_faces) >= 1:
np.append(faces, side_faces, axis=0)
return faces
最后,分别用两个分类器检测脸,再把两个结果合起来。
这样子就完成第一步,有了脸的坐标了。
送你一顶圣诞帽
然后就是根据脸的坐标来把帽子贴上去了,首先第一个问题是,因为帽子的尺寸和图片尺寸可能并不吻合,所以如果直接戴的话,会出现大小不一致的问题,而且位置也比较奇怪。比如说这样子。
所以就需要调整帽子尺寸和位置。代码如下,分成两个函数。
# Adjust the size of hats
def resize_hat(face, hat, scale=1.5):
w = int(face[2] * scale)
h = int(face[3] * scale)
new_hat = new_hat.resize((w, h))
return newhat, w, h
首先调尺寸,我就随便看着头的大小调了调,帽子大小可以根据scale
参数来调整,默认1.5倍脸部的长宽大小。
# Add one hat to a face
def add_one_hat(image, hat_img, face, x_offset_rate=2.8, y_offset_rate=0.95):
# x_offset_rate bigger then the hat will move right
# y_offset_rate bigger then the hat will move above
hat, w, h = resize_hat(face, hat_img)
y = int(face[1] - face[3]*0.95)
x = int(face[0] + face[2]/2 - w/2.8)
image.paste(hat, (x, y), hat)
给一张脸戴帽子,根据多次运行结果,选定了x和y的位置偏移值,不满意默认值也可以自己调偏移参数。
有了上面两个函数之后,只要给我一张脸我就能给它戴帽子了,之后只要把检测到的脸一一输进去就可以了。
# Loop faces add hat to each detected face
def add_hats(img_path, hat_path, faces):
image = Image.open(img_path)
hat_img = Image.open(hat_path)
for face in faces:
add_one_hat(image, hat_img, face)
return image
通过for循环一张张读脸,然后加帽子,于是就完成了基本的功能了,超级简单是不是。嘿。
但是只有一款帽子,不开心,圣诞节怎么能没有绿色的帽子呢。
增加帽子款式,随机选择
这个就非常简单了,首先把帽子的路径都检测出来,之后再在add_hats
函数里面加入随机选择帽子的代码。
# Get paths of all hat
hat_dir = 'photos/hats/'
hat_paths = [hat_dir+f for f in listdir(hat_dir) if f.endswith('png')]
获得所有帽子的路径。
def add_hats(img_path, hats_path, faces):
image = Image.open(img_path)
# Random select one hat from hats path
for face in faces:
hat_path = random.choice(hats_path)
hat_img = Image.open(hat_path)
add_one_hat(image, hat_img, face)
return image
修改add_hats
,然后就完成啦!
喜闻乐见的测试时间
之后就到了喜闻乐见的测试时间啦,让我们来看看效果怎么样吧。
先拿张多人照片测试一下。
嗯,不错不错,都是别出来了。再来看看我喜欢的四重奏。
效果喜人,原来不光有带帽子功能,还能够精确地识别出有没有被NTR,厉害厉害。
好,那再来测试一下人数上限。嘿,接招。
嗯.... 怎么才这么点,难道说只有颜值大于8的人才能带上圣诞帽。不对,我的分类器怎么会这么肤浅呢!
那么调调face_detection
里面detectMultiScale
的参数看看,因为scaleFactor
参数影响了图片中远近人脸的检测,把它调大一点。
Bingo ! 一下就多了好多绿帽子。
恩,看来只要不是太极端的情况,还是很好用的。那么现在来测试一下微信头像吧,就拿老爹的头像来测试一下先。当当!
效果不一般,还很贴心地给后面毛爷爷像加了顶圣诞帽。发给老爹,老爹表示很满意给了个红包。
嗯,还能怎么玩呢。
对了!现在微信不都快成了动物园了吗,拿喵星人来测试一下。当当!
噫,啥也没有,又测试了几只猫居然还不行,看来现在的模型只能检测人脸。那会不会有猫脸检测模型呢,跑去OpenCV的代码库找了找,居然很贴心的还真有。修改了一下face_detection
函数,具体参考文后github的链接。
再一测试,当当!
简直完美!
尾声
下午拿着电脑去给朋友展示自动戴圣诞帽系统,先传输图片,然后输入模型,保存图片,传到手机,整个过程居然只用了10分钟。我只问你,你可见过这么高级的系统吗!!!
朋友默默拿过手机,下载了一款图片编辑app,搜索圣诞帽,贴图,保存。整个过程花了2分钟。
...............................................................................................
劳资才不稀罕这么low逼的P图技术呢!!!