---
title: "Classificacao de Imagens RGB com SVM"
subtitle: "CEAO-802 — Metodos de Analise de Dados"
author:
  - "1T Generoso"
  - "1T João Marcos"
  - "1T Vitor Cesa"
format:
  pptx:
    fig-width: 9
    fig-height: 4.5
execute:
  warning: false
  message: false
  echo: false
---

```{r setup}
library(tidyverse)
library(knitr)
library(scales)

resultados_holdout <- read_csv("outputs/resultados_modelos_rgb.csv", show_col_types = FALSE)
resultados_kfold   <- read_csv("outputs/resultados_kfold_rgb.csv",   show_col_types = FALSE)
importancia_df     <- read_csv("outputs/importancia_variaveis_rgb.csv", show_col_types = FALSE)

cores_modelos <- c(
  "SVM linear"          = "#2C7FB8",
  "SVM radial"          = "#7FCDBB",
  "SVM radial ajustado" = "#41B6C4",
  "PCA + SVM radial"    = "#1D91C0",
  "Random Forest"       = "#225EA8"
)
```

# 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**
- As imagens já vinham **pré-rotuladas** pelo nome do arquivo

Exemplo: `C10_2023_06_M_01_RGB.tiff` → classe **C10 (Piscina)**, junho 2023, manhã

## As 13 Classes

:::: {.columns}

::: {.column width="50%"}
| Classe | Descrição |
|:------:|-----------|
| C1 | Vegetação Arbórea |
| C2 | Vegetação Rasteira |
| C3 | Solo Exposto |
| C4 | Pavimento Asfáltico |
| C5 | Solo Estabilizado |
| C6 | Telhado Cerâmico |
| C7 | Telhado de Fibrocimento |
:::

::: {.column width="50%"}
| 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

```{r dataset-plot}
tibble(
  classe = factor(paste0("C", 1:13), levels = paste0("C", 1:13)),
  n = 50
) |>
  ggplot(aes(x = classe, y = n)) +
  geom_col(fill = "#2C7FB8", width = 0.7) +
  geom_text(aes(label = n), vjust = -0.5, size = 4, fontface = "bold") +
  scale_y_continuous(limits = c(0, 62)) +
  labs(
    title = "50 imagens por classe — amostra estratificada e balanceada",
    subtitle = "Dataset completo: 1.248 imagens TIFF  |  Amostra utilizada: 650 imagens",
    x = "Classe", y = "Imagens na amostra"
  ) +
  theme_minimal(base_size = 14) +
  theme(panel.grid.major.x = element_blank())
```

# Metodologia

## Pipeline de Classificação

```{r pipeline, fig.height=3.2}
tibble(
  x     = 1:5,
  label = c("Imagens\nRGB", "MobileNetV2\n1280 features",
            "Normalização\n+ PCA (opt.)", "SVM / RF",
            "Avaliação\nHoldout / K-Fold")
) |>
  ggplot(aes(x = x, y = 1)) +
  geom_point(size = 20, color = "#2C7FB8") +
  geom_text(aes(label = label), size = 3.8, color = "white",
            fontface = "bold", lineheight = 1.0) +
  annotate("segment",
           x = c(1.38,2.38,3.38,4.38), xend = c(1.62,2.62,3.62,4.62),
           y = 1, yend = 1, linewidth = 1.4, color = "grey40",
           arrow = arrow(length = unit(0.3, "cm"), type = "closed")) +
  scale_x_continuous(limits = c(0.5, 5.5)) +
  scale_y_continuous(limits = c(0.6, 1.4)) +
  theme_void()
```

## Transfer Learning — MobileNetV2

:::: {.columns}

::: {.column width="55%"}
**O que é transfer learning?**

A rede MobileNetV2 foi treinada no **ImageNet** (1,2 milhão de imagens, 1000 categorias). Reutilizamos essa rede como **extrator de atributos**, sem retreiná-la.

- Imagem redimensionada para **224 × 224 px**
- Camada de classificação removida
- Saída: **vetor de 1280 valores** por imagem
- Cada valor representa um padrão visual aprendido
:::

::: {.column width="45%"}
| | Tradicional | Transfer |
|--|:-----------:|:-------:|
| Nº features | ~60 | **1280** |
| Origem | Manual | CNN |
| Treinamento | Não | Pré-treinado |
| Padrões | Cor/textura | Alto nível |

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

::::

## Código — Carregando o Modelo

```r
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)
)
# Saída: vetor de 1280 atributos por imagem
```

## Código — Extraindo Features por Imagem

```r
# Para cada imagem da amostra:
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 imagem
arr  <- (arr / 127.5) - 1.0  # normalização padrão do MobileNetV2 [-1, 1]

feat <- predict(modelo_base, arr, verbose = 0L)
# feat: vetor de 1280 valores — atributos visuais de alto nível
```

Processamento em **lotes de 16 imagens** para eficiência, com **cache em disco** para evitar reprocessamento.

# Modelos Avaliados

## Os 5 Modelos

| # | Modelo | 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** | Ensemble | 500 árvores |

**SVM** 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

```r
library(e1071); library(caret); library(randomForest)

# Pré-processamento: remover variância nula e normalizar
preproc    <- preProcess(x_treino, method = c("center", "scale"))
x_treino_n <- predict(preproc, x_treino)

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

# Modelo 3: Ajuste de hiperparâmetros (grid search com 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)
```

# Avaliacao dos Modelos

## Estratégia 1 — Holdout 70/30

:::: {.columns}

::: {.column width="38%"}
**Divisão única e aleatória**

- 70% treino (~455 imagens)
- 30% teste (~195 imagens)
- Estratificada por classe

**Limitação:** com datasets menores, o resultado pode variar conforme a divisão escolhida.
:::

::: {.column width="62%"}
```{r holdout-plot}
ggplot(resultados_holdout,
       aes(x = reorder(modelo, acuracia), y = acuracia, fill = modelo)) +
  geom_col(show.legend = FALSE, width = 0.7) +
  geom_text(aes(label = percent(acuracia, accuracy = 0.1)),
            hjust = -0.12, size = 4.5, fontface = "bold") +
  coord_flip(ylim = c(0, 0.53)) +
  scale_fill_manual(values = cores_modelos) +
  labs(title = "Acurácia — Holdout 70/30", x = NULL, y = "Acurácia") +
  theme_minimal(base_size = 13)
```
:::

::::

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

:::: {.columns}

::: {.column width="38%"}
**5 partições estratificadas**

- Cada parte usada **uma vez** como teste
- Acurácia final = **média das 5 rodadas**
- Toda imagem aparece no teste

**Vantagem:** estimativa mais **robusta** — não depende de uma única divisão.
:::

::: {.column width="62%"}
```{r kfold-plot}
ggplot(resultados_kfold,
       aes(x = reorder(modelo, acuracia), y = acuracia, fill = modelo)) +
  geom_col(show.legend = FALSE, width = 0.7) +
  geom_text(aes(label = percent(acuracia, accuracy = 0.1)),
            hjust = -0.12, size = 4.5, fontface = "bold") +
  coord_flip(ylim = c(0, 0.53)) +
  scale_fill_manual(values = cores_modelos) +
  labs(title = "Acurácia média — K-Fold (k=5)", x = NULL, y = "Acurácia") +
  theme_minimal(base_size = 13)
```
:::

::::

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

```r
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 apenas com dados do treino deste fold
  pp   <- preProcess(x[idx_treino, ], method = c("center", "scale"))
  x_tr <- predict(pp, x[idx_treino, ])
  x_te <- predict(pp, x[idx_teste,  ])
  y_tr <- dados$classe[idx_treino]
  y_te <- dados$classe[idx_teste]

  modelo <- svm(x = x_tr, y = y_tr, kernel = "linear", cost = 1)
  pred   <- predict(modelo, x_te)
  cm     <- confusionMatrix(pred, y_te)

  metricas_folds[[fold_i]] <- extrair_metricas("SVM linear", cm)
}
# Média das 5 rodadas
resultados_kfold <- bind_rows(metricas_folds) |>
  group_by(modelo) |> summarise(across(everything(), mean))
```

## Comparação: Holdout vs K-Fold

```{r comparacao-plot, fig.height=5}
bind_rows(
  resultados_holdout %>% mutate(estrategia = "Holdout 70/30"),
  resultados_kfold   %>% mutate(estrategia = "K-Fold (k=5)")
) |>
  ggplot(aes(x = reorder(modelo, acuracia), y = acuracia, fill = estrategia)) +
  geom_col(position = position_dodge(width = 0.72), width = 0.68) +
  geom_text(aes(label = percent(acuracia, accuracy = 0.1)),
            position = position_dodge(width = 0.72),
            hjust = -0.1, size = 4) +
  coord_flip(ylim = c(0, 0.55)) +
  scale_fill_manual(values = c("Holdout 70/30" = "#2C7FB8",
                               "K-Fold (k=5)"  = "#E06C00")) +
  labs(title = "Holdout 70/30 vs K-Fold (k=5)",
       x = NULL, y = "Acurácia", fill = NULL) +
  theme_minimal(base_size = 13) +
  theme(legend.position = "top")
```

## Variáveis Mais Importantes (Random Forest)

```{r importancia-plot, fig.height=5}
importancia_df |>
  slice_head(n = 15) |>
  ggplot(aes(x = reorder(variavel, importancia), y = importancia)) +
  geom_col(fill = "#2C7FB8", width = 0.75) +
  geom_text(aes(label = round(importancia, 2)), hjust = -0.15, size = 4) +
  coord_flip(ylim = c(0, max(importancia_df$importancia[1:15]) * 1.18)) +
  labs(
    title = "Top 15 features mais importantes — Random Forest",
    subtitle = "feat_XXXX = neurônio da camada final do MobileNetV2",
    x = NULL, y = "Importância (Mean Decrease Gini)"
  ) +
  theme_minimal(base_size = 13)
```

# Conclusao

## Conclusão

```{r conclusao-calc}
melhor_modelo_kf <- resultados_kfold$modelo[which.max(resultados_kfold$acuracia)]
melhor_acc_kf    <- max(resultados_kfold$acuracia)
```

**Resultado principal**

- Melhor modelo: **`r melhor_modelo_kf`** com acurácia de **`r percent(melhor_acc_kf, accuracy = 0.1)`** (k-fold)
- Acerto aleatório com 13 classes ≈ **7,7%** → resultado ~5× melhor
- Holdout e k-fold convergiram → estimativa **estável e confiável**

**Por que SVM linear ganhou do radial?**

Os 1280 atributos da MobileNetV2 já tornam as classes aproximadamente **linearmente separáveis** — o kernel RBF não agrega ganho relevante neste caso.

**Trabalhos futuros**

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