## Vou fazer o download das imagens do laboratório diretamente do repositório para ficar mais facil....
import requests
import os
# Define o laboratório
laboratorio = 'lab12' ### altere para o laboratório desejado
diretorio = 'lab_images' ### altere para o diretório que deseja salvar as imagens
# Download de um arquivo
def download_file(url, destination):
response = requests.get(url, stream=True)
if response.status_code == 200:
with open(destination, 'wb') as file:
for chunk in response.iter_content(chunk_size=8192):
file.write(chunk)
print(f"Baixado: {destination}")
else:
print(f"Erro ao baixar {url}")
# Monta a URL completa
api_url = "https://api.github.com/repos/arnaldojr/cognitivecomputing/contents/material/aulas/PDI/"
url_completa = api_url + laboratorio
print(f"Fazendo o download de: {url_completa}")
# checa se a URL está acessível
response = requests.get(url_completa)
if response.status_code != 200:
raise Exception(f"Erro ao acessar o repositório: {response.status_code}")
files = response.json()
# Faz o download de cada arquivo
os.makedirs(diretorio, exist_ok=True) # Cria a pasta downloads
for file in files:
file_name = file['name']
if file_name.endswith(('.png', '.jpg', '.jpeg', '.mp4')): # Adicione mais extensões se necessário
file_url = file['download_url']
destination = os.path.join(diretorio, file_name)
download_file(file_url, destination)
print(f"Download concluído. Arquivos salvos na pasta {diretorio}.")
Introdução ao Template Matching¶
Até agora, estudamos técnicas para realizar a segmentação e detecção de objetos e formas simples como círculos, retas e cores. Nesta aula, vamos explorar uma técnica mais avançada chamada template matching.
O template matching é um método usado para localizar um padrão (template) dentro de uma imagem maior. Conceitualmente, é como procurar uma peça específica em um quebra-cabeça, onde:
- Template: É a imagem de referência que queremos encontrar
- Imagem de análise: É a imagem maior onde queremos encontrar o template
Como funciona?¶
O algoritmo de template matching funciona de forma similar aos filtros de convolução que estudamos anteriormente. O processo consiste em:
- Deslizar o template sobre cada posição da imagem maior
- Calcular uma métrica de similaridade entre o template e a região atual
- Gerar um mapa de similaridade onde os valores indicam o grau de correspondência
Na OpenCV, esse processo é implementado através da função cv2.matchTemplate()
.
Detectando um personagem em um cenário de jogo¶
Vamos começar com um exemplo: encontrar o GOOMBA em uma cena do jogo Mario Bros.
Primeiro, vamos carregar a imagem principal:
%matplotlib inline
import cv2
import numpy as np
from matplotlib import pyplot as plt
# Configuração para exibir imagens maiores
plt.figure(figsize=(12, 8))
# Carrega a imagem principal (cena do jogo)
img = cv2.imread('mario.png')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # Converte BGR para RGB para visualização
# Exibe a imagem
plt.title("Cena do jogo Mario Bros")
plt.imshow(img_rgb)
plt.axis('off') # Remove os eixos para melhorar a visualização
plt.show()
Aplicando Template Matching¶
Agora vamos carregar a imagem do template (o GOOMBA que queremos encontrar) e aplicar a técnica de template matching:
%matplotlib inline
import cv2
import numpy as np
from matplotlib import pyplot as plt
# Carrega as imagens
template = cv2.imread('mario-template.png', 0) # Template (GOOMBA) em escala de cinza
img = cv2.imread('mario.png') # Cena completa
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Converte para escala de cinza
# Exibe o template
plt.figure(figsize=(4, 4))
plt.title("Template (GOOMBA)")
plt.imshow(template, cmap="gray")
plt.axis('off')
plt.show()
# Aplica template matching usando o método TM_SQDIFF
# TM_SQDIFF calcula a diferença quadrática - valor menor indica melhor correspondência
res = cv2.matchTemplate(img_gray, template, cv2.TM_SQDIFF)
# Exibe informações sobre o resultado
print(f"Dimensões do mapa de correspondência: {res.shape}")
print(f"Tipo de dados: {res.dtype}")
print(f"Valor mínimo (melhor correspondência): {np.min(res):.2f}")
print(f"Valor máximo (pior correspondência): {np.max(res):.2f}")
# Visualiza o mapa de correspondência
plt.figure(figsize=(10, 8))
plt.title("Mapa de correspondência (valores mais escuros = melhor correspondência)")
plt.imshow(res, cmap="hot")
plt.colorbar(label="Valor de correspondência")
plt.show()
Dimensões do mapa de correspondência: (624, 965) Tipo de dados: float32 Valor mínimo (melhor correspondência): 1859.00 Valor máximo (pior correspondência): 58356124.00
Interpretando o Resultado¶
O resultado do template matching é uma matriz onde cada pixel representa o grau de correspondência entre o template e a região correspondente na imagem original. No método cv2.TM_SQDIFF
que usamos:
- Valores mais baixos indicam melhor correspondência (menor diferença)
- Valores mais altos indicam pior correspondência (maior diferença)
Visualmente, podemos notar que o nosso GOOMBA está destacado na imagem como o pixel mais escuro.
Para capturar a posição exata deste pixel (a melhor correspondência), vamos usar a função cv2.minMaxLoc()
, que retorna os valores mínimo e máximo, além de suas posições na imagem.
# Encontra os valores mínimos e máximos e suas localizações no mapa de correspondência
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
print(f"Valores encontrados:")
print(f"- Valor mínimo: {min_val:.2f} na posição {min_loc} (melhor correspondência para TM_SQDIFF)")
print(f"- Valor máximo: {max_val:.2f} na posição {max_loc}")
# Explicação dos resultados:
print("\nNo método TM_SQDIFF:")
print("- O valor mínimo representa a melhor correspondência")
print("- min_loc é o ponto superior esquerdo onde o template melhor se encaixa")
Valores encontrados: - Valor mínimo: 1859.00 na posição (709, 511) (melhor correspondência para TM_SQDIFF) - Valor máximo: 58356124.00 na posição (927, 11) No método TM_SQDIFF: - O valor mínimo representa a melhor correspondência - min_loc é o ponto superior esquerdo onde o template melhor se encaixa
DESAFIO 1: Destacando o objeto encontrado¶
Agora que encontramos a posição do GOOMBA na imagem, vamos destacá-lo desenhando um retângulo ao seu redor.
Dicas:¶
- Use a função
cv2.rectangle(img, ponto_inicial, ponto_final, cor, espessura)
- O
ponto_inicial
já temos: é omin_loc
- Para o
ponto_final
, precisamos somar as dimensões do template ao ponto inicial - Para cores, use uma tupla (B, G, R) - lembre-se que o OpenCV usa BGR!

# Implemente sua solução aqui
# Dicas:
# 1. Extraia a largura e altura do template
# 2. Calcule o ponto final do retângulo (bottom_right)
# 3. Use cv2.rectangle para desenhar na imagem
# 4. Converta para RGB para visualização e mostre o resultado
# Solução do desafio 1
# Cria uma cópia da imagem para não modificar a original
img_result = img.copy()
# Extrai as dimensões do template (altura e largura)
altura, largura = template.shape
# Calcula a posição do ponto inferior direito do retângulo (bottom_right)
bottom_right = (min_loc[0] + largura, min_loc[1] + altura)
# Desenha o retângulo na imagem
# Parâmetros: imagem, ponto_inicial, ponto_final, cor (B,G,R), espessura
cv2.rectangle(img_result, min_loc, bottom_right, (0, 255, 255), 4)
# Exibe o resultado
plt.figure(figsize=(12, 8))
plt.title("GOOMBA detectado!")
plt.imshow(cv2.cvtColor(img_result, cv2.COLOR_BGR2RGB))
plt.axis('off')
plt.show()
DESAFIO 2: Explorando diferentes métodos de correspondência¶
A função cv2.matchTemplate(imagem, template, método)
suporta diferentes métodos de cálculo de correlação, cada um com suas vantagens e características.
Métodos disponíveis no OpenCV:¶
- cv2.TM_SQDIFF: Diferença quadrática - valor menor indica melhor correspondência
- cv2.TM_SQDIFF_NORMED: Diferença quadrática normalizada (entre 0 e 1)
- cv2.TM_CCORR: Correlação cruzada - valor maior indica melhor correspondência
- cv2.TM_CCORR_NORMED: Correlação cruzada normalizada
- cv2.TM_CCOEFF: Coeficiente de correlação - valor maior indica melhor correspondência
- cv2.TM_CCOEFF_NORMED: Coeficiente de correlação normalizado
Seu desafio:¶
Teste pelo menos 3 métodos diferentes e compare os resultados visuais e numéricos.
Referência: OpenCV Template Matching Tutorial
# Implemente sua solução do Desafio 2 aqui
# Dicas:
# 1. Crie uma função que aplica diferentes métodos e mostra os resultados
# 2. Lembre-se que para TM_SQDIFF e TM_SQDIFF_NORMED, menores valores são melhores
# 3. Para os outros métodos, maiores valores são melhores
Detectando múltiplas ocorrências: Caixas de interrogação¶
No exemplo anterior, encontramos uma única ocorrência do GOOMBA. Mas e se quisermos encontrar múltiplas ocorrências de um objeto na imagem?
Vamos agora tentar localizar todas as caixas de interrogação (?
) na imagem do Mario:
O resultado final deve detectar 4 caixas, como mostrado abaixo:
%matplotlib inline
import cv2
import numpy as np
from matplotlib import pyplot as plt
# Carrega o template da caixa de interrogação
template = cv2.imread('mario-template2.png', 0)
# Carrega a imagem principal
img = cv2.imread('mario.png')
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Exibe o template
plt.figure(figsize=(4, 4))
plt.title("Template (Caixa de interrogação)")
plt.imshow(template, cmap="gray")
plt.axis('off')
plt.show()
# Aplica template matching com normalização
# Usamos TM_SQDIFF_NORMED para obter valores entre 0 e 1
res = cv2.matchTemplate(img_gray, template, cv2.TM_SQDIFF_NORMED)
# Visualiza o mapa de correspondência
plt.figure(figsize=(12, 8))
plt.title("Mapa de correspondência - Observe os múltiplos pontos escuros")
plt.imshow(res, cmap="hot")
plt.colorbar(label="Valor de correspondência (menor = melhor)")
plt.show()
Detectando múltiplas correspondências¶
Podemos notar que as caixas de interrogação aparecem como múltiplos pontos escuros no mapa de correspondência.
Para capturar todas essas ocorrências, a função cv2.minMaxLoc()
não é suficiente, pois ela retorna apenas a melhor correspondência. Precisamos de uma abordagem diferente:
- Definir um valor limiar (threshold) para considerar uma correspondência válida
- Encontrar todas as posições onde o valor de correspondência é menor (ou maior, dependendo do método) que esse limiar
- Para cada uma dessas posições, desenhar um retângulo ao redor do objeto encontrado
Vamos usar a função numpy.where()
para isso.
Conceitos úteis do NumPy e Python¶
Antes de prosseguir, vamos revisar duas funções importantes que utilizaremos:
1. numpy.where()
¶
A função np.where()
retorna os índices onde uma condição é satisfeita em um array NumPy.
Documentação: numpy.where
# Exemplo didático de np.where()
# Criando uma matriz de exemplo 3x3
matriz = np.array([
[0, 1, 5],
[2, 2, 3],
[0, 3, 1]
])
print(f"Matriz original:\n{matriz}\n")
# Encontrando onde os valores são maiores ou iguais a 2
posicoes = np.where(matriz >= 2)
print(f"Resultado de np.where(matriz >= 2):\n{posicoes}")
print(f"Tipo: {type(posicoes)}")
# O resultado é uma tupla com dois arrays:
print(f"\nPrimeira parte (índices das linhas): {posicoes[0]}")
print(f"Segunda parte (índices das colunas): {posicoes[1]}")
# Mostrando os valores encontrados
print("\nValores encontrados nas posições:")
for i, j in zip(posicoes[0], posicoes[1]):
print(f"matriz[{i}, {j}] = {matriz[i, j]}")
Matriz original: [[0 1 5] [2 2 3] [0 3 1]] Resultado de np.where(matriz >= 2): (array([0, 1, 1, 1, 2]), array([2, 0, 1, 2, 1])) Tipo: <class 'tuple'> Primeira parte (índices das linhas): [0 1 1 1 2] Segunda parte (índices das colunas): [2 0 1 2 1] Valores encontrados nas posições: matriz[0, 2] = 5 matriz[1, 0] = 2 matriz[1, 1] = 2 matriz[1, 2] = 3 matriz[2, 1] = 3
2. zip()
¶
A função zip()
em Python permite iterar sobre múltiplas sequências em paralelo, combinando seus elementos.
# Exemplo didático de zip()
letras = ['F', 'I', 'A', 'P']
numeros = [1, 2, 3, 4]
print("Combinando duas listas com zip():")
for letra, numero in zip(letras, numeros):
print(f"Letra: {letra}, Número: {numero}")
# Usando zip com a sintaxe * para "desempacotar" uma tupla
coordenadas = ([1, 2, 3], [10, 20, 30]) # Similar ao resultado de np.where()
print("\nDesempacotando uma tupla de coordenadas:")
for x, y in zip(*coordenadas):
print(f"Ponto: ({x}, {y})")
Combinando duas listas com zip(): Letra: F, Número: 1 Letra: I, Número: 2 Letra: A, Número: 3 Letra: P, Número: 4 Desempacotando uma tupla de coordenadas: Ponto: (1, 10) Ponto: (2, 20) Ponto: (3, 30)
Detectando todas as caixas de interrogação¶
Agora vamos aplicar esses conceitos para encontrar todas as caixas de interrogação na imagem:
# Cria uma cópia da imagem original para não modificá-la
img_result = img.copy()
# Obtém as dimensões do template
altura, largura = template.shape
# Define um valor limiar para considerar uma correspondência válida
# Este valor pode precisar de ajustes dependendo da imagem e do método usado
threshold = 0.1
# Encontra todas as posições onde o valor de correspondência é menor que o limiar
# Como usamos TM_SQDIFF_NORMED, valores menores indicam melhor correspondência
loc = np.where(res <= threshold)
print(f"Encontradas {len(loc[0])} possíveis correspondências")
# Para cada ponto de correspondência, desenha um retângulo
count = 0
for pt in zip(*loc[::-1]): # [::-1] inverte as coordenadas para formato (x,y)
# Desenha o retângulo
cv2.rectangle(
img_result, # Imagem onde desenhar
pt, # Ponto superior esquerdo
(pt[0] + largura, pt[1] + altura), # Ponto inferior direito
(0, 255, 0), # Cor verde (BGR)
3 # Espessura da linha
)
count += 1
print(f"Desenhados {count} retângulos após filtrar correspondências sobrepostas")
# Exibe o resultado
plt.figure(figsize=(12, 8))
plt.title(f"Caixas de interrogação detectadas")
plt.imshow(cv2.cvtColor(img_result, cv2.COLOR_BGR2RGB))
plt.axis('off')
plt.show()
Encontradas 32 possíveis correspondências Desenhados 32 retângulos após filtrar correspondências sobrepostas
DESAFIO 3: Testando outros métodos¶
Agora que entendemos como detectar múltiplas ocorrências, vamos testar outros métodos de template matching para as caixas de interrogação.
Substitua o método TM_SQDIFF_NORMED
por outro e ajuste o threshold conforme necessário:
- Para
TM_SQDIFF
eTM_SQDIFF_NORMED
: useres <= threshold
(valores menores são melhores) - Para
TM_CCORR
,TM_CCORR_NORMED
,TM_CCOEFF
eTM_CCOEFF_NORMED
: useres >= threshold
(valores maiores são melhores)
# Implemente sua solução do Desafio 3 aqui
# Dicas:
# 1. Troque o método TM_SQDIFF_NORMED por outro (ex: TM_CCOEFF_NORMED)
# 2. Se usar métodos como TM_CCOEFF_NORMED, lembre-se de inverter a condição (res >= threshold)
# 3. Ajuste o valor do threshold conforme necessário
DESAFIO 4: Limitações do Template Matching¶
O template matching é uma técnica útil, mas tem limitações importantes. Reflita sobre as seguintes questões:
Escala: O que acontece se o template não estiver na mesma escala do objeto que queremos detectar na imagem?
Iluminação: Como o algoritmo se comporta quando há diferenças de brilho ou contraste entre o template e a imagem?
Rotação: O que acontece se o objeto na imagem estiver rotacionado em relação ao template?
Oclusão parcial: Como o método responde quando o objeto está parcialmente encoberto?
Para cada uma dessas limitações, existe alguma estratégia que poderia ser aplicada para mitigar o problema? Quais outras técnicas de visão computacional poderiam ser mais adequadas nesses casos?