Danny Malter

Data Science Manager - Accenture
M.S. in Predictive Analytics - DePaul University

Danny Malter

Me
Malter Analytics
GitHub
LinkedIn
YouTube Channel
Twitter
Kaggle

Other Work
General Assembly
AriBall

Media
Built In

Face and Emotion Recognition with R using AWS Rekognition

This post will demonstrate how to use the AWS Rekognition API with R to detect faces of new images as well as to attribute emotions to a given face. In order to do this, I use the paws R package to interact with AWS. The output image will label a new, unseen image with the name of the individual as well as the emotions tied to the face for that image. A video of this tutorial can be seen here or below.


### AWS Face and Emotion recognition ###

library(paws)  # used for AWS configuration
library(magick)  # used for image functions
library(tidyverse)

aws_access_key_id = "################"
aws_secret_access_key = "################"

svc <- rekognition()

### Create an AWS collection (server-side containers) ###
# Create a library of faces used for determining the identity of a new photo

svc$create_collection(CollectionId = "photos-r")
#svc$delete_collection(CollectionId = "photos-r")

# photos stored in directory within folders containing the person name
# i.e. all "Danny" photos are in folder named "Danny"

# Get the list of files
path = "~/Desktop/face_detection/photos"
filenames <- list.files(path, recursive=TRUE)

# Loop through the files in the specified folder, add and index them in the collection
for(f in filenames) {
  imgFile = paste(path,f,sep="/")
  # Get the person name, which is embedded in the last file path folder name
  imgName = unlist(strsplit(f,split="/"))[[1]]
  # Add the photos and the name to the AWS collection
  svc$index_faces(CollectionId="photos-r", Image=list(Bytes=imgFile), ExternalImageId=imgName, DetectionAttributes=list())
}

svc$list_faces(CollectionId = "photos-r")

### Label and identify the face of a new photo ###

# Grab a new photo with multiple faces
group_photo = "~/Desktop/face_detection/img1.JPG"
group_file_name = unlist(strsplit(group_photo,split="/"))[[4]]  # used for writing out annotated file

# Read the photo using magick
img = image_read(group_photo)

# Get basic info about the photo to be used for annotation
inf = image_info(img)

# Detect the faces in the image and pull all attributes associated with faces
o = svc$detect_faces(Image=list(Bytes=group_photo), Attributes="ALL")

# Get the face details
all_faces = o$FaceDetails
length(all_faces)


### For each face in photo, draw a rectange with the name and emotions ###
new.img = img  # Duplicate the original image
people_df <- NULL

for(face in all_faces) {

  # Grab emotions from AWS rekognition model
  emo_label = ""
  for(emo in face$Emotions) {
    emo_label = paste(emo_label, emo$Type, " = ", round(emo$Confidence, 2), "\n", sep="")
  }
  
  # Grab ages from AWS rekognition
  age_label = ""
  for(age in list(face$AgeRange)) {
    age_label = paste(age_label, "AGE ESTIMATE: = ", (age$Low+age$High)/2, "\n", sep="")
  }
  
  # Grab genders from AWS rekognition
  #gender_label = ""
  #for(gndr in list(face$Gender)) {
  #  gender_label = paste(gender_label, gndr$Value, " = ", round(gndr$Confidence, 2), "\n", sep="")
  #}
  
  # Append all lists together
  final_label = ""
  final_label <- rbind(emo_label, age_label)
  final_label <- paste(final_label, collapse = '')
  
  # Identify the coordinates of the face. Note that AWS returns percentage values of the total image size. This is
  # why the image info object above is needed
  box = face$BoundingBox
  image_width=inf$width
  image_height=inf$height
  x1 = box$Left*image_width
  y1 = box$Top*image_height
  x2 = x1 + box$Width*image_width
  y2 = y1 + box$Height*image_height  
  
  # Create a subset image in memory that is just cropped around the focal face
  img.crop = image_crop(img, paste(box$Width*image_width,"x",box$Height*image_height,"+",x1,"+",y1, sep=""))
  img.crop = image_write(img.crop, path = NULL, format = "png")
  
  # Search in a specified collection to see if we can label the identity of the face is in this crop
  o = svc$search_faces_by_image(CollectionId="photos-r",Image=list(Bytes=img.crop), FaceMatchThreshold=70)
  
  # Create a graphics device version of the larger photo that we can annotate
  new.img = image_draw(new.img)
  
  # If the face matches something in the collection, then add the name to the image
  if(length(o$FaceMatches) > 0) {
    faceName = o$FaceMatches[[1]]$Face$ExternalImageId
    faceConfidence = round(o$FaceMatches[[1]]$Face$Confidence,3)
    print(paste("Detected: ", faceName, sep=""))
    
    # Annotate with the name of the person
    text(x=x1+(box$Width*image_width)/2, y=y1-20, faceName, adj=0.5, cex=3, col="green")
  }
  
  # Draw a rectangle around the face
  rect(x1,y1,x2,y2, border="red", lty="dashed", lwd=5)   
  
  # Annotate the photo with the emotions information
  text(x=x1+(box$Width*image_width)/2, y=y1+50, final_label, pos=1, cex=1.5, col="red")     
  
  # Create a dataframe of individual data appended together
  individual_emotion_df <- do.call(rbind.data.frame, face$Emotions)
  
  individual_emotion_df <- individual_emotion_df %>% 
    spread(Type, Confidence) %>%
    add_column(faceName)
  individual_emotion_df$image <- strsplit(group_file_name, ".JPG")
  
  individual_emotion_df <- individual_emotion_df%>%
    select(faceName, image, everything()) # move faceName to beginning
  
  individual_age_df <- data.frame(face$AgeRange)
  colnames(individual_age_df) <- c("age_low", "age_high")
  
  individual_df <- cbind(individual_emotion_df, individual_age_df)
  
  people_df <- rbind(individual_df, people_df)
  
}
dev.off()

people_df$age_est <- (people_df$age_low + people_df$age_high)/2
names(people_df) <- tolower(names(people_df))
head(people_df)

# Write the image out to file 
image_write(new.img, path=paste0("~/Desktop/face_detection/annotated/annotated_", group_file_name))

Example table if you want to output the data above into a dataframe.

| faceName | image | angry      | calm        | confused   | disgusted   | fear        | happy    | sad         | surprised  |
|----------|-------|------------|-------------|------------|-------------|-------------|----------|-------------|------------|
| Natalie  | img1  | 0.05724448 | 0.008161878 | 0.08076324 | 0.054936308 | 0.048346419 | 99.64977 | 0.037308376 | 0.06347576 |
| Danny    | img1  | 0.01850546 | 0.008797654 | 0.01369155 | 0.005110143 | 0.009048435 | 99.88663 | 0.003529715 | 0.05468611 |

face-detection face-detection

comments powered by Disqus