云美集 > 杂谈 > 正文

​简单又神奇的面部识别指南,让你不再对韩国女星“脸盲”

2025-01-16 19:25 来源:云美集 点击:

简单又神奇的面部识别指南,让你不再对韩国女星“脸盲”

全文共8587字,预计学习时长20分钟或更长

韩国女团Twice

对很多程序猿/媛来说,脸盲症绝对是个要命的短板,尤其是那些青春靓丽但偏偏又彼此相似的韩国明星……这时候,就需要图像识别中的面部识别技术来助你一臂之力了。

Amazon Rekognition网站提供了许多有用的功能,例如“物体和场景检测”、“面部识别”、“面部分析”和“名人识别”。不过,在针对韩国明星的“名人识别”功能体验中,Amazon Rekognition偶尔会遇到麻烦:有时系统无法识别,还有时识别结果是错误的。

图为女团Twice中的周子瑜,但Amazon把她识别成了金雪炫(另一个韩国女团AOA成员)

因此,本文将用Amazon Rekognition编写一个简单的Python脚本,从而来准确地识别Twice的成员。

用Amazon Rekognition进行人脸检测

为了能在Jupyter Notebook上运行本文中的代码,你需要满足以下几个条件:

1. 拥有亚马逊AWS账户

2. 使用AWS命令行界面(CLI)配置的AWS凭证

3. 更新Boto3至最新版本

首先,导入一些package,这些package可用于下一步。

import boto3
from PIL import Image
%matplotlib inline

现在需要找到一张想要处理的图片。我们以上文出现过的图片为例。把图片发送到Rekognition API,获取图像识别结果。

display(Image.open('Tzuyu.jpeg'))

我们可以把最基本的任务,也就是面部识别交给Rekognition来做。只需几行代码就能完成这个任务。

import io
rekognition = boto3.client('rekognition')
image = Image.open("Tzuyu.jpeg")
stream = io.BytesIO()
image.save(stream,format="JPEG")
image_binary = stream.getvalue()
rekognition.detect_faces(
Image={'Bytes':image_binary},
 Attributes=['ALL']
)

你可以直接把图片作为内存中的二进制文件对象,从本地计算机发送到Rekogntion,也可以上传图片到S3,并在调用rekognition.detect_faces()时将存储桶和密钥作为参数给出。在这里,把图片作为二进制对象发出。上述调用会花费很长时间。你可以从Rekognition的detect_faces功能中获得所有信息。

{'FaceDetails': [{'AgeRange': {'High': 38, 'Low': 20},
 'Beard': {'Confidence': 99.98848724365234, 'Value': False},
 'BoundingBox': {'Height': 0.1584049016237259,
 'Left': 0.4546355605125427,
 'Top': 0.0878104418516159,
 'Width': 0.09999311715364456},
 'Confidence': 100.0,
 'Emotions': [{'Confidence': 37.66959762573242, 'Type': 'SURPRISED'},
 {'Confidence': 29.646778106689453, 'Type': 'CALM'},
 {'Confidence': 3.8459930419921875, 'Type': 'SAD'},
 {'Confidence': 3.134934186935425, 'Type': 'DISGUSTED'},
 {'Confidence': 2.061260938644409, 'Type': 'HAPPY'},
 {'Confidence': 18.516468048095703, 'Type': 'CONFUSED'},
 {'Confidence': 5.1249613761901855, 'Type': 'ANGRY'}],
 'Eyeglasses': {'Confidence': 99.98339080810547, 'Value': False},
 'EyesOpen': {'Confidence': 99.9864730834961, 'Value': True},
 'Gender': {'Confidence': 99.84709167480469, 'Value': 'Female'},
 'Landmarks': [{'Type': 'eyeLeft',
 'X': 0.47338899970054626,
 'Y': 0.15436244010925293},
 {'Type': 'eyeRight', 'X': 0.5152773261070251, 'Y': 0.1474122554063797},
 {'Type': 'mouthLeft', 'X': 0.48312342166900635, 'Y': 0.211111381649971},
 {'Type': 'mouthRight', 'X': 0.5174261927604675, 'Y': 0.20560002326965332},
 {'Type': 'nose', 'X': 0.4872787892818451, 'Y': 0.1808750480413437},
 {'Type': 'leftEyeBrowLeft',
 'X': 0.45876359939575195,
 'Y': 0.14424000680446625},
 {'Type': 'leftEyeBrowRight',
 'X': 0.4760720133781433,
 'Y': 0.13612663745880127},
 {'Type': 'leftEyeBrowUp',
 'X': 0.4654795229434967,
 'Y': 0.13559915125370026},
 {'Type': 'rightEyeBrowLeft',
 'X': 0.5008187890052795,
 'Y': 0.1317606270313263},
 {'Type': 'rightEyeBrowRight',
 'X': 0.5342025756835938,
 'Y': 0.1317359358072281},
 {'Type': 'rightEyeBrowUp',
 'X': 0.5151524543762207,
 'Y': 0.12679456174373627},
 {'Type': 'leftEyeLeft', 'X': 0.4674917757511139, 'Y': 0.15510375797748566},
 {'Type': 'leftEyeRight',
 'X': 0.4817998707

从上面detect_faces的响应示例中可以看出,识别结果不仅包含人脸在图片中的边界框位置,还包含更高级的特征,如情感、性别、年龄段等。

人脸比较

Amazon Rekognition可以帮助对比两张图片中的人脸。例如,如果将子瑜的图片设为源图片,然后发送一张Twice成员的照片作为目标图片,Rekognition能够从目标图片中找到同源图片最相像的脸。我们要用到的Twice集体照就是下面这张。

如果你既不是亚洲人也不是Twice粉的话,可能连作为人类的你也无法完成这项任务。你可以猜猜这张图片里谁是子瑜。现在让我们来看看Rekognition是怎么完成任务的。

sourceFile='Tzuyu.jpeg' 
targetFile='twice_group.jpg'
imageSource=open(sourceFile,'rb')
imageTarget=open(targetFile,'rb')
response = rekognition.compare_faces(SimilarityThreshold=80,
 SourceImage={'Bytes': imageSource.read()},
 TargetImage={'Bytes': imageTarget.read()})
response['FaceMatches']

输入compare_faces作为指令,系统会分析团体照中所有不匹配人脸的信息,这会花费大量时间。因此,我们将指令特殊化成[‘FaceMatches’],要求Rekognition只输入匹配的人脸信息。看上去,Rekognition从这张团体照中找出了一张相似度为97%的人脸。让我们通过边界框信息来检查一下Rekognition有没有在集体照中正确识别出周子瑜。

顺便一提,BoundingBox给出的值是人脸占整个图像大小的比率。所以,为了用BoundingBox的值来绘制边框,你需要将图像的真实长度或宽度乘以比率,计算人脸框的大小。利用下面的代码可以做到这一点。

from PIL import ImageDraw
image = Image.open("twice_group.jpg")
imgWidth,imgHeight = image.size 
draw = ImageDraw.Draw(image)
box = response['FaceMatches'][0]['Face']['BoundingBox']
left = imgWidth * box['Left']
top = imgHeight * box['Top']
width = imgWidth * box['Width']
height = imgHeight * box['Height']
points = (
 (left,top),
 (left + width, top),
 (left + width, top + height),
 (left , top + height),
 (left, top)
)
draw.line(points, fill='#00d400', width=2)
display(image)

Rekognition,干得漂亮!这确实就是子瑜!

创建集合

现在我们能够识别图片中的面部,并且可以从目标图像中识别出源图像的人脸。但是以上功能都是一次性的,我们还需要存储女团中每个成员的面部信息和名字。这样的话,每发送一张Twice的新照片,系统都可以检索数据,识别成员面部并且显示成员名。

为达到该目的,需要应用亚马逊的“基于存储的API操作(Storage-Based API Operations)”。该操作包括两种亚马逊特有的专有术语。“集合”指的是Rekognition存储已识别人脸信息的虚拟数据库。通过使用集合,可以进行“索引”。这里指的是检测图像中的人脸,然后将信息存储在指定的集合中。需要提到的是,Rekognition存储的信息不是实际的图像,而是由算法提取的特征向量。接下来让我们学习如何创建集合并且添加索引。

collectionId='test-collection'
rekognition.create_collection(CollectionId=collectionId)

是的,就是这么简单。由于这是刚刚创建的新集合,因此里面没有存储任何信息。但是,让我们再看一下。

rekognition.describe_collection(CollectionId=collectionId)

在上面的响应中可以看到“FaceCount”的值是0。如果加入人脸索引并将其存储在集合中,这个值就会改变。

人脸索引

同样的,人脸索引也很简单,只需要在Rekognition中添加一行代码。

sourceFile='Tzuyu.jpeg' 
imageSource=open(sourceFile,'rb')
rekognition.index_faces(Image={'Bytes':imageSource.read()},ExternalImageId='Tzuyu',CollectionId=collectionId)

你可以看到,上面的代码中设置了参数ExternalImageId,并把值命名为“子瑜”。当你尝试从新图片中识别出子瑜的脸时,Rekognition就会搜索索引中的面部信息。每当有新的面部被编入索引时,Rekognition就会给它一个独有的face ID。但是我们想在匹配出来的子瑜的脸旁边显示出她的名字。因此,需要用到参数ExternalImageId。现在,如果查看集合的话,会发现一个人脸信息已经被加到该集合中了。

rekognition.describe_collection(CollectionId=collectionId)

按图像搜索人脸

现在子瑜的面部信息已被加入到集合中,可以将一张新的图片发送到Rekognition进行匹配。但是search_faces_by_image功能有一个问题,那就是它每次只能检测一张人脸(图像中面积最大的脸)。因此,如果想在Twice的集体照中找到子瑜,还需要再添加一步。在执行detect_faces前,应利用每张脸的边界框信息,逐个调用search_faces_by_image。首先,让我们检测每个成员的面部信息。

imageSource=open('twice_group.jpg','rb')
resp = rekognition.detect_faces(Image={'Bytes':imageSource.read()})
all_faces = resp['FaceDetails']
len(all_faces)

Rekognition从集体照中识别出了9张脸。不错。接下来裁剪照片,用serach_faces_by_image逐个搜索。

image = Image.open("twice_group.jpg")
image_width,image_height = image.size
for face in all_faces:
 box=face['BoundingBox']
 x1 = box['Left'] * image_width
 y1 = box['Top'] * image_height
 x2 = x1 + box['Width'] * image_width
 y2 = y1 + box['Height'] * image_height
 image_crop = image.crop((x1,y1,x2,y2))
 stream = io.BytesIO()
 image_crop.save(stream,format="JPEG")
 image_crop_binary = stream.getvalue()
response = rekognition.search_faces_by_image(
 CollectionId=collectionId,
 Image={'Bytes':image_crop_binary} 
 )
 print(response)
 print('-'*100)

在search_faces_by_image的9个结果中,Rekognition发现了一张能够和集合中的面部匹配的人脸。我们只建立了子瑜的索引,所以匹配到的人脸就是子瑜的。在图片上显示边界框和名字吧。有关名字的部分,会用到把人脸编入索引时设置的ExternalImageId。

顺便说一下,在所有search_faces_by_image的响应中,“FaceMatches”是一个数组。也就是说,如果集合中有多个面部被匹配,系统则会显示所有的匹配结果。根据亚马逊的说法,这个数组是按照相似性得分排序的,相似性最高的优先。将数组中的第一项作为匹配度最高的结果。

from PIL import ImageFont
import io
image = Image.open("twice_group.jpg")
image_width,image_height = image.size 
for face in all_faces:
 box=face['BoundingBox']
 x1 = box['Left'] * image_width
 y1 = box['Top'] * image_height
 x2 = x1 + box['Width'] * image_width
 y2 = y1 + box['Height'] * image_height
 image_crop = image.crop((x1,y1,x2,y2))
 stream = io.BytesIO()
 image_crop.save(stream,format="JPEG")
 image_crop_binary = stream.getvalue()
response = rekognition.search_faces_by_image(
 CollectionId=collectionId,
 Image={'Bytes':image_crop_binary} 
 )
 if len(response['FaceMatches']) > 0:
 draw = ImageDraw.Draw(image)
 points = (
 (x1,y1),
 (x2, y1),
 (x2, y2),
 (x1 , y2),
 (x1, y1)
)
 draw.line(points, fill='#00d400', width=2)
 fnt = ImageFont.truetype('/Library/Fonts/Arial.ttf', 15)
 draw.text((x1,y2),response['FaceMatches'][0]['Face']['ExternalImageId'], font=fnt, fill=(255, 255, 0))
 display(image)

太棒了!答案又是正确的!

识别Twice的所有成员

现在,让我们开发更多的项目功能,使之能够识别团体照中的所有成员。为了实现这个目标,首先需要把所有人的面部信息编入索引(一共有9个成员)。在Christian Petters编写的亚马逊教程中提到:“为每个人添加多个参考图像会大大提高匹配的成功率。”他的建议简单易懂。因此,我们为每个成员都准备了四张照片,并添加了同一个成员的多张照片。

collectionId='twice'
rekognition.create_collection(CollectionId=collectionId)

import os
path = 'Twice'
for r, d, f in os.walk(path):
 for file in f:
 if file != '.DS_Store':
 sourceFile = os.path.join(r,file)
 imageSource=open(sourceFile,'rb')
rekognition.index_faces(Image={'Bytes':imageSource.read()},ExternalImageId=file.split('_')[0],CollectionId=collectionId)
rekognition.describe_collection(CollectionId=collectionId)

好的。现在所有36张照片都被编入了“Twice”集合的索引。现在是时候检验成果了。升级后的Rekognition能够识别Twice的全部成员吗?

from PIL import ImageFont
image = Image.open("twice_group.jpg")
image_width,image_height = image.size 
for face in all_faces:
 box=face['BoundingBox']
 x1 = box['Left'] * image_width
 y1 = box['Top'] * image_height
 x2 = x1 + box['Width'] * image_width
 y2 = y1 + box['Height'] * image_height
 image_crop = image.crop((x1,y1,x2,y2))
 stream = io.BytesIO()
 image_crop.save(stream,format="JPEG")
 image_crop_binary = stream.getvalue()
response = rekognition.search_faces_by_image(
 CollectionId=collectionId,
 Image={'Bytes':image_crop_binary} 
 )
 if len(response['FaceMatches']) > 0:
 draw = ImageDraw.Draw(image)
 points = (
 (x1,y1),
 (x2, y1),
 (x2, y2),
 (x1 , y2),
 (x1, y1)
)
 draw.line(points, fill='#00d400', width=2)
 fnt = ImageFont.truetype('/Library/Fonts/Arial.ttf', 15)
draw.text((x1,y2),response['FaceMatches'][0]['Face']['ExternalImageId'], font=fnt, fill=(255, 255, 0))
display(image)

是的,它做到了!它正确识别了所有成员!

Jupyter Notebook和所有照片传送门:

https://github.com/tthustla/twice_recognition

留言 点赞 关注

我们一起分享AI学习与发展的干货

欢迎关注全平台AI垂类自媒体 “读芯术”