神龙,神龙,请给我一顶圣诞帽,我和微信官方已经没有爱了。

昨天就想把这个弄出来,结果白天太懒,晚上出去玩,今天眼睁睁地看着有人发了类似文章。但是,嘛,都弄了一半了,就弄完吧,实现方法也有些差别。

从前天起,朋友圈陆陆续续开始出现“给我一顶圣诞帽@微信官方”的信息,自己也被涮了一把。

当时想了半天,微信官方肯定是先把用户的头像提取出来,之后用计算机视觉技术进行脸部识别,找出脸部的坐标,并且判断角度,之后加上帽子。是的,就是这样 !不由感叹,现在腾讯真牛啊,这么快就把这些都用上了。

结果,发现一切都特么是假的...

不过既然有想法了,那不妨自己弄一弄呗,作为调包小能手,只要用现成的库就好了。

实现想法

首先先说说想法,先把整个过程分成几步,这样子每个部分写好不同的函数就好了。

  1. 最开始我们要做的当然是找到脸,并且得到脸部的坐标。
  1. 然后根据脸部的坐标,调整帽子的大小和位置,加入图片。
  2. 加入不同种类的帽子,完工!

什么都别说,先把人给我找出来!

最初想到脸部检测,当然是各种深度学习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图技术呢!!!

github代码: https://github.com/andy-yangz/gimme_a_Santa_hat

推荐阅读更多精彩内容