Processamento Digital de Imagens

Last updated: May 10th, 2017

About

Esta página é destinada ao relatório dos exercícios realizados para a Disciplina de Processamento Digital de Imagens - DCA/UFRN.

Discentes Envolvidos:
  • Daniel Silva de Morais | danielmorais@outlook.com.br
  • Gabriel Felipe Azevedo de Sousa | g.felipes@hotmail.com

1ª Unidade

Exercício 3 - Manipulando pixels em uma imagem

3.1 - Área negativa de uma imagem

Utilizando o programa pixels.cpp como referência, implemente um programa regions.cpp. Esse programa deverá solicitar ao usuário as coordenadas de dois pontos P1 e P2 localizados dentro dos limites do tamanho da imagem e exibir que lhe for fornecida. Entretanto, a região definida pelo retângulo de vértices opostos definidos pelos pontos P1 e P2 será exibida com o negativo da imagem na região correspondente.

Para o desenvolvimento da aplicação, foi utilizado o programa pixels.cpp como referência. Ao executar o código no terminal do linux utilizando do arquivo Makefile, é solicitado que o usuário dê entrada para dois pontos, P1 e P2, com as coordenadas x e y. O resultado pode ser visto a seguir:

Código: regions.cpp

	
#include <iostream>
#include <cv.h>
#include <highgui.h>
#include <opencv2/opencv.hpp>


using namespace cv;
using namespace std;


int main(int, char**){

    Mat image, image2;    
    image = imread("../imagens/biel.png", CV_LOAD_IMAGE_GRAYSCALE);
    image2 = image.clone();

    if(!image.data)
        cout << "Não foi possível abrir ou encontrar a imagem." << endl;
    
    Point p1, p2;
    
    cout << "Informe as coordenadas de dois pontos entre (0, 0) e (255, 255):" << endl;
    do{
        cout << "P1.x: ";
        cin >> p1.x;
        cout << "P1.y: ";
        cin >> p1.y;        
        cout << "P2.x: ";
        cin >> p2.x;
        cout << "P2.y: ";
        cin >> p2.y;        
    }while(p1.x > 255 || p1.x < 0 || p1.y > 255 || p1.y < 0 || 
           p2.x > 255 || p2.x < 0 || p2.y > 255 || p2.y < 0 || 
           p1.x > p2.x || p1.y > p2.y);
    
    cout << "Pontos selecionados: "<< "P1("<< p1.x <<", "<< p1.y<<") e P2("<< p2.x<<", "<< p2.y<<")"<< endl;      
       
    for(int i=p1.x;i < p2.x;i++){
        for(int j=p1.y;j < p2.y;j++){
            image2.at< uchar >(i,j) = 255 - image2.at< uchar >(i,j);
        }
    }
    
    namedWindow("Imagem Original",WINDOW_AUTOSIZE);
    imshow("Imagem Original", image);
    
    namedWindow("Imagem Negativo",WINDOW_AUTOSIZE);
    imshow("Imagem Negativo", image2);
    waitKey(0);
    
    return 0;
}                             
                                
                                
Figura 1 - Terminal
screenshot
Figura 2 - Resultado

Comparação entre a imagem original e a imagem com a região em negativo

screenshot

3.2 - Troca regiões

Utilizando o programa pixels.cpp como referência, implemente um programa trocaregioes.cpp. Seu programa deverá trocar os quadrantes em diagonal na imagem. Explore o uso da classe Mat e seus construtores para criar as regiões que serão trocadas.

Para o desenvolvimento da aplicação, foram criados quatro regiões de interesse, de forma que podemos manipulá-las para obtermos o efeito desejado. A seguir segue o código desenvolvido:

Código: regions.cpp

#include <iostream>
#include <cv.h>
#include <highgui.h>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;


int main(int, char **)
{

    Mat image, aux, aux2;
    image = imread("../imagens/biel.png", CV_LOAD_IMAGE_GRAYSCALE);

    if (!image.data) 
	{
        cout << "Não foi possível abrir ou encontrar a imagem." << endl;
        return 0;
    }

    aux = image(Rect(0, 0, 128, 128)).clone();              //Rect(x0, y0, x, y ) x e y é o quanto eu quero pegar
    aux2 = image(Rect(128, 128, 128, 128)).clone();
    //TROCA DE REGIÕES
    aux.copyTo(image(Rect(128, 128, 128, 128)));
    aux2.copyTo(image(Rect(0, 0, 128, 128)));
    
    aux = image(Rect(0, 128, 128, 128)).clone();            //Rect(x0, y0, x, y ) x e y é o quanto eu quero pegar        
    aux2 = image(Rect(128, 0, 128, 128)).clone();
    //TROCA DE REGIÕES
    aux.copyTo(image(Rect(128, 0, 128, 128)));
    aux2.copyTo(image(Rect(0, 128, 128, 128)));

    namedWindow("Imagem", WINDOW_AUTOSIZE);
    imshow("Imagem", image);

    waitKey(0);

    return 0;
}                        
                                
                                

Para facilitar na programação, foi utilizado o método Rect(), que permite fazer uma cópia dos pixels de uma determinada área da imagem que é especificada como parâmetro. Executando o código, obtemos o seguinte efeito:

Figura 3 - Regiões trocadas
screenshot

Exercício 4 - Preenchendo regiões

4.1 - Questão

Observando-se o programa labeling.cpp como exemplo, é possível verificar que caso existam mais de 255 objetos na cena, o processo de rotulação poderá ficar comprometido. Identifique a situação em que isso ocorre e proponha uma solução para este problema.

Como a imagem é composta por pixels de 8 bits, por ser tom de cinza a imagem poderá ter 256 diferentes tons. Retirando a cor de fundo (preta), temos 255 tons de cinza. Logo, se tivermos mais de 255 objetos não será possível rotulá-los na imagem. O interessante seria especificar um tom de cinza diferente de 0 e 255 e rotular todos os objetos com o mesmo tom. Com isso, não importa a quantidade de objetos.

4.2 - Contagem de objetos

Aprimore o algoritmo de contagem apresentado para identificar regiões com ou sem buracos internos que existam na cena. Assuma que objetos com mais de um buraco podem existir. Inclua suporte no seu algoritmo para não contar bolhas que tocam as bordas da imagem. Não se pode presumir, a priori, que elas tenham buracos ou não.

Uma forma de contar o número de buracos é através de rótulos, ou seja, como os buracos possuem o mesmo tom de cinza do fundo, podemos alterar a cor de fundo e buscar os buracos das bolhas, que terão seu tom de cinza igual ao do fundo original (0). Com o auxílio do método floodFill() podemos encontrar essas regiões e fazer sua contagem.

Código: contaregiao.cpp

#include <iostream>
#include <cv.h>
#include <highgui.h>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main(int argc, char **argv)
{
    Mat image, mask;
    int width, height;
    int nobjects, noburacos;

    CvPoint p;
    image = imread("../imagens/bolhas.png", CV_LOAD_IMAGE_GRAYSCALE);

    if (!image.data) {
        cout << "imagem nao carregou corretamente\n";
        return (-1);
    }
    width = image.size().width;         //colunas
    height = image.size().height;       //linhas

    p.x = 0;
    p.y = 0;
    
    // Elimina os objetos que tocam as laterais da imagem, rotulando-os com a cor de fundo;
    for (int k = 0; k < height; k++) {
        floodFill(image, CvPoint(k,0), 0);
        floodFill(image, CvPoint(k,255), 0);
        floodFill(image, CvPoint(0,k), 0);
        floodFill(image, CvPoint(255,k), 0);
    }
    

    // Contagem de objetos
    nobjects = 0;
    for (int i = 0; i < height; i++) {
        for (int j = 0; j < width; j++) {
            if (image.at < uchar > (i, j) == 255) {
                // achou um objeto
                nobjects++;
                p.x = j;
                p.y = i;

                //Todos os objetos são rotulados no tom de cinza 200;
                floodFill(image, p, 200);
            }
        }
    }    
    
    // Contagem de objetos com buracos
    //Uma forma de contar o número de buracos é através de rótulos, ou seja, como os buracos 
    //possuem o mesmo tom de cinza do fundo, podemos alterar a cor de fundo e buscar os objetos, 
    //que terão seu tom de cinza igual ao do fundo. Depois podemos bunscar na imagem o número de 
    //regiões de tom de cinza 0, que equivale ao buraco.
    
    floodFill(image,cvPoint(0,0), 50);                  // utilizando o floodFill(), mudaremos a cor de fundo
    
    noburacos = 0;
    for (int i = 0; i < height; i++) {
        for (int j = 0; j < width; j++) {
            if (image.at < uchar > (i, j) == 0) {       //busca objetos da cor de fundo original
                p.x = j;
                p.y = i;
                //Todos os buracos são rotulados no tom de cinza 50;
                floodFill(image, p, 50);
                
                if(image.at< uchar>(i, j-1) == 200)
                {
                    noburacos++;
                    // preenche objeto com buraco com cor diferente, de forma que se acharmos um novo buraco e 
                    //um pixel anterior for de cor diferente de 200, ele irá saber que a bolha possui outros 
                    //buracos e não contabilizará novos buracos para aquela bolha.
                    floodFill(image,cvPoint(p.x-1,p.y), 150);
                }
            }
        }
    }  
    
    cout << "Número de objetos: " << nobjects << endl;
    cout << "Número de buracos: " << noburacos << endl;
    cout << "Número de objetos sem buracos: " << nobjects - noburacos << endl;   
    

    imshow("image", image);
    imwrite("labeling.png", image);
    waitKey();
    return 0;
}                       
                                    
                                    

Para testar o código, utilizaremos a Figura 4, que é formada por um fundo preto (0) e bolhas em tom branco (255).

Figura 4 - Bolhas
screenshot
Figura 5 - Contagem de bolhas

Algoritmo modificado, logo um pouco diferente do labeling.cpp

screenshot
Figura 6 - Contagem de bolhas e buracos

Resultado

screenshot

Exercício 5 - Manipulação de histogramas

5.1 - Equalização de histograma

Utilizando o programa histogram.cpp como referência, implemente um programa equalize.cpp. Este deverá, para cada imagem capturada, realizar a equalização do histogram antes de exibir a imagem. Teste sua implementação apontando a câmera para ambientes com iluminações variadas e observando o efeito gerado. Assuma que as imagens processadas serão em tons de cinza.

A equalização de histograma é um método que permite mudar a distribuição dos valores de ocorrência em um histograma permitindo uma redução das diferenças acentuadas de tons e assim, particularmente em imagens, acentuando detalhes não visíveis anteriormente.

Para a realização da aplicação, foi utilizado o exemplo de referência. Dessa forma, temos o seguinte código:

Código: equalize.cpp

#include <iostream>
#include <cv.h>
#include <highgui.h>
#include <opencv2/opencv.hpp>
#include "opencv2/imgproc/imgproc.hpp"

using namespace cv;
using namespace std;

int main(int argc, char** argv){
  Mat image;
  int width, height;
  VideoCapture cap;
    
  vector< Mat> planes;
  Mat histR, histG, histB;
  int nbins = 64;
  float range[] = {0, 256};
  const float *histrange = { range };
  bool uniform = true;
  bool acummulate = false;

  cap.open(0);  //seleciona a camera
  
  
  if(!cap.isOpened()){
    cout << "cameras indisponiveis";
    return -1;
  }
  
  width  = cap.get(CV_CAP_PROP_FRAME_WIDTH);
  height = cap.get(CV_CAP_PROP_FRAME_HEIGHT);

  cout << "largura = " << width << endl;
  cout << "altura  = " << height << endl;

  int histw = nbins, histh = nbins/2;
  Mat histImgR(histh, histw, CV_8UC3, Scalar(0,0,0));
  Mat histImgG(histh, histw, CV_8UC3, Scalar(0,0,0));
  Mat histImgB(histh, histw, CV_8UC3, Scalar(0,0,0));
  
  while(1){   
    
    cap >> image;
    //Redimensionar a captura
    resize(image, image, Size(640, 360));
    ///////////////////////////////////////////////////////////////////////////////////////////////////
    // EQUALIZAÇÃO
    /////////////////////////////////////////////////////////////////////////////////////////////////// 
    
    //Separa a imagem capturada em três canais que são armazenados em "planes" 
    split (image, planes);  
    
    //Equalização das capturas
    equalizeHist(planes[0], planes[0]);
    equalizeHist(planes[1], planes[1]);
    equalizeHist(planes[2], planes[2]);    
        
    //Utilizamos  a função merge() para unir os planos ou canais equalizados em image.
    merge(planes, image);   
    
    /////////////////////////////////////////////////////////////////////////////////////////////////// 
    
    
    calcHist(&planes[0], 1, 0, Mat(), histR, 1,
             &nbins, &histrange,
             uniform, acummulate);
    calcHist(&planes[1], 1, 0, Mat(), histG, 1,
             &nbins, &histrange,
             uniform, acummulate);
    calcHist(&planes[2], 1, 0, Mat(), histB, 1,
             &nbins, &histrange,
             uniform, acummulate);

    normalize(histR, histR, 0, histImgR.rows, NORM_MINMAX, -1, Mat());
    normalize(histG, histG, 0, histImgG.rows, NORM_MINMAX, -1, Mat());
    normalize(histB, histB, 0, histImgB.rows, NORM_MINMAX, -1, Mat());

    histImgR.setTo(Scalar(0));
    histImgG.setTo(Scalar(0));
    histImgB.setTo(Scalar(0));
    
    for(int i=0; i < nbins; i++){
      line(histImgR,
           Point(i, histh),
           Point(i, histh-cvRound(histR.at< float>(i))),
           Scalar(0, 0, 255), 1, 8, 0);
      line(histImgG,
           Point(i, histh),
           Point(i, histh-cvRound(histG.at< float>(i))),
           Scalar(0, 255, 0), 1, 8, 0);
      line(histImgB,
           Point(i, histh),
           Point(i, histh-cvRound(histB.at< float>(i))),
           Scalar(255, 0, 0), 1, 8, 0);
    }
    histImgR.copyTo(image(Rect(0, 0       ,nbins, histh)));
    histImgG.copyTo(image(Rect(0, histh   ,nbins, histh)));
    histImgB.copyTo(image(Rect(0, 2*histh ,nbins, histh)));
    imshow("image", image);
    if(waitKey(30) >= 0) break;
  }
  return 0;
}                       
 
                                    

O cálculo do histograma será realizado para cada uma das componentes de cor de forma independente. Logo, a separação das componentes em matrizes independentes será feita no vetor de matrizes planes. Assim, planes[0], planes[1] e planes[2] armazenarão as componentes de cor Vermelho, Verde e Azul, respectivamente. É utilizado o método split() para a separação dos componentes de cor. Para fazer a equalização, é utilizado o método equalizeHist(), que é do OpenCV, ele é utilizado para todas as componentes da imagem em captura. Para finalizar, é utilizado o método merge() para unir os planos e formar a nova imagem equalizada.

A seguir, a Figura 7 mostra a captura sem equalização e a Figura 8 mostra o resultado da equalização.

Figura 7 - Captura sem equalização
screenshot
Figura 8 - Captura com equalização de histograma
screenshot

Podemos perceber no histograma que que o da figura 7 possui mais picos elevados do que o da Figura 8, isso ocorre devido a equalização.

5.2 - Motion Detection

Utilizando o programa histogram.cpp como referência, implemente um programa motiondetector.cpp. Este deverá continuamente calcular o histograma da imagem (apenas uma componente de cor é suficiente) e compará-lo com o último histograma calculado. Quando a diferença entre estes ultrapassar um limiar pré-estabelecido, ative um alarme. Utilize uma função de comparação que julgar conveniente.

O Motion Detection ou detector de movimento, pode ser utilizado para diferentes aplicações, sendo a mais comum em serviços de segurança e monitoramento. Para Detectarmos movimento, foi utilizado o histograma da captura, sendo nescessário apenas de uma componente. Nesse caso, no início da captura foi armazenado um histograma, de forma que nas capturas seguintes elas são comparadas com a primeira captura, que está armazenada durante a execução da aplicação.

Há um valor que é utilizado como limiar para sabermos se na variação do histograma houve um movimento grande movimento perceptível. A seguir, código desenvolvido.

Código: equalize.cpp

#include <iostream>
#include <cv.h>
#include <highgui.h>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main(int argc, char** argv){
  Mat image;
  int width, height;
  VideoCapture cap;  
  vector< Mat> planes;
  Mat hg_old, hg_new;  // histograma verde antigo e novo
  
  Mat histR, histG, histB;
  int nbins = 64;
  float range[] = {0, 256};
  const float *histrange = { range };
  bool uniform = true;
  bool acummulate = false;

  cap.open(0);
  
  
  if(!cap.isOpened()){
    cout << "cameras indisponiveis";
    return -1;
  }
  
  width  = cap.get(CV_CAP_PROP_FRAME_WIDTH);
  height = cap.get(CV_CAP_PROP_FRAME_HEIGHT);

  cout << "largura = " << width << endl;
  cout << "altura  = " << height << endl;

  int histw = nbins, histh = nbins/2;
  Mat histImgR(histh, histw, CV_8UC3, Scalar(0,0,0));
  Mat histImgG(histh, histw, CV_8UC3, Scalar(0,0,0));
  Mat histImgB(histh, histw, CV_8UC3, Scalar(0,0,0));
  
  
  //As análises serão feitas utilizando o primeiro histograma, ou seja, da primeira imagem tirada do ambiente. 
  //Dessa forma, qualquer alteração dentro do limite x informará movimentação.
  cap >> image;
  split (image, planes);
  calcHist(&planes[1], 1, 0, Mat(), hg_old, 1, &nbins, &histrange, uniform, acummulate);
  
  double x = 0;
  
  while(1){
    cap >> image;
    resize(image, image, Size(640,360));
       
///////////////////////////////////////////////////////////////////////////////////////////////////    
    
    //DETECÇÃO DE MOVIMENTO
    
    split (image, planes);  

    calcHist(&planes[1], 1, 0, Mat(), hg_new, 1, &nbins, &histrange, uniform, acummulate);
    x = compareHist(hg_new, hg_old, CV_COMP_CORREL);
    
    cout << "x = "<< x<< endl;

    if(x<-0.25){
    	putText(image, "ALARME", cvPoint(250, 100), FONT_HERSHEY_COMPLEX_SMALL, 1, Scalar(255, 0, 0), 2);
    }
    
///////////////////////////////////////////////////////////////////////////////////////////////////    
    
    calcHist(&planes[0], 1, 0, Mat(), histR, 1,
             &nbins, &histrange,
             uniform, acummulate);
    calcHist(&planes[1], 1, 0, Mat(), histG, 1,
             &nbins, &histrange,
             uniform, acummulate);
    calcHist(&planes[2], 1, 0, Mat(), histB, 1,
             &nbins, &histrange,
             uniform, acummulate);

    normalize(histR, histR, 0, histImgR.rows, NORM_MINMAX, -1, Mat());
    normalize(histG, histG, 0, histImgG.rows, NORM_MINMAX, -1, Mat());
    normalize(histB, histB, 0, histImgB.rows, NORM_MINMAX, -1, Mat());

    histImgR.setTo(Scalar(0));
    histImgG.setTo(Scalar(0));
    histImgB.setTo(Scalar(0));
    
    for(int i=0; i < nbins; i++){
      line(histImgR,
           Point(i, histh),
           Point(i, histh-cvRound(histR.at< float>(i))),
           Scalar(0, 0, 255), 1, 8, 0);
      line(histImgG,
           Point(i, histh),
           Point(i, histh-cvRound(histG.at< float>(i))),
           Scalar(0, 255, 0), 1, 8, 0);
      line(histImgB,
           Point(i, histh),
           Point(i, histh-cvRound(histB.at< float>(i))),
           Scalar(255, 0, 0), 1, 8, 0);
    }
    histImgR.copyTo(image(Rect(0, 0       ,nbins, histh)));
    histImgG.copyTo(image(Rect(0, histh   ,nbins, histh)));
    histImgB.copyTo(image(Rect(0, 2*histh ,nbins, histh)));
    imshow("image", image);
    if(waitKey(30) >= 0) break;
  }
  return 0;
}                 
 
                                    

Gif 1 - Motion Detector
screenshot

Exercício 6 - Filtragem no domínio espacial I

Utilizando o programa filtroespacial.cpp como referência, implemente um programa laplgauss.cpp. O programa deverá acrescentar mais uma funcionalidade ao exemplo fornecido, permitindo que seja calculado o laplaciano do gaussiano das imagens capturadas. Compare o resultado desse filtro com a simples aplicação do filtro laplaciano.

Utilizando o código de referência, obtemos alguns resultados. Aqui temos o código desenvolvido como a adição do filtro "Laplaciano do Gussiano":

Código: laplgauss.cpp

#include <iostream>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

void printmask(Mat & m)
{
    for (int i = 0; i < m.size().height; i++) {
        for (int j = 0; j < m.size().width; j++) {
            cout << m.at < float >(i, j) << ",";
        }
        cout << endl;
    }
}

void menu()
{
    cout << "\npressione a tecla para ativar o filtro: \n"
            "a - calcular modulo\n"
            "m - media\n"
            "g - gauss\n"
            "v - vertical\n"
            "h - horizontal\n" 
            "l - laplaciano\n"
            "x - laplaciano do gaussiano\n" 
            "esc - sair\n";
}

int main(int argvc, char **argv)
{
    VideoCapture video;
    float media[] = { 1, 1, 1,
                      1, 1, 1,
                      1, 1, 1
    };
    float gauss[] = { 1, 2, 1,
                      2, 4, 2,
                      1, 2, 1
    };
    float horizontal[] = { -1, 0, 1,
                           -2, 0, 2,
                            1, 0, 1
    };
    float vertical[] = {   -1, -2, -1,
                            0,  0,  0,
                            1,  2,  1
    };
    float laplacian[] = { 0, -1,  0,
                         -1,  4, -1,
                          0, -1,  0
    };
    
    // Aplicação da máscara "Laplaciano do Gaussiano", comum na literatura
    float x[]=  {0,  0,  -1,  0,  0,
                 0, -1,  -2, -1,  0,
                -1, -2,  16, -2, -1,
                 0, -1,  -2, -1,  0,
                 0,  0,  -1,  0,  0
    };

    Mat cap, frame, frame32f, frameFiltered;
    Mat mask(3, 3, CV_32F), mask1;
    Mat result, result1;
    double width, height, min, max;
    int absolut;
    char key;

    video.open(0);
    if (!video.isOpened())
        return -1;
    width = video.get(CV_CAP_PROP_FRAME_WIDTH);
    height = video.get(CV_CAP_PROP_FRAME_HEIGHT);
    std::cout << "largura=" << width << "\n";;
    std::cout << "altura =" << height << "\n";;

    namedWindow("filtroespacial", 1);

    mask = Mat(3, 3, CV_32F, media);
    scaleAdd(mask, 1 / 9.0, Mat::zeros(3, 3, CV_32F), mask1);
    swap(mask, mask1);
    absolut = 1;                // calcs abs of the image

    menu();
    for (;;) {
        video >> cap;
        resize(cap, cap, Size(640,360));
        cvtColor(cap, frame, CV_BGR2GRAY);
        flip(frame, frame, 1);
        imshow("original", frame);
        frame.convertTo(frame32f, CV_32F);        
        
        filter2D(frame32f, frameFiltered, frame32f.depth(), mask, Point(1, 1), 0);         
        
        if (absolut) {
            frameFiltered = abs(frameFiltered);
        }
        frameFiltered.convertTo(result, CV_8U);
        imshow("filtroespacial", result);
        key = (char) waitKey(10);
        if (key == 27)
            break;              // esc pressed!
        switch (key) {
        case 'a':
            menu();
            absolut = !absolut;
            break;
        case 'm':
            menu();
            mask = Mat(3, 3, CV_32F, media);
            scaleAdd(mask, 1 / 9.0, Mat::zeros(3, 3, CV_32F), mask1);
            mask = mask1;
            printmask(mask);
            break;
        case 'g':
            menu();
            mask = Mat(3, 3, CV_32F, gauss);
            scaleAdd(mask, 1 / 16.0, Mat::zeros(3, 3, CV_32F), mask1);
            mask = mask1;
            printmask(mask);
            break;
        case 'h':
            menu();
            mask = Mat(3, 3, CV_32F, horizontal);
            printmask(mask);
            break;
        case 'v':
            menu();
            mask = Mat(3, 3, CV_32F, vertical);
            printmask(mask);
            break;
        case 'l':
            menu();
            mask = Mat(3, 3, CV_32F, laplacian);
            printmask(mask);
            break;
            
        //Implementação do case para o "laplaciano do Gaussiano"
        case 'x':
            menu();
            mask = Mat(5, 5, CV_32F, x);
            printmask(mask);
            break;
        default:
            break;
        }
    }
    return 0;
}
     
                                    
Figura 9 - Captura Original
screenshot
Figura 10 - Captura com filtro Laplaciano
screenshot
Figura 11 - Captura com filtro Gaussiano
screenshot
Figura 12 - Captura com filtro Laplaciano do Gaussiano
screenshot

Analizando os resultados, podemos verificar que o "Laplaciano do Gaussiano" deixa as bordas ainda mais aparente, em comparação ao filtro Laplaciano, entretanto, percebe-se mais ruidos.

Exercício 7 - Filtragem no domínio espacial II

Utilizando o programa addweighted.cpp como referência, implemente um programa tiltshift.cpp. Três ajustes deverão ser providos na tela da interface:

  • um ajuste para regular a altura da região central que entrará em foco;
  • um ajuste para regular a força de decaimento da região borrada;
  • um ajuste para regular a posição vertical do centro da região que entrará em foco. Finalizado o programa, a imagem produzida deverá ser salva em arquivo.

Independente de como o resultado é obtido (seja da maneira tradicional, através do uso de lentes especiais, ou por pós-produção digital), a aparência é a mesma graças ao desfoque local em algumas áreas específicas. O olho estranha esse desfoque, que não é natural para objetos e edifícios tão grandes e o cérebro acaba percebendo aquilo como uma miniatura.

Nessa Atividade foi feito um programa que realize o efeito tilt-shift, que consiste em simular miniaturas de imagens desfocando determinadas partes da imagem escolhida. Para implementar tal efeito, deve ser feita uma filtragem de borramento em uma determinada faixa escolhida, além de poder configurar a sua intensidade. É utilizado o createTrackbar() para criar a barra que definirá o efeito do borramento em uma determinada área. Também é feito o cálculo do alfa que é uma relação entre tangentes hiperbólicas envolvendo duas coordenadas definidas.

Código: tiltshift.cpp

#include <iostream>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int height;

int MAX = 100;
double d = 6;
int d_slider = 0;

int t_slider = 0;
int t = 0;

int h = 20;
int h_slider = 0;


Mat image1, image2, alpha, beta;

char TrackbarName[50];

void tiltShift(){
	Mat mWeighted1, mWeighted2, tiltshift;
	int l1 = -h/2;
    int l2 = -l1;
    alpha = Mat::zeros(image1.rows, image1.cols, CV_32F);
    beta = Mat::zeros(image1.rows, image1.cols, CV_32F);
    int i, j;
	
    for (i = 0; i < alpha.rows; i++) {
        int x = i - (t + h/2);
        float alfa = 0.5f * (tanh((x - l1)/d) - tanh((x - l2)/d));
        for (j = 0; j < alpha.cols; j++) {
            alpha.at< float>(i, j) = alfa;
            beta.at< float>(i, j) = 1 - alfa;
        }
    }
	
    Mat auxA[] = {alpha, alpha, alpha};
    Mat auxB[] = {beta, beta, beta};
    merge(auxA, 3, alpha);
    merge(auxB, 3, beta);

    image1.convertTo(mWeighted1, CV_32FC3);
    image2.convertTo(mWeighted2, CV_32FC3);

    multiply(mWeighted1, alpha, mWeighted1);
    multiply(mWeighted2, beta, mWeighted2);

    add(mWeighted1, mWeighted2, tiltshift);
    tiltshift.convertTo(tiltshift, CV_8UC3);
    imshow("tiltshift", tiltshift);

    imshow("imagem", alpha);
	imshow("borramento", beta);
}

void on_trackbar_d(int, void*){
    d = (double) d_slider;
    if (d < 1) {
        d = 1;
    }
    tiltShift();
}

void on_trackbar_t(int, void *){
    t = t_slider*height/MAX;
    tiltShift();
}

void on_trackbar_h(int, void*) {
    h = h_slider*height/MAX;
    if (h == 0) {
        h = 1;
    }

    if (h > height) {
        h = height;
    }
    tiltShift();
}

int main(int argvc, char** argv){
	image1 = imread("../imagens/trem.jpg");
	height = image1.size().height;
	image2 = image1.clone();

	Mat aux, mask, mask1;
    float media[] = {1,1,1,
                     1,1,1,
                     1,1,1};
 
    mask = Mat(3, 3, CV_32F, media);
    scaleAdd(mask, 1/9.0, Mat::zeros(3,3,CV_32F), mask1);
    mask = mask1;
    image2.convertTo(aux, CV_32F);
	
    for (int i = 0; i < 10; i++) {
        filter2D(aux, aux, aux.depth(), mask, Point(1, 1), 0);
    }
	
    aux = abs(aux);
    aux.convertTo(image2, CV_8UC3);

    namedWindow("tiltshift", 1);

    sprintf( TrackbarName, "Altura");
    createTrackbar( TrackbarName, "tiltshift",
                    &h_slider,
                    MAX,
                    on_trackbar_h);

    sprintf( TrackbarName, "Decaimento");
    createTrackbar( TrackbarName, "tiltshift",
                    &d_slider,
                    MAX,
                    on_trackbar_d);

    sprintf( TrackbarName, "Vertical");
    createTrackbar( TrackbarName, "tiltshift",
                    &t_slider,
                    MAX,
                    on_trackbar_t );
    tiltShift();

  waitKey(0);
  return 0;
}                                    
                                    
                                    
Figura 13 - Imagem original
screenshot
Figura 14 - Programa em execução com tiltshift
screenshot
Figura 15 - Imagem com tiltshift
screenshot

2ª Unidade

Exercício 8 - Filtragem no domínio da Frequência

Utilizando o programa dft.cpp como referência, implemente o filtro homomórfico para melhorar imagens com iluminação irregular. Crie uma cena mal iluminada e ajuste os parâmetros do filtro homomórfico para corrigir a iluminação da melhor forma possível. Assuma que a imagem fornecida é em tons de cinza.

Através do modelo de iluminação-reflectância, é possível desenvolver um procedimento no domínio da frequência para melhorar a aparência de uma imagem efetuando simultaneamente a compressão da faixa de intensidade e o realce de contraste. Uma imagem pode ser expressa por $f(x,y) = i(x,y)r(x,y)$ , mas ela não pode ser utilizada diretamente para atuar sobre os componentes de frequência de iluminação e reflectância, pois a Transformada de Fourier de um produto não é o produto das transformadas. Para que isso seja possível, aplicaremos o logaritmo na expressão, pois o logaritmo de um produto é a soma dos logaritmos.

$$z(x,y) = ln(f(x,y)) = ln(i(x,y)) + ln(r(x,y))$$

Podemos seguir os passos abaixo para obtenção da imagem filtrada:

$$f(x,y) \longrightarrow ln \longrightarrow DFT \longrightarrow H(u,v) \longrightarrow (DFT)^{-1} \longrightarrow exp \longrightarrow g(x,y)$$

O cálculo do filtro é dado por: $$H(u,v) = (\gamma_{H} - \gamma_{L})[1-e^{-c[D^2(u,v)/D_0^2]}] + \gamma_{L} $$ onde $c$ é a constante que controla a inclinação da função à medida que ela realiza a transição entre $\gamma_{L}$ e $\gamma_{H}$.

A seguir, código desenvolvido a partir desses conceitos:

Código: homomorfico.cpp

#include <iostream>
#include <highgui.h>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>

using namespace cv;
using namespace std;

Mat complexImage, padded, filter;
Mat image, imagegray, tmp;
Mat_< float > realInput, zeros;
vector < Mat > planos;


int dft_M, dft_N;
int gammaL=0, gammaL_max=100;
int gammaH=0, gammaH_max=100;
int D0=0, D0_max=100;
int C0=0, C0_max= 100;

char key;           // Guarda valor para ESC

void callFilter(int, void*)
{
    int dft_M= tmp.size().height;
    int dft_N= tmp.size().width;
    // Normalização dos parâmetros
    double gH = gammaH/10.0;
    double gL = gammaL/1000.0;
    double d0 = D0/100.0;
    double c0 = C0/100.0;

    // Calcula o filtro pixel a pixel
    for(int i=0; i < dft_M; i++)
    {
        for(int j=0; j < dft_N; j++)
        {
            double D = pow(i-dft_M/2,2)+pow(j-dft_N/2, 2);
            tmp.at< float > (i,j) = (gH-gL)*(1-exp(-c0*D/pow(d0,2)))+gL;
        }
    }

    // cria a matriz com as componentes do filtro e junta
    // ambas em uma matriz multicanal complexa
    Mat comps[]= {tmp, tmp};
    merge(comps, 2, filter);
    imshow("Filtro", tmp);
}

// troca os quadrantes da imagem da DFT
void deslocaDFT(Mat& image )
{
    Mat tmp, A, B, C, D;

    // se a imagem tiver tamanho impar, recorta a regiao para
    // evitar copias de tamanho desigual
    image = image(Rect(0, 0, image.cols & -2, image.rows & -2));
    int cx = image.cols/2;
    int cy = image.rows/2;

    // reorganiza os quadrantes da transformada
    // A B   ->  D C
    // C D       B A
    A = image(Rect(0, 0, cx, cy));
    B = image(Rect(cx, 0, cx, cy));
    C = image(Rect(0, cy, cx, cy));
    D = image(Rect(cx, cy, cx, cy));

    // A <-> D
    A.copyTo(tmp);
    D.copyTo(A);
    tmp.copyTo(D);

    // C <-> B
    C.copyTo(tmp);
    B.copyTo(C);
    tmp.copyTo(B);
}


int main(int, char**)
{
    // cria Janelas e sliders
    namedWindow("Filtro Homomórfico", CV_WINDOW_AUTOSIZE);
    namedWindow("Filtro", CV_WINDOW_AUTOSIZE);
    createTrackbar("GammaH", "Filtro Homomórfico", &gammaH, gammaH_max, callFilter);
    createTrackbar("GammaL", "Filtro Homomórfico", &gammaL, gammaL_max, callFilter);
    createTrackbar("C", "Filtro Homomórfico", &C0, C0_max, callFilter);
    createTrackbar("D0", "Filtro Homomórfico", &D0, D0_max, callFilter);

    // carrega imagem
    image = imread("noite.jpg", CV_LOAD_IMAGE_GRAYSCALE);
    if(!image.data){
        cout<<"Não foi possível abrir a image.!\n";
        return -1;
    }
    resize(image, image, Size(640,480));
    //imwrite("original.png", image);

    // identifica os tamanhos otimos para calculo do FFT
    dft_M = getOptimalDFTSize(image.rows);
    dft_N = getOptimalDFTSize(image.cols);

    // realiza o padding da imagem
    copyMakeBorder(image, padded, 0, dft_M - image.rows, 0, dft_N - image.cols, BORDER_CONSTANT, Scalar::all(0));

    // parte imaginaria da matriz complexa (preenchida com zeros)
    zeros = Mat_ < float > ::zeros(padded.size());

    // prepara a matriz complexa para ser preenchida
    complexImage = Mat(padded.size(), CV_32FC2, Scalar(0));

    // a funcaoo de transferenncia (filtro frequencial) deve ter o
    // mesmo tamanho e tipo da matriz complexa
    filter = complexImage.clone();
    tmp = Mat(dft_M, dft_N, CV_32F);

    // O programa permanecerá em loop para receber as manipulações do Trackbar
    for(;;)
    {
        imagegray = image.clone();

        imshow("original", imagegray);

        // realiza o padding da imagem
        copyMakeBorder(imagegray, padded, 0,
                       dft_M - image.rows, 0,
                       dft_N - image.cols,
                       BORDER_CONSTANT, Scalar::all(0));

        // limpa o array de matrizes que vao compor a
        // imagem complexa
        planos.clear();
        // cria a compoente real
        realInput = Mat_ < float > (padded);

        // soma com 1 para evitar problemas de log(0) o erro e minimo
        realInput += Scalar::all(1);
        // calcula o log da imagem
        log(realInput, realInput);

        // insere as duas componentes no array de matrizes
        planos.push_back(realInput);
        planos.push_back(zeros);

        // combina o array de matrizes em uma unica
        // componente complexa
        merge(planos, complexImage);

        // calcula o dft
        dft(complexImage, complexImage);

        // realiza a troca de quadrantes
        deslocaDFT(complexImage);

        // aplica o filtro frequencial
        mulSpectrums(complexImage,filter,complexImage,0);

        // troca novamente os quadrantes
        deslocaDFT(complexImage);

        // calcula a DFT inversa
        idft(complexImage, complexImage, DFT_SCALE);

        // limpa o array de planos
        planos.clear();

        // separa as partes real e imaginaria da
        // imagem filtrada
        split(complexImage, planos);
        // calcular expodencial
        exp(planos[0], planos[0]);

        // normaliza a parte real para exibicao
        normalize(planos[0], planos[0], 0, 1, CV_MINMAX);
        imshow("Filtro Homomórfico", planos[0]);

        key = (char) waitKey(10);
        if( key == 27 )
            break; // esc pressed
    }


    Mat res;
    planos[0].convertTo(res, CV_8UC1, 255.0);
    // salva a imagem resultante
    imwrite("filtrada.png", res);
    vector < Mat > pFiltro;
    split(filter,pFiltro);
    normalize(pFiltro[0], pFiltro[0], 0, 1, CV_MINMAX);
    pFiltro[0].convertTo(res, CV_8UC1, 255.0);
    // salva uma imagem do filtro
    imwrite("filtro.png", res);
    waitKey(0);

    return 0;
}

                                
Figura 16 - Imagem original colorida
screenshot
Figura 17 - Imagem original em escala de cinza
screenshot
Figura 18 - Programa em execução
screenshot
Figura 19 - Imagem filtrada
screenshot
Figura 20 - Filtro
screenshot

Exercício 11 - Pontilhismo

Utilizando os programas exemplos/canny.cpp e exemplos/pontilhismo.cpp como referência, implemente um programa cannypoints.cpp. A idéia é usar as bordas produzidas pelo algoritmo de Canny para melhorar a qualidade da imagem pontilhista gerada. A forma como a informação de borda será usada é livre. Entretanto, são apresentadas algumas sugestões de técnicas que poderiam ser utilizadas:

Esta atividade teve como intuito o desenvolvimento do filtro de Canny e gerar uma imagem com pontilhismo. Primeiramente foi aplicado na imagem o processo do pontilhismo, em seguida, a imagem bruta passou por algumas iterações do filtro de Canny, sendo possível delimitar suas bordas. Em cada uma das iterações o limiar do filtro é alterado com a intenção de obter uma imagem com menos bordas. Para as bordas ficarem mais destacadas foi necessário percorrer a imagem filtrada e desenhar um círculo em cada pixel que possuísse um tom de cinza maior que zero.

Código: cannypoints.cpp

#include <iostream>
#include <highgui.h>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <fstream>
#include <iomanip>
#include <vector>
#include <algorithm>
#include <numeric>
#include <ctime>
#include <cstdlib>

using namespace std;
using namespace cv;

//Definição de constantes

#define STEP 5
#define RAIO 3
#define JITTER 3

int main(int argc, char** argv){
  vector< int> yrange;
  vector< int> xrange;

  Mat image, borderImage;
  Mat pontilhismo;
  int width, height, gray, x, y;

  image= imread("ponte.jpg",CV_LOAD_IMAGE_GRAYSCALE);

  srand(time(0));

  if(!image.data){
    cout << "Impossível ler a imagem."<< endl;
    exit(0);
  }

  width = image.size().width;
  height = image.size().height;

  xrange.resize(height/STEP);
  yrange.resize(width/STEP);

  iota(xrange.begin(), xrange.end(), 0);
  iota(yrange.begin(), yrange.end(), 0);

  for(int i=0; i < xrange.size(); i++){
    xrange[i] = xrange[i]*STEP+STEP/2;
  }
  
  for(int j=0; j < yrange.size(); j++){
    yrange[j] = yrange[j]*STEP+STEP/2;
  }

  image.copyTo(pontilhismo);
  random_shuffle(xrange.begin(), xrange.end());

  for(auto i : xrange){
    random_shuffle(yrange.begin(), yrange.end());
    for(auto j : yrange){
      x = i+rand()%(2*JITTER)-JITTER+1;
      y = j+rand()%(2*JITTER)-JITTER+1;
      gray = image.at< uchar>(x,y);
      circle(pontilhismo,
             cv::Point(y,x),
             RAIO,
             CV_RGB(gray,gray,gray),
             -1,
             CV_AA);
    }
  }

  imshow("pontilhismo", pontilhismo);
  imwrite("pontilhismo.png", pontilhismo);

  for(int n = 0; n < 5; n++){
     Canny(image, borderImage, 10 * n, 50 * n);
     imshow("cannyborderImage", borderImage);
     imwrite("cannyborderImage.png", borderImage);     
     
     int raio = 5 - n;

     for(int i = 0; i < height; i++){
        for(int j = 0; j < width; j++){
           if(borderImage.at< uchar>(i,j)>0){
              gray = image.at< uchar>(i,j);
              circle(pontilhismo,
                     cv::Point(j,i),
                     raio,
                     CV_RGB(gray,gray,gray),
                     -1,
                     CV_AA);
           }
        }
     }
  }

  imshow("cannyPoints", pontilhismo);
  imwrite("cannyPoints.png", pontilhismo);

  waitKey();
  return 0;
}
                                
Figura 21 - Imagem original
screenshot
Figura 22 - Imagem com o processo do pontilhismo
screenshot
Figura 23 - Imagem com Filtro de Canny
screenshot
Figura 24 - Imagem pontilhista após a utilização do filtro de Canny
screenshot

3ª Unidade

Projeto Final

Referências