Classificação de Imagens Territoriais com CNN
Vamos ensinar um computador a olhar uma foto e dizer se é floresta, geleira, montanha, praia, rua ou construção. Vai entender como uma CNN "enxerga" uma imagem — passo a passo, do zero.
Como um computador "vê" uma imagem?
Para você, uma foto de floresta é óbvia. Para o computador, é só uma grade de números — cada pixel tem um valor de 0 a 255 para vermelho, verde e azul. Uma imagem 150×150 pixels = 67.500 números.
Uma CNN (Convolutional Neural Network) aplica filtros sobre a imagem — igual ao filtro "desfoque" ou "nitidez" do Instagram — e aprende quais combinações de pixels indicam "floresta" vs. "praia" vs. "montanha".
Vamos construir duas CNNs e comparar: uma simples (linha de base) e uma regularizada com Batch Normalization + Dropout.
O que você vai precisar
- Python 3.10+
- TensorFlow / Keras — biblioteca principal para construir CNNs.
- Matplotlib / Seaborn — para visualizar imagens e resultados.
- Scikit-learn — para a matriz de confusão e relatório.
Dataset: Intel Image Classification (Kaggle) — pastas seg_train/, seg_test/ e seg_pred/ com imagens JPG de 6 categorias: buildings, forest, glacier, mountain, sea, street.
Importar bibliotecas e configurar ambiente
from pathlib import Path
import random
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow.keras import layers, models
from sklearn.metrics import classification_report, confusion_matrix
# SEED fixa a aleatoriedade — garante resultados idênticos em cada execução
SEED = 42
random.seed(SEED)
np.random.seed(SEED)
tf.random.set_seed(SEED)
print('TensorFlow:', tf.__version__)
Por que SEED? Redes neurais usam números aleatórios para inicializar os pesos. Sem fixar a semente, os resultados mudam a cada execução — impossível comparar modelos.
BASE_DIR = Path.cwd()
TRAIN_DIR = BASE_DIR / 'seg_train' / 'seg_train'
TEST_DIR = BASE_DIR / 'seg_test' / 'seg_test'
PRED_DIR = BASE_DIR / 'seg_pred' / 'seg_pred'
for pasta in [TRAIN_DIR, TEST_DIR, PRED_DIR]:
print(pasta, '-> existe?', pasta.exists())
O que descobrimos? As 3 pastas existem e estão no lugar certo.
Explorar o dataset de imagens
Antes de treinar, vamos contar quantas imagens temos por classe e visualizar amostras — para ter certeza de que os dados fazem sentido.
def contar_imagens_por_classe(diretorio):
dados = []
for classe_dir in sorted(diretorio.iterdir()):
if classe_dir.is_dir():
quantidade = len(list(classe_dir.glob('*.jpg')))
dados.append({'classe': classe_dir.name, 'imagens': quantidade})
return pd.DataFrame(dados)
contagem_treino = contar_imagens_por_classe(TRAIN_DIR)
display(contagem_treino)
print('Total de treino:', contagem_treino['imagens'].sum())
# Visualizar uma imagem de cada classe
classes = sorted([p.name for p in TRAIN_DIR.iterdir() if p.is_dir()])
plt.figure(figsize=(12, 7))
for i, nome_classe in enumerate(classes):
imagens = list((TRAIN_DIR / nome_classe).glob('*.jpg'))
img = tf.keras.utils.load_img(random.choice(imagens), target_size=(150, 150))
plt.subplot(2, 3, i + 1)
plt.imshow(img)
plt.title(nome_classe)
plt.axis('off')
plt.tight_layout()
plt.show()
O que descobrimos? ~14.000 imagens de treino em 6 categorias. As imagens têm tamanhos variados — precisamos padronizar para 150×150 pixels.
Carregar e pré-processar as imagens
image_dataset_from_directory lê as pastas, redimensiona, embaralha e cria batches automaticamente — economiza muitas linhas de código.
IMG_SIZE = (150, 150)
BATCH_SIZE = 32 # aprende 32 imagens por vez
VALIDATION_SPLIT = 0.2 # 20% do treino vira validação
train_ds = tf.keras.utils.image_dataset_from_directory(
TRAIN_DIR,
validation_split=VALIDATION_SPLIT,
subset='training',
seed=SEED,
image_size=IMG_SIZE,
batch_size=BATCH_SIZE,
label_mode='int' # 0=buildings, 1=forest, 2=glacier, 3=mountain, 4=sea, 5=street
)
val_ds = tf.keras.utils.image_dataset_from_directory(
TRAIN_DIR,
validation_split=VALIDATION_SPLIT,
subset='validation',
seed=SEED,
image_size=IMG_SIZE,
batch_size=BATCH_SIZE,
label_mode='int'
)
AUTOTUNE = tf.data.AUTOTUNE
# .prefetch() carrega o próximo batch enquanto o atual é processado — sem tempo ocioso
train_ds = train_ds.prefetch(buffer_size=AUTOTUNE)
val_ds = val_ds.prefetch(buffer_size=AUTOTUNE)
test_ds = test_ds.prefetch(buffer_size=AUTOTUNE)
O que descobrimos? Dataset carregado com eficiência. O prefetch elimina tempo ocioso entre batches, acelerando o treino.
Construir e treinar a CNN simples
A CNN simples é a nossa linha de base. 3 blocos Conv2D + MaxPooling detectam padrões progressivamente mais complexos: bordas → texturas → formas.
def criar_cnn_simples(num_classes):
return models.Sequential([
layers.Input(shape=(150, 150, 3)),
layers.Rescaling(1./255), # pixels 0-255 → 0-1
# Bloco 1: detecta bordas e cores simples
layers.Conv2D(32, (3, 3), activation='relu'),
layers.MaxPooling2D((2, 2)), # reduz a imagem pela metade
# Bloco 2: detecta texturas
layers.Conv2D(64, (3, 3), activation='relu'),
layers.MaxPooling2D((2, 2)),
# Bloco 3: detecta formas complexas
layers.Conv2D(128, (3, 3), activation='relu'),
layers.MaxPooling2D((2, 2)),
layers.Flatten(), # grade 2D → vetor 1D
layers.Dense(128, activation='relu'),
layers.Dense(num_classes, activation='softmax') # 6 probabilidades de saída
])
cnn_simples = criar_cnn_simples(num_classes=6)
cnn_simples.compile(
optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
historico_simples = cnn_simples.fit(train_ds, validation_data=val_ds, epochs=8)
O que descobrimos? A CNN simples aprende a classificar imagens sem ensinamento explícito. O MaxPooling é essencial: força a rede a focar em características globais, não em pixels específicos.
CNN regularizada: Batch Normalization + Dropout
Batch Normalization normaliza as ativações entre camadas — garante que os valores não explodam nem desapareçam. Dropout desativa neurônios aleatoriamente no treino — a rede não consegue "decorar" e aprende padrões mais gerais.
def criar_cnn_regularizada(num_classes):
return models.Sequential([
layers.Input(shape=(150, 150, 3)),
layers.Rescaling(1./255),
# Bloco 1 com regularização
layers.Conv2D(32, (3, 3), activation='relu'),
layers.BatchNormalization(), # normaliza as ativações
layers.MaxPooling2D((2, 2)),
layers.Dropout(0.20), # desativa 20% dos neurônios aleatoriamente
# Bloco 2
layers.Conv2D(64, (3, 3), activation='relu'),
layers.BatchNormalization(),
layers.MaxPooling2D((2, 2)),
layers.Dropout(0.25),
# Bloco 3
layers.Conv2D(128, (3, 3), activation='relu'),
layers.BatchNormalization(),
layers.MaxPooling2D((2, 2)),
layers.Dropout(0.30),
layers.Flatten(),
layers.Dense(256, activation='relu'),
layers.BatchNormalization(),
layers.Dropout(0.50), # Dropout mais forte na camada densa
layers.Dense(num_classes, activation='softmax')
])
cnn_reg = criar_cnn_regularizada(num_classes=6)
cnn_reg.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
historico_reg = cnn_reg.fit(train_ds, validation_data=val_ds, epochs=8)
O que descobrimos? A CNN regularizada apresentou curvas de validação mais estáveis. As categorias mais difíceis: glacier vs. mountain — ambas têm muito branco e cinza.
Código completo e referências
github.com/Jair-pc/puc-minas-arquiteturas_de_deep_learning
Dataset: Intel Image Classification — disponível no Kaggle.