Mini Livro: Detecção de Posturas com YOLO
1 Introdução
Este mini livro descreve em detalhes a lógica e a implementação de um algoritmo para detecção de posturas (deitado, sentado, em pé, background) utilizando o modelo YOLO (You Only Look Once). Vamos explorar cada linha de código, explicando sua função e a lógica por trás dela.
2 Importação de Bibliotecas e Ambiente
2.1 Configuração Inicial no Google Colab
Este projeto foi desenvolvido para ser executado no Google Colab. Para garantir que tudo funcione corretamente, siga os passos abaixo:
- Instalar a biblioteca YOLO
# Instala a biblioteca ultralytics para uso do modelo YOLO
# !pip install ultralytics- Importar o arquivo de pesos
Certifique-se de que o arquivo de pesos best.pt (modelo previamente treinado) esteja carregado no Colab.
2.2 Importação das Bibliotecas
import cv2 # OpenCV para manipulação de imagens e vídeos
import time # Medição de tempo e pausas na execução do código
import os # Manipulação de arquivos e diretórios
import warnings # Ignora avisos desnecessários
import matplotlib.pyplot as plt # Geração de gráficos
import seaborn as sns # Visualizações estatísticas avançadas
from IPython.display import display, Markdown # Exibição de Markdown em notebooks
from google.colab import files # Upload e download de arquivos no Google Colab
warnings.filterwarnings("ignore", category=UserWarning, module="moviepy")2.2.1 Explicação
- cv2: manipula vídeos e imagens, permitindo leitura, escrita, e anotações visuais.
- time: mede durações, essencial para capturar tempos das posturas.
- os: gerencia arquivos e diretórios.
- warnings: suprime mensagens desnecessárias para manter o console limpo.
- matplotlib: cria gráficos, como os gráficos de pizza.
- seaborn: facilita gráficos estatísticos.
- IPython.display: exibe textos formatados em Markdown.
- google.colab: manipula arquivos diretamente no Google Colab.
3 Função principal: processar_midia
3.1 Estrutura e Lógica Geral
A função processar_midia é responsável por processar imagens ou vídeos, aplicar o modelo YOLO para detecção de posturas e gerar relatórios sobre o tempo gasto em cada postura. Vamos destrinchar cada parte dela.
def processar_midia(caminho_entrada, modelo, tipo='video', caminho_saida='/content/saida.mp4'):
if tipo not in ['imagem', 'video']:
raise ValueError("Tipo deve ser 'imagem' ou 'video'.")3.1.1 Explicação
- caminho_entrada: Caminho para o arquivo de entrada (imagem ou vídeo).
- modelo: O modelo YOLO previamente treinado que será usado para detectar posturas.
- tipo: Define se o arquivo de entrada é uma imagem ou um vídeo. Caso não seja nem ‘imagem’ nem ‘video’, a função lança um erro com o
raise ValueError(). - caminho_saida: Caminho para onde o arquivo processado (com as anotações visuais) será salvo.
3.2 Inicializando Contadores
Os contadores serão usados para medir o tempo que a pessoa passa em cada postura ao longo do vídeo.
tempo_deitado = 0
tempo_sentado = 0
tempo_em_pe = 0
tempo_background = 0
tempo_total = 0
posturas_tempo = []
transicoes = []
duracoes_posturas = {0: [], 1: [], 2: [], 3: []} # 0: Deitado, 1: Sentado, 2: Em Pé, 3: Background
inicio_execucao = time.time()3.2.1 Explicação
- tempo_deitado: Conta quantos frames mostram a pessoa deitada.
- tempo_sentado: Conta quantos frames mostram a pessoa sentada.
- tempo_em_pe: Conta quantos frames mostram a pessoa em pé.
- tempo_background: Conta quantos frames sem nenhuma detecção relevante (background).
- tempo_total: Número total de frames processados.
- posturas_tempo: Lista que registra a postura detectada em cada frame, útil para análises posteriores.
- transicoes: Lista de transições entre posturas, usada para entender mudanças ao longo do vídeo.
- duracoes_posturas: Um dicionário que armazena a duração de cada postura em segundos.
- inicio_execucao: Marca o tempo de início da execução, para depois calcular o tempo total gasto no processamento.
3.3 Processamento de Imagem
Caso o tipo de mídia seja ‘imagem’, o código processa a imagem individualmente.
if tipo == 'imagem':
img = cv2.imread(caminho_entrada)
results = modelo.predict(img, conf=0.1, verbose=False)
annotated_img = results[0].plot()
cv2.imwrite(caminho_saida, annotated_img)
return caminho_saida3.3.1 Explicação
- cv2.imread(caminho_entrada): Lê a imagem localizada no caminho especificado.
- modelo.predict(img, conf=0.1, verbose=False): Aplica o modelo YOLO para detectar posturas na imagem.
conf=0.1: Define a confiança mínima (10%) para considerar uma detecção válida.verbose=False: Desativa saídas excessivas no console.
- results[0].plot(): Desenha as caixas delimitadoras (bounding boxes) ao redor das posturas detectadas.
- cv2.imwrite(caminho_saida, annotated_img): Salva a imagem anotada no caminho especificado.
- return caminho_saida: Retorna o caminho do arquivo gerado.
3.4 Processamento de Vídeo
Agora, vamos ver como o código processa vídeos frame a frame.
else:
cap = cv2.VideoCapture(caminho_entrada)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = int(cap.get(cv2.CAP_PROP_FPS))
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))3.4.1 Explicação
- cv2.VideoCapture(caminho_entrada): Carrega o vídeo no caminho especificado.
- cap.get(cv2.CAP_PROP_FRAME_WIDTH): Obtém a largura dos frames do vídeo.
- cap.get(cv2.CAP_PROP_FRAME_HEIGHT): Obtém a altura dos frames.
- cap.get(cv2.CAP_PROP_FPS): Recupera o número de quadros por segundo (FPS), essencial para converter frames em segundos.
- cap.get(cv2.CAP_PROP_FRAME_COUNT): Conta o número total de frames no vídeo.
3.5 Definição do Codec e Criação do Objeto de Escrita de Vídeo
# Define o codec de vídeo e cria um objeto para salvar o vídeo processado
fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter(caminho_saida, fourcc, fps, (width, height))3.5.1 Explicação
- fourcc: Define o codec usado para compressão do vídeo.
*'XVID': Codec Xvid, amplamente suportado e eficiente.
- cv2.VideoWriter: Cria um objeto para salvar o vídeo processado.
- caminho_saida: Caminho do arquivo de saída.
- fourcc: Codec para compressão.
- fps: Frames por segundo.
- (width, height): Dimensões do vídeo.
3.6 Controle de Transições
# Inicializa variáveis para rastrear transições entre posturas
postura_anterior = None
duracao_atual = 03.6.1 Explicação
- postura_anterior: Armazena a postura do frame anterior para comparação.
- duracao_atual: Conta quantos frames a postura atual perdura.
3.7 Loop de Processamento de Frames
# Loop para processar cada quadro do vídeo
while cap.isOpened():
ret, frame = cap.read() # Lê um quadro do vídeo
if not ret:
break # Encerra o loop quando não há mais quadros3.7.1 Explicação
- cap.isOpened(): Verifica se o vídeo foi aberto corretamente.
- cap.read(): Lê o próximo frame.
- ret: Retorna
Falsequando os quadros terminam. - break: Encerra o loop ao final do vídeo.
3.8 Aplicação do Modelo YOLO
# Aplica o modelo ao quadro e obtém os resultados
results = modelo.predict(frame, conf=0.1, verbose=False)
annotated_frame = results[0].plot() # Adiciona anotações ao quadro
out.write(annotated_frame) # Salva o quadro anotado3.8.1 Explicação
- modelo.predict(): Aplica YOLO ao frame.
- frame: Quadro atual.
- conf=0.1: Confiança mínima de 10% para detectar posturas.
- verbose=False: Desativa saídas no console.
- results[0].plot(): Desenha as caixas delimitadoras (bounding boxes).
- out.write(): Grava o quadro anotado no vídeo de saída.
3.9 Classificação das Posturas
frame_classificado = False
class_id = 3 # Background padrão
for result in results:
for box in result.boxes:
class_id = int(box.cls)
frame_classificado = True
break3.9.1 Explicação
- frame_classificado: Marca se houve alguma detecção.
- class_id = 3: Assume que o frame é background por padrão.
- for result in results: Itera sobre os resultados.
- int(box.cls): Obtém a classe detectada.
- break: Para após a primeira detecção válida.
3.10 Contagem de Posturas
if not frame_classificado:
tempo_background += 1
else:
if class_id == 0:
tempo_deitado += 1
elif class_id == 1:
tempo_sentado += 1
elif class_id == 2:
tempo_em_pe += 13.10.1 Explicação
- Atualiza os contadores de tempo para cada postura.
- tempo_background: Incrementado caso nenhuma postura seja detectada.
- class_id == 0: Postura deitado.
- class_id == 1: Postura sentado.
- class_id == 2: Postura em pé.
3.11 Controle de Transições
if postura_anterior is None:
postura_anterior = class_id
duracao_atual = 1
elif postura_anterior == class_id:
duracao_atual += 1
else:
duracoes_posturas[postura_anterior].append(duracao_atual / fps)
transicoes.append((postura_anterior, class_id))
postura_anterior = class_id
duracao_atual = 13.11.1 Explicação
- postura_anterior: Guarda a última postura detectada.
- duracao_atual: Incrementa enquanto a postura não muda.
- duracoes_posturas: Armazena a duração de cada postura em segundos.
- transicoes: Registra mudanças de postura.
3.12 Finalizando o Processo
tempo_total += 1
posturas_tempo.append(class_id)
cap.release()
out.release()3.12.1 Explicação
- tempo_total: Contagem total de frames.
- posturas_tempo: Lista com a sequência de posturas.
- cap.release(): Fecha o vídeo de entrada.
- out.release(): Finaliza e salva o vídeo anotado.
3.13 Registro do Tempo de Execução
# Registra o tempo de execução
tempo_execucao = fim_execucao - inicio_execucao3.13.1 Explicação
- fim_execucao: Marca o momento em que o processamento do vídeo é concluído.
- inicio_execucao: Foi definido antes do loop de processamento.
- tempo_execucao: Subtraindo um pelo outro, obtemos o tempo total gasto para processar o vídeo.
3.14 Geração do Relatório de Posturas
# Exibir relatório de tempo em formato Markdown
relatorio_tabela = f"""
# 📊 Relatório de Tempo (em Segundos)
| Categoria | Tempo (s) | Porcentagem |
|---------------|----------|------------|
| **Deitado** | {tempo_deitado / fps:.2f} | {(tempo_deitado / tempo_total) * 100:.2f}% |
| **Sentado** | {tempo_sentado / fps:.2f} | {(tempo_sentado / tempo_total) * 100:.2f}% |
| **Em Pé** | {tempo_em_pe / fps:.2f} | {(tempo_em_pe / tempo_total) * 100:.2f}% |
| **Background** | {tempo_background / fps:.2f} | {(tempo_background / tempo_total) * 100:.2f}% |
| **Duração do Vídeo** | {total_frames / fps:.2f} | 100% |
| **Tempo de Processamento** | {tempo_execucao:.2f} | - |
"""
display(Markdown(relatorio_tabela))3.14.1 Explicação
- relatorio_tabela: Usa uma string formatada para criar um relatório em Markdown.
- tempo_deitado / fps: Converte frames em segundos.
- (tempo_deitado / tempo_total) * 100: Calcula a porcentagem de tempo gasto em cada postura.
- display(Markdown(…)): Exibe o relatório no formato elegante de Markdown.
3.15 Visualizações Gráficas
3.15.1 Gráfico de Pizza: Distribuição de Posturas
# Gráfico de pizza para distribuição de tempo por postura
labels = ['Deitado', 'Sentado', 'Em Pé', 'Background']
tempos = [tempo_deitado, tempo_sentado, tempo_em_pe, tempo_background]
colors = ['#FF6384', '#36A2EB', '#FFCE56', '#B0B0B0']
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
axes[0, 0].pie(tempos, labels=labels, autopct='%1.1f%%', colors=colors)
axes[0, 0].set_title('Distribuição de Tempos por Posição')3.15.2 Explicação
- labels: Nomes das posturas.
- tempos: Lista de tempos acumulados em cada postura.
- colors: Cores atribuídas a cada postura.
- plt.pie: Cria o gráfico de pizza.
- autopct=‘%1.1f%%’: Exibe as porcentagens diretamente no gráfico.
3.15.3 Gráfico KDE: Duração das Posturas
# Gráfico KDE para distribuição de durações por postura
for class_id, label, color in zip([0, 1, 2], ['Deitado', 'Sentado', 'Em Pé'], ['#FF6384', '#36A2EB', '#FFCE56']):
if duracoes_posturas[class_id]:
sns.kdeplot(duracoes_posturas[class_id], ax=axes[0, 1], fill=True, color=color, label=label, bw_adjust=1.5, clip=(0, None))
axes[0, 1].set_title('Distribuição das Durações por Posição')
axes[0, 1].set_xlabel('Duração (s)')
axes[0, 1].set_ylabel('Densidade')
axes[0, 1].legend()
sns.despine(ax=axes[0, 1])3.15.4 Explicação
- zip(): Itera sobre as classes, rótulos e cores simultaneamente.
- sns.kdeplot: Cria gráficos de densidade (KDE) para exibir a distribuição das durações.
- bw_adjust=1.5: Ajusta a suavidade das curvas.
3.15.5 Histograma: Transições entre Posturas
# Histograma de mudanças de postura
transicoes_numericas = [f'{t[0]}→{t[1]}' for t in transicoes]
sns.histplot(transicoes_numericas, ax=axes[1, 0])
axes[1, 0].set_title('Frequência das Mudanças Entre Posturas')
axes[1, 0].set_xlabel('Transição')
sns.despine(ax=axes[1, 0])3.15.6 Explicação
- transicoes_numericas: Transforma transições de posturas em strings formatadas.
- sns.histplot: Plota um histograma da frequência das transições.
- set_title(): Define o título do gráfico.
- set_xlabel(): Adiciona rótulo ao eixo X.
3.16 Exibição Final dos Gráficos
# Ajuste final da visualização
tplt.delaxes(axes[1, 1]) # Remove espaço vazio
plt.tight_layout()
plt.show()3.16.1 Explicação
- plt.delaxes(): Remove um gráfico vazio para manter o layout limpo.
- plt.tight_layout(): Garante que os gráficos não fiquem sobrepostos.
- plt.show(): Renderiza os gráficos.
Com isso, concluímos a análise visual e estatística das posturas detectadas. Podemos agora avançar para interpretar esses resultados e entender como eles podem informar decisões práticas! 🚀
4 Exemplo de Aplicação do Modelo
# Carrega o modelo YOLO previamente treinado usando o arquivo de pesos "best.pt"
modelo = YOLO('/content/best.pt')
# Aplica o modelo ao vídeo "teste.mp4" para detectar posturas
# O resultado será salvo e processado automaticamente pela função "processar_midia"
processar_midia('/content/teste_2.mp4', modelo, tipo='video')Exemplo de output:
Com isso, concluímos a análise visual e estatística das posturas detectadas. Essencialmente, a transição do modelo de classificação para a EDA transforma simples etiquetas (“Deitado”, “Sentado”) em uma narrativa estatística sobre o comportamento ao longo do tempo.
