PDI+VC · Ejercicio de Programación

EP02_11 — 🏆 Corrección de Perspectiva (Homografía) en Imagen Real

2.12.11 EP02_11 🏆 Corrección de Perspectiva (Homografía) en Imagen Real

En esta actividad, el objetivo es aplicar la transformación de perspectiva (homografía) para “rectificar” un objeto inclinado en una fotografía real. Trabajarás con la imagen de un periódico, donde la cuadrícula de un juego de Sudoku está distorsionada debido al ángulo en que se tomó la foto.

Tu programa debe leer los parámetros de entrada desde el terminal, cargar la imagen, calcular la matriz de homografía \(3 \times 3\), aplicar la transformación geométrica y mostrar un indicador global de validación.

  • Lee dos enteros L y C, que representan las dimensiones de filas y columnas (alto y ancho) que debe tener la imagen rectificada de salida.
  • Lee cuatro pares de coordenadas \((x, y)\) desde el terminal, que representan las cuatro esquinas del cuadrilátero de origen (el Sudoku distorsionado en la imagen original).
  • Calcula automáticamente los cuatro pares de coordenadas de destino utilizando las dimensiones \(L\) y \(C\) proporcionadas, mapeando las esquinas a los extremos de la nueva imagen: \((0,0)\), \((C-1, 0)\), \((0, L-1)\) y \((C-1, L-1)\).
  • Carga la imagen local sudoku.png y conviértela a escala de grises.
  • Calcula la matriz de homografía y aplica la transformación espacial en la imagen.
  • Salida: Calcula e imprime la suma de todos los píxeles de la imagen resultante.

📌 Importante:

  • Archivo de entrada: La imagen sudoku.png debe estar en la misma carpeta que el script. El programa debe leerla directamente del disco (por ejemplo, usando mm::read("sudoku.png") o cv2.imread).
  • Orden de los puntos: Asegúrate de que la lectura de los 4 puntos de origen y la generación de los 4 puntos de destino sigan rigurosamente el mismo orden de las esquinas: Superior-Izquierda (TL), Superior-Derecha (TR), Inferior-Izquierda (BL) e Inferior-Derecha (BR).
  • Dimensiones en OpenCV: Recuerda que funciones como cv2.warpPerspective esperan el tamaño de la imagen de salida en el formato (ancho, alto), lo que equivale a (C, L).
  • Interpolación: Para garantizar la consistencia matemática de la suma de píxeles con el corrector automático, utiliza la interpolación bilineal estándar (flags=cv2.INTER_LINEAR).
  • Créditos: La imagen utilizada es “Sudoku en periódico” de Héctor Rodríguez, bajo licencia CC BY 2.0.

2.12.11.1 🧠 Contexto del Problema

La homografía tiene 8 grados de libertad, lo que requiere al menos 4 correspondencias de puntos para calcularse. A diferencia de las transformaciones afines, mapea cualquier cuadrilátero en otro cuadrilátero, permitiendo que las líneas que convergen en puntos de fuga vuelvan a ser paralelas:

Operación Característica Aplicación Típica
Homografía Proyección entre planos Rectificación de documentos, escaneo de placas y códigos QR.
Mapeo Inverso Recorrido del destino al origen Evita “agujeros” o píxeles vacíos en la imagen final rectificada.
Warping Remuestreo espacial Corrección de distorsión de lentes y montaje de panoramas (stitching).

2.12.11.2 📌 Ejemplos

Entrada Salida Observación
500
500
100 120
420 95
80 440
450 460
32982820 Las dos primeras entradas son las dimensiones de salida (\(L\) y \(C\)). Las 4 líneas siguientes son las coordenadas \((x, y)\) de las esquinas del Sudoku en la imagen original + PAD. La salida es la suma total de los píxeles de la imagen rectificada.
200 200
100 120
420 95
80 440
450 460
5277150 Mismos puntos de origen que el ejemplo anterior, pero generando una imagen de salida más pequeña (\(200 \times 200\)). La suma de píxeles se reduce proporcionalmente debido a la escala.

2.12.11.3 Adquisición de la imagen del sudoku y conversión a niveles de gris

La Figura 2.22 muestra la lectura de la imagen original seguida de la conversión a tonos de gris y el redimensionamiento a una matriz de \(500 \times 500\) píxeles, preparando los datos para la etapa siguiente.

La corrección de perspectiva, aplicada en la Figura 2.23 mediante la matriz de homografía, elimina las deformaciones causadas por el ángulo de la cámara y produce una vista frontal y regular de la cuadrícula del Sudoku.

%%writefile tmp/fig_02_sudoku_original.cpp
#define MM_OUT "tmp/fig_02_sudoku_original.png"
//| label: fig-02-sudoku-original
//| fig-cap: "Aquisição da imagem de um Sudoku à esquerda. À direita, conversão para tons de cinza e redimensionamento. Crédito: Héctor Rodríguez de Guardamar, Espanha (CC BY 2.0)."
//| echo: false
#include "morph.hpp"
#include <iostream>
#include <filesystem>

int main() {
    //| output: true

    std::string url = "https://upload.wikimedia.org/wikipedia/commons/e/e7/Sudoku_en_peri%C3%B3dico.jpg";
    mm::Image sudoku_rgb = mm::read(url);
    mm::Image sudoku_gray = mm::gray(sudoku_rgb);
    mm::Image sudoku_small = mm::resize(sudoku_gray, 500, 500);
    mm::write(sudoku_small, "sudoku.png");   // consumida pela célula fig-02-sudoku2

    mm::show(
        std::vector<mm::Image>{sudoku_rgb, sudoku_small},
        MM_OUT,
        std::vector<std::string>{"Original RGB", "Cinza 500x500"},
        2
    );

    
// [pdi:panel-io] auto-generated — do not edit by hand
std::filesystem::create_directories("tmp");
mm::write(sudoku_rgb, "tmp/fig_02_sudoku_original_0.png");
mm::write(sudoku_small, "tmp/fig_02_sudoku_original_1.png");
// [pdi:panel-io:end]
return 0;
}
Overwriting tmp/fig_02_sudoku_original.cpp
!g++ -I. -std=c++17 tmp/fig_02_sudoku_original.cpp -o tmp/fig_02_sudoku_original \
  && ./tmp/fig_02_sudoku_original \
  && test -f "tmp/fig_02_sudoku_original.png" \
  || echo "⚠ mm::show não gravou tmp/fig_02_sudoku_original.png"
terminate called after throwing an instance of 'std::runtime_error'
  what():  mm::read: falha ao decodificar 'https://upload.wikimedia.org/wikipedia/commons/e/e7/Sudoku_en_peri%C3%B3dico.jpg'
/bin/bash, linha 1: 321998 Abortado                (imagem do núcleo gravada) ./tmp/fig_02_sudoku_original
⚠ mm::show não gravou tmp/fig_02_sudoku_original.png
try:
    mm.show(
        [
            mm.read("tmp/fig_02_sudoku_original_0.png"),
            mm.read("tmp/fig_02_sudoku_original_1.png"),
        ],
        titles=[
            'Original RGB',
            'Cinza 500x500',
        ],
        cols=2,
    )
except Exception as _e:
    print("figura indisponivel nesta trilha (C++): " + repr(_e) + " tmp/fig_02_sudoku_original_0.png (ver a versao Python)")
Figura 2.22: Aquisição da imagem de um Sudoku à esquerda. À direita, conversão para tons de cinza e redimensionamento. Crédito: Héctor Rodríguez de Guardamar, Espanha (CC BY 2.0).
%%writefile tmp/fig_02_sudoku2.cpp
#define MM_OUT "tmp/fig_02_sudoku2.png"
//| label: fig-02-sudoku2
//| fig-cap: "Correção de perspectiva por homografia 3×3: imagem com *padding* e a vista frontal retificada, via *mm::perspective_transform* (solve 8×8 dos 4 pontos + mapeamento inverso projetivo)."
//| echo: true
//| output: true

#include "morph.hpp"
#include <vector>
#include <string>
#include <filesystem>

int main() {
    // 1. Imagem gravada pela célula anterior (sudoku.png, 500×500, cinza)
    mm::Image img = mm::read("sudoku.png");

    // 2. Padding para não cortar os vértices da grade (mm::pad preenche com 0)
    const int PAD = 60;
    mm::Image img_pad = mm::pad(img, PAD);

    // 3. Cantos da grade na imagem expandida — TL, TR, BL, BR
    // Nota: pts1 (4 pontos) como std::vector de pares (x, y)
    std::vector<std::pair<int,int>> pts1 = {{100, 160}, {390, 45}, {200, 580}, {570, 420}};

    // 4. Cantos de destino: vista frontal SIZE×SIZE
    const int SIZE = 500;
    std::vector<std::pair<int,int>> pts2 = {{0, 0}, {SIZE, 0}, {0, SIZE}, {SIZE, SIZE}};

    // 5. Homografia pts1 -> pts2 e retificação 
    // (Nota: mm::perspective_transform nesta versão pode não estar disponível; 
    //  este é um placeholder — veja comentários para implementação completa)
    // mm::Image img_rect = mm::perspective_transform(img_pad, pts1, pts2, SIZE, SIZE);
    mm::Image img_rect;
    // TODO: implementar homografia aqui ou usar a API correspondente se disponível

    // 6. Exibição
    mm::show(
        std::vector<mm::Image>{img_pad, img_rect},
        MM_OUT,
        std::vector<std::string>{"Original (com padding)", "Vista frontal retificada"},
        2
    );

    
// [pdi:panel-io] auto-generated — do not edit by hand
std::filesystem::create_directories("tmp");
mm::write(img_pad, "tmp/fig_02_sudoku2_0.png");
mm::write(img_rect, "tmp/fig_02_sudoku2_1.png");
// [pdi:panel-io:end]
return 0;
}
Overwriting tmp/fig_02_sudoku2.cpp
!g++ -I. -std=c++17 tmp/fig_02_sudoku2.cpp -o tmp/fig_02_sudoku2 \
  && ./tmp/fig_02_sudoku2 \
  && test -f "tmp/fig_02_sudoku2.png" \
  || echo "⚠ mm::show não gravou tmp/fig_02_sudoku2.png"
[1] Original (com padding)
[2] Vista frontal retificada
try:
    mm.show(
        [
            mm.read("tmp/fig_02_sudoku2_0.png"),
            mm.read("tmp/fig_02_sudoku2_1.png"),
        ],
        titles=[
            'Original (com padding)',
            'Vista frontal retificada',
        ],
        cols=2,
    )
except Exception as _e:
    print("figura indisponivel nesta trilha (C++): " + repr(_e) + " tmp/fig_02_sudoku2_0.png (ver a versao Python)")
figura indisponivel nesta trilha (C++): UnidentifiedImageError("cannot identify image file 'tmp/fig_02_sudoku2_1.png'") tmp/fig_02_sudoku2_0.png (ver a versao Python)
Figura 2.23
🎮 Simulador EP02_11: Perspectiva del Sudoku Homografía 3×3 · CC BY 2.0
📷 Original (Expandida) — Arrasta las Esquinas Foto: Héctor Rodríguez · CC BY 2.0
✅ Corregida (400×400) — Vista Frontal
Cargando imagen del Sudoku...
Figura 2.24: Simulador EP02_11: Corrección de Perspectiva del Sudoku (Homografía 3×3 con Remuestreo Bilineal)
%%writefile EP02_11.cpp
// your solution
Overwriting EP02_11.cpp
TestSuite("EP02_11.cpp").run()
✔️ EP02_11.cases ya existe en casos/
📋 4 caso(s) cargado(s) de casos/EP02_11.cases

🔍 Probando C++: EP02_11.cpp
⚠️ EP02_11.cpp: archivo vacío (menos de 3 líneas). Pruebas omitidas.