Classificacao de Imagens RGB com SVM

CEAO-802 — Metodos de Analise de Dados

1T Generoso
1T João Marcos
1T Vitor Cesa

Introducao e Dataset

Contexto do Problema

  • Imagens aéreas coletadas por drone sobre Guaratinguetá (SP)
  • Objetivo: classificar cada recorte em uma das 13 classes de cobertura urbana
  • Dataset disponibiliza 4 modalidades: RGB dia, RGB noite, TIR dia, TIR noite
  • Este trabalho utiliza somente as imagens RGB

Note

As imagens já vinham pré-rotuladas pelo nome do arquivo. Exemplo: C10_2023_06_M_01_RGB.tiff → classe C10 (Piscina), junho de 2023, manhã

O problema é de classificação supervisionada: aprende-se com exemplos rotulados e avalia-se em exemplos não vistos.

As 13 Classes

Classe Descrição
C1 Vegetação Arbórea
C2 Vegetação Rasteira
C3 Solo Exposto
C4 Pavimento Asfáltico
C5 Pavimento de Solo Estabilizado
C6 Telhado Cerâmico
C7 Telhado de Fibrocimento
Classe Descrição
C8 Telhado Metálico
C9 Placa Fotovoltaica
C10 Piscina
C11 Pedra Sabão
C12 Caixa D’água
C13 Veículos

Amostra Balanceada

Metodologia

Pipeline de Classificação

Transfer Learning — O que é?

MobileNetV2 pré-treinada no ImageNet

  • Treinada em 1,2 milhão de imagens de 1000 categorias
  • Carregada sem a camada de classificação final
  • Imagem → redimensionada para 224 × 224 px
  • Saída: vetor de 1280 valores por imagem

Esses 1280 valores capturam padrões visuais complexos: bordas, texturas, formas e estruturas aprendidas em milhões de imagens.

Tradicional Transfer
Features ~60 1280
Tipo Manual CNN
Exemplos Média RGB Padrões visuais
Treinamento Não Pré-treinado

Tip

Não é necessário treinar a rede do zero — apenas reutilizamos o que ela já aprendeu.

Código — Carregando o Modelo e Extraindo Features

library(keras3)

# Carregar MobileNetV2 sem a camada de classificação
modelo_base <- application_mobilenet_v2(
  include_top = FALSE,     # remove a camada final de 1000 classes
  weights     = "imagenet",# pesos pré-treinados no ImageNet
  pooling     = "avg",     # GlobalAveragePooling -> vetor 1D
  input_shape = c(224L, 224L, 3L)
)
# modelo_base$output_shape  -->  (None, 1280)

# Extrair features de uma imagem
img  <- image_load(arquivo, target_size = c(224L, 224L))
arr  <- image_to_array(img)                       # array 224x224x3
arr  <- array_reshape(arr, c(1L, 224L, 224L, 3L)) # batch de 1
arr  <- (arr / 127.5) - 1.0                       # normalização MobileNetV2

feat <- predict(modelo_base, arr, verbose = 0L)
# feat: matrix 1 x 1280

Modelos Avaliados

Os 5 Modelos

# Modelo Kernel / Tipo Parâmetros
1 SVM linear Linear cost = 1
2 SVM radial RBF cost = 10, gamma = 0.01
3 SVM radial ajustado RBF cost e gamma via grid search
4 PCA + SVM radial RBF PCA (≥95% variância) + RBF
5 Random Forest 500 árvores

Note

SVM (Support Vector Machine): encontra o hiperplano que maximiza a margem entre as classes. Com kernel RBF, fronteiras não-lineares são possíveis via kernel trick.

Código — Treinamento dos Modelos

library(e1071); library(randomForest); library(caret)

# Pré-processamento: remover NZV e normalizar (center + scale)
preproc      <- preProcess(x_treino, method = c("center", "scale"))
x_treino_n   <- predict(preproc, x_treino)
x_teste_n    <- predict(preproc, x_teste)

# Modelo 1: SVM linear
modelo_linear <- svm(x = x_treino_n, y = y_treino,
                     kernel = "linear", cost = 1, scale = FALSE)

# Modelo 3: Ajuste automático de hiperparâmetros (grid search + CV interna)
ajuste <- tune.svm(x = x_treino_n, y = y_treino, kernel = "radial",
                   cost  = c(0.1, 1, 10, 100),
                   gamma = c(0.001, 0.01, 0.05, 0.1),
                   tunecontrol = tune.control(cross = 5))

# Modelo 5: Random Forest
modelo_rf <- randomForest(x = x_treino_n, y = y_treino,
                          ntree = 500, importance = TRUE)

Avaliação dos Modelos

Estratégia 1 — Holdout 70/30

O que é?

  • Divisão única e aleatória do dataset
  • 70% → treino (~455 imagens)
  • 30% → teste (~195 imagens)
  • Estratificada por classe

Limitação: o resultado depende de como essa divisão ocorreu. Com datasets pequenos, pode ser instável.

Estratégia 2 — Validação Cruzada K-Fold (k=5)

O que é?

  • Dataset dividido em 5 partes iguais e estratificadas
  • Cada parte é usada uma vez como teste
  • Acurácia final = média das 5 rodadas

Vantagem: toda imagem aparece no teste exatamente uma vez — estimativa mais robusta e confiável.

Código — Validação Cruzada K-Fold

folds <- createFolds(dados$classe, k = 5, list = TRUE, returnTrain = FALSE)

for (fold_i in seq_len(5)) {
  idx_teste  <- folds[[fold_i]]
  idx_treino <- setdiff(seq_len(nrow(dados)), idx_teste)

  # Pré-processar com base APENAS no treino do fold
  pp   <- preProcess(dados[idx_treino, preditores], method = c("center","scale"))
  x_tr <- predict(pp, dados[idx_treino, preditores])
  x_te <- predict(pp, dados[idx_teste,  preditores])

  # Treinar modelos
  m_linear <- svm(x = x_tr, y = dados$classe[idx_treino],
                  kernel = "linear", cost = 1, scale = FALSE)

  # Avaliar
  pred <- predict(m_linear, x_te)
  cm   <- confusionMatrix(pred, dados$classe[idx_teste])

  # Guardar métricas do fold
  metricas_folds[[fold_i]] <- extrair_metricas(cm)
}

resultados_kfold <- bind_rows(metricas_folds) %>%
  group_by(modelo) %>%
  summarise(across(everything(), mean))

Comparação: Holdout vs K-Fold

Variáveis Mais Importantes

Conclusão

Conclusão

Resultado principal

  • Melhor modelo: SVM linear — acurácia de 40.2% (k-fold)
  • Acerto aleatório com 13 classes ≈ 7,7% — obtivemos ~40%
  • Holdout e k-fold convergiram → resultado estável e confiável

Por que SVM linear ganhou do radial?

Os 1280 atributos da MobileNetV2 já são suficientemente representativos para que as classes sejam aproximadamente linearmente separáveis nesse espaço — o kernel RBF não agrega ganho relevante.

Trabalhos futuros

  • Outros extratores CNN: EfficientNetB0, ResNet50, VGG19
  • Incorporar canal TIR (térmico) ao vetor de atributos
  • Aumentar a amostra por classe (atualmente 50 imagens)