前几天在浏览器上看到推送的文章用人脸识别做表情包。当时并不为意,没过多久就想弄一下了。无奈浏览器开了无痕找不着历史记录,上网搜也搜不到。CSDN里有过半都是简单地把两张图拼一起(你别说,经过我这么一找发现CSDN里面很多文章都是一模一样的,一个字都没动。真是我有句粗口不知当讲不当讲)。
思路是这样的,首先用脸部识别把脸的轮廓描下来,然后生成脸的掩膜。接着利用掩膜把脸抠出来放到熊猫头上
首先先导入包
import face_recognition
import numpy as np
import cv2
from PIL import Image, ImageDraw, ImageFont
这里要插一句,导入的face_recognition需要依赖dlib库,dlib这玩意儿超难装。我是下了cmake然后自己按照教程手动编译了一次才装上的。如果没有cmake+boost环境不建议pip install这两个库,网上很多跟你说把轮子下下来装就可以的都是骗人的,归根结底还是要有C++环境来编译这两个库才能用。不信装上以后按Ctrl+鼠标点击你会发现它的源码全是C++。
OK,扯远了。 然后就是导入带抓取的表情了。
image = face_recognition.load_image_file(r"myWife.jpg")
恩用了我们家小老婆的丑照所以打码了(/手动再见)。不过做脸部识别的时候不能打码哦,不然上哪找脸去
接着我们使用face_recognition.face_landmarks来提取脸部特征,并用下巴,两个眉毛把脸的轮廓描出来
face_line = list()#用于记录脸部轮廓信息,之后做掩膜要用
for face_landmarks in face_landmarks_list:
#打印此图像中眉毛和脸型组成的轮廓
facial_features = [
'chin',
'left_eyebrow',
'right_eyebrow',
]
for facial_feature in facial_features:
print("The {} in this face has the following points: {}".format(facial_feature, face_landmarks[facial_feature]))
#让我们在图像中描绘出每个人脸特征!
pil_image = Image.fromarray(image)
d = ImageDraw.Draw(pil_image)
chin = face_landmarks['chin']
left_eyebrow = face_landmarks['left_eyebrow']
right_eyebrow = face_landmarks['right_eyebrow']
chin.reverse()
list_all = left_eyebrow + right_eyebrow + chin + [left_eyebrow[0]]
face_line.append(list_all)
d.line(list_all, width=5)
pil_image.save("Facial_contour.jpg")
把脸部轮廓描出来就要做一个掩膜用来把感兴趣区域(ROI)选出来。这个掩膜用cv2的水漫填充把不感兴趣的地方填充起来,再用对掩膜进行腐蚀膨胀出来,让掩膜框出来的地方更符合要求
#新建一个和原图一样大小的全白图片,用ImageDraw在上面勾出人脸轮廓,作为掩膜的模板
mask = np.ones(image.shape, dtype=np.uint8)*255
mask = Image.fromarray(mask)
q = ImageDraw.Draw(mask)
q.line(face_line[0], width=5, fill=(0, 0, 0))
mask.save("mask.jpg")#将图片写出是为了交给OpenCV处理
#生成掩膜
mask = cv2.imread('mask.jpg')
h, w = mask.shape[:2] # 读取图像的宽和高
mask_flood = np.zeros([h + 2, w + 2], np.uint8) # 新建图像矩阵 +2是官方函数要求
cv2.floodFill(mask, mask_flood, (75, 75), (0, 0, 0))#这里使用OpenCV的水漫填充,把轮廓外部涂成黑色,内部为白色
#用一个5*5的卷积核对掩膜进行闭运算,去掉噪声
#erosion的迭代次数2次是我试出来的,感觉效果比较好
kernel = np.ones((5, 5), np.uint8)
erosion = cv2.erode(mask, kernel, iterations=2)
dilation = cv2.dilate(erosion, kernel, iterations=1)
mask = dilation
轮廓做出来是这样的
然后就要把ROI框出来了,这里我直接将原图位置对应掩膜的像素涂成黑的,而ROI保持原状。掩膜就是下面那样,大家自己想象一下有个人头在白色区域
#重新读入原图,框出RIO,交给OpenCV处理
image = cv2.imread("myWife.jpg")
image[mask == 0] = 0
接着就是对表情的处理了,怎么样把表情和谐地提出来是一个大问题。如果提太过了很可能会缺很多部分,但是处理少了又会让整个表情黑成一坨。这里主要是用膨胀腐蚀来消除噪声以及强化表情部分。
#将处理过的图片变为灰度图
GrayImage = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
#二值化处理
ret, image = cv2.threshold(GrayImage, 80, 255, cv2.THRESH_BINARY)
#再来一次水漫填充,这次把轮廓之外的地方变成白色
h, w = image.shape[:2]
mask_flood = np.zeros([h + 2, w + 2], np.uint8)
cv2.floodFill(image, mask_flood, (1, 1), (255, 255, 255))
#用一个2*2的卷积核对RIO进行多次腐蚀膨胀处理,去掉噪声,这里进行多少次膨胀腐蚀要结合图片效果来进行
kernel = np.ones((2,2),np.uint8)
dilation = cv2.dilate(image,kernel,iterations=2)
erosion = cv2.erode(dilation,kernel,iterations=1)
dilation = cv2.dilate(image,kernel,iterations=1)
image = cv2.erode(dilation,kernel,iterations=1)
#这是表情需要截取的部分,不同图这个地方的参数不同
image = image[130:410, 170:505]
cv2.imwrite("last.png",image)#这里输出图片是为了给Image做处理
提出来的表情就是这样的了。
之后,就是要对背景下手了,也就是熊猫头(/括弧笑)。
首先要先确定哪一部分是要放入表情的地方,我下的这个图的插入位置在box中,box的四个值分别是[左上角顶点坐标的x,左上角顶点坐标的y,右下角顶点坐标的x,右下角顶点坐标的y]。
box = (160, 155, 465, 405)#背景图要被替换的部分
base_img = Image.open('background.png')
image = Image.open('last.png')
image = image.resize((box[2] - box[0], box[3] - box[1]))#缩放表情,贴入的表情必须和背景被替换的地方大小相同
base_img.paste(image, box)
base_img.show()
base_img.save('out.png')#这里输出图片是为了给cv2做处理
这时候输出的图就是这样的,ps痕迹非常明显。要是是这样的话我是拿不出手的。这样我们就要继续对这个图进行处理了。这时候又是我们的膨胀腐蚀大法。
#这是为了让图片看起来更自然,而且本身表情就是黑白的,所以对图片再进行一次二值化处理
image = cv2.imread(r'out.png')
GrayImage = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
ret,image = cv2.threshold(GrayImage,85,255,cv2.THRESH_BINARY)
#这里继续膨胀腐蚀,让图片更好看一点
erosion = cv2.erode(image,kernel,iterations=1)
dilation = cv2.dilate(erosion,kernel,iterations=2)
erosion = cv2.erode(dilation,kernel,iterations=1)
image = erosion
cv2.imwrite("last_out.jpg", image)
cv2.waitKey(0)
到这里这个表情包就算是完成了。后面什么加文字吖什么的CSDN有很多一模一样的文章都会说怎么做,我就不做了。然后就是发图给小老婆看看了。小老婆喊我拿着键盘去不知道要干什么呢。