{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "cellUniqueIdByVincent": "aa357"
   },
   "source": [
    "## Features e Descritores em Visão Computacional\n",
    "\n",
    "## Objetivos da aula:\n",
    "\n",
    "* Compreender o conceito de **FEATURES** (características) em imagens\n",
    "* Aplicar algoritmos de detecção de pontos-chave (keypoints)\n",
    "* Utilizar **DESCRITORES** para reconhecimento de objetos\n",
    "* Implementar matching de imagens com invariância a escala e rotação\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "cellUniqueIdByVincent": "c9f43"
   },
   "source": [
    "## Contextualização do Problema\n",
    "\n",
    "Em aulas anteriores, exploramos várias técnicas de processamento de imagens, incluindo o **template matching**. Embora essa técnica seja eficaz para encontrar padrões exatos em imagens, ela apresenta limitações significativas:\n",
    "\n",
    "* Sensibilidade a **mudanças de escala**\n",
    "* Problemas com **rotação** do objeto\n",
    "* Vulnerabilidade a variações de **iluminação**\n",
    "* Dificuldade com **oclusões parciais**\n",
    "\n",
    "Para superar essas limitações, vamos explorar técnicas baseadas em **features locais**, que são mais robustas a estas transformações."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "cellUniqueIdByVincent": "0b33a"
   },
   "source": [
    "## O Desafio: Detectar um Objeto em uma Cena\n",
    "\n",
    "Queremos localizar esta caixa específica:\n",
    "\n",
    "![](box.png)\n",
    "\n",
    "Dentro desta cena mais complexa:\n",
    "![](box_in_scene.png)\n",
    "Observe que a caixa aparece em uma posição diferente, com rotação e escala alteradas, além de estar parcialmente obstruída."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "cellUniqueIdByVincent": "c1c84"
   },
   "source": [
    "## Reflexão Inicial\n",
    "\n",
    "🤔 **Pense:** Com as técnicas que já conhecemos (como template matching, detecção de contornos ou segmentação por cor), seria possível resolver este problema de forma robusta?\n",
    "\n",
    "* Quais seriam as limitações?\n",
    "* Como poderíamos detectar o objeto mesmo quando ele está rotacionado?\n",
    "* Como lidar com diferentes escalas?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "cellUniqueIdByVincent": "3843a"
   },
   "source": [
    "## Introdução às Features em Visão Computacional\n",
    "\n",
    "### O que são Features?\n",
    "\n",
    "**Features** (características) são pontos ou regiões de interesse em uma imagem que possuem propriedades distintivas, podendo ser identificadas de forma consistente mesmo sob diferentes transformações.Podem ser:\n",
    "\n",
    "- Pontos (como cantos ou junções)\n",
    "- Bordas (mudanças abruptas de intensidade)\n",
    "- Blobs (regiões com propriedades aproximadamente constantes)\n",
    "- Regiões (áreas com textura específica)\n",
    "\n",
    "### Features Ideais devem ser:\n",
    "\n",
    "* **Repetíveis**: Detectáveis mesmo com mudanças de iluminação, ruído, etc.\n",
    "* **Distintivas**: Facilmente diferenciáveis de outras features\n",
    "* **Locais**: Ocupando uma pequena área da imagem\n",
    "* **Numerosas**: Em quantidade suficiente para representar o objeto\n",
    "* **Precisas**: Localizáveis com exatidão\n",
    "* **Eficientes**: Rápidas de computar\n",
    "\n",
    "### Principais Algoritmos de Detecção e Descrição de Features:\n",
    "\n",
    "1. **SIFT** (Scale-Invariant Feature Transform) - Robusto mas computacionalmente intensivo\n",
    "2. **SURF** (Speeded-Up Robust Features) - Mais rápido que SIFT, também patenteado\n",
    "3. **ORB** (Oriented FAST and Rotated BRIEF) - Código aberto, mais rápido\n",
    "4. **FAST** (Features from Accelerated Segment Test) - Detector rápido, sem descritor\n",
    "5. **BRISK** (Binary Robust Invariant Scalable Keypoints) - Alternativa binária\n",
    "\n",
    "### Aplicações:\n",
    "\n",
    "* Reconhecimento de objetos\n",
    "* Stitching de imagens (panoramas)\n",
    "* Reconstrução 3D\n",
    "* Rastreamento de objetos (tracking)\n",
    "* Realidade aumentada\n",
    "* Indexação e recuperação de imagens"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "cellUniqueIdByVincent": "26761"
   },
   "source": [
    "## Detecção de Features com ORB\n",
    "\n",
    "Vamos usar o algoritmo **ORB** (Oriented FAST and Rotated BRIEF), que é uma excelente alternativa aos algoritmos patenteados como SIFT e SURF. O ORB foi desenvolvido pela OpenCV Labs e combina:\n",
    "\n",
    "* O detector FAST modificado para detecção de keypoints\n",
    "* O descritor BRIEF modificado para descrição de features\n",
    "* Adição de orientação para garantir invariância à rotação\n",
    "\n",
    "**Documentação:** [OpenCV ORB](https://docs.opencv.org/4.x/db/d95/classcv_1_1ORB.html)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "cellUniqueIdByVincent": "8fda9"
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 291748 characters]",
      "text/plain": [
       "<Figure size 800x800 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 618840 characters]",
      "text/plain": [
       "<Figure size 1200x1000 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "%matplotlib inline\n",
    "import cv2\n",
    "from matplotlib import pyplot as plt\n",
    "import numpy as np\n",
    "\n",
    "# Configurações para melhor visualização\n",
    "plt.rcParams['figure.figsize'] = [12, 8]\n",
    "plt.rcParams['font.size'] = 12\n",
    "\n",
    "img1 = cv2.imread('lab_images/box.png', 0)  # Template (objeto de referência)\n",
    "img2 = cv2.imread('lab_images/box_in_scene.png', 0)  # Imagem de busca, pode ser uma imagem ou video\n",
    "    \n",
    "    \n",
    "# Exibir o template\n",
    "plt.figure(figsize=(8, 8))\n",
    "plt.title(\"Template - Objeto de Referência\")\n",
    "plt.imshow(img1, cmap=\"gray\")\n",
    "plt.axis('on')\n",
    "plt.show()\n",
    "\n",
    "# Exibir a imagem de busca\n",
    "plt.figure(figsize=(12, 10))\n",
    "plt.title(\"Imagem de Busca - Cena Completa\")\n",
    "plt.imshow(img2, cmap=\"gray\")\n",
    "plt.axis('on')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "cellUniqueIdByVincent": "b36a8"
   },
   "source": [
    "## O que são Descritores?\n",
    "\n",
    "Um **descritor** é uma \"assinatura\" matemática que codifica a informação ao redor de uma feature detectada. É tipicamente um vetor numérico que caracteriza:\n",
    "\n",
    "- O padrão de intensidade ao redor do ponto\n",
    "- A distribuição de gradientes na vizinhança\n",
    "- Outros atributos que permitem a correspondência entre features em diferentes imagens"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "cellUniqueIdByVincent": "6723d"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Detector ORB inicializado com sucesso!\n"
     ]
    }
   ],
   "source": [
    "# Inicializar o detector e descritor ORB\n",
    "# Por padrão, detectará até 500 keypoints\n",
    "orb = cv2.ORB_create()\n",
    "\n",
    "print(\"Detector ORB inicializado com sucesso!\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "cellUniqueIdByVincent": "3e516"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Foram detectados 453 keypoints na imagem template\n",
      "Formato dos descritores: (453, 32)\n"
     ]
    }
   ],
   "source": [
    "# Detectar keypoints e calcular os descritores na imagem template\n",
    "\n",
    "# Abordagem separada (passo-a-passo):\n",
    "# 1. Detectar keypoints\n",
    "# kp = orb.detect(img1, None)\n",
    "# 2. Calcular descritores\n",
    "# kp, des = orb.compute(img1, kp)\n",
    "\n",
    "# Abordagem combinada (mais eficiente):\n",
    "kp1, des1 = orb.detectAndCompute(img1, None)\n",
    "\n",
    "print(f\"Foram detectados {len(kp1)} keypoints na imagem template\")\n",
    "print(f\"Formato dos descritores: {des1.shape if des1 is not None else 'Nenhum descritor calculado'}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "cellUniqueIdByVincent": "05905"
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 526848 characters]",
      "text/plain": [
       "<Figure size 1000x1000 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Visualizar os keypoints detectados (versão simples)\n",
    "img_keypoints = cv2.drawKeypoints(img1, kp1, outImage=None, color=(0,255,0), \n",
    "                                 flags=cv2.DrawMatchesFlags_DEFAULT)\n",
    "\n",
    "plt.figure(figsize=(10, 10))\n",
    "plt.title(\"Keypoints detectados na imagem template\")\n",
    "plt.imshow(cv2.cvtColor(img_keypoints, cv2.COLOR_BGR2RGB))\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "cellUniqueIdByVincent": "a9303"
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 875684 characters]",
      "text/plain": [
       "<Figure size 1000x1000 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Visualizar os keypoints com informação de escala e orientação\n",
    "img_keypoints_rich = cv2.drawKeypoints(img1, kp1, outImage=None, \n",
    "                                      flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)\n",
    "\n",
    "plt.figure(figsize=(10, 10))\n",
    "plt.title(\"Keypoints com escala e orientação\")\n",
    "plt.imshow(cv2.cvtColor(img_keypoints_rich, cv2.COLOR_BGR2RGB))\n",
    "plt.xlabel(\"As setas indicam a orientação do gradiente dominante em cada ponto\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "cellUniqueIdByVincent": "526ae"
   },
   "source": [
    "## Desafio 1: Explorando Parâmetros do ORB\n",
    "\n",
    "Por padrão, o detector ORB é configurado para encontrar 500 features na imagem.\n",
    "\n",
    "### Sua missão:\n",
    "1. Consulte a [documentação do ORB](https://docs.opencv.org/4.x/db/d95/classcv_1_1ORB.html)\n",
    "2. Descubra como alterar o número máximo de features detectadas\n",
    "3. Identifique outros parâmetros importantes do ORB\n",
    "4. Implemente um exemplo que use parâmetros personalizados\n",
    "5. Compare o resultado com a implementação padrão\n",
    "\n",
    "**Dica:** Alguns parâmetros importantes incluem `nfeatures`, `scaleFactor`, `nlevels` e `edgeThreshold`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "cellUniqueIdByVincent": "9949d"
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 376420 characters]",
      "text/plain": [
       "<Figure size 1000x1000 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "\n",
    "# Implemente sua solução para o Desafio 1 aqui\n",
    "\n",
    "# Exemplo de implementação com parâmetros customizados\n",
    "orb_custom = cv2.ORB_create(\n",
    "    nfeatures=50,      # Número máximo de features a serem retidas (padrão: 500)\n",
    "    scaleFactor=1.2,    # Fator de escala entre níveis na pirâmide (padrão: 1.2)\n",
    "    nlevels=8,          # Número de níveis na pirâmide (padrão: 8)\n",
    "    edgeThreshold=31,   # Tamanho da borda (padrão: 31)\n",
    "    firstLevel=0,       # Nível da pirâmide onde é colocada a imagem de entrada (padrão: 0)\n",
    "    WTA_K=2,            # Número de pontos para produzir cada elemento do descritor BRIEF (2 ou 3, padrão: 2)\n",
    "    scoreType=cv2.ORB_HARRIS_SCORE,  # Tipo de pontuação (HARRIS_SCORE ou FAST_SCORE)\n",
    "    patchSize=31,       # Tamanho do patch usado para orientação (padrão: 31)\n",
    "    fastThreshold=20    # Limiar para o detector de cantos FAST (padrão: 20)\n",
    ")\n",
    "\n",
    "# Compare os resultados com diferentes configurações\n",
    "# ...\n",
    "kp1, des1 = orb_custom.detectAndCompute(img1, None)\n",
    "kp2, des2 = orb_custom.detectAndCompute(img2, None)\n",
    "\n",
    "\n",
    "# Visualizar os keypoints com informação de escala e orientação\n",
    "img_keypoints_rich = cv2.drawKeypoints(img1, kp1, outImage=None, \n",
    "                                      flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)\n",
    "\n",
    "# Visualizar os keypoints com informação de escala e orientação\n",
    "img2_keypoints_rich = cv2.drawKeypoints(img2, kp2, outImage=None, \n",
    "                                      flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "plt.figure(figsize=(10, 10))\n",
    "plt.subplot(1, 2, 1)\n",
    "plt.title(\"Keypoints na Imagem Template\")\n",
    "plt.imshow(cv2.cvtColor(img_keypoints_rich, cv2.COLOR_BGR2RGB))\n",
    "plt.subplot(1, 2, 2)\n",
    "plt.title(\"Keypoints na Imagem de Busca\")\n",
    "plt.imshow(cv2.cvtColor(img2_keypoints_rich, cv2.COLOR_BGR2RGB))\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "cellUniqueIdByVincent": "bd811"
   },
   "source": [
    "### Exemplo de resultado com poucas features\n",
    "\n",
    "Com apenas 4 features detectadas, é possível visualizar melhor cada keypoint, sua escala e orientação:\n",
    "\n",
    "![](box_4features.png)\n",
    "\n",
    "Cada círculo representa um keypoint, com:\n",
    "- O raio do círculo indicando a escala\n",
    "- A linha dentro do círculo representando a orientação"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "cellUniqueIdByVincent": "aca3d"
   },
   "source": [
    "## Detectando o Objeto na Cena\n",
    "\n",
    "Agora que já extraímos os keypoints e descritores da imagem de referência (template), precisamos:\n",
    "\n",
    "1. Detectar keypoints e calcular descritores na imagem da cena\n",
    "2. Encontrar correspondências (matches) entre os descritores das duas imagens\n",
    "3. Filtrar as melhores correspondências\n",
    "4. Determinar a localização do objeto"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "cellUniqueIdByVincent": "aa448"
   },
   "source": [
    "## Desafio 2: Comparando Keypoints\n",
    "\n",
    "### Sua missão:\n",
    "1. Calcule os keypoints e descritores para a imagem da cena (box_in_scene.png)\n",
    "2. Visualize os keypoints detectados nas duas imagens\n",
    "3. Compare os resultados e analise:\n",
    "   - Quantos keypoints foram detectados em cada imagem?\n",
    "   - Existe alguma região com maior concentração de keypoints?\n",
    "   - Você consegue identificar visualmente pontos correspondentes entre as duas imagens?\n",
    "\n",
    "**Dica:** Use a mesma configuração do ORB para ambas as imagens para uma comparação justa."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "cellUniqueIdByVincent": "fd404"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Keypoints na imagem template: 47\n",
      "Keypoints na imagem da cena: 500\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 1000028 characters]",
      "text/plain": [
       "<Figure size 1000x1000 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Implemente sua solução para o Desafio 2 aqui\n",
    "\n",
    "# Detectar keypoints e calcular descritores na imagem da cena\n",
    "kp2, des2 = orb.detectAndCompute(img2, None)\n",
    "\n",
    "print(f\"Keypoints na imagem template: {len(kp1)}\")\n",
    "print(f\"Keypoints na imagem da cena: {len(kp2)}\")\n",
    "\n",
    "# Visualizar os keypoints nas duas imagens\n",
    "# ...\n",
    "\n",
    "\n",
    "# Visualizar os keypoints com informação de escala e orientação\n",
    "img2_keypoints_rich = cv2.drawKeypoints(img2, kp2, outImage=None, \n",
    "                                      flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)\n",
    "\n",
    "plt.figure(figsize=(10, 10))\n",
    "plt.title(\"Keypoints com escala e orientação\")\n",
    "plt.imshow(cv2.cvtColor(img2_keypoints_rich, cv2.COLOR_BGR2RGB))\n",
    "plt.xlabel(\"As setas indicam a orientação do gradiente dominante em cada ponto\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "cellUniqueIdByVincent": "8cfd2"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Keypoints na imagem template: 865\n",
      "Keypoints na imagem da cena: 1000\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 948960 characters]",
      "text/plain": [
       "<Figure size 1500x700 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "%matplotlib inline\n",
    "import cv2\n",
    "from matplotlib import pyplot as plt\n",
    "import numpy as np\n",
    "\n",
    "# Carregar as imagens\n",
    "img1 = cv2.imread('lab_images/box.png', 0)  # Template (objeto de referência)\n",
    "img2 = cv2.imread('lab_images/box_in_scene.png', 0)  # Imagem de busca\n",
    "\n",
    "# Criar detector ORB\n",
    "orb = cv2.ORB_create(nfeatures=1000)  # Aumentando o número de features para melhor visualização\n",
    "\n",
    "# Calcular keypoints e descritores\n",
    "kp1, des1 = orb.detectAndCompute(img1, None)\n",
    "kp2, des2 = orb.detectAndCompute(img2, None)\n",
    "\n",
    "print(f\"Keypoints na imagem template: {len(kp1)}\")\n",
    "print(f\"Keypoints na imagem da cena: {len(kp2)}\")\n",
    "\n",
    "# Desenhar keypoints nas imagens\n",
    "img1_keypoints = cv2.drawKeypoints(img1, kp1, outImage=None, color=(0, 255, 0),\n",
    "                                  flags=cv2.DrawMatchesFlags_DEFAULT)\n",
    "img2_keypoints = cv2.drawKeypoints(img2, kp2, outImage=None, color=(0, 255, 0),\n",
    "                                  flags=cv2.DrawMatchesFlags_DEFAULT)\n",
    "\n",
    "# Visualizar os resultados\n",
    "plt.figure(figsize=(15, 7))\n",
    "\n",
    "plt.subplot(1, 2, 1)\n",
    "plt.title(\"Keypoints no Template\")\n",
    "plt.imshow(img1_keypoints)\n",
    "\n",
    "plt.subplot(1, 2, 2)\n",
    "plt.title(\"Keypoints na Cena\")\n",
    "plt.imshow(img2_keypoints)\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "cellUniqueIdByVincent": "029b2"
   },
   "source": [
    "## Matching de Features: Encontrando Correspondências\n",
    "\n",
    "Agora que detectamos keypoints em ambas as imagens, precisamos encontrar quais pontos da imagem de referência correspondem aos pontos da imagem de busca.\n",
    "\n",
    "### Algoritmos de Matching:\n",
    "\n",
    "1. **Brute-Force Matcher**: Compara cada descritor da primeira imagem com todos os descritores da segunda imagem.\n",
    "\n",
    "2. **FLANN (Fast Library for Approximate Nearest Neighbors)**: Mais rápido para grandes conjuntos de dados.\n",
    "\n",
    "Vamos usar o Brute-Force Matcher que é mais simples e adequado para nosso exemplo:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "cellUniqueIdByVincent": "08018"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(< cv2.DMatch 0x12fa901f0>, < cv2.DMatch 0x12e6a2a30>, < cv2.DMatch 0x12e6a2a70>, < cv2.DMatch 0x12e6a1130>, < cv2.DMatch 0x12e6a3c10>, < cv2.DMatch 0x12e6a35b0>, < cv2.DMatch 0x12e6a1230>, < cv2.DMatch 0x12e6a3f90>, < cv2.DMatch 0x12e6a0470>, < cv2.DMatch 0x12e6a0fb0>, < cv2.DMatch 0x12e6a3f70>, < cv2.DMatch 0x12e6a2cb0>, < cv2.DMatch 0x12e6a2d50>, < cv2.DMatch 0x12e6a1570>, < cv2.DMatch 0x12e6a0e10>, < cv2.DMatch 0x12e6a3a50>, < cv2.DMatch 0x12e6a2d30>, < cv2.DMatch 0x12e6a2d10>, < cv2.DMatch 0x12e6a07b0>, < cv2.DMatch 0x12e6a2d70>, < cv2.DMatch 0x12e6a2cd0>, < cv2.DMatch 0x12e6a2c90>, < cv2.DMatch 0x12e6a3210>, < cv2.DMatch 0x12e6a32d0>, < cv2.DMatch 0x12e6a3230>, < cv2.DMatch 0x12e6a3190>, < cv2.DMatch 0x12e6a2f50>, < cv2.DMatch 0x12e6a2f10>, < cv2.DMatch 0x12e6a31f0>, < cv2.DMatch 0x12e6a3250>, < cv2.DMatch 0x12e6a31b0>, < cv2.DMatch 0x12e6a33d0>, < cv2.DMatch 0x12e6a3150>, < cv2.DMatch 0x12e6a2fb0>, < cv2.DMatch 0x12e6a3170>, < cv2.DMatch 0x12e6a2db0>, < cv2.DMatch 0x12e6a3450>, < cv2.DMatch 0x12e6a31d0>, < cv2.DMatch 0x12e6a3090>, < cv2.DMatch 0x12e6a2e70>, < cv2.DMatch 0x12e6a3350>, < cv2.DMatch 0x12e6a2690>, < cv2.DMatch 0x12e6a3110>, < cv2.DMatch 0x12e6a2f70>, < cv2.DMatch 0x12e6a2dd0>, < cv2.DMatch 0x12e6a2630>, < cv2.DMatch 0x12e6a1eb0>, < cv2.DMatch 0x12e6a32b0>, < cv2.DMatch 0x12e6a3130>, < cv2.DMatch 0x12e6a2f90>, < cv2.DMatch 0x12e6a2d90>, < cv2.DMatch 0x12e6a2670>, < cv2.DMatch 0x12e6a2650>, < cv2.DMatch 0x12e6a3330>, < cv2.DMatch 0x12e6a1ed0>, < cv2.DMatch 0x12e6a2c70>, < cv2.DMatch 0x12e6a2cf0>, < cv2.DMatch 0x12e6a2c30>, < cv2.DMatch 0x12e6a2ad0>, < cv2.DMatch 0x12e6a25d0>, < cv2.DMatch 0x12e6a2590>, < cv2.DMatch 0x12e6a2350>, < cv2.DMatch 0x12e6a2310>, < cv2.DMatch 0x12e6a2c50>, < cv2.DMatch 0x12e6a2bb0>, < cv2.DMatch 0x12e6a2a90>, < cv2.DMatch 0x12e6a2770>, < cv2.DMatch 0x12e6a2ab0>, < cv2.DMatch 0x12e6a28d0>, < cv2.DMatch 0x12e6a2730>, < cv2.DMatch 0x12e6a2750>, < cv2.DMatch 0x12e6a20b0>, < cv2.DMatch 0x12e6a2110>, < cv2.DMatch 0x12e6a2070>, < cv2.DMatch 0x12e6a2050>, < cv2.DMatch 0x12e6a3490>, < cv2.DMatch 0x12e6a3370>, < cv2.DMatch 0x12e6a30f0>, < cv2.DMatch 0x12e6a3010>, < cv2.DMatch 0x12e6a3390>, < cv2.DMatch 0x12e6a3410>, < cv2.DMatch 0x12e6a1f50>, < cv2.DMatch 0x12e6a1f10>, < cv2.DMatch 0x12e6a1e90>, < cv2.DMatch 0x12e6a2790>, < cv2.DMatch 0x12e6a2b50>, < cv2.DMatch 0x12e6a2e50>, < cv2.DMatch 0x12e6a2850>, < cv2.DMatch 0x12e6a2b70>, < cv2.DMatch 0x12e6a2bd0>, < cv2.DMatch 0x12e6a2ed0>, < cv2.DMatch 0x12e6a26b0>, < cv2.DMatch 0x12e6a2b90>, < cv2.DMatch 0x12e6a2eb0>, < cv2.DMatch 0x12e6a2c10>, < cv2.DMatch 0x12e6a2e90>, < cv2.DMatch 0x12e6a2bf0>, < cv2.DMatch 0x12e6a2e30>, < cv2.DMatch 0x12e6a2e10>, < cv2.DMatch 0x12e6a24f0>, < cv2.DMatch 0x12e6a24d0>, < cv2.DMatch 0x12e6a24b0>, < cv2.DMatch 0x12e6a2530>, < cv2.DMatch 0x12e6a2470>, < cv2.DMatch 0x12e6a2490>, < cv2.DMatch 0x12e6a2550>, < cv2.DMatch 0x12e6a2430>, < cv2.DMatch 0x12e6a2450>, < cv2.DMatch 0x12e6a1cb0>, < cv2.DMatch 0x12e6a23f0>, < cv2.DMatch 0x12e6a2410>, < cv2.DMatch 0x12e6a23d0>, < cv2.DMatch 0x12e6a23b0>, < cv2.DMatch 0x12e6a2390>, < cv2.DMatch 0x12e6a3610>, < cv2.DMatch 0x12e6a3650>, < cv2.DMatch 0x12e6a34d0>, < cv2.DMatch 0x12e6a2ff0>, < cv2.DMatch 0x12e6a3510>, < cv2.DMatch 0x12e6a3710>, < cv2.DMatch 0x12e6a35f0>, < cv2.DMatch 0x12e6a3550>, < cv2.DMatch 0x12e6a37d0>, < cv2.DMatch 0x12e6a35d0>, < cv2.DMatch 0x12e6a2b10>, < cv2.DMatch 0x12e6a37b0>, < cv2.DMatch 0x12e6a36d0>, < cv2.DMatch 0x12e6a3570>, < cv2.DMatch 0x12e6a36f0>, < cv2.DMatch 0x12e6a3890>, < cv2.DMatch 0x12e6a3750>, < cv2.DMatch 0x12e6a3030>, < cv2.DMatch 0x12e6a3870>, < cv2.DMatch 0x12e6a3590>, < cv2.DMatch 0x12e6a3730>, < cv2.DMatch 0x12e6a37f0>, < cv2.DMatch 0x12e6a3950>, < cv2.DMatch 0x12e6a3810>, < cv2.DMatch 0x12e6a2b30>, < cv2.DMatch 0x12e6a3930>, < cv2.DMatch 0x12e6a3630>, < cv2.DMatch 0x12e6a3790>, < cv2.DMatch 0x12e6a38b0>, < cv2.DMatch 0x12e6a3a10>, < cv2.DMatch 0x12e6a38d0>, < cv2.DMatch 0x12e6a30d0>, < cv2.DMatch 0x12e6a39f0>, < cv2.DMatch 0x12e6a3770>, < cv2.DMatch 0x12e6a3850>, < cv2.DMatch 0x12e6a3070>, < cv2.DMatch 0x12e6a2fd0>, < cv2.DMatch 0x12e6a30b0>, < cv2.DMatch 0x12e6a1510>, < cv2.DMatch 0x12e6a21b0>, < cv2.DMatch 0x12e6a1db0>, < cv2.DMatch 0x12e6a2190>, < cv2.DMatch 0x12e6a2130>, < cv2.DMatch 0x12e6a3290>, < cv2.DMatch 0x12e6a2150>, < cv2.DMatch 0x12e6a2830>, < cv2.DMatch 0x12e6a2870>, < cv2.DMatch 0x12e6a2950>, < cv2.DMatch 0x12e6a27f0>, < cv2.DMatch 0x12e6a2890>, < cv2.DMatch 0x12e6a28f0>, < cv2.DMatch 0x12e6a29f0>, < cv2.DMatch 0x12e6a27b0>, < cv2.DMatch 0x12e6a28b0>, < cv2.DMatch 0x12e6a29d0>, < cv2.DMatch 0x12e6a2930>, < cv2.DMatch 0x12e6a29b0>, < cv2.DMatch 0x12e6a2910>, < cv2.DMatch 0x12e6a2990>, < cv2.DMatch 0x12e6a2970>, < cv2.DMatch 0x12e6a1cd0>, < cv2.DMatch 0x12e6a1d30>, < cv2.DMatch 0x12e6a2230>, < cv2.DMatch 0x12e6a1dd0>, < cv2.DMatch 0x12e6a06d0>, < cv2.DMatch 0x12e6a22d0>, < cv2.DMatch 0x12e6a1d50>, < cv2.DMatch 0x12e6a22b0>, < cv2.DMatch 0x12e6a2290>, < cv2.DMatch 0x12e6a2270>, < cv2.DMatch 0x12e6a2250>, < cv2.DMatch 0x12e6a20f0>, < cv2.DMatch 0x12e6a20d0>, < cv2.DMatch 0x12e6a2010>, < cv2.DMatch 0x12e6a3270>, < cv2.DMatch 0x12e6a2090>, < cv2.DMatch 0x12e6a1f90>, < cv2.DMatch 0x12e6a1f70>, < cv2.DMatch 0x12e6a1f30>, < cv2.DMatch 0x12e6a1e30>, < cv2.DMatch 0x12e6a1ef0>, < cv2.DMatch 0x12e6a26d0>, < cv2.DMatch 0x12e6a2610>, < cv2.DMatch 0x12e6a25f0>, < cv2.DMatch 0x12e6a21d0>, < cv2.DMatch 0x12e6a3470>, < cv2.DMatch 0x12e6a2210>, < cv2.DMatch 0x12e6a21f0>, < cv2.DMatch 0x12e6a2030>, < cv2.DMatch 0x12e6a1fd0>, < cv2.DMatch 0x12e6a1fb0>, < cv2.DMatch 0x12e6a1ff0>, < cv2.DMatch 0x12e6a1e70>, < cv2.DMatch 0x12e6a17f0>, < cv2.DMatch 0x12e6a3310>, < cv2.DMatch 0x12e6a2f30>, < cv2.DMatch 0x12e6a2ef0>, < cv2.DMatch 0x12e6a2a50>, < cv2.DMatch 0x12e6a2a10>, < cv2.DMatch 0x12e6a25b0>, < cv2.DMatch 0x12e6a2570>, < cv2.DMatch 0x12e6a2330>, < cv2.DMatch 0x12e6a22f0>, < cv2.DMatch 0x12e6a1e10>, < cv2.DMatch 0x12e6a14d0>, < cv2.DMatch 0x12e6a1b70>, < cv2.DMatch 0x12e6a0df0>, < cv2.DMatch 0x12e6a1bd0>, < cv2.DMatch 0x12e6a0c90>, < cv2.DMatch 0x12e6a03f0>, < cv2.DMatch 0x12e6a0d50>, < cv2.DMatch 0x12e6a17d0>, < cv2.DMatch 0x12e6a1810>, < cv2.DMatch 0x12e6a1df0>, < cv2.DMatch 0x12e6a15f0>, < cv2.DMatch 0x12e6a32f0>, < cv2.DMatch 0x12e6a05d0>, < cv2.DMatch 0x108ad7350>, < cv2.DMatch 0x108ad59b0>, < cv2.DMatch 0x12e603c90>, < cv2.DMatch 0x12e600550>, < cv2.DMatch 0x12e600410>, < cv2.DMatch 0x12e6004b0>, < cv2.DMatch 0x12e600270>, < cv2.DMatch 0x12e6004d0>, < cv2.DMatch 0x12e602c90>, < cv2.DMatch 0x12e6029b0>, < cv2.DMatch 0x12e602c30>, < cv2.DMatch 0x12e601630>, < cv2.DMatch 0x12e602bd0>, < cv2.DMatch 0x12e602910>, < cv2.DMatch 0x12e602810>, < cv2.DMatch 0x12e602d70>, < cv2.DMatch 0x12e602d50>, < cv2.DMatch 0x12e602930>, < cv2.DMatch 0x12e603a50>, < cv2.DMatch 0x12e6034f0>, < cv2.DMatch 0x12e603dd0>, < cv2.DMatch 0x12e603e30>, < cv2.DMatch 0x12e602a10>, < cv2.DMatch 0x12e603c70>, < cv2.DMatch 0x12e6028d0>, < cv2.DMatch 0x12e601590>, < cv2.DMatch 0x12e603170>, < cv2.DMatch 0x12e603e50>, < cv2.DMatch 0x12e6026f0>, < cv2.DMatch 0x12e600470>, < cv2.DMatch 0x12e6010d0>, < cv2.DMatch 0x12e600f30>, < cv2.DMatch 0x12e603d70>, < cv2.DMatch 0x12e6031d0>, < cv2.DMatch 0x12e6027f0>, < cv2.DMatch 0x12e6028f0>, < cv2.DMatch 0x12e602750>, < cv2.DMatch 0x12e6033f0>, < cv2.DMatch 0x12e602970>, < cv2.DMatch 0x12e6028b0>, < cv2.DMatch 0x12e602990>, < cv2.DMatch 0x12e602790>, < cv2.DMatch 0x12e6030b0>, < cv2.DMatch 0x12e6030d0>, < cv2.DMatch 0x12e601730>, < cv2.DMatch 0x12e603110>, < cv2.DMatch 0x12e603f50>, < cv2.DMatch 0x12e603130>, < cv2.DMatch 0x12e603e70>, < cv2.DMatch 0x12e602d90>, < cv2.DMatch 0x12e602b70>, < cv2.DMatch 0x12e603510>, < cv2.DMatch 0x12e603fb0>, < cv2.DMatch 0x12e602830>, < cv2.DMatch 0x12e6031b0>)\n",
      "Foram encontradas 286 correspondências entre as imagens\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 1789624 characters]",
      "text/plain": [
       "<Figure size 2000x1000 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Criar o objeto BFMatcher (Brute-Force Matcher)\n",
    "# NORM_HAMMING é usado para descritores binários como ORB\n",
    "# crossCheck=True garante que as correspondências sejam mutuamente melhores\n",
    "bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)\n",
    "\n",
    "# Encontrar correspondências entre os descritores\n",
    "matches = bf.match(des1, des2)\n",
    "\n",
    "print(matches)\n",
    "\n",
    "print(f\"Foram encontradas {len(matches)} correspondências entre as imagens\")\n",
    "\n",
    "# Desenhar todas as correspondências\n",
    "img_matches = cv2.drawMatches(img1, kp1, img2, kp2, matches, None, \n",
    "                             flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)\n",
    "\n",
    "plt.figure(figsize=(20, 10))\n",
    "plt.title('Todas as correspondências encontradas')\n",
    "plt.imshow(img_matches)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "cellUniqueIdByVincent": "1f03e"
   },
   "source": [
    "## Filtragem de Correspondências\n",
    "\n",
    "Como podemos observar, nem todas as correspondências encontradas são corretas. Muitas são falsos positivos. Podemos melhorar o resultado ordenando as correspondências por qualidade e selecionando apenas as melhores.\n",
    "\n",
    "### Como filtrar?\n",
    "\n",
    "A qualidade de uma correspondência é determinada pelo atributo `distance` do objeto `DMatch`. **Quanto menor** a distância, **melhor** é a correspondência.\n",
    "\n",
    "Vamos ordenar as correspondências por distância e ficar apenas com as melhores:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "cellUniqueIdByVincent": "6bbef"
   },
   "source": [
    "### Sobre ordenação em Python com `sorted()` e funções lambda\n",
    "\n",
    "Em Python, podemos usar a função `sorted()` para ordenar uma lista. Quando queremos ordenar por um critério específico, usamos a função `lambda` como uma função de chave:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "cellUniqueIdByVincent": "ee272"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Lista não ordenada: [('Banana', 18), ('Maçã', 1), ('Goiaba', 20), ('Uva', 22), ('Pera', 12)]\n",
      "\n",
      "Lista ordenada por nome (ordem alfabética): [('Banana', 18), ('Goiaba', 20), ('Maçã', 1), ('Pera', 12), ('Uva', 22)]\n",
      "\n",
      "Lista ordenada por preço (crescente): [('Maçã', 1), ('Pera', 12), ('Banana', 18), ('Goiaba', 20), ('Uva', 22)]\n"
     ]
    }
   ],
   "source": [
    "# Exemplo: ordenação em Python com sorted() e funções lambda\n",
    "\n",
    "# Lista de compras: (item, preço)\n",
    "lista = [\n",
    "    ('Banana', 18),\n",
    "    ('Maçã', 1),\n",
    "    ('Goiaba', 20),\n",
    "    ('Uva', 22),\n",
    "    ('Pera', 12)\n",
    "]\n",
    "\n",
    "print(\"Lista não ordenada:\", lista)\n",
    "\n",
    "# Ordenar por ordem alfabética (primeiro elemento da tupla)\n",
    "lista_ord = sorted(lista)\n",
    "print(\"\\nLista ordenada por nome (ordem alfabética):\", lista_ord)\n",
    "\n",
    "# Ordenar por preço (segundo elemento da tupla)\n",
    "lista_ord_preco = sorted(lista, key=lambda x: x[1])\n",
    "print(\"\\nLista ordenada por preço (crescente):\", lista_ord_preco)\n",
    "\n",
    "# # Ordenar por preço decrescente\n",
    "# lista_ord_preco_desc = sorted(lista, key=lambda x: x[1], reverse=True)\n",
    "# print(\"\\nLista ordenada por preço (decrescente):\", lista_ord_preco_desc)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "cellUniqueIdByVincent": "e5976"
   },
   "source": [
    "## Aplicando Filtragem às Correspondências\n",
    "\n",
    "Agora vamos ordenar as correspondências (matches) encontradas pelo atributo `distance` e selecionar apenas as melhores para visualização:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "cellUniqueIdByVincent": "04789"
   },
   "source": [
    "### Propriedades do objeto DMatch:\n",
    "\n",
    "- **DMatch.distance**: Distância entre os descritores (menor = melhor)\n",
    "- **DMatch.queryIdx**: Índice do keypoint na imagem de consulta (template)\n",
    "- **DMatch.trainIdx**: Índice do keypoint na imagem de teste (cena)\n",
    "- **DMatch.imgIdx**: Índice da imagem de teste (útil em casos com múltiplas imagens)\n",
    "\n",
    "[https://docs.opencv.org/4.9.0/d4/de0/classcv_1_1DMatch.html](https://docs.opencv.org/4.9.0/d4/de0/classcv_1_1DMatch.html)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "cellUniqueIdByVincent": "1b518"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on class DMatch in module cv2:\n",
      "\n",
      "class DMatch(builtins.object)\n",
      " |  Methods defined here:\n",
      " |\n",
      " |  __init__(self, /, *args, **kwargs)\n",
      " |      Initialize self.  See help(type(self)) for accurate signature.\n",
      " |\n",
      " |  __repr__(self, /)\n",
      " |      Return repr(self).\n",
      " |\n",
      " |  ----------------------------------------------------------------------\n",
      " |  Static methods defined here:\n",
      " |\n",
      " |  __new__(*args, **kwargs)\n",
      " |      Create and return a new object.  See help(type) for accurate signature.\n",
      " |\n",
      " |  ----------------------------------------------------------------------\n",
      " |  Data descriptors defined here:\n",
      " |\n",
      " |  distance\n",
      " |      distance\n",
      " |\n",
      " |  imgIdx\n",
      " |      imgIdx\n",
      " |\n",
      " |  queryIdx\n",
      " |      queryIdx\n",
      " |\n",
      " |  trainIdx\n",
      " |      trainIdx\n",
      "\n"
     ]
    }
   ],
   "source": [
    "help(cv2.DMatch)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "cellUniqueIdByVincent": "3a751"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Distâncias das 10 melhores correspondências:\n",
      "Match 1: 23.00\n",
      "Match 2: 24.00\n",
      "Match 3: 27.00\n",
      "Match 4: 30.00\n",
      "Match 5: 30.00\n",
      "Match 6: 31.00\n",
      "Match 7: 33.00\n",
      "Match 8: 35.00\n",
      "Match 9: 35.00\n",
      "Match 10: 36.00\n",
      "Match 11: 37.00\n",
      "Match 12: 37.00\n",
      "Match 13: 38.00\n",
      "Match 14: 38.00\n",
      "Match 15: 38.00\n",
      "Match 16: 38.00\n",
      "Match 17: 40.00\n",
      "Match 18: 40.00\n",
      "Match 19: 41.00\n",
      "Match 20: 41.00\n",
      "Match 21: 42.00\n",
      "Match 22: 44.00\n",
      "Match 23: 44.00\n",
      "Match 24: 44.00\n",
      "Match 25: 45.00\n",
      "Match 26: 45.00\n",
      "Match 27: 45.00\n",
      "Match 28: 45.00\n",
      "Match 29: 46.00\n",
      "Match 30: 46.00\n",
      "Match 31: 47.00\n",
      "Match 32: 47.00\n",
      "Match 33: 47.00\n",
      "Match 34: 48.00\n",
      "Match 35: 49.00\n",
      "Match 36: 49.00\n",
      "Match 37: 49.00\n",
      "Match 38: 49.00\n",
      "Match 39: 50.00\n",
      "Match 40: 51.00\n",
      "Match 41: 51.00\n",
      "Match 42: 51.00\n",
      "Match 43: 52.00\n",
      "Match 44: 52.00\n",
      "Match 45: 52.00\n",
      "Match 46: 53.00\n",
      "Match 47: 53.00\n",
      "Match 48: 53.00\n",
      "Match 49: 53.00\n",
      "Match 50: 54.00\n",
      "Match 51: 54.00\n",
      "Match 52: 54.00\n",
      "Match 53: 54.00\n",
      "Match 54: 54.00\n",
      "Match 55: 55.00\n",
      "Match 56: 55.00\n",
      "Match 57: 55.00\n",
      "Match 58: 55.00\n",
      "Match 59: 56.00\n",
      "Match 60: 56.00\n",
      "Match 61: 56.00\n",
      "Match 62: 56.00\n",
      "Match 63: 56.00\n",
      "Match 64: 57.00\n",
      "Match 65: 57.00\n",
      "Match 66: 57.00\n",
      "Match 67: 57.00\n",
      "Match 68: 57.00\n",
      "Match 69: 57.00\n",
      "Match 70: 57.00\n",
      "Match 71: 57.00\n",
      "Match 72: 57.00\n",
      "Match 73: 58.00\n",
      "Match 74: 58.00\n",
      "Match 75: 58.00\n",
      "Match 76: 58.00\n",
      "Match 77: 58.00\n",
      "Match 78: 58.00\n",
      "Match 79: 58.00\n",
      "Match 80: 59.00\n",
      "Match 81: 59.00\n",
      "Match 82: 59.00\n",
      "Match 83: 59.00\n",
      "Match 84: 59.00\n",
      "Match 85: 59.00\n",
      "Match 86: 59.00\n",
      "Match 87: 59.00\n",
      "Match 88: 60.00\n",
      "Match 89: 60.00\n",
      "Match 90: 60.00\n",
      "Match 91: 60.00\n",
      "Match 92: 60.00\n",
      "Match 93: 61.00\n",
      "Match 94: 61.00\n",
      "Match 95: 61.00\n",
      "Match 96: 61.00\n",
      "Match 97: 61.00\n",
      "Match 98: 61.00\n",
      "Match 99: 61.00\n",
      "Match 100: 61.00\n",
      "Match 101: 62.00\n",
      "Match 102: 62.00\n",
      "Match 103: 62.00\n",
      "Match 104: 62.00\n",
      "Match 105: 62.00\n",
      "Match 106: 62.00\n",
      "Match 107: 62.00\n",
      "Match 108: 62.00\n",
      "Match 109: 62.00\n",
      "Match 110: 62.00\n",
      "Match 111: 63.00\n",
      "Match 112: 63.00\n",
      "Match 113: 63.00\n",
      "Match 114: 63.00\n",
      "Match 115: 63.00\n",
      "Match 116: 63.00\n",
      "Match 117: 63.00\n",
      "Match 118: 63.00\n",
      "Match 119: 63.00\n",
      "Match 120: 63.00\n",
      "Match 121: 63.00\n",
      "Match 122: 63.00\n",
      "Match 123: 64.00\n",
      "Match 124: 64.00\n",
      "Match 125: 64.00\n",
      "Match 126: 64.00\n",
      "Match 127: 64.00\n",
      "Match 128: 64.00\n",
      "Match 129: 64.00\n",
      "Match 130: 64.00\n",
      "Match 131: 64.00\n",
      "Match 132: 65.00\n",
      "Match 133: 65.00\n",
      "Match 134: 65.00\n",
      "Match 135: 65.00\n",
      "Match 136: 65.00\n",
      "Match 137: 65.00\n",
      "Match 138: 65.00\n",
      "Match 139: 65.00\n",
      "Match 140: 65.00\n",
      "Match 141: 65.00\n",
      "Match 142: 65.00\n",
      "Match 143: 65.00\n",
      "Match 144: 65.00\n",
      "Match 145: 66.00\n",
      "Match 146: 66.00\n",
      "Match 147: 66.00\n",
      "Match 148: 66.00\n",
      "Match 149: 66.00\n",
      "Match 150: 66.00\n",
      "Match 151: 66.00\n",
      "Match 152: 66.00\n",
      "Match 153: 66.00\n",
      "Match 154: 66.00\n",
      "Match 155: 66.00\n",
      "Match 156: 66.00\n",
      "Match 157: 66.00\n",
      "Match 158: 66.00\n",
      "Match 159: 66.00\n",
      "Match 160: 66.00\n",
      "Match 161: 67.00\n",
      "Match 162: 67.00\n",
      "Match 163: 67.00\n",
      "Match 164: 67.00\n",
      "Match 165: 67.00\n",
      "Match 166: 67.00\n",
      "Match 167: 67.00\n",
      "Match 168: 67.00\n",
      "Match 169: 67.00\n",
      "Match 170: 67.00\n",
      "Match 171: 67.00\n",
      "Match 172: 67.00\n",
      "Match 173: 67.00\n",
      "Match 174: 68.00\n",
      "Match 175: 68.00\n",
      "Match 176: 68.00\n",
      "Match 177: 68.00\n",
      "Match 178: 68.00\n",
      "Match 179: 68.00\n",
      "Match 180: 68.00\n",
      "Match 181: 69.00\n",
      "Match 182: 69.00\n",
      "Match 183: 69.00\n",
      "Match 184: 69.00\n",
      "Match 185: 69.00\n",
      "Match 186: 69.00\n",
      "Match 187: 69.00\n",
      "Match 188: 69.00\n",
      "Match 189: 69.00\n",
      "Match 190: 69.00\n",
      "Match 191: 69.00\n",
      "Match 192: 69.00\n",
      "Match 193: 69.00\n",
      "Match 194: 69.00\n",
      "Match 195: 69.00\n",
      "Match 196: 70.00\n",
      "Match 197: 70.00\n",
      "Match 198: 70.00\n",
      "Match 199: 70.00\n",
      "Match 200: 70.00\n",
      "Match 201: 70.00\n",
      "Match 202: 70.00\n",
      "Match 203: 70.00\n",
      "Match 204: 70.00\n",
      "Match 205: 70.00\n",
      "Match 206: 70.00\n",
      "Match 207: 70.00\n",
      "Match 208: 70.00\n",
      "Match 209: 71.00\n",
      "Match 210: 71.00\n",
      "Match 211: 71.00\n",
      "Match 212: 71.00\n",
      "Match 213: 71.00\n",
      "Match 214: 71.00\n",
      "Match 215: 71.00\n",
      "Match 216: 71.00\n",
      "Match 217: 71.00\n",
      "Match 218: 71.00\n",
      "Match 219: 71.00\n",
      "Match 220: 71.00\n",
      "Match 221: 71.00\n",
      "Match 222: 72.00\n",
      "Match 223: 72.00\n",
      "Match 224: 72.00\n",
      "Match 225: 72.00\n",
      "Match 226: 72.00\n",
      "Match 227: 72.00\n",
      "Match 228: 72.00\n",
      "Match 229: 72.00\n",
      "Match 230: 72.00\n",
      "Match 231: 72.00\n",
      "Match 232: 73.00\n",
      "Match 233: 73.00\n",
      "Match 234: 73.00\n",
      "Match 235: 73.00\n",
      "Match 236: 73.00\n",
      "Match 237: 73.00\n",
      "Match 238: 73.00\n",
      "Match 239: 73.00\n",
      "Match 240: 73.00\n",
      "Match 241: 73.00\n",
      "Match 242: 73.00\n",
      "Match 243: 74.00\n",
      "Match 244: 74.00\n",
      "Match 245: 74.00\n",
      "Match 246: 74.00\n",
      "Match 247: 74.00\n",
      "Match 248: 74.00\n",
      "Match 249: 74.00\n",
      "Match 250: 74.00\n",
      "Match 251: 74.00\n",
      "Match 252: 74.00\n",
      "Match 253: 74.00\n",
      "Match 254: 75.00\n",
      "Match 255: 75.00\n",
      "Match 256: 75.00\n",
      "Match 257: 75.00\n",
      "Match 258: 75.00\n",
      "Match 259: 75.00\n",
      "Match 260: 75.00\n",
      "Match 261: 75.00\n",
      "Match 262: 75.00\n",
      "Match 263: 76.00\n",
      "Match 264: 76.00\n",
      "Match 265: 76.00\n",
      "Match 266: 76.00\n",
      "Match 267: 76.00\n",
      "Match 268: 76.00\n",
      "Match 269: 77.00\n",
      "Match 270: 77.00\n",
      "Match 271: 77.00\n",
      "Match 272: 77.00\n",
      "Match 273: 77.00\n",
      "Match 274: 77.00\n",
      "Match 275: 77.00\n",
      "Match 276: 78.00\n",
      "Match 277: 78.00\n",
      "Match 278: 79.00\n",
      "Match 279: 79.00\n",
      "Match 280: 79.00\n",
      "Match 281: 80.00\n",
      "Match 282: 81.00\n",
      "Match 283: 81.00\n",
      "Match 284: 82.00\n",
      "Match 285: 83.00\n",
      "Match 286: 84.00\n"
     ]
    }
   ],
   "source": [
    "# Ordenar as correspondências por distância (menor para maior)\n",
    "matches = sorted(matches, key=lambda x: x.distance)\n",
    "\n",
    "# Ver as distâncias das 10 melhores correspondências\n",
    "print(\"Distâncias das 10 melhores correspondências:\")\n",
    "for i, match in enumerate(matches[:]):\n",
    "    print(f\"Match {i+1}: {match.distance:.2f}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "cellUniqueIdByVincent": "c49f7"
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 1082692 characters]",
      "text/plain": [
       "<Figure size 2000x1000 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Desenhar apenas as 15 melhores correspondências\n",
    "num_best_matches = 10\n",
    "img_best_matches = cv2.drawMatches(img1, kp1, img2, kp2, matches[:num_best_matches], None, \n",
    "                                  flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)\n",
    "\n",
    "plt.figure(figsize=(20, 10))\n",
    "plt.title(f'As {num_best_matches} melhores correspondências')\n",
    "plt.imshow(img_best_matches)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "cellUniqueIdByVincent": "2ed90"
   },
   "source": [
    "## Desafio 3: Implementando SIFT\n",
    "\n",
    "**SIFT** (Scale-Invariant Feature Transform) é um dos algoritmos mais robustos para detecção e descrição de features. Vamos implementar o mesmo exemplo usando SIFT em vez de ORB.\n",
    "\n",
    "### Sua missão:\n",
    "1. Modifique o código para usar o algoritmo SIFT\n",
    "2. Para matching, use o método `knnMatch()` do BFMatcher que permite encontrar os k vizinhos mais próximos\n",
    "3. Implemente a filtragem de correspondências usando o teste de ratio de Lowe\n",
    "   - Este teste compara a distância do melhor match com o segundo melhor\n",
    "   - Se ratio = d1/d2 < 0.75, então é um bom match\n",
    "\n",
    "**Dica:** Use `cv2.SIFT_create()` para criar o detector e descritor SIFT. Para o BFMatcher, use `cv2.NORM_L2` em vez de `cv2.NORM_HAMMING`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "cellUniqueIdByVincent": "5dcb9"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Foram encontrados 43 bons matches após filtro de ratio\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 1276224 characters]",
      "text/plain": [
       "<Figure size 2000x1000 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Implemente sua solução para o Desafio 3 aqui\n",
    "\n",
    "# Exemplo de implementação com SIFT\n",
    "import cv2\n",
    "import numpy as np\n",
    "from matplotlib import pyplot as plt\n",
    "\n",
    "# Carregar as imagens\n",
    "img1 = cv2.imread('lab_images/box.png', 0)  # Template\n",
    "img2 = cv2.imread('lab_images/box_in_scene.png', 0)  # Cena\n",
    "\n",
    "# Inicializar SIFT\n",
    "sift = cv2.SIFT_create()\n",
    "\n",
    "# Detectar keypoints e descritores\n",
    "kp1, des1 = sift.detectAndCompute(img1, None)\n",
    "kp2, des2 = sift.detectAndCompute(img2, None)\n",
    "\n",
    "# Criar o BFMatcher - NORM_L2 para descritores baseados em gradiente como SIFT\n",
    "bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=False)\n",
    "\n",
    "# Encontrar os 2 melhores matches para cada descritor\n",
    "matches = bf.knnMatch(des1, des2, k=2)\n",
    "\n",
    "# Aplicar o teste de ratio de Lowe\n",
    "good_matches = []\n",
    "for m, n in matches:\n",
    "    if m.distance < 0.50 * n.distance:\n",
    "        good_matches.append(m)\n",
    "\n",
    "print(f\"Foram encontrados {len(good_matches)} bons matches após filtro de ratio\")\n",
    "\n",
    "# Desenhar os matches\n",
    "img_matches = cv2.drawMatches(img1, kp1, img2, kp2, good_matches, None, \n",
    "                             flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)\n",
    "\n",
    "plt.figure(figsize=(20, 10))\n",
    "plt.title('SIFT: Matches após filtro de ratio de Lowe')\n",
    "plt.imshow(img_matches)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## O problema do BFMatcher\n",
    "\n",
    "O BFMatcher funciona bem em cenários pequenos, mas é computacionalmente caro:\n",
    "ele compara todos contra todos (força bruta), resultando em O(N²) operações.\n",
    "\n",
    "Em imagens com milhares de descritores (como SIFT), isso se torna lento demais."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Foram encontrados 38 bons matches após filtro de ratio\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 1262260 characters]",
      "text/plain": [
       "<Figure size 2000x1000 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Implemente sua solução para o Desafio 3 aqui\n",
    "\n",
    "# Exemplo de implementação com SIFT\n",
    "import cv2\n",
    "import numpy as np\n",
    "from matplotlib import pyplot as plt\n",
    "\n",
    "# Carregar as imagens\n",
    "img1 = cv2.imread('lab_images/box.png', 0)  # Template\n",
    "img2 = cv2.imread('lab_images/box_in_scene.png', 0)  # Cena\n",
    "\n",
    "# Inicializar SIFT\n",
    "sift = cv2.SIFT_create()\n",
    "\n",
    "# Detectar keypoints e descritores\n",
    "kp1, des1 = sift.detectAndCompute(img1, None)\n",
    "kp2, des2 = sift.detectAndCompute(img2, None)\n",
    "\n",
    "# Parâmetros do FLANN para descritores float (SIFT/SURF)\n",
    "FLANN_INDEX_KDTREE = 1\n",
    "index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)\n",
    "search_params = dict(checks=50)\n",
    "\n",
    "# Cria o matcher\n",
    "flann = cv2.FlannBasedMatcher(index_params, search_params)\n",
    "\n",
    "# Encontra os 2 melhores matches\n",
    "matches = flann.knnMatch(des1, des2, k=2)\n",
    "\n",
    "# Aplicar o teste de ratio de Lowe\n",
    "good_matches = []\n",
    "for m, n in matches:\n",
    "    if m.distance < 0.45 * n.distance:\n",
    "        good_matches.append(m)\n",
    "\n",
    "print(f\"Foram encontrados {len(good_matches)} bons matches após filtro de ratio\")\n",
    "\n",
    "# Desenhar os matches\n",
    "img_matches = cv2.drawMatches(img1, kp1, img2, kp2, good_matches, None, \n",
    "                             flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)\n",
    "\n",
    "plt.figure(figsize=(20, 10))\n",
    "plt.title('SIFT: Matches após filtro de ratio de Lowe')\n",
    "plt.imshow(img_matches)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 1217724 characters]",
      "text/plain": [
       "<Figure size 1500x700 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Desenhar keypoints nas imagens\n",
    "img1_keypoints = cv2.drawKeypoints(img1, kp1, outImage=None, color=(0, 255, 0),\n",
    "                                  flags=cv2.DrawMatchesFlags_DEFAULT)\n",
    "img2_keypoints = cv2.drawKeypoints(img2, kp2, outImage=None, color=(0, 255, 0),\n",
    "                                  flags=cv2.DrawMatchesFlags_DEFAULT)\n",
    "\n",
    "# Visualizar os resultados\n",
    "plt.figure(figsize=(15, 7))\n",
    "\n",
    "plt.subplot(1, 2, 1)\n",
    "plt.title(\"Keypoints no Template\")\n",
    "plt.imshow(img1_keypoints)\n",
    "\n",
    "plt.subplot(1, 2, 2)\n",
    "plt.title(\"Keypoints na Cena\")\n",
    "plt.imshow(img2_keypoints)\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "cellUniqueIdByVincent": "76aa9"
   },
   "source": [
    "## Desafio 4: Detecção em Tempo Real\n",
    "\n",
    "> **IMPORTANTE**: Este desafio deve ser executado localmente em sua máquina, não no Google Colab.\n",
    "\n",
    "### Sua missão:\n",
    "1. Crie um script Python (.py) que capture vídeo da webcam \n",
    "2. Implemente a detecção de um objeto em tempo real usando técnicas de feature matching\n",
    "3. Desenhe um contorno ao redor do objeto quando for encontrado\n",
    "\n",
    "### Procedimentos:\n",
    "1. Escolha uma imagem de referência (template) - sugestão: um livro ou outro objeto plano com textura\n",
    "2. Extraia features do template usando ORB ou SIFT\n",
    "3. Para cada frame do vídeo:\n",
    "   - Extraia features\n",
    "   - Encontre matches\n",
    "   - Filtre os bons matches\n",
    "   - Use `findHomography` para mapear as coordenadas \n",
    "   - Desenhe um polígono ao redor do objeto detectado\n",
    "\n",
    "### Função auxiliar para desenhar o contorno:\n",
    "\n",
    "```python\n",
    "def desenhaContorno(qp, tp, refImg, frame):\n",
    "    \"\"\"\n",
    "    Função que desenha o contorno ao encontrar matches entre duas imagens\n",
    "    \n",
    "    Parâmetros:\n",
    "     - qp: keypoints da imagem de consulta convertidos para np.float32\n",
    "     - tp: keypoints da imagem alvo convertidos para np.float32\n",
    "     - refImg: imagem de referência (template)\n",
    "     - frame: imagem onde será desenhado o contorno\n",
    "    \"\"\"\n",
    "    # Encontrar a transformação homográfica\n",
    "    H, status = cv2.findHomography(qp, tp, cv2.RANSAC, 3.0)\n",
    "    \n",
    "    # Obter dimensões da imagem de referência\n",
    "    h, w = refImg.shape\n",
    "    \n",
    "    # Definir os pontos das bordas da imagem de referência\n",
    "    refBorda = np.float32([[[0,0],[0,h-1],[w-1,h-1],[w-1,0]]])\n",
    "    \n",
    "    # Transformar estes pontos usando a homografia encontrada\n",
    "    frameBorda = cv2.perspectiveTransform(refBorda, H)\n",
    "    \n",
    "    # Desenhar o polígono no frame\n",
    "    cv2.polylines(frame, [np.int32(frameBorda)], True, (0,255,0), 5)\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "cellUniqueIdByVincent": "1d4a6"
   },
   "outputs": [],
   "source": [
    "# ATENÇÃO: Este desafio deve ser executado em sua máquina local\n",
    "# Não execute este código no navegador ou no Google Colab\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "cellUniqueIdByVincent": "aa3d9"
   },
   "source": [
    "## Aplicação Prática: Criando Imagens Panorâmicas\n",
    "\n",
    "Uma aplicação muito comum de feature matching é a criação de imagens panorâmicas (stitching). Este processo envolve:\n",
    "\n",
    "1. Detectar keypoints e calcular descritores em ambas as imagens\n",
    "2. Encontrar correspondências entre os descritores\n",
    "3. Calcular a transformação homográfica entre as imagens\n",
    "4. Aplicar warping para alinhar as imagens\n",
    "5. Combinar (blend) as imagens alinhadas\n",
    "\n",
    "A OpenCV fornece a classe `cv2.Stitcher` que implementa todo esse processo de forma otimizada."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {
    "cellUniqueIdByVincent": "3841c"
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 1336876 characters]",
      "text/plain": [
       "<Figure size 1500x800 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "%matplotlib inline\n",
    "import cv2\n",
    "from matplotlib import pyplot as plt\n",
    "import numpy as np\n",
    "\n",
    "\n",
    "img1 = cv2.imread(\"lab_images/q11.jpg\")\n",
    "img2 = cv2.imread(\"lab_images/q22.jpg\")\n",
    "    \n",
    "# Exibir as imagens que serão combinadas\n",
    "plt.figure(figsize=(15, 8))\n",
    "plt.subplot(1, 2, 1)\n",
    "plt.title(\"Imagem 1\")\n",
    "plt.imshow(cv2.cvtColor(img1, cv2.COLOR_BGR2RGB))\n",
    "\n",
    "\n",
    "plt.subplot(1, 2, 2)\n",
    "plt.title(\"Imagem 2\")\n",
    "plt.imshow(cv2.cvtColor(img2, cv2.COLOR_BGR2RGB))\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {
    "cellUniqueIdByVincent": "6b98a"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ Sucesso! Panorama criado com sucesso.\n",
      "Dimensões da imagem resultante: (2896, 3402, 3)\n"
     ]
    }
   ],
   "source": [
    "# Criar o objeto Stitcher\n",
    "# Modos disponíveis: PANORAMA (padrão) ou SCANS\n",
    "stitcher = cv2.Stitcher.create(mode=cv2.Stitcher_PANORAMA)\n",
    "\n",
    "# Realizar o stitching\n",
    "# status: código de erro/sucesso\n",
    "# result: imagem panorâmica resultante\n",
    "(status, result) = stitcher.stitch((img1, img2))\n",
    "\n",
    "# Verificar o resultado\n",
    "if status == cv2.Stitcher_OK:\n",
    "    print(\"✅ Sucesso! Panorama criado com sucesso.\")\n",
    "    print(f\"Dimensões da imagem resultante: {result.shape}\")\n",
    "else:\n",
    "    print(\"❌ Falha ao criar panorama.\")\n",
    "    print(f\"Status: {status}\")\n",
    "    # Códigos de erro possíveis:\n",
    "    # cv2.Stitcher_ERR_NEED_MORE_IMGS (1): Precisa de mais imagens\n",
    "    # cv2.Stitcher_ERR_HOMOGRAPHY_EST_FAIL (2): Falha na estimativa de homografia\n",
    "    # cv2.Stitcher_ERR_CAMERA_PARAMS_ADJUST_FAIL (3): Falha no ajuste de parâmetros da câmera"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {
    "cellUniqueIdByVincent": "e662a"
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 1000016 characters]",
      "text/plain": [
       "<Figure size 1500x1000 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Exibir o resultado\n",
    "if status == cv2.Stitcher_OK:\n",
    "    plt.figure(figsize=(15, 10))\n",
    "    plt.title(\"Panorama resultante\")\n",
    "    plt.imshow(cv2.cvtColor(result, cv2.COLOR_BGR2RGB))\n",
    "    plt.axis('off')\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "cellUniqueIdByVincent": "1ea48"
   },
   "source": [
    "## Pós-processamento: Recortando as Bordas\n",
    "\n",
    "Como podemos observar, o panorama resultante possui bordas irregulares devido ao processo de warping. Podemos realizar um recorte (crop) para obter uma imagem retangular limpa."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {
    "cellUniqueIdByVincent": "e04ee"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Dimensões originais: 2896 x 3402\n",
      "Dimensões após recorte: 2318 x 3334\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 1278980 characters]",
      "text/plain": [
       "<Figure size 1500x1000 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Recortando as bordas da imagem panorâmica\n",
    "if status == cv2.Stitcher_OK:\n",
    "    # Obter dimensões da imagem\n",
    "    h, w = result.shape[:2]\n",
    "    print(f\"Dimensões originais: {h} x {w}\")\n",
    "    \n",
    "    # Método simples: recorte manual\n",
    "    # Ajuste estes valores conforme necessário para sua imagem específica\n",
    "    margin_top = int(h * 0.05)      # 5% de margem superior\n",
    "    margin_bottom = int(h * 0.15)   # 15% de margem inferior\n",
    "    margin_left = int(w * 0.01)     # 1% de margem esquerda\n",
    "    margin_right = int(w * 0.01)    # 1% de margem direita\n",
    "    \n",
    "    crop = result[margin_top:h-margin_bottom, \n",
    "                  margin_left:w-margin_right]\n",
    "    \n",
    "    print(f\"Dimensões após recorte: {crop.shape[0]} x {crop.shape[1]}\")\n",
    "    \n",
    "    plt.figure(figsize=(15, 10))\n",
    "    plt.title(\"Panorama recortado\")\n",
    "    plt.imshow(cv2.cvtColor(crop, cv2.COLOR_BGR2RGB))\n",
    "    plt.axis('off')\n",
    "    plt.show()\n",
    "    \n",
    "    # Alternativa: Para um recorte mais preciso, você poderia:\n",
    "    # 1. Converter para escala de cinza\n",
    "    # 2. Binarizar a imagem para encontrar a região não-preta\n",
    "    # 3. Encontrar o retângulo de recorte otimizado\n",
    "    # Este é um exercício adicional que você pode implementar!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Sucesso, imagem gerada, exiba o result\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 701228 characters]",
      "text/plain": [
       "<Figure size 1000x1000 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "%matplotlib inline\n",
    "import cv2\n",
    "from matplotlib import pyplot as plt\n",
    "import numpy as np\n",
    "\n",
    "# carrega as imagens\n",
    "img = cv2.imread(\"lab_images/quarto.jpeg\")\n",
    "img1 = cv2.imread(\"lab_images/quarto1.jpeg\")\n",
    "img2 = cv2.imread(\"lab_images/quarto2.jpeg\")\n",
    "img3 = cv2.imread(\"lab_images/quarto3.jpeg\")\n",
    "img4 = cv2.imread(\"lab_images/quarto4.jpeg\")\n",
    "\n",
    "# Cria o objeto Stitcher\n",
    "stitcher = cv2.Stitcher.create()\n",
    "\n",
    "# Essa é a parte mais complicada do código, é aqui que o hard work é feito. \n",
    "# o método devolve em status se foi possivel realizar overlap (sopreposição) das imagens na variavel status,\n",
    "# se sim, a imagem esta panoramica está em result.\n",
    "(status, result) = stitcher.stitch((img,img2,img3,img1,img4))\n",
    "\n",
    "if (status == cv2.STITCHER_OK):\n",
    "    print(\"Sucesso, imagem gerada, exiba o result\")\n",
    "    plt.figure(figsize = (10,10))\n",
    "    plt.imshow(cv2.cvtColor(result, cv2.COLOR_BGR2RGB))\n",
    "    plt.show()\n",
    "    # não é a melhor tecnica, mas resolve...\n",
    "    # print(result.shape)\n",
    "    # h = result.shape[0]\n",
    "    # w = result.shape[1]\n",
    "    # print(h,w)\n",
    "    # crop = result[150:2700, 50:3350]\n",
    "    # plt.figure(figsize = (10,10))\n",
    "    # plt.imshow(cv2.cvtColor(crop, cv2.COLOR_BGR2RGB)); plt.show();  \n",
    "else:\n",
    "    print(\"falha, não consegui gerar a imagem\")\n",
    "\n",
    "\n",
    "\n",
    "# # Exibe as imagens que serão usadas\n",
    "# plt.figure(figsize = (10,10))\n",
    "# plt.subplot(1, 2, 1), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))\n",
    "# plt.subplot(1, 2, 2), plt.imshow(cv2.cvtColor(img2, cv2.COLOR_BGR2RGB))\n",
    "# plt.show();"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv (3.13.5)",
   "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.13.5"
  },
  "vincent": {
   "sessionId": "ae99e04712d21a22ca793331_2025-06-03T21-59-55-888Z"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
