15

好机会,我用Python给女同事头像戴上口罩!

 4 years ago
source link: http://developer.51cto.com/art/202002/611071.htm
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

突如其来的新型冠状病毒让全国人民春节过的诚惶诚恐,出门必须带口罩已经达成共识。

AryYrmE.jpg!web

图片来自 Pexels

疫情防控期间,我们大家要做的就是遵从政府号令:“少出门,戴口罩、勤洗手......”,乖乖宅家就是为抗击疫情做贡献。

看到一些朋友叫设计同学帮忙给自己的头像戴上口罩,作为技术人,心想一定还有更多人有这样的诉求,不如开发一个简单的程序来实现这个需求,也算是帮助设计小姐姐减少工作量。

于是我花了些时间,写了一个叫做 face-mask[1] 的命令行工具,能够轻松的给图片中的人像戴上口罩,而且口罩的方向和大小都是适应人脸的哦~

使用

①安装 face-mask

pip install face-mask 

确保 Python 版本在 3.6 及以上。

②使用 face-mask

直接指定图片路径即可为图片中的人像戴上口罩,并会生成一个新的图片(额外有 -with-mask 后缀):

face-mask /path/to/face/picture 

通过指定 --show 选项,还可以使用默认图片查看器打开新生成的图片:

face-mask /path/to/face/picture --show 

③效果

给一个人戴上口罩,效果如下图:

6VBvE3A.jpg!web

给多个人戴上口罩,效果如下图:

bYjaAfN.jpg!web

给动漫人物戴上口罩:

yqMvIrr.jpg!web

实现

思路

要想实现上面的效果,我们应该怎么做?不妨这么想:

  • 首先是识别出人的鼻子(nose_bridge)和脸轮廓(chin)。
  • 通过脸轮廓确定出脸左点(chin_left_point)、脸底点(chin_bottom_point)和脸右点(chin_right_point)。
  • 由鼻子和脸底点确定口罩大小的高度、中心线。
  • 将口罩左右平均分为两个部分,调整左口罩大小,宽度为脸左点到中心线的距离;调整右口罩大小,宽度为脸右点到中心线的距离;合并左右口罩为新口罩。
  • 旋转新口罩,角度为中心线相对于 y 轴的旋转角。
  • 将新口罩放在原图适当位置。

关于人脸识别,可以使用 face_recognition[2] 库进行识别。关于图像处理,可以使用 Pillow[3] 库进行处理。

代码

有了思路之后,实现就是件相对轻松的事情。不过对库的熟悉和图片的变换计算可能要花些时间。

详细的代码请参考如下链接,这里仅说明下最核心的步骤:

https://github.com/Prodesire/face-mask

人脸识别:

import face_recognition 
 
face_image_np = face_recognition.load_image_file('/path/to/face/picture') 
face_landmarks = face_recognition.face_landmarks(face_image_np) 

借助 face_recognition 库可以轻松的识别出人像,最终得到的 face_landmarks 是一个列表,里面的每个 face_landmark 都表示一个人像数据。

face_landmark 是一个字典,其中的键表示人像特征,值表示该特征的点的列表。比如:

  • 键 nose_bridge 表示鼻梁
  • 键 chin 表示脸颊

我们需要根据每个 face_landmark,给对应的头像戴上口罩。

获得鼻子和脸颊的特征点:

import numpy as np 
 
nose_bridge = face_landmark['nose_bridge'] 
nose_point = nose_bridge[len(nose_bridge) * 1 // 4] 
nose_v = np.array(nose_point) 
 
chin = face_landmark['chin'] 
chin_len = len(chin) 
chin_bottom_point = chin[chin_len // 2] 
chin_bottom_v = np.array(chin_bottom_point) 
chin_left_point = chin[chin_len // 8] 
chin_right_point = chin[chin_len * 7 // 8] 

通过上述代码,我们获得了:

  • 表示上鼻梁的一个点:nose_point
  • 表示脸左点:chin_left_point
  • 表示脸右点:chin_right_point
  • 表示脸底点:chin_bottom_point

拆分、缩放和合并口罩:

from PIL import Image 
 
_face_img = Image.fromarray(face_image_np) 
_mask_img = Image.open('/path/to/mask/picture') 
 
# split mask and resize 
width = _mask_img.width 
height = _mask_img.height 
width_ratio = 1.2 
new_height = int(np.linalg.norm(nose_v - chin_bottom_v)) 
 
# left 
mask_left_img = _mask_img.crop((0, 0, width // 2, height)) 
mask_left_width = get_distance_from_point_to_line(chin_left_point, nose_point, chin_bottom_point) 
mask_left_width = int(mask_left_width * width_ratio) 
mask_left_img = mask_left_img.resize((mask_left_width, new_height)) 
 
# right 
mask_right_img = _mask_img.crop((width // 2, 0, width, height)) 
mask_right_width = get_distance_from_point_to_line(chin_right_point, nose_point, chin_bottom_point) 
mask_right_width = int(mask_right_width * width_ratio) 
mask_right_img = mask_right_img.resize((mask_right_width, new_height)) 
 
# merge mask 
size = (mask_left_img.width + mask_right_img.width, new_height) 
mask_img = Image.new('RGBA', size) 
mask_img.paste(mask_left_img, (0, 0), mask_left_img) 
mask_img.paste(mask_right_img, (mask_left_img.width, 0), mask_right_img) 

上述代码主要做了如下内容:

  • 将口罩左右平均分为两个部分。
  • 调整左口罩大小,宽度为脸左点到中心线的距离*宽度系数 1.2。
  • 调整右口罩大小,宽度为脸右点到中心线的距离*宽度系数 1.2。
  • 合并左右口罩为新口罩。

get_distance_from_point_to_line 用来获取一个点到一条线的距离,具体实现可看源代码。

width_ratio 是宽度系数,用来适当扩大口罩。原因我们是根据脸颊的宽度计算口罩的宽度,但口罩是待在耳朵上的,真实宽度应该要更宽。

旋转口罩、并放到原图适当位置:

# rotate mask 
angle = np.arctan2(chin_bottom_point[1] - nose_point[1], chin_bottom_point[0] - nose_point[0]) 
rotated_mask_img = mask_img.rotate(angle, expand=True) 
 
# calculate mask location 
center_x = (nose_point[0] + chin_bottom_point[0]) // 2 
center_y = (nose_point[1] + chin_bottom_point[1]) // 2 
 
offset = mask_img.width // 2 - mask_left_img.width 
radian = angle * np.pi / 180 
box_x = center_x + int(offset * np.cos(radian)) - rotated_mask_img.width // 2 
box_y = center_y + int(offset * np.sin(radian)) - rotated_mask_img.height // 2 
 
# add mask 
_face_img.paste(mask_img, (box_x, box_y), mask_img) 

上述代码主要做了如下内容:

  • 旋转新口罩,角度为中心线相对于 y 轴的旋转角。
  • 计算口罩应该放置的坐标。
  • 将新口罩放在原图的计算出的坐标下。

最后就是将新图片保存到本地路径,代码不再展示。

总结

我们借助 face_recognition 库可以轻松的识别出人像,然后根据脸颊的宽度和鼻梁位置计算出口罩的大小、方向和位置,并最终生成出戴上口罩的图片。

整个过程并不复杂,但在坐标计算上要格外小心,如此,我们便打造了一个短小精悍的“自动戴上口罩”程序!

z2Ebuyi.gif


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK