Materiais de Aula

Processamento de Imagem e Visão Computacional • Prof. Me. William Tenório

Sobre esta página

O conteúdo desta disciplina é apresentado principalmente na lousa, durante as aulas. Esta página não substitui a aula: ela reúne material de apoio — sobretudo imagens muito representativas de algum conceito explicado em sala, e tabelas com dados que vocês vão precisar consultar.

Quando um exemplo visto na lousa tiver uma imagem que ajude a fixar o conceito, ela será postada aqui e citada durante a explicação, para consulta posterior.

Matriz de Pixels e Canais RGB

Zoom em um quadro mostrando a matriz de pixels correspondente
Figura 1 – Uma imagem digital é uma matriz de pixels: cada posição (x, y) armazena um valor de intensidade.
Imagem em tons de cinza ao lado da matriz de valores de intensidade correspondente
Figura 2 – Imagem em tons de cinza e a matriz de valores de intensidade (0 a 255) que ela representa.
Tabela relacionando o valor do canal Red com a cor resultante
Figura 3 – Relação entre o valor do canal Red e a cor resultante, com Green e Blue fixados em 0.
Imagem colorida decomposta nos canais Red, Green e Blue
Figura 4 – Decomposição de uma imagem colorida em seus três canais: Red, Green e Blue.
Ampliação de uma imagem mostrando os pixels compostos pela sobreposição dos canais RGB
Figura 5 – Ampliação de uma região da imagem, mostrando como cada pixel é composto pela sobreposição dos canais RGB.

Formação e Digitalização da Imagem

Esquema mostrando a luz refletida por um objeto sendo capturada por uma câmera e projetada em uma grade de pixels
Figura 6 – Formação de uma imagem digital: a luz refletida pelo objeto é capturada pela câmera e projetada sobre uma grade, gerando a matriz de pixels.
Sequência mostrando uma fotografia original, a grade de amostragem sobreposta, o resultado quantizado em pixels e a matriz de intensidades correspondente
Figura 7 – Processo de amostragem e quantização: (a) imagem original, (b) grade de amostragem, (c) imagem discretizada em pixels e (d) matriz de valores de intensidade resultante.

Exemplos em Python: Amostragem e Quantização

Reamostragem espacial (redução de resolução) a partir de uma imagem carregada da web:

import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
from io import BytesIO
import requests

# URL da imagem
url = "https://www.unip.br/assets/img/fotos/og-images/og-unip.webp"

# Carrega a imagem
resposta = requests.get(url)
img = np.array(Image.open(BytesIO(resposta.content)))

# Intervalo de amostragem
intervalo = 10

# Reamostragem
img_reduzida = img[::intervalo, ::intervalo]

# Exibe as imagens
plt.figure(figsize=(10, 5))

plt.subplot(1, 2, 1)
plt.imshow(img)
plt.title("Imagem original")
plt.axis("off")

plt.subplot(1, 2, 2)
plt.imshow(img_reduzida)
plt.title("Imagem reamostrada")
plt.axis("off")

plt.show()

Quantização das intensidades da imagem:

import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
from io import BytesIO
import requests

# URL da imagem
url = "https://www.unip.br/assets/img/fotos/og-images/og-unip.webp"

# Carrega a imagem
resposta = requests.get(url)
img = np.array(Image.open(BytesIO(resposta.content)))

# Número de níveis desejados
niveis = 4

# Quantização
intervalo = 256 // niveis

img_quantizada = (img // intervalo) * intervalo

# Exibe as imagens
plt.figure(figsize=(10, 5))

plt.subplot(1, 2, 1)
plt.imshow(img)
plt.title("Imagem original")
plt.axis("off")

plt.subplot(1, 2, 2)
plt.imshow(img_quantizada)
plt.title(f"Imagem quantizada - {niveis} níveis")
plt.axis("off")

plt.show()

Por que usar o NumPy para representar imagens? Como visto nas figuras anteriores, uma imagem digital nada mais é do que uma matriz de valores de intensidade (ou, no caso de imagens coloridas, uma matriz tridimensional com os canais Red, Green e Blue). O NumPy é a biblioteca padrão do Python para trabalhar com esse tipo de estrutura: ele armazena a imagem como um array, o que permite acessar e transformar pixels e regiões inteiras com operações vetorizadas — como a fatia img[::intervalo, ::intervalo] usada na reamostragem, ou a operação img // intervalo aplicada a todos os pixels de uma vez na quantização. Essa vetorização é muito mais rápida e legível do que percorrer a imagem pixel a pixel com laços for, e é justamente por isso que bibliotecas de imagem como Pillow e OpenCV são compatíveis com o formato de array do NumPy.

Exemplo em Python: Conversão para Escala de Cinza

Conversão pixel a pixel de uma imagem colorida (RGB) para escala de cinza, usando a ponderação padrão dos canais Red, Green e Blue:

import matplotlib.pyplot as plt
from PIL import Image

imagem = Image.open("foto.jpg").convert("RGB")

largura, altura = imagem.size

cinza = Image.new("L", (largura, altura))

for y in range(altura):
    for x in range(largura):
        r, g, b = imagem.getpixel((x, y))

        valor = int(0.299 * r + 0.587 * g + 0.114 * b)

        cinza.putpixel((x, y), valor)

cinza.save("foto_cinza.jpg")

# Exibe as imagens
plt.figure(figsize=(10, 5))

plt.subplot(1, 2, 1)
plt.imshow(imagem)
plt.title("Imagem original")
plt.axis("off")

plt.subplot(1, 2, 2)
plt.imshow(cinza, cmap="gray")
plt.title("Imagem em escala de cinza")
plt.axis("off")

plt.show()

De onde vêm os pesos 0.299, 0.587 e 0.114? Eles refletem a sensibilidade do olho humano a cada cor: somos mais sensíveis ao verde, depois ao vermelho, e menos ao azul. Por isso o canal Green tem o maior peso na conversão para cinza, e o Blue o menor — essa é a fórmula de luminância utilizada pelo padrão ITU-R BT.601 (o mesmo usado por bibliotecas como Pillow e OpenCV em suas conversões automáticas para escala de cinza, como Image.open(...).convert("L")).

Exemplo em Python: Histograma

O histograma de uma imagem mostra a distribuição de frequência dos valores de intensidade (0 a 255) presentes nela. Contagem pixel a pixel a partir da imagem em escala de cinza:

import matplotlib.pyplot as plt
from PIL import Image

imagem = Image.open("foto.jpg").convert("RGB")
cinza = imagem.convert("L")

largura, altura = cinza.size

histograma = [0] * 256

for y in range(altura):
    for x in range(largura):
        valor = cinza.getpixel((x, y))
        histograma[valor] += 1

# Exibe a imagem e o histograma
plt.figure(figsize=(10, 5))

plt.subplot(1, 2, 1)
plt.imshow(cinza, cmap="gray")
plt.title("Imagem em escala de cinza")
plt.axis("off")

plt.subplot(1, 2, 2)
plt.bar(range(256), histograma, width=1, color="black")
plt.title("Histograma")
plt.xlabel("Nível de cinza")
plt.ylabel("Número de pixels")

plt.show()

Para que serve o histograma? Ele revela características globais da imagem sem olhar diretamente para ela: uma concentração de barras à esquerda indica uma imagem escura, à direita indica uma imagem clara, e um histograma espalhado por toda a faixa indica bom contraste. É a base para técnicas de processamento como equalização e alargamento de contraste, que serão vistas mais adiante.

Filtragem e Redução de Ruído

Imagem com ruído do tipo sal e pimenta ao lado da mesma imagem após aplicação de um filtro de mediana, com o ruído bastante atenuado
Figura 8 – Redução de ruído por filtragem: à esquerda, imagem original com ruído; à direita, resultado após um filtro de mediana, que substitui cada pixel pela mediana dos valores da sua vizinhança.
Imagem original ao lado de três versões filtradas com kernels de tamanho 3x3, 5x5 e 7x7, mostrando desfoque crescente
Figura 9 – Efeito do tamanho do kernel na filtragem: quanto maior o kernel (3x3, 5x5, 7x7), maior a vizinhança considerada em cada pixel e mais forte o desfoque (e a perda de detalhes) resultante.

Implementação do filtro da média pixel a pixel, percorrendo cada janela local da imagem:

import cv2
import numpy as np

def filtro_media(imagem, tamanho_kernel):
    # Preenche as bordas da imagem com zeros (zero-padding)
    # Isso garante que o filtro também possa ser aplicado aos pixels da borda
    imagem_com_borda = cv2.copyMakeBorder(imagem,
                                      tamanho_kernel//2,
                                      tamanho_kernel//2,
                                      tamanho_kernel//2,
                                      tamanho_kernel//2,
                                      cv2.BORDER_CONSTANT)

    # Cria uma imagem vazia para armazenar o resultado filtrado
    imagem_filtrada = np.zeros_like(imagem)

    # Percorre cada pixel da imagem
    for y in range(imagem.shape[0]):
        for x in range(imagem.shape[1]):
            # Extrai a janela local centrada no pixel atual
            janela = imagem_com_borda[y:y+tamanho_kernel, x:x+tamanho_kernel]
            # Calcula o valor médio da janela
            valor_medio = np.mean(janela)
            # Atribui o valor médio ao pixel correspondente na imagem filtrada
            imagem_filtrada[y, x] = valor_medio

    return imagem_filtrada

# Carrega a imagem em escala de cinza
imagem = cv2.imread('sua_imagem.jpeg', cv2.IMREAD_GRAYSCALE)

# Define o tamanho do kernel do filtro da média
tamanho_kernel = 3

# Aplica o filtro da média à imagem
imagem_filtrada = filtro_media(imagem, tamanho_kernel)

# Exibe a imagem original e a imagem filtrada
cv2.imshow('Imagem Original', imagem)
cv2.imshow('Imagem com Filtro da Média', imagem_filtrada)
cv2.waitKey(0)
cv2.destroyAllWindows()

Na prática, o OpenCV já traz o filtro da média pronto na função cv2.blur, dispensando a implementação manual:

import cv2

# Carrega a imagem
imagem = cv2.imread("foto.jpg")

# Aplica filtro de média 3x3
suavizada = cv2.blur(imagem, (3, 3))

# Exibe as imagens
cv2.imshow("Original", imagem)
cv2.imshow("Suavizada", suavizada)

cv2.waitKey(0)
cv2.destroyAllWindows()