👨💻 Projeto desenvolvido por: Brenno Ostemberg, José Pedro Cândido L.P., Rafael Torres Nantes e Sarah Baraldi.
- 📚 Contextualização do projeto
- 🛠️ Tecnologias/Ferramentas utilizadas
- 🖥️ Funcionamento do sistema
- 📁 Estrutura do projeto
- 📌 Como executar o projeto
- 🔗 Endpoints
- 🕵️ Dificuldades Encontradas
O projeto tem o objetivo de criar uma API que receba imagens postadas no AWS S3, utilize o Amazon Rekognition identificar bichos e humanos, utilize o Amazon Bedrock para extrair dicas de como cuidar dos pets reconhecidos e grave os logs dos resultados utilizando CloudWatch.
Utilizando o framework Serverless, enviamos um json via POST à rota /v1/vision
, onde utilizamos o Amazon Rekognition para detectar as faces e emoções. Além disso, são efetuados logs referentes a cada solitação no AWS Cloudwatch.
{
"bucket": "nomeBucket",
"imageName": "nomeFoto.jpg"
}
Ou
{
"body": {
"bucket": "sprint08-my-photos",
"imageName": "Fofos.png"
}
}
- Caso haja apenas uma face:
{
"url_to_image": "https://sprint08-my-photos.s3.amazonaws.com/FaceOne.jpg",
"created_image": "02-07-2024 00:51:03",
"faces": [
{
"position": {
"Height": 0.2435552030801773,
"Left": 0.6044005751609802,
"Top": 0.15545067191123962,
"Width": 0.17254146933555603
},
"classified_emotion": "HAPPY",
"classified_emotion_confidence": 100.0
}
]
}
- Caso haja mais de uma face, o elemento "faces": [{...}] recebe mais de um objeto:
{
"url_to_image": "https://sprint08-my-photos.s3.amazonaws.com/Faces_1.jpg",
"created_image": "02-07-2024 00:51:05",
"faces": [
{
"position": {
"Height": 0.7969402074813843,
"Left": 0.1317732036113739,
"Top": 0.06133376806974411,
"Width": 0.3078056275844574
},
"classified_emotion": "HAPPY",
"classified_emotion_confidence": 100.0
},
{
"position": {
"Height": 0.7686970233917236,
"Left": 0.5813419818878174,
"Top": 0.0527547188103199,
"Width": 0.3050912320613861
},
"classified_emotion": "CALM",
"classified_emotion_confidence": 93.5546875
}
]
}
- Caso NÃO haja faces, os elementos contidos em "faces": [{...}] recebem valor None:
{
"url_to_image": "https://sprint08-my-photos.s3.amazonaws.com/Bola.jpg",
"created_image": "02-07-2024 00:54:10",
"faces": [
{
"position": {
"Height": None,
"Left": None,
"Top": None,
"Width": None
},
"classified_emotion": None,
"classified_emotion_confidence": None
}
]
}
- Detectando as faces: utilizamos a função
detect_faces
da seguinte forma:
response = self.rekognition.detect_faces(
Image={
"S3Object": {
"Bucket": bucket,
"Name": image_name
}
},
Attributes=["ALL"]
)
- Detectando as emoções: utilizamos a função
detect_labels
da seguinte forma:
response = self.rekognition.detect_labels(
Image={
"S3Object": {
"Bucket": bucket,
"Name": image_name
}
},
MaxLabels=10,
MinConfidence=80
)
De maneira análoga à Parte 1, desenvolvemos o sistema utilizamos o framework Serverless para enviarmos um json via POST à rota /v2/vision
, onde utilizamos o Amazon Rekognition para detectar os pets, suas emoções e raças. Além disso, utilizamos o Amazon Bedrock para receber dicas de cuidados para cada raça dos pets reconhecidos. Por fim, são efetuados logs referentes a cada solitação no AWS Cloudwatch.
{
"bucket": "sprint08-my-photos",
"imageName": "Fofos.png"
}
Ou
{
"body": {
"bucket": "sprint08-my-photos",
"imageName": "Fofos.png"
}
}
- Caso haja apenas um pet:
{
"url_to_image": "https://sprint08-my-photos.s3.amazonaws.com/Fofos.png",
"created_image": "01-07-2024 01:25:36",
"faces": [
{
"position":
{
"Height": null,
"Left": null,
"Top": null,
"Width": null
},
"classified_emotion": null,
"classified_emotion_confidence": null
}
],
"pets": [
{
"labels": [
{
"Confidence": 98.87677001953125,
"Name": "Puppy"
}
],
"Dicas": "O Puppy é um pet inteligente e alegre que gosta de jogar e ser socializado. Ele tem um nível de energia alto e necessita de atividade regular para mantê-lo saudável. O temperamento de Puppy é amigável e carinhoso, ele é facilmente trainado e pode ser adaptado a diferentes ambientes. Os cuidados de Puppy incluem alimentação balanceada, higiene regular, exames regulares com o veterinário, vacina"
}
]
}
- Caso haja uma pessoa e um pet, retorna uma resposta como à da Parte 1, e também informações de pet, como no exemplo anterior:
{
"url_to_image": "https://sprint08-my-photos.s3.amazonaws.com/Faces_And_Dog.jpg",
"created_image": "02-07-2024 00:51:03",
"faces": [
{
"position": {
"Height": 0.2435552030801773,
"Left": 0.6044005751609802,
"Top": 0.15545067191123962,
"Width": 0.17254146933555603
},
"classified_emotion": "HAPPY",
"classified_emotion_confidence": 100.0
}
],
"pets": [
{
"labels": [
{
"Confidence": 99.94889831542969,
"Name": "Golden Retriever"
}
],
"Dicas": "\nDicas sobre Golden Retriever: \n\n- Nível de Energia e Necessidades de Exercícios: \n O Golden Retriever é um pet bastante ativo e necessita de exercícios regulares para mantê-lo saudável e feliz. Sua energia é alta e gosta de jogar e brincar, por isso é recomendado que ele se exercite regularmente durante aproximadamente 1 a 2 horas diariamente. \n\n- Temperamento e Comportamento"
}
]
}
- Caso NÃO haja pets (nem faces), os elementos contidos em "faces": [{...}] recebem valor None e o objeto pets:[...] fica vazio:
{
"url_to_image": "https://sprint08-my-photos.s3.amazonaws.com/Bola.jpg",
"created_image": "02-07-2024 00:54:10",
"faces": [
{
"position": {
"Height": None,
"Left": None,
"Top": None,
"Width": None
},
"classified_emotion": None,
"classified_emotion_confidence": None
}
],
"pets": [
]
}
- Detectando os pets: utilizamos a função
detect_labels
:
def detect_pets(self, bucket, image_name):
labels_response = self.detect_labels(bucket, image_name)
animal_characteristics = self.animal_characteristics(labels_response)
pets = self.pets_labels_treatment(animal_characteristics)
return pets
- Filtramos a resposta da função para obtermos os pets:
def animal_characteristics(self, rekognition_response):
# Lista de rótulos de possíveis animais de estimação
pet_labels = ["Dog", "Cat", "Bird", "Fish", "Reptile", "Mammal", "Pet"]
animal_characteristics = []
for label in rekognition_response["Labels"]:
for parent in label["Parents"]:
if parent.get("Name") in pet_labels:
animal_characteristics.append(label)
return animal_characteristics
- Para obtermos mais precisão no reconhecimento das raças dos animais, filtramos "raças genéricas" do elemento breed.
def _process_pets(self, pet_labels):
pets = []
filtered_breeds = ["Animal", "Pet", "Dog", "Bird", "Mammal", "Vertebrate", "Canidae","Canine", "Carnivore", "Terrestrial animal", "Dog breed", "Dog like mammal"]
unique_breeds = {breed["Name"]: breed for pet in pet_labels["pets"] for breed in pet["labels"] if breed["Name"] not in filtered_breeds}.values()
# Gera o log das raças únicas detectadas pelo Rekognition
logger(f"Raças únicas: {unique_breeds}")
# Para cada raça de animal de estimação detectada pelo Rekognition
for breed in unique_breeds:
self.bedrock_service.set_pet_breed(breed["Name"])
response = self.bedrock_service.invoke_model()
if response["statusCode"] == 200:
tips = json.loads(response["Dicas"])
else:
tips = "Erro ao obter dicas do Bedrock"
pet_info = {
"labels": [{"Confidence": breed["Confidence"], "Name": breed["Name"]}],
"Dicas": tips
}
pets.append(pet_info)
return pets
Tanto na Parte 1 quanto na Parte 2, inserimos logs no Cloudwatch. Os logs foram formatados da seguinte maneira:
log_event = {
"logGroupName": log_group_name,
"logStreamName": log_stream_name,
"logEvents": [
{
"timestamp": int(datetime.datetime.now().timestamp() * 1000),
"message": json.dumps(message)
}
]
}
Criamos duas classificações de logs.
- Quando a requisição for um sucesso:
def logger(message):
print(message)
logger_instance.log_message("rekognition-logs", "vision-logs", message)
- Quando houver algum erro:
def error(message):
print(message)
logger_instance.log_message("rekognition-logs", "vision-errors", message)
O projeto foi dividido nos seguintes diretórios, baseando-se no modelo MVC (Model-View-Controller) com devidas adaptações:
-
controller
→ Realiza a chamada dos services (em ./services) criados para gerenciar os serviços AWS, sendo bucket na S3, reconhecimento no Amazon Rekognition e criação de texto no Amazon Bedrock. -
services
→ Manipulam os serviços AWS obtendo os metadados e gera URL das imagens no S3, detecta faces e rótulos no Amazon Rekognition, cria prompt e obtem respostas no Amazon Bedrock. -
utils
→ Manipula os logs no Cloudwatch e faz upload de imagens no S3.
-
handler.py
→ Contém as funções que sintetizam a API e define suas rotas. Verifica a saúde da API, recebe a imagem do S3 e retorna os detalhes do reconhecimento do Amazon Rekognition. -
serverless.yml
→ Define as políticas IAM para permitir que as funções Lambda acessem os serviços necessários e rotas das requisições que serão usadas no handler.py.
$ git clone https://github.com/Compass-pb-aws-2024-MARCO/sprint-8-pb-aws-marco.git
$ cd sprints-8-pb-aws-marco
$ git checkout grupo-2
$ serverless
$ npm install -g serverless
$ npm install serverless-python-requirements serverless-dotenv-plugin
$ aws configure
$ serverless login
$ cd visao-computacional
$ serverless deploy
🔸 GET - https://qo1bqt78bg.execute-api.us-east-1.amazonaws.com/
🔸 GET - https://qo1bqt78bg.execute-api.us-east-1.amazonaws.com/v1
🔸 GET - https://qo1bqt78bg.execute-api.us-east-1.amazonaws.com/v2
🔹 POST - https://qo1bqt78bg.execute-api.us-east-1.amazonaws.com/v1/vision
🔹 POST - https://qo1bqt78bg.execute-api.us-east-1.amazonaws.com/v2/vision
O Rekognition retorna diversos dados, incluindo informações que não eram necessárias para nosso uso, referidas como "raças genéricas". Conseguimos solucionar o problema aplicando filtros aos dados. No entanto, essa solução só foi alcançada após a análise de vários exemplos de retorno e uma extensa pesquisa na documentação do Rekognition sobre o tópico, especialmente na API Vision V2 (Emoções + Pets).
Outra dificuldade que enfrentamos foi o timeout ao configurarmos o Bedrock. O API Gateway possui um limite de 30 segundos para requisições HTTP, enquanto o Bedrock levava quase 5 minutos para retornar os dados, mesmo para apenas um pet. Resolvemos esse problema realizando algumas modificações no código, ajustando os atributos maxTokenCount, temperature e topP no arquivo bedrock_services.py
.
def generate_request_body(self):
request_body = {
"inputText": self.create_prompt(),
"textGenerationConfig": {
"maxTokenCount": 128,
# temperature: aleatoriedade na geração de texto
"temperature": 0.7,
# topP: tokens que compõem o top p% da probabilidade cumulativa
"topP": 0.9
},
}
return json.dumps(request_body)