Angelo Marcelino Cordeiro

Página desenvolvida como decumentação para os projetos desenvolvidos durante a disciplina DCA0445 - PROCESSAMENTO DIGITAL DE IMAGENS.

1. Primeira unidade

Neste tópico estão descritos as atividades realizadas referentes a primeira unidade da disciplina DCA0445. Para a compilação dos códigos referentes à esta unidade, é necessário possuir, no mesmo diretório do arquivo .cpp o arquivo Makefile, e executar os seguintes comandos no seu console:

Listing 1. Makefile
$ make <nome_arquivo>
$ ./<nome_arquivo> <caminho_para_imagem>

1.1. Regiões

Este código tem a função de um programa que recebe do usuário dois pontos P1 e P2 e verifica se os pontos fornecidos estão dentro da imagem. Em seguida delimita-se a região definida pelo retângulo de vértices opostos definidos pelos pontos P1 e P2 na qual é exibido o negativo da imagem efeito esse alcançado subtraindo 255 do valor de cada pixel que compõe o retângulo.

O código fonte regions.cpp está está descrito abaixo:

Listing 2. regions.cpp
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

struct ponto
{
    int x, y;
};

int main(int argc, char **argv)
{
    srand(time(NULL));
    Mat image;
    ponto p1, p2;
    Vec3b val;

    image = imread(argv[1], CV_LOAD_IMAGE_GRAYSCALE);
    if (!image.data)
        cout << "A imagem nao foi carregada corretamente" << endl;

    namedWindow("janela", WINDOW_AUTOSIZE);

    cout << "Insira os pontos do retângulo dentro dos limites da imagem ("
         << image.rows << "," << image.cols << "): ";
    cin >> p1.x >> p1.y >> p2.x >> p2.y;

    imshow("janela", image);
    imwrite("grayscale.png", image);
    waitKey();

    for (int i = p1.x; i < p2.x; i++)
    {
        for (int j = p1.y; j < p2.y; j++)
        {
            image.at<uchar>(i, j) = 255 - image.at<uchar>(i, j);
        }
    }

    imshow("janela", image);
    imwrite("negativo_grayscale.png", image);
    waitKey();

    image = imread(argv[1], CV_LOAD_IMAGE_COLOR);
    if (!image.data)
        cout << "A imagem nao foi carregada corretamente" << endl;

    imshow("janela", image);
    waitKey();

    val[0] = 255; //B
    val[1] = 255; //G
    val[2] = 255; //R

    for (int i = p1.x; i < p2.x; i++)
    {
        for (int j = p1.y; j < p2.y; j++)
        {
            image.at<Vec3b>(i, j) = val - image.at<Vec3b>(i, j);
        }
    }

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

A seguir estão as imagens de entrada do código acima:

Entrada do programa
Figure 1. Entrada do programa
Converção tom de cinza
Figure 2. Conversão para grayscale

.

Negativo com RGB
Figure 3. Negativo com RGB
Negativo de grayscale
Figure 4. Negativo do grayscale

.

1.2. Trocar Regiões

A atividade propõe a construção de um programa que recebe uma imagem, determina e troca os quadrantes diagonalmente numa imagem de saída. Para resolver este problema foi seguido o seguinte algoritmo:

  • Obtêm-se os quadrantes da imagem defindo o tamanho da grid como metade da altura e largura da imagem, e utilizando o construtor da classe Rect, seleciona um retângulo da matriz igual ao quadrante selecionado.

  • Com o construtor da classe Mat, copia-se cada quadrante para uma matriz separada.

  • Por fim, concatenam-se os quadrantes utilizando as funções vconcat() e hconcat() na imagem, como descreve o código trocaregioes.cpp abaixo:

Listing 3. trocaregioes.cpp
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main(int argc, char **argv)
{
    Mat image, A, B, C, D, Vab, Vcd;
    Rect rect;

    image = imread(argv[1], CV_LOAD_IMAGE_COLOR);
    if (!image.data)
    {
        cout << "A imagem nao foi carregada corretamente" << endl;
        return -1;
    }
    namedWindow("janela", WINDOW_AUTOSIZE);

    imshow("janela", image);
    cout << image.rows << " " << image.cols << endl;
    waitKey();

    rect = Rect(Point(0, 0), Point(image.rows / 2, image.cols / 2));
    A = Mat(image, rect);
    rect = Rect(Point(0, image.cols / 2), Point(image.rows / 2, image.cols));
    B = Mat(image, rect);
    rect = Rect(Point(image.rows / 2, 0), Point(image.rows, image.cols / 2));
    C = Mat(image, rect);
    rect = Rect(Point(image.rows / 2, image.cols / 2), Point(image.rows, image.cols));
    D = Mat(image, rect);

    vconcat(B, A, Vab);
    vconcat(D, C, Vcd);
    hconcat(Vcd, Vab, image);
    imshow("janela", image);
    imwrite("regioestrocadas.png", image);
    waitKey();

    return 0;
}

A seguir estão exemplos de imagens de entrada e saída do código acima:

Entrada do programa
Figure 5. Entrada do programa
Saída do programa
Figure 6. Saída com regiões trocadas

.

1.3. Preenchendo Regiões

Este algoritmo tem a função de contar as bolhas que possuem buracos e as que não possuem buracos, eliminando as que tocam as bordas.

Entrada do programa
Figure 7. Entrada do programa

Primeiramente, a exclusão dos objetos que tocam as bordas foi realizada percorrendo-se as primeiras e últimas linhas e colunas da imagem a procura de objetos e, em seguida, preecehendo-os com o valor de fundo usando a função floodFill().

Entrada sem borda
Figure 8. Entrada sem objetos de borda

Assim, pega-se o ponto (0.0) da imagem e aplica o algoritmo floodfill neste ponto com a cor de background que será utilizada para preencher todo o fundo da imagem. Como é suposto que os objetos são aréas com cor em grayscale de 255 e os buracos são da mesma cor do fundo antigo, 0, resta então contabilizar quantos objetos existem e quantos deles possuem buracos, seguindo o algoritmo descrito abaixo:

  • Caso encotre um buraco com um objeto associado, contabilize o objeto com buraco e preencha o objeto com a nova cor de fundo, por fim, preecher o buraco achado com a cor de fundo.

  • Caso encontre um buraco sem objeto associado, preencha-o com a nova cor de fundo.

  • Por fim, contabilize os objetos restantes, preenchendo-os com a cor de fundo.

Este processo pode ser visualizado nas figuras abaixo:

Novo background
Figure 9. Imagem com novo fundo
Sem buracos
Figure 10. Imagem sem buracos
Saida do programa
Figure 11. Saida do programa

.

O código implementado, rotulacao.cpp, juntamente da execução do programa estão apresentados abaixo:

Listing 4. Execução do programa
$ make rotulacao
$ ./rotulacao bolhas.png
nholes = 7
nobjects = 14
Listing 5. rotulacao.cpp
#include <iostream>
#include <stdio.h>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

#define BKGROUND 140

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

    nobjects = nholes = 0;

    CvPoint p;
    image = imread(argv[1], CV_LOAD_IMAGE_GRAYSCALE);

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

    p.x = 0;
    p.y = 0;

    // busca objetos na borda de cima e de baixo
    for (int col = 0; col < width; col++)
    {
        if (image.at<uchar>(0, col) == 255)
        {
            // achou um objeto
            p.x = col;
            p.y = 0;
            floodFill(image, p, 0);
        }
        if (image.at<uchar>(height - 1, col) == 255)
        {
            // achou um objeto
            p.x = col;
            p.y = height - 1;
            floodFill(image, p, 0);
        }
    }
    // busca objetos na bordas da esq e dir
    for (int row = 0; row < height; row++)
    {
        if (image.at<uchar>(row, 0) == 255)
        {
            // achou um objeto
            p.x = 0;
            p.y = row;
            floodFill(image, p, 0);
        }
        if (image.at<uchar>(row, width - 1) == 255)
        {
            // achou um objeto
            p.x = width - 1;
            p.y = row;
            floodFill(image, p, 0);
        }
    }
    imshow("image", image);
    imwrite("bolhas_borda.png", image);
    waitKey();
    // mudar o fundo
    floodFill(image, cvPoint(0, 0), BKGROUND);
    imshow("image", image);
    imwrite("bolhas_fundo.png", image);
    waitKey();
    // encontrando buracos
    for (int row = 0; row < height; row++)
    {
        for (int col = 0; col < width; col++)
        {
            if (image.at<uchar>(row, col) == 0)
            {
                if (image.at<uchar>(row, col - 1) == 255)
                {
                    // achou um buraco
                    nholes++;
                    p.x = col - 1;
                    p.y = row;
                    floodFill(image, p, BKGROUND);
                }
                p.x = col;
                p.y = row;
                floodFill(image, p, BKGROUND);
            }
        }
    }
    imshow("image", image);
    imwrite("bolhas_semburacos.png", image);
    waitKey();
    // encontrando regioes
    for (int row = 1; row < height; row++)
    {
        for (int col = 1; col < width; col++)
        {
            if (image.at<uchar>(row, col) == 255)
            {
                nobjects++;
                p.x = col;
                p.y = row;
                floodFill(image, p, BKGROUND);
            }
        }
    }
    imshow("image", image);
    imwrite("bolhas_result.png", image);
    cout << "nholes = " << nholes << endl;
    cout << "nobjects = " << nobjects << endl;
    waitKey();
    return 0;
}

1.4. Equalização de Histogramas

"Um histograma é uma contagem de dados onde se organiza as ocorrências por faixas de valores predefinidos. Em se tratando de imagens digitais em tons de cinza, por exemplo, costuma-se associar um histograma com a contagem de ocorrências de cada um dos possíveis tons em uma imagem. A grosso modo, o histograma oferece uma estimativa da probabilidade de ocorrência dos tons de cinza na imagem."[1]

O que a equalização de um histograma faz é, basicamente, estender o intervalo dessas probabilidades.

Para tanto, o código equalize.cpp foi criado para implementar este conceito para uma imagem em escala de cinza, mostrando tanto seu histograma e imagem em modo normal quanto ambos equalizados.

Listing 6. equalize.cpp
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main(int argc, char **argv)
{
    int nbins = 64;
    float range[] = {0, 256};
    const float *histrange = {range};
    bool uniform = true;
    bool acummulate = false;
    Mat hist, histEq;

    VideoCapture cap;

    cap.open(0);
    if (!cap.isOpened())
    {
        cout << "Erro ao acessar a camera!\n";
        return -1;
    }

    // Calcula a largura e altura dos framas capturados
    int width = cap.get(CV_CAP_PROP_FRAME_WIDTH),
        height = cap.get(CV_CAP_PROP_FRAME_HEIGHT);

    cout << "Width: " << width << endl
         << "Height: " << height << endl;

    while (true)
    {
        Mat img, imgEq;
        cap >> img;

        // Inverte a imagem
        flip(img, img, 1);

        // Tranforma a imagem capturada para grayscale
        cvtColor(img, img, CV_BGR2GRAY);

        // Equaliza o histograma
        equalizeHist(img, imgEq);

        // Calcula o histograma
        calcHist(&img, 1, 0, Mat(), hist, 1,
                 &nbins, &histrange,
                 uniform, acummulate);
        calcHist(&imgEq, 1, 0, Mat(), histEq, 1,
                 &nbins, &histrange,
                 uniform, acummulate);

        int histw = nbins, histh = nbins / 2;
        Mat histImg(histh, histw, CV_8UC1, Scalar(0, 0, 0));
        Mat histImgEq(histh, histw, CV_8UC1, Scalar(0, 0, 0));

        // Normaliza o histograma para aparecer nas imagens
        normalize(hist, hist, 0, histImg.rows, NORM_MINMAX, -1, Mat());
        normalize(histEq, histEq, 0, histImgEq.rows, NORM_MINMAX, -1, Mat());

        for (int i = 0; i < nbins; i++)
        {
            line(histImg,
                 Point(i, histh),
                 Point(i, histh - cvRound(hist.at<float>(i))),
                 Scalar(255, 255, 255), 1, 8, 0);
            line(histImgEq,
                 Point(i, histh),
                 Point(i, histh - cvRound(histEq.at<float>(i))),
                 Scalar(255, 255, 255), 1, 8, 0);
        }

        histImg.copyTo(img(Rect(0, 0, nbins, histh)));
        histImgEq.copyTo(imgEq(Rect(0, 0, nbins, histh)));
        imshow("Equalized Histogram", imgEq);
        imshow("Normal", img);
        if (waitKey(30) == 'a')
            break;
    }

    return 0;
}

Estre progrmam tem saídas como as seguintes:

Low light histogram
Figure 12. Saida com luz normal
High light histogram
Figure 13. Saida com muita luz

.

1.5. Detector de Movimento

Com o conceito de histograma mais fixado, foi implementado, então um programa detector de movimento utilizando esses conhecimentos.

Como o histograma é um tipo de descrição para uma imagem, a sua comparação com outras pode identificar a similaridade entre elas. Seguindo esse conceito, o código motiondetector.cpp implementa essa comparação entre frames de um vídeo e caso esta comparação seja maior que um limite pré-definido, um quadro branco aparecerá no vídeo e o console mostrará a quantidade de frames nos quais algum movimento foi detectado e qual foi o valor de comparação entre o retorno da a função compareHist(currentHist, previousHist, 1) subtraída do limite definido.

Listing 7. motiondetector.cpp
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main(int argc, char **argv)
{
    VideoCapture cap;

    cap.open(0);
    if (!cap.isOpened())
    {
        cout << "Erro ao acessar a camera!\n";
        return -1;
    }

    // Calcula a largura e altura dos framas capturados
    int width = cap.get(CV_CAP_PROP_FRAME_WIDTH),
        height = cap.get(CV_CAP_PROP_FRAME_HEIGHT);

    // Treshold de detecção de movimento
    double threshold = 30;
    int a = 0;

    cout << "Width: " << width << endl
         << "Height: " << height << endl
         << "Threshold =" << threshold << endl;
    // Exibição do histograma
    int nbins = 64;
    float range[] = {0, 256};
    const float *histRange = {range};
    Mat previousHist, currentHist;

    // Exibição da imagem
    Mat img;

    // Primeiro quadro a ser guardado
    cap >> img;
    flip(img, img, 1);
    cvtColor(img, img, CV_BGR2GRAY);
    calcHist(&img, 1, 0, Mat(), previousHist, 1, &nbins, &histRange);
    normalize(previousHist, previousHist, 0, previousHist.rows, NORM_MINMAX, -1, Mat());

    while (true)
    {
        cap >> img;

        // Inverte a imagem
        flip(img, img, 1);

        // Tranforma a imagem capturada para grayscale
        cvtColor(img, img, CV_BGR2GRAY);

        // Calcula o histograma
        calcHist(&img, 1, 0, Mat(), currentHist, 1, &nbins, &histRange);

        // Normaliza o histograma
        normalize(currentHist, currentHist, 0, currentHist.rows, NORM_MINMAX, -1, Mat());

        // Comparação de histogramas
        double correlation = compareHist(currentHist, previousHist, 1);

        if ((correlation - threshold) > threshold)
        {
            Mat flag(64, 64, CV_8UC1, 255);
            flag.copyTo(img(Rect(0, 0, 64, 64)));
            cout << a << " " << (correlation - threshold) << endl;
            a++;
        }

        currentHist.copyTo(previousHist);

        imshow("Motion Detector", img);
        if (waitKey(30) == 'a')
            break;
    }

    return 0;
}

1.6. Filtragem espacial I

O seguinte programa laplgauss.cpp permite que seja calculado o Laplaciano do Gaussiano (LoG) das imagens capturadas. Para tanto, foi criada uma opção que permite a aplicação da máscara gaussiana, seguida da laplaciana (3x3). Além disto, foi adicionado também a máscara do LoG (5x5). Para efeito de comparação, o programa implementado dispõe das opções:

  • Laplaciano 3x3

  • Laplaciano do Gaussiano 3x3

  • Laplaciano do Gaussiano 5x5

Listing 8. 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 correspondente para ativar o filtro: \n"
            "a - imagem original\n"
            "l - laplaciano 3x3 sem gauss\n"
            "g - laplaciano do gaussiano 3x3\n"
            "5 - laplaciano do gaussiano 5x5\n"
            "esc - sair\n";
}

int main(int argvc, char** argv) {
    VideoCapture video;
    // Masks
    Mat mgauss, mlaplacian, mlog, mask, mask1;
    float id[] = {0, 0, 0, 0, 1, 0, 0, 0, 0};
    float gauss[] = {1, 2, 1, 2, 4, 2, 1, 2, 1};
    float laplacian[] = {0, -1, 0, -1, 4, -1, 0, -1, 0};
    float lapofgau[] = {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 result, result1;
    double width, height;

    mask = mask1 = Mat(3, 3, CV_32F, id);
    mgauss = Mat(3, 3, CV_32F, gauss) / (float)(16);
    mlaplacian = Mat(3, 3, CV_32F, laplacian);
    mlog = Mat(5, 5, CV_32F, lapofgau);

    video.open(0);
    if (!video.isOpened()) return -1;

    width = video.get(CV_CAP_PROP_FRAME_WIDTH);
    height = video.get(CV_CAP_PROP_FRAME_HEIGHT);
    cout << "Largura = " << width << "\n";
    cout << "Altura = " << height << "\n";

    namedWindow("filtroespacial", 1);
    menu();

    while (true) {
        video >> cap;
        cvtColor(cap, frame, CV_BGR2GRAY);
        flip(frame, frame, 1);
        imshow("original", frame);

        frame.convertTo(frame32f, CV_32F);
        filter2D(frame32f, frameFiltered, -1, mask, Point(-1, -1), 0,
                 BORDER_DEFAULT);
        filter2D(frameFiltered, frameFiltered, -1, mask1, Point(-1, -1), 0,
                 BORDER_DEFAULT);
        frameFiltered.convertTo(result, CV_8U);
        imshow("filtroespacial", result);

        char key = (char)waitKey(30);
        if (key == 27) break;
        switch (key) {
            case 'a':
                menu();
                mask = mask1 = Mat(3, 3, CV_32F, id);
                printmask(mask);
                break;
            case 'l':
                menu();
                mask = mlaplacian;
                mask1 = Mat(3, 3, CV_32F, id);
                printmask(mask);
                break;
            case 'g':
                menu();
                mask = mgauss;
                printmask(mask);
                mask1 = mlaplacian;
                printmask(mask1);
                break;
            case '5':
                menu();
                mask = mgauss;
                mask1 = mlog;
                printmask(mask1);
                break;
            default:
                break;
        }
    }
    return 0;
}

Estre progrmam tem saídas como as seguintes:

Laplaciano puro
Figure 14. Saida laplaciano puro (3x3)
Laplaciano do gaussiano
Figure 15. Saida laplaciano do gaussiano
Laplaciano do gaussiano 5x5
Figure 16. Saida laplaciano do gaussiano (5x5)

.

É interessante notar que a máscara do LoG 5x5 produz uma definição de bordas bastante significativa em comparação com as máscaras 3x3, tanto que bordas suaves como as de uma tatuagem aparecem nítidamente nesta máscara.

1.6. Filtragem espacial II

A ativadade propõe a criação de uma interface que proporcione ao usuário aplicar o efeito Tilf shift em imgaens, para tal criada duas imagens a partir da imagem de entrada, uma usada apenas para a multiplicação ponderada e outra usada para armazenar o borramento da imagem de entrada. Ao final é feita a a multiplicação ponderada dos pixels seguindo o controle de desfoque feito manualmente a partir de sliders presentes na interface.

O seguinte programa tiltshift.cpp permite que seja calculado os pesos para que seja reproduzido o efeito de Tilt Shift.

Listing 9. tiltshift.cpp
#include <iostream>
#include <opencv2/opencv.hpp>
#include <math.h>

using namespace cv;
using namespace std;

// GLOBAL VARIABLES

int blurValue = 7 ;
int blur_slider = 0;
int blur_slider_max = 10;

int MAX = 100;
double ponderacao = 6;
int ponderacao_slider = 0;

int top_slider = 0;
int posicao_vertical = 0;

int tamanho_faixa = 20;
int altura_slider = 0;

Mat img1, img2, alpha, beta1;
int height;

char TrackbarName[50];

// FUNCTIONS

void refresh(){
    Mat srcImg, blurImg, tiltshiftImg;
    img1.convertTo(srcImg, CV_32FC3);
    img2.convertTo(blurImg, CV_32FC3);
    multiply(srcImg, alpha, srcImg);
    multiply(blurImg, beta1, blurImg);
    add(srcImg, blurImg, tiltshiftImg);
    tiltshiftImg.convertTo(tiltshiftImg, CV_8UC3);
    imshow("tiltshift", tiltshiftImg);
    imwrite("tiltshift.jpg", tiltshiftImg);
}

void blur(int blurValue){
    Mat aux, mask;
    float media[] = {1,1,1,
                     1,1,1,
                     1,1,1};
    // Creates the Mask matrix
    mask = Mat(3, 3, CV_32F, media);
    scaleAdd(mask, 1/9.0, Mat::zeros(3,3,CV_32F), mask);
    // "img1" is used otherwise the blur will always increase
    img1.convertTo(aux, CV_32F);
    for (int i = 0; i < blurValue; i++) {
        filter2D(aux, aux, aux.depth(), mask, Point(1, 1), 0);
    }
    aux=abs(aux);
    aux.convertTo(img2, CV_8UC3);
}

void tiltShiftCalc() {
    int l1 = -tamanho_faixa/2;
    int l2 = -l1;

    alpha = Mat::zeros(img1.rows, img1.cols, CV_32F);
    beta1 = Mat::zeros(img1.rows, img1.cols, CV_32F);
    int i, j;
    for (i = 0; i < alpha.rows; i++) {
        int x = i - (posicao_vertical + tamanho_faixa/2);
        float alphaValue = 0.5f * (tanh((x - l1)/ponderacao)
                                    - tanh((x - l2)/ponderacao));
        for (j = 0; j < alpha.cols; j++) {
            alpha.at<float>(i, j) = alphaValue;
            beta1.at<float>(i, j) = 1 - alphaValue;
        }
    }
    Mat auxA[] = {alpha, alpha, alpha};
    Mat auxB[] = {beta1, beta1, beta1};
    merge(auxA, 3, alpha);
    merge(auxB, 3, beta1);
    // imshow("alpha", alpha);
    // imshow("beta", beta1);
    refresh();
}

// TRACKBARS

void on_trackbar_blur(int, void*){
    blurValue = blur_slider;
    // Blurs image
    blur(blurValue);
    // Updates window
    refresh();
}

void on_trackbar_ponderacao(int, void*){
    ponderacao = (double) ponderacao_slider;
    if (ponderacao < 1) {
        ponderacao = 1;
    }
    tiltShiftCalc();
}

void on_trackbar_posicaoVertical(int, void*){
    posicao_vertical = top_slider*height/MAX;
    tiltShiftCalc();
}

void on_trackbar_altura_regiao(int, void*) {
    tamanho_faixa = altura_slider*height/MAX;
    if (tamanho_faixa == 0) {
        tamanho_faixa = 1;
    }

    if (tamanho_faixa > height) {
        tamanho_faixa = height;
    }
    tiltShiftCalc();
}

// MAIN

int main(int argvc, char** argv){
    img1 = imread(argv[1], IMREAD_COLOR);
    height = img1.size().height;
    img2 = img1.clone();
    img2.convertTo(img2, CV_8UC3);

    blur(7);

    namedWindow("tiltshift", 1);

    // sprintf( TrackbarName, "Blur Value");
    // createTrackbar( TrackbarName, "Blur",
    //                 &blur_slider,
    //                 blur_slider_max,
    //                 on_trackbar_blur);
    // on_trackbar_blur(blur_slider, 0);
    sprintf( TrackbarName, "Posição Vertical");
    createTrackbar( TrackbarName, "tiltshift",
                    &top_slider,
                    MAX,
                    on_trackbar_posicaoVertical );
    sprintf( TrackbarName, "Altura da região");
    createTrackbar( TrackbarName, "tiltshift",
                    &altura_slider,
                    MAX,
                    on_trackbar_altura_regiao);
    sprintf( TrackbarName, "Ponderação");
    createTrackbar( TrackbarName, "tiltshift",
                    &ponderacao_slider,
                    MAX,
                    on_trackbar_ponderacao);
    tiltShiftCalc();
    waitKey(0);

    return 0;
}

Estre progrmam tem a execução como se segue:

Entrada do programa
Figure 17. Entrada do Programa
Saída do Programa
Figure 18. Saida esperada, após mudanças manuais

.

O conceito de do programa tiltshift.cpp também pode ser aplicado à videos, assim o programa tiltshift.cpp foi criado, mudando somente a função main.cpp :

Listing 10. tiltshiftvideo.cpp
// MAIN

int main(int argvc, char** argv){
    VideoCapture video;
    video.open(argv[1]);

    if(!video.isOpened()){
        cout << "Error opening video stream or file" << endl;
        return -1;
    }

    height = video.get(CV_CAP_PROP_FRAME_HEIGHT);
    cout << "Altura = " << height << "\n";

    namedWindow("tiltshift", 1);

    sprintf( TrackbarName, "Posição Vertical");
    createTrackbar( TrackbarName, "tiltshift",
                    &top_slider,
                    MAX,
                    on_trackbar_posicaoVertical );
    sprintf( TrackbarName, "Altura da região");
    createTrackbar( TrackbarName, "tiltshift",
                    &altura_slider,
                    MAX,
                    on_trackbar_altura_regiao);
    sprintf( TrackbarName, "Ponderação");
    createTrackbar( TrackbarName, "tiltshift",
                    &ponderacao_slider,
                    MAX,
                    on_trackbar_ponderacao);

    int frameCount = 0;
    Mat discardFrame;
    while(true){
        video >> discardFrame;

        if (discardFrame.empty()){
            video.release();
            video.open(argv[1]);
            if(!video.isOpened()){
            cout << "Error opening video stream or file" << endl;
            return -1;
            }
            frameCount = 0;
            continue;
        }

        frameCount++;

        if(frameCount%2 != 0){
            img1 = discardFrame.clone();
            img2 = img1.clone();

            blur(7);
            tiltShiftCalc();
        }

        char key = (char)waitKey(30);
        if (key == 27) break;
    }

    // When everything done, release the video capture object
    video.release();

    // Closes all the frames
    destroyAllWindows();
    return 0;
}

2. Segunda unidade

Neste tópico estão descritos as atividades realizadas referentes a segunda unidade da disciplina DCA0445. Para a compilação dos códigos referentes à esta unidade, é necessário possuir, no mesmo diretório do arquivo .cpp o arquivo Makefile, e executar os seguintes comandos no seu console:

Listing 11. Makefile
$ make <nome_arquivo>
$ ./<nome_arquivo> <caminho_para_imagem>

2.1. Filtragem no domínio da Frequência

O exercíco propõe a criação de um programa que auxilie no melhoramento de imagens com iluminação irregular utilizando o filtro homomórfico, para tal foi criada uma interface com sliders para ajustar os paramentros do filtro e são mostradas em janelas diferentes a imagem com a aplicação do filtro, a imagem original e o filtro que está sendo aplicado.

O programa homomorfico.cpp, está descrito abaixo:

Listing 12. homomorfico.cpp
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

void deslocaDFT(Mat& image ){
  Mat tmp, A, B, C, D;
  image = image(Rect(0, 0, image.cols & -2, image.rows & -2));
  int cx = image.cols/2;
  int cy = image.rows/2;
  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.copyTo(tmp);  D.copyTo(A);  tmp.copyTo(D);
  C.copyTo(tmp);  B.copyTo(C);  tmp.copyTo(B);
}

int gammaL_slider = 3, gammaH_slider = 20, sharpC_slider = 1, cutoff_slider = 5;
const int gammaL_max = 10, gammaH_max = 50, sharpC_max = 100, cutoff_max = 200;
int gammaL, gammaH, sharpC, cutoff;
Mat image, imageFiltered,padded;
int dft_M, dft_N;

Mat homomorphicFilter(double gl, double gh, double c, double d0){
  Mat filter = Mat(padded.size(), CV_32FC2, Scalar(0));
  Mat tmp = Mat(dft_M, dft_N, CV_32F);

  for(int i=0; i<dft_M; i++){
    for(int j=0; j<dft_N; j++){
      tmp.at<float> (i,j) = (gh - gl)*(1 - exp(-c*(( (i-dft_M/2)*(i-dft_M/2) + (j-dft_N/2)*(j-dft_N/2) ) / (d0*d0) ))) + gl;
    }
  }

  Mat comps[]= {tmp,tmp};
  imshow("Filter", tmp);
  merge(comps, 2, filter);
  return filter;
}

void applyFilter(void){
  vector<Mat> planos; planos.clear();
  Mat zeros = Mat_<float>::zeros(padded.size());
  Mat realInput = Mat_<float>(padded);
  Mat complex;
  realInput += Scalar::all(1);
  log(realInput,realInput);
  planos.push_back(realInput);
  planos.push_back(zeros);
  merge(planos, complex);

  dft(complex, complex);
  deslocaDFT(complex);
  resize(complex,complex,padded.size());
  normalize(complex,complex,0,1,CV_MINMAX);

  Mat filter = homomorphicFilter(gammaL,gammaH,sharpC,cutoff);

  mulSpectrums(complex,filter,complex,0);
  deslocaDFT(complex);
  idft(complex, complex);

  planos.clear();
  split(complex, planos);
  exp(planos[0],planos[0]);
  normalize(planos[0], planos[0], 0, 1, CV_MINMAX);
  imageFiltered = planos[0].clone();
}

void on_trackbar(int, void*){
  gammaL = (double) gammaL_slider/10;
  gammaH = (double) gammaH_slider/10;
  sharpC = (double) sharpC_slider;
  cutoff = (double) cutoff_slider;
  applyFilter();
  imshow("Homomorphic",imageFiltered);
}

int main(int argc, char** argv){
  if(argc < 2) {
    cout <<  "Argumentos faltando!\nUso: ./homorfico <caminho_para_imagem>\n";
    return -1;
  }

  image = imread(argv[1], CV_LOAD_IMAGE_GRAYSCALE);

  if(!image.data) {
    cout<<"Erro ao abrir a imagem!\n";
    return -1;
  }

  namedWindow("Homomorphic", WINDOW_KEEPRATIO);
  namedWindow("Original", WINDOW_KEEPRATIO);
  namedWindow("Filter", WINDOW_KEEPRATIO);

  imshow("Original",image);

  dft_M = getOptimalDFTSize(image.rows);
  dft_N = getOptimalDFTSize(image.cols);

  copyMakeBorder(image, padded, 0, dft_M - image.rows, 0, dft_N - image.cols, BORDER_CONSTANT, Scalar::all(0));

  imageFiltered = padded.clone();

  cout << "Original: " << image.rows << "x" << image.cols << endl;
  cout << "Padded: "   << padded.rows << "x" << padded.cols << endl;

  char TrackbarName[50];

  sprintf( TrackbarName, "Gamma L x %d", gammaL_max );
  createTrackbar( TrackbarName, "Homomorphic", &gammaL_slider, gammaL_max, on_trackbar);

  sprintf( TrackbarName, "Gamma H x %d", gammaH_max );
  createTrackbar( TrackbarName, "Homomorphic", &gammaH_slider, gammaH_max, on_trackbar);

  sprintf( TrackbarName, "C x %d", sharpC_max );
  createTrackbar( TrackbarName, "Homomorphic", &sharpC_slider, sharpC_max, on_trackbar);

  sprintf( TrackbarName, "Cutoff Frequency x %d", cutoff_max );
  createTrackbar( TrackbarName, "Homomorphic", &cutoff_slider, cutoff_max, on_trackbar);

  waitKey(0);
  return 0;
}
Exemplo do programa
Figure 19. Execução do Programa

.

2.2. Detecção de bordas com o algoritmo de Canny

O exercício solicita a implementação de um programa para usar as bordas produzidas pelo algoritmo de Canny para melhorar a qualidade da imagem pontilhista gerada, para tal utiliza-se a posição dos pixels de borda encontrados pelo algoritmo de Canny para desenhar pontos nos respectivos locais na imagem gerada através do programa cannyPoints.cpp:

Listing 13. cannyPoints.cpp
#include <iostream>
#include <vector>
#include <opencv2/opencv.hpp>

#define RAIO 3

using namespace std;
using namespace cv;

int canny_slider = 15;
int canny_slider_max = 200;

char TrackbarName[50];

Mat image, edges, points;

void getPointillisticImage() {
    // Cria variaveis para as dimensoes da imagem
    int height = image.size().height, width = image.size().width;

    // Cria as matrizes para as bordas da imagem e para a imagem de pontos
    points = Mat(height, width, CV_8UC3, Scalar(255, 255, 255));

    Canny(image, edges, canny_slider, 3 * canny_slider);

    // Percorre a matriz com as bordas e preenche um vetor com as coordenadas
    // dos pixels de valor 255
    vector<vector<int>> edgesCoordinates;

    for(int i = 0; i < height; i++) {
        for(int j = 0; j < width; j++) {
            if(edges.at<uchar>(i, j) == 255) {
                edgesCoordinates.push_back({i, j});
            }
        }
    }

    // Embaralha as coordenas onde os circulos serão desenhados
    // para aumentar a autenticidade do resultado
    random_shuffle(edgesCoordinates.begin(), edgesCoordinates.end());

    // Percorre as coordenadas e desenha em uma nova matrix os pontos
    // com cor igual a cor da imagem na posição correspodente
    for(auto coordinate : edgesCoordinates) {
        Vec3b color = image.at<Vec3b>(coordinate[0], coordinate[1]);

        circle(points, Point(coordinate[1], coordinate[0]), RAIO, color, -1, CV_AA);
    }

}

void on_trackbar_canny(int, void*){
    getPointillisticImage();

    imshow("Canny Points", points);
}

int main(int argc, char**argv) {
    if(argc < 2) {
        cout << "Argumentos faltando!\nUso: ./cannyPoints <caminho_para_imagem>\n";
        return -1;
    }

    // Carrega a imagem
    image = imread(argv[1], CV_LOAD_IMAGE_COLOR);

    if(!image.data) {
        cout << "Error ao carregar a imagem!\n";
        return -1;
    }

    sprintf(TrackbarName, "Threshold", canny_slider_max);

    namedWindow("Canny Points", WINDOW_KEEPRATIO);

    createTrackbar(
        TrackbarName,
        "Canny Points",
        &canny_slider,
        canny_slider_max,
        on_trackbar_canny
    );

    on_trackbar_canny(canny_slider, 0);

    waitKey(0);
    imwrite("canny.png", points);
    return 0;
}

As entradas e execução do programa estão exemplificadas a seguir:

Entrada do programa
Figure 20. Entrada do Programa
Saída do Programa
Figure 21. Saida esperada, após mudanças manuais

1. Texto original do site Introdução ao processamento digital de imagens com OpenCV