当前位置: 首页 > 科技观察

好机会,我用Python给女同事头戴了个面具!

时间:2023-03-17 23:15:10 科技观察

突如其来的新型冠状病毒疫情让全国人民在春节期间陷入恐慌,出门必须戴口罩已成为共识。图片来自Pexels疫情防控期间,我们要做的就是听从政府的命令:“少出门、戴口罩、勤洗手……”,乖乖待在家里就是在做贡献抗击疫情。看到有朋友请设计同学帮忙给自己的头像贴上面具。作为一名技术人员,我认为有这种需求的人一定更多。何不开发一个简单的程序来实现这个需求,也算是帮助设计小姐姐减轻了工作量。于是花了点时间,写了一个命令行工具,叫face-mask[1],可以很方便的给图片中的人像贴上面具,而且面具的方向和大小都是自适应脸型的~使用①安装face-maskpipinstallface-mask确保Python版本为3.6及以上。②使用face-mask直接指定图片路径给图片中的人像蒙上面具,生成新图片(附加-with-mask后缀):face-mask/path/to/face/picture通过指定--显示选项,也可以使用默认的图片查看器打开新生成的图片:face-mask/path/to/face/picture--show③效果给一个人戴上面具,效果如下:多人,效果如下图:给动漫角色戴上面具:实现思路要达到上面的效果,我们应该怎么做呢?让我们这样想:首先,识别鼻子(nose_bridge)和脸部轮廓(chin)。通过人脸轮廓确定人脸左点(chin_left_point)、人脸底点(chin_bottom_point)和人脸右点(chin_right_point)。口罩尺寸的高度和中心线由鼻子和面部底部决定。将左右面罩平分两部分,调整左边面罩的大小,宽度为脸部左侧点到中心线的距离;调整右侧面具的大小,宽度为面部右侧点到中心线的距离;将左右蒙版合并为一个新蒙版。将新蒙版旋转中心线相对于y轴的旋转角度。将新蒙版放在原图合适的位置。关于人脸识别,可以使用face_recognition[2]库进行识别。关于图像处理,可以使用Pillow[3]库进行处理。代码有了思路之后,实现起来就比较容易了。但是,熟悉库和图像变换计算可能需要一些时间。详细代码请参考以下链接。这里只讲解核心步骤:https://github.com/Prodesire/face-mask人脸识别:importface_recognitionface_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给对应的头像贴上mask。获取鼻子和脸颊的特征点:importnumpyasnpnose_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]通过上面的代码,我们得到:A面部点:nose_point代表面部左侧点:chin_left_point代表面部右侧点:chin_right_point代表面部底部点:chin_bottom_point分割、缩放和合并蒙版:fromPILimportImage_face_img=Image.fromarray(face_image_np)_mask_img=Image.open('/path/to/mask/picture')#splitmaskandresizewidth=_mask_img.widthheight=_mask_img.heightwidth_ratio=1.2new_height=int(np.linalg.norm(nose_v-chin_bottom_v))#leftmask_left_img=_mask_img.crop((0,0,宽度//2,高度))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))#rightmask_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))#mergemasksize=(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)上述代码主要做了以下工作:将mask的左右两边分成两部分,调整左边mask的大小。宽度是面部左侧点到中心线的距离*宽度系数1.2。调整右侧面罩的大小,宽度为面部右侧点到中心线的距离*宽度因子1.2。将左右蒙版合并为一个新蒙版。get_distance_from_point_to_line用于获取点到线的距离,具体实现见源码。width_ratio为宽度系数,用于适当扩展mask。原因是我们是根据脸颊的宽度来计算口罩的宽度,但是口罩是留在耳朵上的,所以实际的宽度应该更宽一些。旋转蒙版并将其放在原始图像的适当位置:#rotatemaskangle=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)#calculatemasklocationcenter_x=(nose_point[0]+chin_bottom_point[0])//2center_y=(nose_point[1]+chin_bottom_point[1])//2offset=mask_img.width//2-mask_left_img.widthradian=angle*np.pi/180box_x=center_x+int(offset*np.cos(radian))-rotated_mask_img.width//2box_y=center_y+int(offset*np.sin(radian))-rotated_mask_img.height//2#addmask_face_img.paste(mask_img,(box_x,box_y),mask_img)上面的代码主要做了以下工作:旋转新的mask,角度为中心线相对于y轴的旋转角度。计算应放置面罩的坐标。将新蒙版放置在原始图像的计算坐标处。最后是将新图片保存到本地路径,代码就不再显示了。总结我们可以借助face_recognition库轻松识别人像,然后根据脸颊的宽度和鼻梁的位置计算出口罩的大小、方向和位置,最后生成佩戴图片一张面具。整个过程并不复杂,只是坐标计算要格外小心,所以我们制作了一个短小精悍的“自动戴上口罩”程序!