{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Capítulo 2: Processamento de Imagens" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![cover](cover.jpeg)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## **Técnicas para o Pré-processamento de Imagens**\n", "\n", "O pré-processamento de imagens é uma etapa essencial em muitas aplicações de visão computacional. Consiste na aplicação de técnicas para melhorar a qualidade da imagem, remover ruídos e prepará-la para etapas posteriores de análise. Como destacam Gonzalez e Woods (2018), entre os métodos mais utilizados destaca-se a **normalização**, que padroniza características visuais para facilitar o processamento.\n", "\n", "### Normalização\n", "\n", "**Objetivos da Normalização**\n", "A normalização ajusta e padroniza as propriedades de uma imagem, tornando-a mais adequada para análise. Segundo Gonzalez e Woods (2018), suas principais vantagens incluem:\n", "\n", "- **Melhoria da Qualidade**: Corrige distorções, ajusta o contraste e remove ruídos, aumentando a precisão de análises posteriores.\n", "- **Padronização**: Garante consistência em conjuntos de imagens, facilitando comparações.\n", "- **Eficiência em Algoritmos**: Técnicas de visão computacional, como detecção de bordas e reconhecimento de padrões, dependem de imagens pré-processadas para melhor desempenho.\n", "- **Redução de Ruído**: Minimiza interferências (como variações de iluminação ou artefatos) que poderiam comprometer os resultados.\n", "\n", "#### Técnicas de Normalização\n", "Diversas técnicas podem ser aplicadas, dependendo do objetivo e do tipo de imagem. As mais comuns são:\n", "\n", "**Equalização de Histograma**\n", "Como descrevem Gonzalez e Woods (2018), esta técnica redistribui os níveis de intensidade da imagem para maximizar o contraste, destacando detalhes antes pouco visíveis. É particularmente útil em imagens com baixa variação tonal.\n", "\n", "**Filtros Espaciais**\n", "Aplicados diretamente nos pixels da imagem para suavizar ou remover ruídos. Incluem:\n", "- **Filtro de Média**: Reduz ruídos, mas pode borrar detalhes.\n", "- **Filtro de Mediana**: Eficaz contra ruídos do tipo *sal e pimenta*.\n", "- **Filtro Gaussiano**: Suaviza a imagem preservando melhor as bordas.\n", "\n", "**Transformadas de Fourier**\n", "Convertem a imagem para o **domínio da frequência**, permitindo a remoção de ruídos periódicos ou padrões indesejados (GONZALEZ; WOODS, 2018).\n", "\n", "**Normalização de Cores**\n", "Ajusta as cores da imagem para corrigir variações de iluminação ou balanço de branco, sendo essencial em aplicações como reconhecimento facial.\n", "\n", "**Normalização de Intensidade**\n", "Mapeia os valores de pixel para uma escala padronizada (ex.: [0, 1] ou [-1, 1]), garantindo consistência para algoritmos de aprendizado de máquina.\n", "\n", "**Normalização de Tamanho**\n", "Redimensiona imagens para dimensões fixas, sendo crucial em redes neurais convolucionais (CNNs), onde todas as entradas devem ter o mesmo tamanho.\n", "\n", "**Normalização de Histograma**\n", "Ajusta a distribuição de intensidades para seguir um padrão específico, facilitando a comparação entre diferentes imagens (GONZALEZ; WOODS, 2018)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Primeiro Exemplo de Normalização \n", "\n", "**Normalização de Intensidade**\n", "\n", "Vamos começar com o exemplo mais básico de normalização — a **normalização de intensidade**, que ajusta os valores dos pixels para uma faixa específica. Este é um excelente ponto de partida para entender o pré-processamento de imagens.\n", "\n", "**Definição Matemática**\n", "\n", "A **normalização min-max** transforma cada valor $x$ para um novo valor $x'$ no intervalo desejado, geralmente $[0, 1]$, usando a fórmula:\n", "\n", "$$\n", "x' = \\frac{x - x_{\\text{min}}}{x_{\\text{max}} - x_{\\text{min}}}\n", "$$\n", "\n", "- $x$ é o valor original do pixel; \n", "- $x_{\\text{min}}$ e $x_{\\text{max}}$ são os valores mínimo e máximo da imagem; \n", "- $x'$ será o valor normalizado no intervalo $[0, 1]$.\n", "\n", "No caso de imagens em 8 bits, é comum usar diretamente:\n", "\n", "$$\n", "x' = \\frac{x}{255}\n", "$$\n", "\n", "pois os valores de $x$ estão entre 0 e 255.\n", "\n", "**Exemplo Prático: Normalizando para [0, 1]**\n", "\n", "O código abaixo demonstra como normalizar uma imagem em tons de cinza para o intervalo $[0, 1]$ usando Python e OpenCV:\n", "\n", "Imagem Original: [Sun](sun.jpeg)\n", "\n", "```python\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "\n", "# Carregar a imagem\n", "img = plt.imread('sun.jpeg')\n", "\n", "# Converter para escala de cinza\n", "if img.ndim == 3:\n", " img_gray = img[..., :3].mean(axis=2) # Média dos canais RGB (ignora alpha)\n", "else:\n", " img_gray = img # Já está em grayscale\n", "\n", "# Normalizar diretamente para [0, 1]\n", "if img_gray.max() > 1.0: # Se os valores estiverem em [0, 255]\n", " img_normalizada = img_gray.astype('float32') / 255.0\n", "else: # Se já estiver em [0, 1]\n", " img_normalizada = img_gray.astype('float32')\n", "\n", "# Exibir resultados\n", "plt.figure(figsize=(12, 6))\n", "\n", "# Subplot para imagem original (em escala de cinza)\n", "plt.subplot(1, 2, 1)\n", "plt.imshow(img_gray, cmap='gray', vmin=0, vmax=255 if img_gray.max() > 1.0 else 1)\n", "plt.title(f'Imagem Original\\n(0-{\"255\" if img_gray.max() > 1.0 else \"1\"})')\n", "plt.axis('off')\n", "\n", "# Subplot para imagem normalizada\n", "plt.subplot(1, 2, 2)\n", "plt.imshow(img_normalizada, cmap='gray', vmin=0, vmax=1)\n", "plt.title('Imagem Normalizada\\n(0.0-1.0)')\n", "plt.axis('off')\n", "\n", "plt.tight_layout()\n", "plt.show()\n", "\n", "# Valores mínimos/máximos\n", "print(f\"Original - Min: {img_gray.min()}, Max: {img_gray.max()}\")\n", "print(f\"Normalizada - Min: {img_normalizada.min():.4f}, Max: {img_normalizada.max():.4f}\")\n", "```\n", "\n", "![](sun_norm_int.png)\n", "\n", "\n", "**Por que normalizar para [0, 1]?**\n", "\n", "- **Padronização**: Todos os pixels estarão na mesma escala \n", "- **Compatibilidade**: Muitos algoritmos de ML esperam valores nesse intervalo \n", "- **Estabilidade numérica**: Reduz problemas com overflow/underflow em cálculos \n", "- **Facilidade de visualização**: Valores entre 0 e 1 são intuitivos\n", "\n", "Podemos também normalizar cada canal (R, G, B) separadamente para manter a imagem colorida normalizada, o que preserva a relação entre as cores originais." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 📝 Exercício: Normalização de Imagem Colorida por Canal RGB\n", "\n", "**Objetivo:** Aprender a normalizar imagens coloridas considerando as características individuais de cada canal de cor.\n", "\n", "**Tarefa:** \n", "- Carregar uma imagem colorida em formato JPEG ou PNG \n", "- Separar a imagem em seus três canais de cor: Vermelho (R), Verde (G) e Azul (B) \n", "- Observar que cada canal possui seus próprios valores mínimo e máximo de intensidade \n", "- Normalizar cada canal individualmente para o intervalo [0, 1], onde: \n", " * O valor 0 corresponde à intensidade mínima encontrada no canal \n", " * O valor 1 corresponde à intensidade máxima encontrada no canal \n", "- Recombinar os três canais normalizados para obter a imagem colorida final \n", "\n", "**Resultado Esperado.** Uma nova versão da imagem onde: \n", "- Cada pixel em cada canal terá valores entre 0 e 1 \n", "- A aparência visual original será mantida \n", "- As proporções entre os canais serão preservadas \n", "- A distribuição de intensidades em cada canal estará normalizada \n", "\n", "**📌 Importante** \n", "Lembre-se que a normalização deve considerar os valores extremos específicos de cada canal, pois podem variar entre os canais R, G e B." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Histograma de Imagens\n", "\n", "Um histograma de imagem é uma representação gráfica que descreve a distribuição das intensidades dos pixels. Ele indica quantos pixels possuem cada valor de intensidade — normalmente de 0 a 255 em imagens de 8 bits.\n", "\n", "**Definição Matemática**\n", "\n", "O histograma de uma imagem digital é uma função discreta representada por:\n", "\n", "$$\n", "H(k) = n_k \\quad \\text{para} \\quad k \\in [0, L-1]\n", "$$\n", "\n", "Onde:\n", "- $L$ é o número de níveis de intensidade possíveis (tipicamente $L = 256$ para imagens de 8 bits);\n", "- $n_k$ é o número de pixels com intensidade igual a $k$;\n", "- $N$ é o número total de pixels na imagem: $N = \\sum_{k=0}^{L-1} n_k$\n", "\n", "A versão **normalizada** do histograma, interpretada como uma função de probabilidade, é dada por:\n", "\n", "$$\n", "P(k) = \\frac{H(k)}{N}\n", "$$\n", "\n", "### Tabela: Interpretação de Histogramas\n", "\n", "| **Característica** | **Comportamento no Histograma** | **Interpretação** | **Exemplo Prático** | **Ajuste Sugerido** |\n", "|---------------------|-------------------------------|----------------------------------------------------------------------------------|---------------------------------------------|---------------------------------------------|\n", "| **Distribuição** | Picos agudos | Grandes áreas com tons uniformes (ex: céu, paredes) | Céu azul sem nuvens | Aplicar texturas ou variações tonais |\n", "| | Vales profundos | Falta de pixels na faixa tonal (pode indicar falta de detalhes) | Transições bruscas entre objetos | Suavizar transições ou equalizar histograma |\n", "| **Contraste** | Amplo (0-255) | Boa distribuição tonal - imagem com alto contraste | Cenas bem iluminadas com sombras definidas | Manter como referência |\n", "| | Estreito (<50% da escala) | Baixo contraste - tons concentrados em faixa limitada | Neblina ou fotos em condições de baixa luz | Equalização ou ajuste de curvas |\n", "| | Cortado nos extremos | Perda de informação (clipping) em sombras ou altas-luzes | Reflexos em água ou sol direto | Reduzir exposição ou usar HDR |\n", "| **Exposição** | Deslocado à esquerda (0-127) | Subexposição - detalhes escuros perdidos | Fotografia noturna mal exposta | Aumentar brilho ou sombras |\n", "| | Deslocado à direita (128-255) | Superexposição - áreas estouradas (highlight clipping) | Neve ou cenas muito claras | Reduzir brilho ou recuperar altas-luzes |\n", "| | Curva balanceada (~128) | Exposição equilibrada - detalhes visíveis em sombras e altas-luzes | Retrato em luz difusa | Ideal - nenhuma correção necessária |\n", "| **Casos Especiais** | Bimodal | Cena com dois grupos tonais dominantes (ex: objeto claro em fundo escuro) | Silhuetas contra o céo | Avaliar se é efeito desejado |\n", "| | Multimodal | Vários objetos com tons distintos (ex: cena com múltiplos elementos coloridos) | Natureza com flores coloridas | Processamento por regiões |\n", "\n", "**Calculando o Histograma Para Imagem em Escala de Cinza:**\n", "\n", "```python\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "# Carrega a imagem\n", "img = plt.imread('sun.jpeg')\n", "\n", "# Converte para escala de cinza, se for colorida (com 3 canais RGB)\n", "if img.ndim == 3: \n", " img = img.mean(axis=2) # Faz a média dos 3 canais (R, G, B)\n", "\n", "# Normaliza para faixa [0, 1] se os valores estiverem acima de 1\n", "if img.max() > 1.0: \n", " img = img / 255.0 # Normaliza dividindo por 255\n", "\n", "# Calcula o histograma usando NumPy (agora com range [0, 1])\n", "hist, bins = np.histogram(img, bins=256, range=(0, 1))\n", "\n", "# Visualização da imagem e do histograma\n", "fig, axes = plt.subplots(1, 2, figsize=(12, 5))\n", "\n", "# Exibe a imagem em tons de cinza (agora com vmax=1)\n", "axes[0].imshow(img, cmap='gray', vmin=0, vmax=1)\n", "axes[0].axis('off')\n", "axes[0].set_title('Imagem em Escala de Cinza')\n", "\n", "# Plota o histograma de intensidades\n", "axes[1].plot(bins[:-1], hist, color='black') # bins[:-1] para alinhar com os valores de hist\n", "axes[1].set_title('Distribuição de Intensidades')\n", "axes[1].set_xlabel('Valor do Pixel (0 a 1)')\n", "axes[1].set_ylabel('Frequência')\n", "\n", "plt.tight_layout()\n", "plt.show()\n", "```\n", "\n", "![](sun_histogram.png)\n", "\n", "A função `np.histogram` **calcula a distribuição dos níveis de intensidade** da imagem, retornando dois arrays:\n", "\n", "- `hist`: o número de pixels em cada faixa de intensidade (ou *bin*);\n", "- `bins`: os valores que delimitam cada faixa (inclusive o limite superior final).\n", "\n", "```python\n", "hist, bins = np.histogram(img, bins=256, range=(0, 256))\n", "```\n", "Você pode usar `plt.plot()` para uma linha ou `plt.bar()` para barras verticais:\n", "\n", "```python\n", "plt.plot(bins[:-1], hist, color='black') # bins[:-1] para alinhar com hist\n", "```\n", "\n", "\n", "**Para Imagem Colorida (RGB):**\n", "\n", "```python\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "# Carrega a imagem colorida\n", "img_color = plt.imread('sun.jpeg')\n", "\n", "# Normaliza para [0, 1] se os valores estiverem acima de 1\n", "if img_color.max() > 1.0:\n", " img_color = img_color / 255.0\n", "\n", "# Calcula os histogramas dos canais e armazena os máximos\n", "colors = ('red', 'green', 'blue')\n", "hist_list = []\n", "max_freq = 0\n", "\n", "for i in range(3):\n", " canal = img_color[..., i].ravel() # equivalente a img_color[:, :, i].ravel()\n", " hist, bins = np.histogram(canal, bins=256, range=(0, 1))\n", " hist_list.append((hist, bins))\n", " max_freq = max(max_freq, hist.max()) # Atualiza o maior valor de frequência\n", "\n", "# Plotagem\n", "plt.figure(figsize=(15, 5))\n", "\n", "# Imagem original (agora mostrando com vmax=1)\n", "plt.subplot(141)\n", "plt.imshow(img_color, vmin=0, vmax=1)\n", "plt.axis('off')\n", "plt.title('Imagem Colorida')\n", "\n", "# Histogramas dos canais com mesma escala no eixo Y\n", "for i, color in enumerate(colors):\n", " hist, bins = hist_list[i] \n", " plt.subplot(142 + i)\n", " plt.plot(bins[:-1], hist, color=color)\n", " plt.ylim(0, max_freq * 1.05) # Limite ajustado com margem de 5%\n", " plt.title(f'Canal {color.title()}')\n", " plt.xlabel('Intensidade (0 a 1)')\n", " plt.ylabel('Frequência')\n", "\n", "plt.tight_layout()\n", "plt.show()\n", "\n", "```\n", "![](sun_histogram_rgb.png)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Equalização de Histograma\n", "\n", "**Conceito Básico** \n", "A equalização de histograma é uma transformação não-linear que redistribui os valores de intensidade de uma imagem para maximizar seu contraste global. O método se baseia na estatística dos pixels para criar uma transformação adaptativa.\n", "\n", "**Matemática da Equalização**\n", "\n", "**1. Função de Distribuição Cumulativa (CDF):**\n", "\n", "A CDF (Cumulative Distribution Function) representa a probabilidade acumulada de ocorrência dos níveis de intensidade:\n", "\n", "$$\n", "\\text{CDF}(r_k) = \\sum_{i=0}^{k} \\frac{n_i}{N}\n", "$$\n", "\n", "Onde:\n", "- $ n_i $ = número de pixels com intensidade $ i $ \n", "- $ N $ = número total de pixels (altura × largura) \n", "- $ r_k $ = nível de intensidade (de 0 a $L-1 $) \n", "\n", "Essa função indica a fração de pixels com intensidade menor ou igual a $ r_k $.\n", "\n", "**2. Transformação de Equalização:**\n", "\n", "A transformação que realiza a equalização é:\n", "\n", "$$\n", "s_k = T(r_k) = \\text{round}\\left( (L-1) \\cdot \\text{CDF}(r_k) \\right)\n", "$$\n", "\n", "**Onde:**\n", "- $ r_k $ é o valor original de intensidade de um pixel. \n", "- $ \\text{CDF}(r_k) $ calcula a fração acumulada de pixels com intensidade até $ r_k $. \n", "- Multiplicamos essa fração por $ L - 1 $) (valor máximo da faixa de intensidade) para escalar o resultado ao intervalo de saída. \n", "- O resultado é arredondado para garantir que o novo valor $ s_k $ seja um número inteiro válido.\n", "\n", "Essa transformação redistribui os valores de intensidade com base na frequência acumulada, de forma que regiões de baixa variação ganhem mais contraste e a faixa dinâmica da imagem seja melhor aproveitada.\n", "\n", "**Propriedades Chave:**\n", "- Preserva a ordem dos níveis de intensidade \n", "- É uma função monotonicamente crescente \n", "- Mapeia $ [0, L-1] $ $\\rightarrow [0, L-1] $\n", "\n", "**3. Normalização do CDF:**\n", "\n", "Na prática, normalizamos a CDF para garantir o uso completo da faixa de intensidades, especialmente quando os níveis mais baixos não aparecem na imagem:\n", "\n", "$$\n", "\\text{CDF}_{\\text{norm}}(r_k) = \\frac{\\text{CDF}(r_k) - \\text{CDF}_{\\text{min}}}{1 - \\text{CDF}_{\\text{min}}}\n", "$$\n", "\n", "Onde $ \\text{CDF}_{\\text{min}} $ é o menor valor não-zero da CDF.\n", "\n", "\n", "**Implementação Comparativa**\n", "\n", "```python\n", "import numpy as np\n", "import cv2\n", "import matplotlib.pyplot as plt\n", "\n", "def detailed_histogram_equalization(img, L=256):\n", " \"\"\"Implementação didática com todas as etapas matemáticas\"\"\"\n", " # Passo 1: Calcular histograma\n", " hist = np.bincount(img.flatten(), minlength=L)\n", " \n", " # Passo 2: Calcular PMF e CDF\n", " pmf = hist / hist.sum() # Função massa de probabilidade\n", " cdf = np.cumsum(pmf) # Função distribuição cumulativa\n", " \n", " # Passo 3: Normalização do CDF\n", " cdf_min = cdf[hist > 0][0] # Primeiro valor não-zero\n", " cdf_norm = (cdf - cdf_min) / (1 - cdf_min)\n", " \n", " # Passo 4: Aplicar transformação\n", " transformed = np.round((L-1) * cdf_norm).astype(np.uint8)\n", " \n", " # Passo 5: Mapear pixels\n", " return transformed[img]\n", "\n", "# Pipeline completo de análise\n", "img_original = cv2.imread('sun.jpeg', cv2.IMREAD_GRAYSCALE)\n", "\n", "# Versões processadas\n", "img_numpy = detailed_histogram_equalization(img_original)\n", "img_opencv = cv2.equalizeHist(img_original)\n", "\n", "# Análise comparativa\n", "plt.figure(figsize=(15, 10))\n", "\n", "# Visualização das imagens\n", "for i, (title, img) in enumerate(zip(\n", " ['Original', 'NumPy Equalization', 'OpenCV Equalization'],\n", " [img_original, img_numpy, img_opencv]\n", ")):\n", " plt.subplot(2, 3, i+1)\n", " plt.imshow(img, cmap='gray', vmin=0, vmax=255)\n", " plt.title(title)\n", " plt.axis('off')\n", "\n", "# Visualização dos histogramas\n", "for i, (title, img, color) in enumerate(zip(\n", " ['Histograma Original', 'Histograma NumPy', 'Histograma OpenCV'],\n", " [img_original, img_numpy, img_opencv],\n", " ['red', 'green', 'blue']\n", ")):\n", " plt.subplot(2, 3, i+4)\n", " plt.hist(img.ravel(), bins=256, range=[0,256], color=color)\n", " plt.title(title)\n", " plt.xlabel('Intensidade')\n", " plt.ylabel('Frequência')\n", "\n", "plt.tight_layout()\n", "plt.show()\n", "```\n", "\n", "![](sun_equalized.jpeg)\n", "\n", "*Figura: Resultado da equalização mostrando a expansão do histograma e melhoria de contraste. O método preserva os detalhes estruturais enquanto redistribui as intensidades.*\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 📝 Exercício: Equalização de Histograma em Imagens Coloridas\n", "\n", "Implemente um pipeline de processamento de imagens coloridas com as seguintes etapas:\n", "\n", "- **Leitura da Imagem** \n", " - Carregue uma imagem JPEG ou PNG e normalize os pixels se necessário.\n", "\n", "- **Histogramas RGB (Pré-equalização)** \n", " - Calcule e plote os histogramas dos canais R, G e B, usando cores correspondentes.\n", "\n", "- **Equalização por Canal** \n", " - Equalize cada canal (R, G, B) separadamente com OpenCV, preservando as cores.\n", "\n", "- **Visualização dos Resultados** \n", " - Mostre a imagem original e a equalizada lado a lado. \n", " - Exiba os histogramas antes e depois da equalização, organizados em subplots.\n", "\n", "- **Análise** \n", " - Compare as distribuições antes e depois: \n", " - A equalização tornou os histogramas mais uniformes? \n", " - Alguma perda de informação ou artefato visual foi introduzida?\n", "\n", "**Desafio:**\n", "\n", "Implemente a **equalização de histograma para vídeos coloridos**.\n", "\n", "- **Objetivo**: aplicar a equalização de histograma a cada quadro (frame) de um vídeo.\n", "- **Passos sugeridos**:\n", " - Leia o vídeo usando `cv2.VideoCapture`.\n", " - Para cada frame, aplique o mesmo pipeline de equalização por canal.\n", " - Salve o vídeo final usando `cv2.VideoWriter`.\n", "- **Dica**: Certifique-se de manter o mesmo `fps` e dimensões do vídeo original.\n", "\n", "**Download dos Arquivos**\n", "\n", "- [📷 Baixar imagem](case.png) \n", "- [🎞️ Baixar vídeo](vid_cp.mp4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Convolução de Imagens\n", "\n", "A [convolução (vid)](https://youtu.be/yb2tPt0QVPY?si=6X1mz11i40KMIceb) é uma operação matemática central no processamento digital de imagens. Seu principal objetivo é extrair e realçar características relevantes — como bordas, texturas, contornos ou regiões homogêneas — ao combinar uma imagem com um **kernel** (ou filtro). O resultado é uma nova imagem, cujas propriedades visuais são modificadas de acordo com a estrutura do kernel utilizado.\n", "\n", "### **Matemática da Convolução**\n", "\n", "Para imagens digitais, representadas como matrizes bidimensionais de pixels, a convolução discreta é definida por:\n", "\n", "$$\n", "(f * g)[x,y] = \\sum_{i=-k}^{k} \\sum_{j=-k}^{k} f[x-i,y-j] \\cdot g[i,j]\n", "$$\n", "\n", "Onde:\n", "- $f$: Matriz da imagem original (dimensão M×N) \n", "- $g$: Kernel ou filtro (dimensão K×K, com K ímpar, como 3×3 ou 5×5) \n", "- $(x,y)$: Coordenadas do pixel na imagem de saída \n", "- $k$: Metade do tamanho do kernel, ou seja, $(K-1)/2$, necessário para centralizar o kernel sobre o pixel atual \n", "\n", "Esse processo é repetido para cada pixel da imagem, levando em conta a vizinhança local e os coeficientes definidos pelo kernel.\n", "\n", "\n", "### **Kernels: O Coração da Convolução**\n", "\n", "Os kernels são pequenas matrizes de pesos que determinam como os valores dos pixels vizinhos devem ser combinados para gerar um novo valor. Suas propriedades determinam o tipo de transformação que será aplicada à imagem.\n", "\n", "- **Dimensões Ímpares** \n", " Garantem que o kernel tenha um centro bem definido para alinhar ao pixel sendo processado.\n", "\n", "- **Normalização e Balanceamento** \n", " Preserva o brilho da imagem (soma dos pesos = 1) ou enfatiza transições com pesos positivos e negativos.\n", "\n", "- **Direcionalidade e Gradientes** \n", " Detectam variações horizontais, verticais ou diagonais — úteis para encontrar bordas.\n", "\n", "- **Objetivo Específico** \n", " Cada kernel serve a uma tarefa: suavizar, realçar, detectar ruído, etc.\n", "\n", "- **Localidade e Paralelismo** \n", " Operação local e independente — ideal para paralelismo em GPUs e redes neurais convolucionais.\n", "\n", "#### Exemplo de Kernel de Detecção de Bordas (Sobel Vertical):\n", "\n", "$$\n", "\\begin{bmatrix}\n", "-1 & 0 & 1 \\\\\n", "-2 & 0 & 2 \\\\\n", "-1 & 0 & 1\n", "\\end{bmatrix}\n", "$$\n", "\n", "Esse kernel detecta bordas verticais ao responder a variações horizontais na imagem. Parece um pouco abstrato mas entederemos em seguida.\n", "\n", "### **Convolução Passo a Passo: Suavização com Filtro de Média**\n", "\n", "Suponha uma imagem 5×5 e um kernel de média 3×3. A janela do kernel percorre a imagem, realizando multiplicações ponto a ponto seguidas de uma soma.\n", "\n", "**Matriz Original (Imagem A):**\n", "\n", "$$\n", "\\begin{bmatrix}\n", "10 & 20 & 30 & 40 & 50 \\\\\n", "20 & \\boxed{30} & \\boxed{40} & \\boxed{50} & 60 \\\\\n", "30 & \\boxed{40} & \\boxed{50} & \\boxed{60} & 70 \\\\\n", "40 & \\boxed{50} & \\boxed{60} & \\boxed{70} & 80 \\\\\n", "50 & 60 & 70 & 80 & 90\n", "\\end{bmatrix}\n", "$$\n", "\n", "Submatriz usada na convolução:\n", "\n", "$$\n", "\\boxed{\n", "\\begin{bmatrix}\n", "30 & 40 & 50 \\\\\n", "40 & 50 & 60 \\\\\n", "50 & 60 & 70\n", "\\end{bmatrix}\n", "}\n", "$$\n", "\n", "**Kernel de Média (B):**\n", "\n", "$$\n", "\\frac{1}{9} \\begin{bmatrix}\n", "1 & 1 & 1 \\\\\n", "1 & 1 & 1 \\\\\n", "1 & 1 & 1\n", "\\end{bmatrix}\n", "$$\n", "\n", "#### Convolução - exemplo de uma etapa:\n", "\n", "$$\n", "\\frac{1}{9}(30 + 40 + 50 + 40 + 50 + 60 + 50 + 60 + 70) = 50\n", "$$\n", "\n", "\n", "### **Efeitos de Borda e Estratégias de Padding**\n", "\n", "Durante a convolução, nas bordas da imagem faltam vizinhos para o kernel. Para corrigir isso, se for de interesse, usamos técnicas de *padding*.\n", "\n", "#### Modos Comuns de Padding\n", "\n", "| Modo | Descrição | Efeito na Tamanho da Imagem |\n", "|----------|-------------------------------------------|----------------------------------|\n", "| `valid` | Sem preenchimento | Reduz a imagem |\n", "| `same` | Preenchimento com zeros | Mantém o tamanho original |\n", "| `reflect`| Espelha os valores da borda | Mantém ou quase mantém o tamanho |\n", "| `replicate` | Repete o valor do pixel da borda | Semelhante ao `reflect` |\n", "\n", "#### Cálculo da Dimensão de Saída\n", "\n", "Seja:\n", "- $I$: tamanho da imagem original (largura ou altura)\n", "- $K$: tamanho do kernel\n", "- $P$: padding aplicado (pixels adicionados em cada lado)\n", "- $S$: *stride* (passo da janela, geralmente 1)\n", "\n", "A dimensão da imagem de saída será:\n", "\n", "$$\n", "O = \\left\\lfloor \\frac{I - K + 2P}{S} \\right\\rfloor + 1\n", "$$\n", "\n", "Veja que:\n", "\n", "- `valid`: $P = 0$ → a imagem encolhe\n", "- `same`: $P = \\left\\lfloor \\frac{K}{2} \\right\\rfloor$ → a imagem mantém tamanho\n", "\n", "\n", "**Exemplo — Modo `valid` (sem padding)**\n", "\n", "- Imagem: $ I = 5 $\n", "- Kernel: $ K = 3 $\n", "- Padding: $ P = 0 $\n", "- Stride: $ S = 1 $\n", "\n", "Aplicando na fórmula, temos:\n", "\n", "$$\n", "O = \\left\\lfloor \\frac{5 - 3 + 2 \\cdot 0}{1} \\right\\rfloor + 1 = \\left\\lfloor \\frac{2}{1} \\right\\rfloor + 1 = 2 + 1 = 3\n", "$$\n", "\n", "**Saída: 3×3**\n", "\n", "\n", "**Exemplo — Modo `same` (padding para manter tamanho)**\n", "\n", "Para manter o tamanho $ O = I $, usamos:\n", "\n", "$$\n", "P = \\left\\lfloor \\frac{K}{2} \\right\\rfloor\n", "$$\n", "\n", "- Imagem: $ I = 5 $\n", "- Kernel: $ K = 3 $\n", "- Stride: $ S = 1 $\n", "- Padding: $ P = \\left\\lfloor \\frac{3}{2} \\right\\rfloor = 1 $\n", "\n", "Aplicando:\n", "\n", "$$\n", "O = \\left\\lfloor \\frac{5 - 3 + 2 \\cdot 1}{1} \\right\\rfloor + 1 = \\left\\lfloor \\frac{4}{1} \\right\\rfloor + 1 = 4 + 1 = 5\n", "$$\n", "\n", "**Saída: 5×5** (mesma dimensão da imagem original)\n", "\n", "\n", "**Exemplo Visual: Comparação de Modos**\n", "\n", "**Imagem Original (3×3):**\n", "\n", "$$\n", "\\begin{bmatrix}\n", "10 & 20 & 30 \\\\\n", "40 & 50 & 60 \\\\\n", "70 & 80 & 90\n", "\\end{bmatrix}\n", "$$\n", "\n", "**Kernel (3×3):**\n", "\n", "$$\n", "\\frac{1}{9} \\begin{bmatrix}\n", "1 & 1 & 1 \\\\\n", "1 & 1 & 1 \\\\\n", "1 & 1 & 1\n", "\\end{bmatrix}\n", "$$\n", "\n", "\n", "**`valid`: apenas regiões totalmente dentro da imagem** \n", "Aplica o kernel **apenas onde ele cabe por completo**, por isso há **redução** no tamanho da saída.\n", "\n", "$$\n", "\\begin{bmatrix}\n", "50\n", "\\end{bmatrix}\n", "$$\n", "\n", "\n", "**`same` (com zero padding): mantém o tamanho da imagem** \n", "\n", "Para isso, adicionamos uma borda de zeros em volta da imagem original:\n", "\n", "**Imagem com padding zero:**\n", "\n", "$$\n", "\\begin{bmatrix}\n", "0 & 0 & 0 & 0 & 0 \\\\\n", "0 & 10 & 20 & 30 & 0 \\\\\n", "0 & 40 & 50 & 60 & 0 \\\\\n", "0 & 70 & 80 & 90 & 0 \\\\\n", "0 & 0 & 0 & 0 & 0\n", "\\end{bmatrix}\n", "$$\n", "\n", "A aplicação do kernel gera:\n", "\n", "$$\n", "\\begin{bmatrix}\n", "13.3 & \\cdots & 23.3 \\\\\n", "\\cdots & 50 & \\cdots \\\\\n", "33.3 & \\cdots & 43.3\n", "\\end{bmatrix}\n", "$$\n", "\n", "\n", "**`reflect` (espelhamento das bordas):** \n", "As bordas são **refletidas**, evitando zeros e preservando mais o conteúdo da imagem.\n", "\n", "$$\n", "\\begin{bmatrix}\n", "36.7 & \\cdots & 43.3 \\\\\n", "\\cdots & 50 & \\cdots \\\\\n", "46.7 & \\cdots & 53.3\n", "\\end{bmatrix}\n", "$$\n", "\n", "\n", "### **Aplicações Práticas com Python**\n", "\n", "**Visualizando a Convolução**\n", "\n", "O código a seguir demonstra a convolução utilizando Python:\n", "\n", "```python\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "from scipy.signal import convolve2d\n", "\n", "# Define a matriz da imagem\n", "A = np.array([[10, 20, 30, 40, 50],\n", " [20, 30, 40, 50, 60],\n", " [30, 40, 50, 60, 70],\n", " [40, 50, 60, 70, 80],\n", " [50, 60, 70, 80, 90]])\n", "\n", "# Define o kernel de média\n", "B = np.array([[1/9, 1/9, 1/9],\n", " [1/9, 1/9, 1/9],\n", " [1/9, 1/9, 1/9]])\n", "\n", "# Realiza a convolução usando a função convolve2d do scipy\n", "C = convolve2d(A, B, mode='valid')\n", "\n", "# Subplots com aspect ratio\n", "fig, axes = plt.subplots(1, 3, figsize=(16, 6), \n", " gridspec_kw={'width_ratios': [5, 3, 3]}) # Proporção dos tamanhos\n", "\n", "# Imagem original\n", "im1 = axes[0].imshow(A, cmap='gray', aspect='equal')\n", "axes[0].set_title(f'Imagem Original ({A.shape[0]}x{A.shape[1]})')\n", "#fig.colorbar(im1, ax=axes[0], fraction=0.046, pad=0.04)\n", "\n", "# Kernel - respeitando a proporção\n", "im2 = axes[1].imshow(B, cmap='gray', aspect='equal')\n", "axes[1].set_title(f'Kernel ({B.shape[0]}x{B.shape[1]})')\n", "axes[1].set_xticks([]) # Remove os valores do eixo x\n", "axes[1].set_yticks([]) # Remove os valores do eixo y\n", "#fig.colorbar(im2, ax=axes[1], fraction=0.046, pad=0.04)\n", "\n", "# Imagem convolucionada\n", "im3 = axes[2].imshow(C, cmap='gray', aspect='equal')\n", "axes[2].set_title(f'Imagem Convolucionada ({C.shape[0]}x{C.shape[1]})')\n", "axes[2].set_xticks([]) # Remove os valores do eixo x\n", "axes[2].set_yticks([]) # Remove os valores do eixo y\n", "#fig.colorbar(im3, ax=axes[2], fraction=0.046, pad=0.04)\n", "\n", "plt.tight_layout()\n", "plt.show()\n", "```\n", "\n", "Este código utiliza a função `convolve2d` da biblioteca `scipy` para realizar a convolução da matriz da imagem com o kernel de média. Ele exibe a imagem original, o kernel e a imagem resultante da convolução.\n", "\n", "\"Exemplo\n", "\n", "\n", ">> Vamos escolher uma imagem para realizar os testes a seguir. \n", "\n", "\n", "#### 🔹 **Com OpenCV (Funções prontas e otimizadas)**\n", "\n", "- [📷 Baixar imagem](bridge.jpeg)\n", "\n", "\n", "**Suavização com Filtro Gaussiano**\n", "```python\n", "import cv2\n", "import matplotlib.pyplot as plt\n", "\n", "# Carrega a imagem\n", "img = cv2.imread('bridge.jpeg')\n", "img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n", "\n", "# Número de iterações do filtro Gaussiano\n", "num_iterations = 5 # Altere conforme desejar\n", "\n", "# Cria uma cópia da imagem para aplicar o filtro iterativamente\n", "blurred_img = img.copy()\n", "\n", "for _ in range(num_iterations):\n", " blurred_img = cv2.GaussianBlur(blurred_img, (5, 5), 0) #img, kernel, std\n", "\n", "# Plota apenas a original e a última versão borrada\n", "plt.figure(figsize=(10, 5))\n", "\n", "plt.subplot(1, 2, 1)\n", "plt.imshow(img)\n", "plt.title(\"Imagem Original\")\n", "plt.axis('off')\n", "\n", "plt.subplot(1, 2, 2)\n", "plt.imshow(blurred_img)\n", "plt.title(f\"Filtro Gaussiano ({num_iterations}x)\")\n", "plt.axis('off')\n", "\n", "plt.tight_layout()\n", "plt.show()\n", "```\n", "\n", "**Explicação:**\n", "- `cv2.GaussianBlur(src, ksize, sigmaX)` aplica uma suavização com base em uma distribuição gaussiana.\n", " - `src`: imagem original.\n", " - `ksize=(5,5)`: tamanho da máscara (kernel). Quanto maior, mais borrado o resultado.\n", " - `sigmaX=0`: o desvio padrão da Gaussiana. Zero permite que o OpenCV calcule automaticamente com base no tamanho do kernel.\n", "- Aqui, aplicamos o filtro várias vezes para reforçar o efeito.\n", "- Útil para remover ruídos leves e suavizar gradientes antes de aplicar detecção de bordas.\n", "\n", "\n", "\n", "**Detecção de Bordas com Sobel**\n", "```python\n", "import cv2\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "# Carrega a imagem original\n", "img = cv2.imread('bridge.jpeg', cv2.IMREAD_GRAYSCALE) # Sobel funciona melhor em tons de cinza\n", "\n", "# Aplica Sobel nos dois eixos\n", "sobel_x = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)\n", "sobel_y = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)\n", "sobel_combined = cv2.magnitude(sobel_x, sobel_y)\n", "\n", "# Aplica Gaussiano DEPOIS (opcional para suavizar ruído nas bordas)\n", "blurred_edges = cv2.GaussianBlur(sobel_combined, (5,5), 0)\n", "\n", "# Plota os resultados\n", "fig, axs = plt.subplots(1, 3, figsize=(15,5))\n", "axs[0].imshow(img, cmap='gray')\n", "axs[0].set_title(\"Original\")\n", "axs[0].axis('off')\n", "\n", "axs[1].imshow(sobel_combined, cmap='gray')\n", "axs[1].set_title(\"Sobel Puro\")\n", "axs[1].axis('off')\n", "\n", "axs[2].imshow(blurred_edges, cmap='gray')\n", "axs[2].set_title(\"Sobel + Gaussiano\")\n", "axs[2].axis('off')\n", "\n", "plt.tight_layout()\n", "plt.show()\n", "```\n", "\n", "**Explicação:**\n", "- `cv2.Sobel(src, ddepth, dx, dy, ksize)` calcula derivadas para encontrar bordas.\n", " - `ddepth=cv2.CV_64F`: profundidade dos dados de saída (64 bits flutuante para evitar perda de sinal negativo).\n", " - `dx=1, dy=0`: para detectar bordas horizontais (vice-versa para verticais).\n", " - `ksize=3`: tamanho do kernel de derivada.\n", "- `cv2.magnitude(x, y)` combina as bordas em X e Y para formar uma borda geral.\n", "- Aplicar o filtro Gaussiano depois ajuda a suavizar ruídos nas bordas detectadas.\n", "\n", "\n", "\n", "**Redução de Ruído com Filtro Mediano**\n", "```python\n", "import cv2\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "# Carregar imagem original\n", "img = cv2.imread('bridge.jpeg') # Substitua pelo seu arquivo\n", "img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n", "\n", "# Adicionar ruído artificial (sal e pimenta)\n", "def add_salt_pepper_noise(image, prob=0.05):\n", " output = np.copy(image)\n", " # Ruído sal (branco)\n", " salt = np.random.rand(*image.shape[:2]) < prob/2\n", " output[salt] = 255\n", " # Ruído pimenta (preto)\n", " pepper = np.random.rand(*image.shape[:2]) < prob/2\n", " output[pepper] = 0\n", " return output\n", "\n", "noisy_img = add_salt_pepper_noise(img, prob=0.3) # 30% de ruído\n", "\n", "# Aplicar filtro mediano\n", "median = cv2.medianBlur(noisy_img, 5) # Kernel size 5 (deve ser ímpar)\n", "\n", "# Visualização comparativa\n", "fig, axs = plt.subplots(1, 2, figsize=(12, 6))\n", "\n", "axs[0].imshow(noisy_img)\n", "axs[0].set_title(f\"Imagem com Ruído (30% sal-pimenta)\")\n", "axs[0].axis('off')\n", "\n", "axs[1].imshow(median)\n", "axs[1].set_title(\"Filtro Mediano (kernel=5)\")\n", "axs[1].axis('off')\n", "\n", "plt.tight_layout()\n", "plt.show()\n", "```\n", "\n", "**Explicação:**\n", "- `cv2.medianBlur(src, ksize)` aplica um filtro que substitui cada pixel pela **mediana** da vizinhança.\n", " - Muito eficaz contra ruídos do tipo **sal e pimenta** (pontinhos pretos e brancos aleatórios).\n", " - `ksize` deve ser ímpar (3, 5, 7...).\n", "- Aqui, um ruído artificial foi adicionado para mostrar a eficácia do filtro.\n", "- Dica: para preservar bordas, o filtro mediano é geralmente melhor que o Gaussiano.\n", "\n", "\n", "#### 🔹 **Com NumPy (Definindo o Kernel Manualmente)**\n", "\n", "> Utilizando `scipy.signal.convolve2d` para aplicar filtros 2D com controle total, ideal para fins didáticos e experimentação com kernels personalizados.\n", "\n", "**Filtro de Média (Suavização Simples)**\n", "```python\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "from scipy.signal import convolve2d\n", "\n", "# Carregar imagem em escala de cinza\n", "img = plt.imread('bridge.jpeg')\n", "img = img.mean(axis=2) # Converter para escala de cinza\n", "\n", "# Define o kernel de média (suavização simples)\n", "kernel = np.ones((3, 3)) / 9\n", "\n", "# Aplica a convolução\n", "output = img.copy()\n", "\n", "# num de convoluções\n", "n = 10\n", "for i in range(n):\n", " output = convolve2d(output, kernel, mode='same', boundary='fill', fillvalue=0)\n", "\n", "# Visualização\n", "fig, axs = plt.subplots(1, 2, figsize=(10, 4))\n", "axs[0].imshow(img, cmap='gray')\n", "axs[0].set_title(\"Imagem Original (Gray)\")\n", "axs[0].axis('off')\n", "\n", "axs[1].imshow(output, cmap='gray')\n", "axs[1].set_title(\"Filtro de Média (3x3)\")\n", "axs[1].axis('off')\n", "\n", "plt.tight_layout()\n", "plt.show()\n", "```\n", "\n", "**Explicação:**\n", "- `np.ones((3,3))/9` cria um kernel onde todos os elementos têm peso igual — média aritmética dos vizinhos.\n", "- `convolve2d(img, kernel, mode='same', boundary='fill', fillvalue=0)`:\n", " - `mode='same'`: saída com o mesmo tamanho da imagem original.\n", " - `boundary='fill'`: bordas externas tratadas como zero.\n", "- Suaviza transições na imagem, útil para remover ruídos leves.\n", "\n", "**Detecção de Bordas com Filtro Laplaciano**\n", "```python\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "from scipy.signal import convolve2d\n", "\n", "# Carregar imagem em escala de cinza\n", "img = plt.imread('bridge.jpeg')\n", "if img.ndim == 3: # Se for colorida (RGB)\n", " img = img.mean(axis=2) # Converter para escala de cinza\n", "\n", "# Define o kernel Laplaciano\n", "kernel = np.array([[0, 1, 0],\n", " [1, -4, 1],\n", " [0, 1, 0]])\n", "\n", "# Aplica convolução\n", "output = convolve2d(img, kernel, mode='same', boundary='fill', fillvalue=0)\n", "\n", "# Pré-processamento para visualização\n", "output_visual = np.abs(output) # Valor absoluto para ver todas as bordas\n", "output_visual = (output_visual - output_visual.min()) / (output_visual.max() - output_visual.min()) # Normaliza para [0,1]\n", "\n", "# Visualização melhorada\n", "fig, axs = plt.subplots(1, 3, figsize=(15, 5))\n", "\n", "# Imagem original\n", "axs[0].imshow(img, cmap='gray')\n", "axs[0].set_title(\"Imagem Original\")\n", "axs[0].axis('off')\n", "\n", "# Resultado Laplaciano (valores brutos)\n", "axs[1].imshow(output, cmap='seismic', vmin=-100, vmax=100) # Cores: vermelho=negativo, azul=positivo\n", "axs[1].set_title(\"Laplaciano (valores reais)\")\n", "axs[1].axis('off')\n", "\n", "# Resultado Laplaciano (absoluto normalizado)\n", "axs[2].imshow(output_visual, cmap='gray')\n", "axs[2].set_title(\"Laplaciano (absoluto normalizado)\")\n", "axs[2].axis('off')\n", "\n", "plt.tight_layout()\n", "plt.show()\n", "```\n", "\n", "**Explicação:**\n", "- O kernel realça **regiões com mudança brusca de intensidade** (bordas).\n", "- O centro negativo e vizinhos positivos atuam como uma segunda derivada discreta.\n", "- Ideal para detectar bordas finas e simétricas.\n", "\n", "\n", "### CNNs \n", "Daqui a pouco estudaremos as CNNs (Convolução em Redes Neurais), é importante saber que os kernels são aprendidos automaticamente durante o treinamento. Cada camada convolucional extrai características específicas e cada vez mais complexas da imagem:\n", "\n", "| Camada | Tamanho do Kernel | Nº de Filtros | Função da Camada |\n", "|--------|-------------------|---------------|--------------------------|\n", "| 1 | 3×3 | 32 | Bordas e cores básicas |\n", "| 2 | 5×5 | 64 | Padrões e texturas |\n", "| 3 | 3×3 | 128 | Formas complexas e objetos |\n", "\n", "Esses filtros tornam possíveis aplicações como reconhecimento facial, análise médica, e detecção de objetos em tempo real.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 📝 (Exercício Prático) ´**Processamento de Vídeo com OpenCV** \n", "\n", "**Objetivo:** \n", "Aprender a carregar, modificar e salvar vídeos usando a biblioteca OpenCV.\n", "\n", "**Tarefas:** \n", "- **Carregar um vídeo** (de arquivo ou webcam). \n", "- **Converter cada frame para escala de cinza**. \n", "- **Salvar o vídeo processado** em um novo arquivo.\n", "\n", " **Código Base (Passo a Passo):**\n", "```python\n", "import cv2\n", "\n", "# 1. Carregar vídeo (substitua 'video.mp4' pelo seu arquivo ou use 0 para webcam)\n", "cap = cv2.VideoCapture('video.mp4')\n", "\n", "# Verifica se o vídeo foi carregado corretamente\n", "if not cap.isOpened():\n", " print(\"Erro ao abrir o vídeo!\")\n", " exit()\n", "\n", "# 2. Obter propriedades do vídeo (largura, altura e FPS)\n", "width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))\n", "height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))\n", "fps = cap.get(cv2.CAP_PROP_FPS)\n", "\n", "# 3. Criar o objeto de escrita do novo vídeo (grayscale)\n", "output = cv2.VideoWriter('video_out.mp4',\n", " cv2.VideoWriter_fourcc(*'mp4v'), # Codec MP4\n", " fps,\n", " (width, height),\n", " isColor=False) # Vídeo em tons de cinza\n", "\n", "# 4. Processar frame a frame, aplicar filtros, etc\n", "# ...\n", "\n", "# 5. Liberar recursos\n", "cap.release()\n", "output.release()\n", "cv2.destroyAllWindows()\n", "\n", "print(\"Vídeo processado e salvo como 'video_out.mp4'!\")\n", "```\n", "\n", "**Próximos Desafios (opcional):** \n", "- Aplique **equalização de histograma** em cada frame antes de salvar. \n", "- Escolha e aplique um **filtro** (mediana, gaussiano, bordas etc). \n", "- Crie um vídeo que combine **frames originais e processados lado a lado**.\n", "\n", "**Desafio:**\n", "- Criar um gerador de filtros adaptativo e especializado para CNNs. Falar com o professor! " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 🧠 Exercícios Conceituais\n", "\n", "**1. Importância do Pré-processamento de Imagens**\n", "Explique por que o pré-processamento é uma etapa essencial em projetos de visão computacional.\n", "- Quais problemas podem surgir ao utilizar imagens brutas sem pré-processamento?\n", "- Cite algumas técnicas comuns de pré-processamento e suas finalidades.\n", "\n", "**2. Normalização de Intensidade**\n", "A normalização de intensidade ajusta os valores dos pixels para uma faixa específica.\n", "- Descreva como a normalização min-max transforma os valores de uma imagem.\n", "- Em quais situações essa técnica é particularmente útil?\n", "\n", "**3. Equalização de Histograma**\n", "A equalização de histograma redistribui os níveis de intensidade para melhorar o contraste da imagem.\n", "- Como essa técnica pode realçar detalhes em imagens com baixa variação tonal?\n", "- Quais são as possíveis limitações da equalização de histograma?\n", "\n", "**4. Filtros Espaciais: Média, Mediana e Gaussiano**\n", "Compare os filtros de média, mediana e Gaussiano em termos de suas aplicações e efeitos nas imagens.\n", "- Qual filtro seria mais adequado para remover ruídos do tipo \"sal e pimenta\"?\n", "- Em que casos o filtro Gaussiano é preferido?\n", "\n", "**5. Realce de Bordas em Imagens**\n", "O realce de bordas é fundamental para destacar transições abruptas de intensidade.\n", "- Quais operadores são comumente utilizados para essa finalidade?\n", "- Como o realce de bordas contribui para a segmentação de objetos em uma imagem?\n", "\n", "**6. Redimensionamento de Imagens**\n", "Redimensionar imagens para dimensões fixas é uma prática comum em redes neurais convolucionais (CNNs).\n", "- Quais são os desafios ao redimensionar imagens sem distorcer informações importantes?\n", "- Como o redimensionamento afeta o desempenho de modelos de aprendizado de máquina?\n", "\n", "**7. Convolução e Kernels**\n", "A convolução é uma operação fundamental em processamento de imagens.\n", "- Explique como os kernels (ou filtros) são utilizados na convolução para detectar características específicas na imagem.\n", "- Dê exemplos de kernels comuns e suas aplicações.\n", "\n", "**8. Padding em Convoluções**\n", "O padding é uma técnica utilizada para lidar com as bordas durante a convolução.\n", "- Quais são os tipos comuns de padding e como eles influenciam o resultado da convolução?\n", "- Por que o uso adequado de padding é importante em redes neurais convolucionais?\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Referências e Conteúdo Extra\n", "- GONZALEZ, R. C.; WOODS, R. E. *Digital Image Processing*. 4th ed. Pearson, 2018.\n", "- Vídeo(s)\n", " - [Mas o que é uma convolução?](https://youtu.be/KuXjwB4LzSA?si=6A5SNZ9Z551lWPBk)\n", " - [Kernel Size and Why Everyone Loves 3x3](https://youtu.be/V9ZYDCnItr0?si=VieyTuEgcAUjjGE2)\n", "- Site(s):\n", " - [Documentação Oficial do SciPy](https://scipy.org/)\n", " - [Image Kernels](https://setosa.io/ev/image-kernels/)" ] } ], "metadata": { "colab": { "provenance": [], "toc_visible": true }, "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.20" } }, "nbformat": 4, "nbformat_minor": 4 }