top of page

Treinamento de Modelo para Classificação de Imagens

  • Foto do escritor: Vinicius Goia
    Vinicius Goia
  • 4 de jan. de 2024
  • 6 min de leitura

ree

Nesta postagem você acompanhará o processo de criação de um modelo para classificação de imagens para uma aplicação de filmagem em tempo real. No caso, a aplicação seria para a detecção de sinais de sonolência em motoristas.


Analise a fundo o que sua aplicação necessita


Este é um ponto de extrema importância para que você economize tempo em seus testes. Vamos utilizar como exemplo a aplicação comentada acima. Se você irá trabalhar com vídeo em tempo real, nada mais lógico do que utilizar vídeos para o treinamento do seu modelo. Foi isso que levei em consideração nos primeiros códigos desenvolvidos. Depois de muitas pesquisas e testes, consegui desenvolver um modelo com 67% de acurácia (nada tão significante) e com grandes sinais se sobreajuste. E vou ser sincero, demorei mais tempo do que eu imaginava.


O processo de treinamento basicamente consistia em carregar cada vídeo através de um gerador de frames, configurá-lo como um dataset destinado ao Tensorflow e aplicá-lo em uma rede neural convolucional através da ferramenta Keras. Mesmo com um modelo duvidoso, decidi testá-lo na aplicação. Nesse momento, percebi que todo o trabalho realizado não serviria para o que eu estava me propondo a fazer. Muitas vezes ficamos tão fissurados em desenvolver um modelo performático que esquecemos de uma coisa simples: o formato dos dados que o modelo treinado necessita para realizar as previsões. Nesse caso, o modelo esperava um formato de 10 frames com imagens 100x100 pixels com 3 camadas de cores. Para uma aplicação em tempo real, esse tipo de formato demandava um tempo considerável para a realização das previsões. Aí eu percebi que deveria trabalhar apenas com imagens para a classificação de cada frame do vídeo. Esclarecido isso, vamos ao nosso processo.


Dados para treinamento


Os dados utilizados para o treinamento do modelo estão disponíveis no Kaggle. Chamado de FL3D, foi construído usando o conjunto de dados NITYMED (Night-Time Yawning-Microsleep-Eyblink-driver Distraction), proposto pelos autores N. Petrellis, S. Zogas, P. Christakos, G. Keramidas, P. Mousouliotis, N. Voros, C. Antonopoulos em “High Speed Implementation of the Deformable Shape Tracking Face Alignment Algorithm”. O FL3D consiste em 53.331 imagens de homens e mulheres rotulados em 'alert', 'microsleep' ou 'yawning'.

ree

Para carregar os dados em um dataframe, utilizaremos a biblioteca pandas para o carregamento do arquivo json presente na pasta baixada do Kaggle.

# Importação de bibliotecas necessárias
import pandas as pd

# Importação do arquivo json
df = pd.read_json(".../classification_frames/annotations_all.json",orient='index',)

# Reset de índice e renomeação de coluna
df = df.reset_index()
df = df.rename(columns={'index': 'file'})
df = df.drop(columns='landmarks',axis=0)
df.head()
ree






Como o caminho das imagens não está referenciado ao disco do computador, adicionaremos o caminho faltante:

# Criação de lista vazia
files = []

# Iteração para obter caminho completo do arquivo
for i in df.index:
  caminho = 'caminho_faltante'+ df.iloc[i][0].split('.')[1]+'.jpg'
  files.append(caminho)

# Criação de lista vazia
classes = []

# Iteração para obter a classificação de cada arquivo
for i in df.index:
  clas = df.iloc[i][1]
  classes.append(clas)

# Agrupamento em lista de tuplas
lista_de_tuplas = list(zip(files, classes))

# Criação do dataframe
df = pd.DataFrame(lista_de_tuplas, columns=['File', 'Classes'])

Preparando os dados


Utilizaremos agora a biblioteca Scikit-Learn para misturar um pouco os nossos dados:

# Importação de biblioteca necessária
import sklearn

# Aplicação de embaralhamento
df = sklearn.utils.shuffle(df)

# Reset de índice
df.reset_index(inplace=True,drop=True)

Vamos observar algumas informações do dataset:

# Visualização de informações
df.info()
ree





# Contagem das classes
df.Classes.value_counts()
ree



Como observado, a quantidade de amostras com classificação 'alert', a qual seria o estado normal das pessoas, é muito elevado com relação às outras classificações. Se continuássemos o processo, nosso modelo obteria um número elevado de acurácia, porém não classificaria de modo correto os exemplos de 'microsleep' e 'yawning', que é o nosso objetivo. Portanto, o ideal é realizarmos um balanceamento de classes. Poderíamos utilizar o RandomUnderSampler no processo de leitura das imagens, o que seria o ideal, porém demandaria tempo e memória elevados. Desta maneira, realizaremos de maneira manual a diminuição da classe 'alert' excluindo algumas amostras. Também utilizaremos o LabelEncoder para transformar as nossas classes em números, o que facilita o entendimento do algoritmo.

# Importação de biblioteca necessária
from sklearn.preprocessing import LabelEncoder

# Instanciamento
LE = LabelEncoder()

# Aplicação do método
df.Classes = LE.fit_transform(df.Classes)

# Exclusão de amostras aleatórias
drop = df.loc[df.Classes == 0].sample(frac=0.75, random_state=1)
df = df.drop(drop.index)

Após o balanceamento manual, realizaremos a divisão do dataset em treino, validação e teste.

# Importação de biblioteca necessária
from sklearn.model_selection import train_test_split

# Definição do dataset de teste
df_teste = df.sample(frac=0.2, random_state=1)
df = df.drop(df_teste.index)

# Reset de índice
df_teste = df_teste.reset_index(drop=True)
df = df.reset_index(drop=True)

# Divisão de dados em Features e Target
X = df.drop("Classes", axis=1)
y = df["Classes"]

# Divisão dos dados de treino e teste
X_train, X_val, y_train, y_val = train_test_split(X, y)

# Contagem
y_train.value_counts()
ree



Observa-se que a classe 'alert', representada agora pelo número 0, possui uma quantidade de amostras mais próximas do  'microsleep' (1) e 'yawning' (2).


Para a classificação de imagens, utilizaremos um algoritmo de rede neural convolucional a partir do módulo Keras do Tensorflow. Sendo assim, necessitados preparar nossos dados para que eles possuam o formato adequado para o treinamento. Iremos, então, definir classes e funções para que cada imagem seja carregada a partir do OpenCV, normalizadas, redimensionadas e padronizadas para o Tensorflow.

# Importação de bibliotecas necessárias
import tensorflow as tf
import cv2

# Função para leitura e conversão de imagens
def format_frames(frame, output_size):
  """
    Pad and resize an image from a video.

    Args:
      frame: Image that needs to resized and padded.
      output_size: Pixel size of the output frame image.

    Return:
      Formatted frame with padding of specified output size.
  """

  frame = cv2.imread(frame)
  frame = frame/255.0
  frame = tf.image.convert_image_dtype(frame, tf.float32)
  frame = tf.image.resize_with_pad(frame, *output_size)

  return frame

# Classe para agrupamento de imagem e classificação (apenas dataset teste)
class tensor_pattern_1():
  def __init__(self,df):
     self.df = df

  def __call__(self):
    for i in self.df.index:
      frame = format_frames(self.df.iloc[i][0], output_size=(100,100))
      label = self.df.iloc[i][1]
      yield frame, label

# Classe para agrupamento de imagem e classificação 
class tensor_pattern_2():
  def __init__(self,X_train, y_train):
     self.X_train = X_train
     self.y_train = y_train

  def __call__(self):
    for i in self.X_train.File.index:
      frame = format_frames(self.X_train.File[i], output_size=(100,100))
      label = self.y_train[i]
      yield frame, label

Através dos comandos abaixo, podemos testar as funções:

tp = tensor_pattern_2(X_train, y_train)

frame, label = next(tp())

print(f"Shape: {frame.shape}")
print(f"Label: {label}")
ree


Os comandos abaixo realizarão as configurações finais para os datasets:

# Configuração de saída para dataset
output_signature = (tf.TensorSpec(shape = (None, None, 3), dtype = tf.float32),tf.TensorSpec(shape = (), dtype = tf.int16))

# Criação de dataset de treino padrão Tensorflow
train_ds = tf.data.Dataset.from_generator(tensor_pattern_2(X_train, y_train),output_signature = output_signature)

# Criação de dataset de validação padrão Tensorflow
val_ds = tf.data.Dataset.from_generator(tensor_pattern_2(X_val, y_val), output_signature =output_signature)

# Criação de dataset de teste padrão Tensorflow
test_ds = tf.data.Dataset.from_generator(tensor_pattern_1(df_teste), output_signature = output_signature)

# Print dos tamanhos dos dados
train_frames, train_labels = next(iter(train_ds))
print(f'Shape of training set of frames: {train_frames.shape}')
print(f'Shape of training labels: {train_labels.shape}')

val_frames, val_labels = next(iter(val_ds))
print(f'Shape of validation set of frames: {val_frames.shape}')
print(f'Shape of validation labels: {val_labels.shape}')

test_frames, test_labels = next(iter(test_ds))
print(f'Shape of test set of frames: {test_frames.shape}')
print(f'Shape of test labels: {test_labels.shape}')

# Configuração para desempenho
AUTOTUNE = tf.data.AUTOTUNE

train_ds = train_ds.cache().shuffle(500).prefetch(buffer_size = AUTOTUNE)
val_ds = val_ds.cache().shuffle(500).prefetch(buffer_size = AUTOTUNE)

# Criação de dimensão adicional
train_ds = train_ds.batch(2)
val_ds = val_ds.batch(2)

train_frames, train_labels = next(iter(train_ds))
print(f'Shape of training set of frames: {train_frames.shape}')
print(f'Shape of training labels: {train_labels.shape}')

val_frames, val_labels = next(iter(val_ds))
print(f'Shape of validation set of frames: {val_frames.shape}')
print(f'Shape of validation labels: {val_labels.shape}')

Criação de Modelo de Classificação de Imagem


Para a criação de nosso modelo, utilizaremos uma base pré-treinada para otimizar o processo. No caso, utilizaremos o InceptionV3.

# Importação de bibliotecas necessárias
from tensorflow.keras.applications import InceptionV3
from tensorflow.keras.applications.inception_v3 import preprocess_input

# Carregar o modelo pré-treinado InceptionV3 sem as camadas densas
base_model = InceptionV3(input_shape=(100, 100, 3),include_top=False, weights='imagenet')

# Congelar as camadas convolucionais pré-treinadas
base_model.trainable = False

# Adaptar o modelo para processar as imagens
model = models.Sequential([
    base_model,
    layers.GlobalAveragePooling2D(),
    layers.Dropout(0.5),
    layers.Dense(128, activation='relu'),
    layers.Dropout(0.5),
    layers.Dense(3, activation='softmax')  # Saída binária para detecção de fadiga
])

# Compilar o modelo
model.compile(optimizer = tf.keras.optimizers.Adam(learning_rate=0.0001),
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

# Realizar o treinamento
history = model.fit(x = train_ds,
                    epochs = 10,
                    batch_size=32,
                    validation_data = val_ds)
ree

Podemos plotar os resultados com os seguintes comandos:

import matplotlib.pyplot as plt

def plot_history(history):
  """
    Plotting training and validation learning curves.

    Args:
      history: model history with all the metric measures
  """
  fig, (ax1, ax2) = plt.subplots(2)

  fig.set_size_inches(18.5, 10.5)

  # Plot loss
  ax1.set_title('Loss')
  ax1.plot(history.history['loss'], label = 'train')
  ax1.plot(history.history['val_loss'], label = 'test')
  ax1.set_ylabel('Loss')

  # Determine upper bound of y-axis
  max_loss = max(history.history['loss'] + history.history['val_loss'])

  ax1.set_ylim([0, np.ceil(max_loss)])
  ax1.set_xlabel('Epoch')
  ax1.legend(['Train', 'Validation'])

  # Plot accuracy
  ax2.set_title('Accuracy')
  ax2.plot(history.history['accuracy'],  label = 'train')
  ax2.plot(history.history['val_accuracy'], label = 'test')
  ax2.set_ylabel('Accuracy')
  ax2.set_ylim([0, 1])
  ax2.set_xlabel('Epoch')
  ax2.legend(['Train', 'Validation'])

  plt.show()

plot_history(history)
ree

Com o modelo treinado, configuraremos o dataset de teste e aplicaremos uma avaliação para verificar o desempenho.

# Criação de dimensão adicional
test_ds = test_ds.batch(2)

test_frames, test_labels = next(iter(test_ds))
print(f'Shape of testing set of frames: {test_frames.shape}')
print(f'Shape of testing labels: {test_labels.shape}')

# Avaliação
history.evaluate(test_ds, return_dict=True)
ree


Como observado, nosso modelo atingiu uma acurácia de 96%.


Podemos salvá-lo da seguinte maneira:

history.model.save('model_keras.h5')

Conclusão


Na execução da aplicação, o modelo está conseguindo prever se as pessoas estão em estado de alerta, sonolência ou bocejando, com uma precisão considerável. Porém, como temos vídeo em tempo real, certas precauções devem ser tomadas na programação para que as previsões sejam realizadas de forma ágil e precisa.


Mais uma vez gostaria de enfatizar o estudo preliminar do caso para a definição de um modelo a ser treinado. Esta etapa pode otimizar todo o processo e um caminho errado pode resultar em muito tempo perdido, como foi nesse caso.


Esta postagem está aberta a sugestões de melhoria, então fiquem à vontade para comentar.


Referências


Comentários


©2023 by Natural Engines. Flowing Knowledge.

bottom of page