基于Amazon Rekognition 的具有人脸替换相应emoji功能的情绪识别系统 Emotion recognition system with emoji replacement face function
NUS SOC SWS summerworkshop 2022 cloud computing 小组做的项目,放到了亚马逊的EC2服务器上运行
功能:
用amazon rekognition识别人脸的情绪,年龄,性别,胡子,眼镜等面部特征,然后用具有相应信息的emoji覆盖脸
图片处理的效果:
图片的处理思路:
图片处理代码:
#Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#PDX-License-Identifier: MIT-0 (For details, see https://github.com/awsdocs/amazon-rekognition-developer-guide/blob/master/LICENSE-SAMPLECODE.)
import os
from PIL import Image
import boto3
def detect_faces(photo, bucket):
s3 = boto3.client('s3')
# with open("download.png", 'wb') as f:
# s3.download_fileobj(bucket, photo, f) # S3 download 从S3下载,会慢 ,
client = boto3.client('rekognition')
response = client.detect_faces(Image = {'S3Object': {'Bucket': bucket, 'Name': photo}},Attributes=['ALL'])
xx=[]
print('Detected faces for ' + photo)
for faceDetail in response['FaceDetails']:
print('The detected face is between ' + str(faceDetail['AgeRange']['Low'])
+ ' and ' + str(faceDetail['AgeRange']['High']) + ' years old,'+str(faceDetail['Gender'])+str(faceDetail['Emotions']))
# print('Here are the other attributes:')
# print(json.dumps(faceDetail, indent=4, sort_keys=True))
# print(response['Faces'])
print("----------------------------------")
print(faceDetail)
tech_names = {'AgeRange', 'Gender','Emotions','BoundingBox','Beard'}
result={key: value for key, value in faceDetail.items() if key in tech_names} #只包含'AgeRange', 'Gender','Emotions','BoundingBox','Beard'的一张脸的dict
n = []
n.append(result)
xx=xx+n
addEmoji(xx,"./test/angryPeople.jpg") #加上emoji
#json.dump(xx, open('result.json', 'w'), indent=4) # 生成json文件 xx是包含所有脸的'AgeRange', 'Gender','Emotions','BoundingBox','Beard'的list
return len(response['FaceDetails'])
def main():
photo='angryPeople.jpg'
# photo = 'angry3.jpg' #识别的照片名
# photo='angry.png' #------------------------------------这里要上传的名字
bucket='nussocsws2022' #bucket名字
face_count=detect_faces(photo, bucket)
print("Faces detected: " + str(face_count)) #识别的脸数
def addEmoji(xx,address):
# 载入要更改的图片
# 除了导入本地的图片也可以根据url或者其他方式导入图片
# img = Image.open("test/1.png")
if os.path.exists(address):
# 如果文件存在 # 删除文件,可使用以下两种方法。
img = Image.open(address)
# os.unlink(path)
else:
return 'no such file' # 则返回文件不存在
# img = Image.open("download.png") #----------------------这里要传到EC2的原图片,用完记得删掉图片
# 载入要更改图片的信息
# 如果直接传的就是json列表的话可以不用载入文件,dataList就是json列表
# f = open("test/result.json", encoding='utf-8')
dataList = xx
# f.close()
# 处理图片
for data in dataList:
# 分离特征
# print(data)
age = data["AgeRange"]["High"]
gender = data["Gender"]["Value"]
emotion = data["Emotions"][0]["Type"]
isBeard = data["Beard"]["Value"]
if 0 <= age <= 3:
people = "baby"
elif 3 < age <= 15 and gender == "Male":
people = "boy"
elif 3 < age <= 15 and gender == "Female":
people = "girl"
elif 15 < age <= 40 and gender == "Male":
people = "man"
elif 15 < age <= 40 and gender == "Female":
people = "woman"
elif age > 40 and gender == "Male":
people = "oldman"
elif age > 40 and gender == "Female":
people = "oldwoman"
# 根据图片信息载入相应的表情包
if people == "man" or people == "oldman":
if isBeard == True:
emoji = Image.open("emoji/" + emotion.lower() + "/" + people + "_" + emotion.lower() + ".png")
else:
emoji = Image.open("emoji/" + emotion.lower() + "/" + people + "_" + emotion.lower() + "_nobeard.png")
else:
emoji = Image.open("emoji/" + emotion.lower() + "/" + people + "_" + emotion.lower() + ".png")
left = int(img.size[0] * data["BoundingBox"]["Left"])
top = int(img.size[1] * data["BoundingBox"]["Top"])
width = int(img.size[0] * data["BoundingBox"]["Width"])
height = int(img.size[1] * data["BoundingBox"]["Height"])
emoji = emoji.resize((width, height))
box = (left, top, left + width, top + height)
img.paste(emoji, box)
# 展示图片
# img.show()
# 保存图片
img.save(address)
if __name__ == "__main__":
main()
视频处理思路:
以前没搞过,自己的想法是直接用rekognition识别视频的情绪,然后把视频的每一帧分开(把音频保存了),把相应的情绪的emoji加上去,在把每一帧合起来(加上音频),就好了。
但这样有一个缺点,rekognition返回的结果是同一小段时间内识别结果相同,如果视频中人脸移动的快一些脸部的位置就会跟不上
解决方法应该是不用视频的情绪识别,而是对每一帧用图片的情绪识别,应该会慢一些,但效果会很好
视频处理的代码:
videoFace是处理视频的核心文件
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# PDX-License-Identifier: MIT-0 (For details, see https://github.com/awsdocs/amazon-rekognition-developer-guide/blob/master/LICENSE-SAMPLECODE.)
import boto3
import json
import sys
import time
from main import addEmoji
from audio import extract_audio,video_add_audio
from videoConverter import videoToPic,PicToVideo,delete
class VideoDetect:
jobId = ''
rek = boto3.client('rekognition')
sqs = boto3.client('sqs')
sns = boto3.client('sns')
roleArn = ''
bucket = ''
video = ''
startJobId = ''
sqsQueueUrl = ''
snsTopicArn = ''
processType = ''
def __init__(self, role, bucket, video):
self.roleArn = role
self.bucket = bucket
self.video = video
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# PDX-License-Identifier: MIT-0 (For details, see https://github.com/awsdocs/amazon-rekognition-developer-guide/blob/master/LICENSE-SAMPLECODE.)
# ============== Faces===============
def StartFaceDetection(self):
response = self.rek.start_face_detection(Video={'S3Object': {'Bucket': self.bucket, 'Name': self.video}},
FaceAttributes="ALL",
NotificationChannel={'RoleArn': self.roleArn,
'SNSTopicArn': self.snsTopicArn})
self.startJobId = response['JobId']
print('Start Job Id: ' + self.startJobId)
def GetFaceDetectionResults(self):
maxResults = 10
paginationToken = ''
finished = False
m=0
lastStamp = ""
while finished == False:
response = self.rek.get_face_detection(JobId=self.startJobId,
MaxResults=maxResults,
NextToken=paginationToken)
# print('Codec: ' + response['VideoMetadata']['Codec'])
# print('Duration: ' + str(response['VideoMetadata']['DurationMillis']))
# print('Format: ' + response['VideoMetadata']['Format'])
# print('Frame rate: ' + str(response['VideoMetadata']['FrameRate']))
# print()
xx = []
for faceDetection in response['Faces']:
print("str(faceDetection['Timestamp'])"+str(faceDetection['Timestamp']))
print("lastStamp"+lastStamp)
print(str(faceDetection['Timestamp'])==lastStamp)
if(str(faceDetection['Timestamp'])==lastStamp):
m=m-15
else:
xx=[]
lastStamp=str(faceDetection['Timestamp'])
print('AgeRange: ' + str(faceDetection['Face']["AgeRange"]))
print('Gender: ' + str(faceDetection['Face']["Gender"]))
print('Beard: ' + str(faceDetection['Face']["Beard"]))
print('Emotions: ' + str(faceDetection['Face']["Emotions"][0]))
print('BoundingBox: ' + str(faceDetection['Face']["BoundingBox"]))
print('Timestamp: ' + str(faceDetection['Timestamp']))
print()
tech_names = {'AgeRange', 'Gender', 'Emotions', 'BoundingBox', 'Beard'}
result = {key: value for key, value in faceDetection['Face'].items() if
key in tech_names} # 只包含'AgeRange', 'Gender','Emotions','BoundingBox','Beard'的一张脸的dict
# print(result)
n = []
n.append(result)
xx = xx + n
# print("n:")
# print(json.dumps(n, indent=4, sort_keys=True))
# # print("numberOfPic: " + str(numberOfPic))
# print("XX:")
# print(json.dumps(xx, indent=4, sort_keys=True))
i = 1
for i in range(15):
m = m + 1
print("m: " + str(m))
print("numberOfPic"+str(numberOfPic))
print(m == numberOfPic)
if (m >= numberOfPic):
break
addEmoji(xx, 'D:/Projects/Python_Project/NUS/Pic/pic' + str(m) + '.jpg')
print("add emoji pic" + str(m))
# for faceDetail in faceDetection['Face']:
# # print('The detected face is between ' + str(faceDetail['AgeRange']['Low'])
# # + ' and ' + str(faceDetail['AgeRange']['High']) + ' years old,' + str(faceDetail['Gender']) + str(
# # faceDetail['Emotions']))
# # print('Here are the other attributes:')
# # print(json.dumps(faceDetail, indent=4, sort_keys=True))
# # print(response['Faces'])
# # print("----------------------------------")
# # print(faceDetection['Face'])
# # print("----------------------------------")
# # print(faceDetection)
# print("----------------------------------")
# # print(faceDetail)
if 'NextToken' in response:
paginationToken = response['NextToken']
else:
finished = True
def GetSQSMessageSuccess(self):
jobFound = False
succeeded = False
dotLine = 0
while jobFound == False:
sqsResponse = self.sqs.receive_message(QueueUrl=self.sqsQueueUrl, MessageAttributeNames=['ALL'],
MaxNumberOfMessages=10)
if sqsResponse:
if 'Messages' not in sqsResponse:
if dotLine < 40:
print('.', end='')
dotLine = dotLine + 1
else:
print()
dotLine = 0
sys.stdout.flush()
time.sleep(5)
continue
for message in sqsResponse['Messages']:
notification = json.loads(message['Body'])
rekMessage = json.loads(notification['Message'])
print(rekMessage['JobId'])
print(rekMessage['Status'])
if rekMessage['JobId'] == self.startJobId:
print('Matching Job Found:' + rekMessage['JobId'])
jobFound = True
if (rekMessage['Status'] == 'SUCCEEDED'):
succeeded = True
self.sqs.delete_message(QueueUrl=self.sqsQueueUrl,
ReceiptHandle=message['ReceiptHandle'])
else:
print("Job didn't match:" +
str(rekMessage['JobId']) + ' : ' + self.startJobId)
# Delete the unknown message. Consider sending to dead letter queue
self.sqs.delete_message(QueueUrl=self.sqsQueueUrl,
ReceiptHandle=message['ReceiptHandle'])
return succeeded
def CreateTopicandQueue(self):
millis = str(int(round(time.time() * 1000)))
# Create SNS topic
snsTopicName = "AmazonRekognitionExample" + millis
topicResponse = self.sns.create_topic(Name=snsTopicName)
self.snsTopicArn = topicResponse['TopicArn']
# create SQS queue
sqsQueueName = "AmazonRekognitionQueue" + millis
self.sqs.create_queue(QueueName=sqsQueueName)
self.sqsQueueUrl = self.sqs.get_queue_url(QueueName=sqsQueueName)['QueueUrl']
attribs = self.sqs.get_queue_attributes(QueueUrl=self.sqsQueueUrl,
AttributeNames=['QueueArn'])['Attributes']
sqsQueueArn = attribs['QueueArn']
# Subscribe SQS queue to SNS topic
self.sns.subscribe(
TopicArn=self.snsTopicArn,
Protocol='sqs',
Endpoint=sqsQueueArn)
# Authorize SNS to write SQS queue
policy = """{{
"Version":"2012-10-17",
"Statement":[
{{
"Sid":"MyPolicy",
"Effect":"Allow",
"Principal" : {{"AWS" : "*"}},
"Action":"SQS:SendMessage",
"Resource": "{}",
"Condition":{{
"ArnEquals":{{
"aws:SourceArn": "{}"
}}
}}
}}
]
}}""".format(sqsQueueArn, self.snsTopicArn)
response = self.sqs.set_queue_attributes(
QueueUrl=self.sqsQueueUrl,
Attributes={
'Policy': policy
})
def DeleteTopicandQueue(self):
self.sqs.delete_queue(QueueUrl=self.sqsQueueUrl)
self.sns.delete_topic(TopicArn=self.snsTopicArn)
def main():
roleArn =
bucket =
# video = 'testVideo.mov'
# video = 'Django Unchained.mov'
video = 'testVideo.mov'
analyzer = VideoDetect(roleArn, bucket, video)
analyzer.CreateTopicandQueue()
analyzer.StartFaceDetection()
if analyzer.GetSQSMessageSuccess() == True:
analyzer.GetFaceDetectionResults()
analyzer.DeleteTopicandQueue()
if __name__ == "__main__":
numberOfPic=videoToPic()
extract_audio()
main()
PicToVideo()
video_add_audio()
# delete()
videoConverter负责视频转图片和图片转视频,删除中间产生的图片
import boto3
import cv2
from cv2 import VideoWriter, VideoWriter_fourcc, imread, resize
import os
from PIL import Image
import shutil
from main import addEmoji
# 此处添加上述两个函数
def downloadVideo(bucket, photo):
s3 = boto3.client('s3')
with open("download.mov", 'wb') as f:
s3.download_fileobj(bucket, photo, f) # S3 download 从S3下载,会慢 ,
def videoToPic():
cap = cv2.VideoCapture('./Video/testVideo.mov')
# cap = cv2.VideoCapture(address)
fourcc = cv2.VideoWriter_fourcc(*'XVID')
fps = cap.get(cv2.CAP_PROP_FPS)
# fps=30
size = (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
# size=(960,544)
i = 0
while (cap.isOpened()):
i = i + 1
ret, frame = cap.read()
if ret == True:
# cv2.imwrite('D:/Projects/Python_Project/NUS/Pic/pic' + str(i) + '.jpg', frame)
cv2.imwrite('./Pic/pic' + str(i) + '.jpg', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
else:
break
cap.release()
cv2.destroyAllWindows()
return i
def PicToVideo():
fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v') # 设置输出视频为mp4格式
cap = cv2.VideoCapture('D:/Projects/Python_Project/NUS/Video/testVideo.mov')
cap_fps = cap.get(cv2.CAP_PROP_FPS)
print(cap_fps)
# cap_fps是帧率,可以根据随意设置
# cap_fps = 30
# 注意!!!
# size要和图片的size一样,但是通过img.shape得到图像的参数是(height,width,channel),但是此处的size要传的是(width,height),这里一定要注意注意不然结果会打不开,比如通过img.shape得到常用的图片尺寸
# size = (1920, 1080)
# image = Image.open("D:/Projects/Python_Project/NUS/Pic/pic1.jpg")
image = Image.open("./Pic/pic1.jpg")
# 设置输出视频的参数,如果是灰度图,可以加上 isColor = 0 这个参数
# video = cv2.VideoWriter('results/result.avi',fourcc, cap_fps, size, isColor=0)
video = cv2.VideoWriter('./cache/result.mp4', fourcc, cap_fps, image.size)
# 这里直接读取py文件所在目录下的pics目录所有图片。
# path = 'D:/Projects/Python_Project/NUS/Pic/'
path = './Pic/'
file_lst = os.listdir(path)
i=0
for filename in range(len(file_lst)):
i=i+1
print('./Pic/pic' + str(i) + '.jpg')
# img = cv2.imread(path + file_lst[filename]) imgPath + str(frame_count)+ '.jpg'
img = cv2.imread('./Pic/pic' + str(i) + '.jpg')
video.write(img)
video.release()
def delete():
shutil.rmtree("./Pic")
os.mkdir("./Pic")
shutil.rmtree("./cache")
os.mkdir("./cache")
if __name__ == '__main__':
# photo = './Video/testVideo.mov'
# bucket = '' # bucket名字
# downloadVideo(bucket, photo)
videoToPic()
PicToVideo()
delete()
最后效果:
具有人脸替换相应emoji功能的情绪演示视频
github链接:github.com/anti177/HappyCodingEnd