Programas/atividades desenvolvidas para a disciplina DCA0445 - Processamento Digital de Imagens, do curso de Engenharia de Computação da Universidade Federal do Rio grande do Norte UFRN

Prefácio

Todos os programas, neste documento, foram desenvolvidas em C++ utilizando-se da biblioteca OpenCV e em ambiente Linux. Para compilar qualquer programa presente neste documento, pode-se fazer uso deste Makefile, coloca o Makefile na mesma pasta do código fonte, extensão .cpp, e execute via terminal o comando make <nome_do_programa>. Todos os códigos encontram-se no Repositório do github.

1. Programa Region

Este programa consiste em negativar uma certa região dentro de uma imagem, delimitada por um retângulo informado pelo usuário. o programa varre a área correspondente na imagem e troca os valores dos pixels para seus inversos, ou seja 255 - valor_atual.

Compilando e Executando.

$ make region
$ ./region <caminho_para_a_imagem>

O código fonte completo se encontra aqui region.cpp.

void region(Mat &img, CvPoint *p)
{
  for(unsigned int i = p[0].x; i < p[1].x; i++)
    for(unsigned int j = p[0].y; j < p[1].y; j++)
      img.at<uint8_t>(i,j) = 255 - img.at<uint8_t>(i,j);
}
toto mini
Figura 1. Entrada do programa Region
region result
Figura 2. Saída do programa Region

2. Troca Regiões

O usuário deve passar uma imagem qualquer, e o programa passara para escala de cinza e particionara a imagem em 4(quatro) partes simétricas e realizara a troca na diagonal dessas quatro subimagens.

Para este programa uma imagem foi pensada sendo composta por 4 regiões da seguinte forma:

A

B

C

D

Compilando e Executando.

$ make trocaregioes
$ ./trocaregioes <caminho_para_a_imagem>

Código completo em trocaregioes.cpp

int main(int argc, char** argv){
  Mat img;
  Mat result;
  int w, h;

  img = imread(argv[1],CV_LOAD_IMAGE_GRAYSCALE);
  //if fail to read the image
  if ( img.empty() )
  {
       cout << "Error loading the image" << endl;
       return -1;
  }

  w = img.size().width;
  h = img.size().height;
  result = img.clone();

  img(cv::Rect(0,0, w/2, h/2)).copyTo(result(cv::Rect((w-1)/2, (h-1)/2, w/2, h/2)));  (1)
  img(cv::Rect((w-1)/2, 0, w/2, h/2)).copyTo(result(cv::Rect(0, (h-1)/2, w/2, h/2))); (2)
  img(cv::Rect(0, (h-1)/2, w/2, h/2)).copyTo(result(cv::Rect((w-1)/2, 0, w/2, h/2))); (3)
  img(cv::Rect((w-1)/2, (h-1)/2, w/2, h/2)).copyTo(result(cv::Rect(0, 0, w/2, h/2))); (4)

  imshow("Original", img);
  imshow("Resultado", result);
  waitKey();

  imwrite("resultados/trocaRegiao_resultado.png", result);

  return 0;
}
1 Sobrepoe A da img original em D de result
2 Sobrepoe B da img original em C de result
3 Sobrepoe C da img original em B de result
4 Sobrepoe D da img original em A de result

Resultado

D

C

B

A

toto mini
Figura 3. Entrada do programa trocaregioes
trocaRegiao resultado
Figura 4. Saída do programa trocaregioes

3. Conta Bolhas

Este programa consiste em contar o número de regiões brancas puras, com e sem "buracos", o fundo da imagem deve ser puramente preto e os objetos puramente brancos, o programa foi testado utilizando a imagem bolhas.png. Mas o mesmo deve funcionar para qualquer imagem que siga o padrão especificado a cima.

bolhas
Figura 5. Bolhas.png

O algoritmo consiste em 4 passos bem definidos. O código completo se encontra neste link: contaregioes.cpp.

  • Passo 1- Remover objetos das bordas

//remove da borda superior e inferior
for(int i = 0; i < width; i++){
  if(image.at<uint8_t>(0, i) == OBJ_COLOR)
    floodFill(image, CvPoint(i, 0), BACK_COLOR);
  if(image.at<uint8_t>(height-1,i) == OBJ_COLOR)
    floodFill(image, CvPoint(i, height-1), BACK_COLOR);
}

//remove das laterais
for(int i = 0; i < height; i++){
  //lateral esquerda
  if(image.at<uint8_t>(i, 0) == OBJ_COLOR)
    floodFill(image, CvPoint(0, i), BACK_COLOR);
  //lateral direita
  if(image.at<uint8_t>(i, width-1) == OBJ_COLOR)
    floodFill(image, CvPoint(width-1, i), BACK_COLOR);
}
  • Passo 2- Contar bolhas com buraco

//troca o background, para facilitar a identificar os buracos das bolhas
floodFill(image, CvPoint(0,0), NEW_BACK_COLOR);
for(int i = 0; i < height; i++)
  for(int j = 0; j < width; j++)
  {
    //identifica uma bolha com buraco
    if(image.at<uint8_t>(i,j) == BACK_COLOR && image.at<uint8_t>(i,j-1) == OBJ_COLOR){
      //soma um no numero de bolhas e "apaga" a bolha encontrada
      nbolhas_com_buracos++;
      floodFill(image, CvPoint(j-1, i), NEW_BACK_COLOR);
    }
  }
  • Passo 3- Contar bolhas sem buracos

//conta bolhas sem buracos
for(int i = 0; i < height; i++)
  for(int j = 0; j < width; j++)
  {
    //identifica uma bola
    if(image.at<uint8_t>(i,j) == OBJ_COLOR){
      //soma um no numero de bolhas e "apaga" a bolha encontrada
      nbolhas_sem_buracos++;
      floodFill(image, CvPoint(j, i), NEW_BACK_COLOR);
    }
  }

Resultado

result
Figura 6. Gif das etapas (gerado com o imagemagick)
conta bolhas resultado
Figura 7. Resultado da contagem.

4. Histograma

Breve descrição do que seja um histograma…​

4.1. Equalização de Histograma

Implementação de um Equalizador de Histograma para imagens em tons de cinza.

Algoritmo de equalização, para imagens em tons de cinza:

  1. Calcular Histograma: \(h(r_k), k \in [0,255\)];

  2. Calcular Histograma Acumulado: \(ha(r_k) = \sum{h(r_j)}, j \in [0,255\)];

  3. Normalizar o Histograma Acumulado, na faixa de [0, 255]: \(ha(r_k) = ha(r_k)/ha(r_255)\);

  4. Transformar a imagem: \(f(x,y) = ha(f(x,y))\).

Antes e Depois da equalização do histograma.

equalize input
Figura 8. Entrada do programa equalize
equalize output
Figura 9. Resultado do programa equalize

Download do código completo: equalize.cpp

Clique aqui pra ver o código completo

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

using namespace cv;
using namespace std;

int main(int argc, char** argv){
  Mat frame;
  Mat hist;
  VideoCapture cap;
  uint8_t histEq[256];

  int histsize = 256;
  int sum;
  float range[] = {0, 256};
  const float *histrange = { range };

  cap.open(0);

  if(!cap.isOpened()){
    cout << "cameras indisponiveis\n";
    return -1;
  }

  std::cout << "Pressione qualquer tecla para encerrar o programa." << '\n';
  while(1){
    cap >> frame;

    cvtColor(frame, frame, CV_BGR2GRAY);
    imshow("Original", frame);
    //Calculo do histograma
    calcHist(&frame, 1, 0, Mat(), hist, 1, &histsize, &histrange);


    /*calculo do histograma acumulado */
    /*e normalizacao do histograma acumulado*/
    /**
     * Calcula o vetor que ira realizar a transformacao nos valores dos pixels
     */
    sum = 0;
    for(int i = 0; i < histsize; i++)
    {
      sum+= hist.at<float>(i);
      histEq[i] = sum*255.0/frame.total();
    }

    //substituicao dos valores dos pixels
    for(int i = 0; i < frame.size().height; i++)
      for(int j = 0; j < frame.size().width; j++)
        frame.at<uint8_t>(i,j) = histEq[frame.at<uint8_t>(i,j)];
    imshow("Equalizado", frame);

    if(waitKey(10) != 255)break;
  }

  return 0;
}

4.2. Detector de Movimento

Utilizando comparação de histogramas entre frames consecutivos, comparando-o por calculo da correlação(usando função do OpenCV, compareHist), para identificar ocorrência de movimento, para isso foi estabelecido, de forma empírica, um limiar para a correlação, ao se identificar um valor de correlação abaixo do limiar, um circulo verde é desenhado no canto superior direito da imagem, indicando uma detecção de movimento.

Makefile utilizado para compilar o programa motiondetector, é diferente pois inclui a capacidade de gerar gifs.

Motiondetector
Figura 10. Resultado do Motiondetector.

Download do código fonte: Motiondetector.cpp.

Clique aqui pra ver o código completo

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

#include <GraphicsMagick/Magick++.h>

using namespace cv;
using namespace std;

#define NUM_FRAMES_GIF 7
#define GIF_DELAY 100 //ms

int main(int argc, char** argv){
  Magick::InitializeMagick(NULL);
  vector<Magick::Image> gifFrames(NUM_FRAMES_GIF);

  Mat frame, grayFrame;
  Mat H1, H2;
  VideoCapture cap;
  int i = 0;
  double r_correl = 0;

  int histsize = 256;
  float range[] = {0, 256};
  const float *histrange = { range };

  cap.open(0);

  if(!cap.isOpened()){
    cout << "cameras indisponiveis";
    return -1;
  }

  cap >> frame; //captura um frame
  cvtColor(frame, frame, CV_BGR2GRAY); //converte para escala de cinza
  calcHist(&frame, 1, 0, Mat(), H1, 1, &histsize, &histrange);

  while(true){
    H2 = H1.clone();
    cap >> frame; //captura um frame
    cvtColor(frame, grayFrame, CV_BGR2GRAY); //converte para escala de cinza
    calcHist(&grayFrame, 1, 0, Mat(), H1, 1, &histsize, &histrange);

    r_correl =  compareHist(H1, H2, CV_COMP_CORREL);

    if(r_correl <= 0.95)//movimento
    {
      circle(frame, Point(frame.cols - 20, 20), 10, Scalar(0, 255, 0), CV_FILLED);
    }else{
      circle(frame, Point(frame.cols - 20, 20), 10, Scalar(255, 255 ,255), CV_FILLED);
    }

    imshow("Live", frame);

    //Salva o frame para o array de frames que sera usado para gerar o gif
    gifFrames[i] = Magick::Image(frame.cols,
                                 frame.rows, "BGR",
                                 Magick::StorageType::CharPixel,
                                (uint8_t*)frame.data);
    gifFrames[i].animationDelay(GIF_DELAY);
    i = (i+1)%NUM_FRAMES_GIF; //para simular uma fila circular com o vector

    if(waitKey(30) != 255)break;
  }

  //Gera o gif, colocando-o no arquivo "saidaKmeans.gif"
  Magick::writeImages(gifFrames.begin(), gifFrames.end(), "Motiondetector.gif");
  return 0;
}

5. Filtros 2D

Explicação

5.1. Laplaciano do Gaussiano (Lapgauss)

Filter2D
Figura 11. Resultado do lapgauss.cpp

código completo: lapgauss.cpp

Clique aqui pra ver o código completo

#include <iostream>
#include <opencv2/opencv.hpp>
#include <GraphicsMagick/Magick++.h>

using namespace cv;
using namespace std;

#define MAX_FILTER 2

#define NUM_FRAMES_GIF 100
#define GIF_DELAY 10 //ms

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"
           "i - identidade\n"
           "e - laplaciano do Gaussiano\n"
	         "esc - sair\n";
}

int main(int argvc, char** argv){
  Magick::InitializeMagick(NULL);
  vector<Magick::Image> gifFrames(NUM_FRAMES_GIF);
  int k = 0;

  VideoCapture video;
  float identidade[] = {0, 0, 0,
                        0, 1, 0,
                        0, 0, 0};
  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};

  Mat cap, frame, frame32f, frameFiltered;
  Mat mask[] = { Mat(3,3, CV_32F), Mat(3,3, CV_32F) };
  Mat maskAux;
  Mat result;
  char* text = "Media";
  double width, height;
  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[0] = Mat(3, 3, CV_32F, media);
  scaleAdd(mask[0], 1/9.0, Mat::zeros(3,3,CV_32F), maskAux);
  mask[0] = maskAux.clone();
  absolut = 1; // calcs abs of the image
  mask[1] = Mat(3, 3, CV_32F, identidade);

  menu();
  for(;;){
    video >> cap;
    cvtColor(cap, frame, CV_BGR2GRAY);//converte a imagem para tons de cinza
    flip(frame, frame, 1); //espelha a imagem

    imshow("original", frame);//exibe a imagem

    frame.convertTo(frameFiltered, CV_32F); //cria uma imagem tipo float
    //aplicacao dos filtros, seguindo uma ordem, da mascara[0] ate mascara[i]
    //fazendo com que os efeitos das filtragens sejam cascateados
    for(int i = 0; i < MAX_FILTER; i++)
    {
      filter2D(frameFiltered, frameFiltered, frameFiltered.depth(), mask[i], Point(1,1), 0); //aplica o filtro 1
      if(absolut)frameFiltered = abs(frameFiltered);
    }

    frameFiltered.convertTo(result, CV_8U); //converte a imagem filtrada de float para byte em tons de cinza
    putText(result, text, Point(5,50), FONT_HERSHEY_DUPLEX, 1, Scalar(255), 2);

    imshow("filtroespacial", result);//exibe a imagem filtrada


    key = (char) waitKey(10);
    if( key == 27 ) break; // esc pressed!
    switch(key){
    case 'a':
	  menu();
      absolut=!absolut;
      break;
    case 'm':
	  menu();
      mask[0] = Mat(3, 3, CV_32F, media);
      scaleAdd(mask[0], 1/9.0, Mat::zeros(3,3,CV_32F), maskAux);
      mask[0] = maskAux.clone();
      mask[1] = Mat(3, 3, CV_32F, identidade);
      text = "Media";



      break;
    case 'g':
	  menu();
      mask[0] = Mat(3, 3, CV_32F, gauss);
      scaleAdd(mask[0], 1/16.0, Mat::zeros(3,3,CV_32F), maskAux);
      mask[0] = maskAux.clone();
      mask[1] = Mat(3, 3, CV_32F, identidade);
      text = "Gaussiano";
      break;
    case 'h':
	  menu();
      mask[0] = Mat(3, 3, CV_32F, horizontal);
      mask[1] = Mat(3, 3, CV_32F, identidade);
      text = "Horizontal";
      break;
    case 'v':
	  menu();
      mask[0] = Mat(3, 3, CV_32F, vertical);
      mask[1] = Mat(3, 3, CV_32F, identidade);
      text = "Vertical";
      break;
    case 'l':
	  menu();
      mask[0] = Mat(3, 3, CV_32F, laplacian);
      mask[1] = Mat(3, 3, CV_32F, identidade);
      text = "Laplaciano";
      break;
    case 'i':
	  menu();
      mask[0] = Mat(3, 3, CV_32F, identidade);
      mask[1] = Mat(3, 3, CV_32F, identidade);
      text = "Identidade";
      break;
    case 'e':
	  menu();
      mask[0] = Mat(3, 3, CV_32F, gauss);
      scaleAdd(mask[0], 1/16.0, Mat::zeros(3,3,CV_32F), maskAux);
      mask[0] = maskAux.clone();
      mask[1] = Mat(3, 3, CV_32F, laplacian);
      text = "Laplaciano do Gaussiano";
      break;
    default:
      break;
    }


    for(int i = 0; i < height; i++)
      for(int j = 0; j < width; j++)
        result.at<uint8_t>(i,j) = 255 - result.at<uint8_t>(i,j);

    gifFrames[k] = Magick::Image(result.cols,
                                 result.rows, "K",
                                 Magick::StorageType::CharPixel,
                                (uint8_t*)result.data);

    gifFrames[k].animationDelay(GIF_DELAY);
    k = (k+1)%NUM_FRAMES_GIF; //para simular uma fila circular com o vector
  }
  //Gera o gif, colocando-o no arquivo "saidaKmeans.gif"
  Magick::writeImages(gifFrames.begin(), gifFrames.end(), "Filter2D.gif");
  return 0;
}

5.2. Tilt-Shift

Demonstração Tiltshift

Código completo: tiltshift.cpp.

Clique aqui pra ver o código completo

#include <iostream>
#include <math.h>       /* tanh, log */
#include <opencv2/opencv.hpp>

using namespace cv;

bool tiltShift(Mat &image_src,
               Mat &image_dest,
               float delta_l, //0 to 1
               float decay,   //0 to 1
               float center,  //0 to 1
               float blurring);

void slot_sliders(int, void*);
double alfa_func(int x, int l1, int l2, double d);

Mat image, blender;
int decay_int = 50;
int center_int = 50;
int c_width_int = 50;
int blurring_int = 50;
const char mainWindow[] = "TiltSfhit";


int main(int argc, char** argv){
  const int max_slider = 100;

  if(argc != 1){//usuario passou um argumento, vou entender como sendo um endereco de uma imagem
    image = imread(argv[1]);
  }else{
    image = imread("imagens/toto_bola_cores.png");
  }

  if(image.data == NULL){
    std::cerr << "Erro ao abrir a Imagem!!" << '\n';
    return 1;
  }

  namedWindow(mainWindow, WINDOW_AUTOSIZE);
  imshow(mainWindow, image);

  createTrackbar( "Central Width [0,100]", mainWindow,
                  &c_width_int,
                  max_slider,
                  slot_sliders);

  createTrackbar( "Center [0,100]", "TiltSfhit",
        				  &center_int,
        				  max_slider,
        				  slot_sliders);

  createTrackbar( "Decay [0,100]", mainWindow,
        				  &decay_int,
        				  max_slider,
        				  slot_sliders);

  createTrackbar( "Blurring [0,100]", mainWindow,
                  &blurring_int,
                  max_slider,
                  slot_sliders);

  slot_sliders(0,0);

  waitKey(0);
  return 0;
}

void slot_sliders(int, void*){
  tiltShift(image, blender, c_width_int/100.0, decay_int/100.0, center_int/100.0, blurring_int/100.0);
  imshow(mainWindow, blender);
};

double alfa_func(int x, int l1, int l2, double d){
  return 0.5*( tanh((x-l1)/d) - tanh((x-l2)/d) );
}

bool tiltShift(Mat &image_src,
               Mat &image_dest,
               float delta_l, //0 to 1
               float decay,   //0 to 1
               float center,  //0 to 1
               float blurring)
{
  if(delta_l > 1 || center > 1 || decay > 1 || blurring > 1)return true;
  if(delta_l < 0 || center < 0 || decay < 0 || blurring < 0)return true;
  if(image_src.data == NULL)return true;

  const static int intensity_max = 100;
  Mat blurry_img;
  const int x_max = image_src.rows;
  Mat mask, image_32f;
  float weigth = 1/18.0;
  float average[] = {2, 2, 2, 2, 2, 2, 2, 2, 2};


  mask = Mat(3, 3, CV_32F, average);
  scaleAdd(mask, weigth, Mat::zeros(3,3,CV_32F), mask);
  image_src.convertTo(image_32f, CV_32F);
  for(int i = 0; i < intensity_max*blurring; i++)
  {
    filter2D(image_32f, image_32f, image_32f.depth(), mask, Point(1,1));
    image_32f = abs(image_32f);
  }
  image_32f.convertTo(blurry_img, CV_8U);

  #define L1 (center - delta_l/2.0)*x_max
  #define L2 (center + delta_l/2.0)*x_max
  image_dest = image_src.clone();
  double alfa;
  for(int x = 0; x < x_max; x++)
  {
      alfa = alfa_func(x, L1, L2, decay);
      addWeighted(  image_src.row(x),
                    alfa,
                    blurry_img.row(x),
                    1-alfa,
                    0.0,
                    image_dest.row(x));
  }

  return false;
};

5.3. Tilt-Shift em Vídeo

Demonstração Tiltshift em vídeo

Código completo em: tiltshift_video.cpp.

Clique aqui pra ver o código completo

#include <iostream>
#include <math.h>       /* tanh, log */
#include <opencv2/opencv.hpp>

using namespace cv;

bool tiltShift(Mat &image_src,
               Mat &image_dest,
               float delta_l, //0 to 1
               float decay,   //0 to 1
               float center,  //0 to 1
               float blurring);//0 to 1

int main(int argc, char** argv){
  VideoCapture video_src;
  Mat frame;

  video_src.open(argv[1]);
  if(video_src.isOpened() == false){
    std::cerr << "Erro ao carregar arquivo de video!" << '\n';
    return 1;
  }

  int frame_width = static_cast<int>(video_src.get(CAP_PROP_FRAME_WIDTH));
  int frame_height = static_cast<int>(video_src.get(CAP_PROP_FRAME_HEIGHT));
  Size frame_size(frame_width, frame_height);
  int frames_per_second = 3;
  int steps = 10;

  //Create and initialize the VideoWriter object
  VideoWriter video_result("resultados/tiltshift_video.avi", VideoWriter::fourcc('X','V','I','D'),
                                                             frames_per_second, frame_size, true);
  if(video_result.isOpened() == false){
   std::cerr << "Erro ao carregar arquivo de video!" << '\n';
   return 1;
  }

  std::cout << "Processando..." << '\n';

  double alpha = 1.6;
  int beta = 1;

  while(true){
    video_src >> frame;
    if(frame.data == NULL)break;

    tiltShift(frame, frame, 0.3, 0.0001, 0.4, 0.15);

    for( int y = 0; y < frame.rows; y++ ) {
        for( int x = 0; x < frame.cols; x++ ) {
            for( int c = 0; c < 3; c++ ) {
                frame.at<Vec3b>(y,x)[c] =
                saturate_cast<uchar>( alpha*( frame.at<Vec3b>(y,x)[c] ) + beta );
            }
        }
    }

    video_result.write(frame);

    int i;
    for(i = 0; i < steps; i++)
    {
      if(frame.data == NULL)break; //final do arquivo
      video_src >> frame;
    }
    if(i != steps)break;
  }

  video_src.release();
  video_result.release();

  std::cout << "Terminou!" << '\n';

  system("vlc resultados/tiltshift_video.avi");

  return 0;
}


double alfa_func(int x, int l1, int l2, double d){
  return 0.5*( tanh((x-l1)/d) - tanh((x-l2)/d) );
}

bool tiltShift(Mat &image_src,
               Mat &image_dest,
               float delta_l, //0 to 1
               float decay,   //0 to 1
               float center,  //0 to 1
               float blurring)
{
  if(delta_l > 1 || center > 1 || decay > 1 || blurring > 1)return true;
  if(delta_l < 0 || center < 0 || decay < 0 || blurring < 0)return true;
  if(image_src.data == NULL)return true;

  const static int intensity_max = 100;
  Mat blurry_img;
  const int x_max = image_src.rows;
  Mat mask, image_32f;
  float weigth = 1/9.0;
  float average[] = {1, 1, 1, 1, 1, 1, 1, 1, 1};


  mask = Mat(3, 3, CV_32F, average);
  scaleAdd(mask, weigth, Mat::zeros(3,3,CV_32F), mask);
  image_src.convertTo(image_32f, CV_32F);
  for(int i = 0; i < intensity_max*blurring; i++)
  {
    filter2D(image_32f, image_32f, image_32f.depth(), mask, Point(1,1));
    image_32f = abs(image_32f);
  }
  image_32f.convertTo(blurry_img, CV_8U);

  #define L1 (center - delta_l/2.0)*x_max
  #define L2 (center + delta_l/2.0)*x_max
  image_dest = image_src.clone();
  double alfa;
  for(int x = 0; x < x_max; x++)
  {
      alfa = alfa_func(x, L1, L2, decay);
      addWeighted(  image_src.row(x),
                    alfa,
                    blurry_img.row(x),
                    1-alfa,
                    0.0,
                    image_dest.row(x));
  }

  return false;
};

6. Filtro Homomórfico

Em processo de documentação, desculpe…​

Considerando que uma imagem pode ser expressa como o produto dos componentes de iluminação, \(i(x,y)\) e refletância, \(r(x,y)\).

\(f(x,y) = i(x,y)r(x,y)\)

Esta aplicação se baseia na separação dos componentes, iluminação e refletância, por meio do logaritmo da imagem, pois pela consideração anterior, conseguimos o seguinte resultado.

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

E considerando que o componente de iluminação de uma imagem geralmente é caracterizado por variações espaciais suaveis, enquanto o componente de refletância tende a variar abruptamente. Essas características levam a associar o componente de iluminação as baixas frequências e o de refletância as altas frequências, na transformada de Fourier do logaritmo de uma imagem.

Com isso podemos controlar significativamente essas propriedades por meio de um filtro homomórfico, por meio de um filtro, H(u,v) que afete os componentes de baixa e alta frequências de forma diferente e controláveis.

filtragemHomomorfica
Figura 12. Passos de uma filtragem homomórfica.

Com o objetivo de atenuar a influência da iluminação na imagem e realce da refletância, o seguinte filtro passa altas foi utilizado.

\(H(u,v) = (\gamma_H - \gamma_L)[1 - e^{\frac{-cD_{(u,v)}^2 }{D_0^2}} \)]

Filtro
Figura 13. Filtro Homomórfico.
Demonstração do Filtro Homomórfico

Download do código completo: homo_filter.cpp

Clique aqui pra ver o código completo

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

#include <stdlib.h>     /* system, NULL, EXIT_FAILURE */

using namespace cv;
using namespace std;


Mat filter_tmp;
int percent_GamaL = 0;
int percent_GamaH = 2;
int percentD0 = 20;
int percent_C = 0;
float d_max;

void slot_sliders(int, void*);
void calcFilter(Mat& H, float gamaL, float gamaH, float D0, float c);
// 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 cópias 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 argc, char**argv){
  const char mainWindow[] = "Filtro";
  const int max_slider = 100;
  Mat image;
  Mat padded, filter;
  Mat complexImage, complexImage_out;
  Mat_<float> realInput, zeros;
  int dft_M, dft_N;
  vector<Mat> planos;

  if(argc != 1){//usuario passou um argumento, vou entender como sendo um endereco de uma imagem
    image = imread(argv[1]);
    //converte para escala de cinza
    cvtColor(image, image, CV_BGR2GRAY);
    imshow("Entrada", image);
  }else{
    std::cerr << "Erro ao carregar imagem!" << '\n';
    return -1;
  }

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

  // 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));

  // cria a componente real
  realInput = Mat_<float>(padded);
  realInput += cv::Scalar::all(1);
  cv::log(realInput, realInput);
  // combina o array de matrizes em uma unica
  // componente complexa
  Mat comps[] = {realInput, zeros};
  merge(comps, 2,  complexImage);
  // calcula o dft
  dft(complexImage, complexImage);
  // realiza a troca de quadrantes
  deslocaDFT(complexImage);
  normalize(complexImage, complexImage, 0, 1, CV_MINMAX);
  // a funcao de transferencia (filtro frequencial) deve ter o
  // mesmo tamanho e tipo da matriz complexa
  filter = complexImage.clone();

  // matriz das componentes real
  // e imaginaria do filtro
  filter_tmp = Mat(dft_M, dft_N, CV_32F);
  calcFilter(filter_tmp, percent_GamaL/100.0,
                        (percent_GamaH/100.0)*9+1,
                        (percentD0/100.0)*d_max,
                        (percent_C/100.0)*4+1);

  namedWindow(mainWindow, WINDOW_AUTOSIZE);

  createTrackbar( "Gama L [0-100%]", mainWindow,
                  &percent_GamaL,
                  max_slider,
                  slot_sliders);
  createTrackbar( "Gama H [0-100%]", mainWindow,
                  &percent_GamaH,
                  max_slider,
                  slot_sliders);
  createTrackbar( "D0 [0-100%]", mainWindow,
                  &percentD0,
                  max_slider,
                  slot_sliders);
  createTrackbar( "c [0-100%]", mainWindow,
                  &percent_C,
                  max_slider,
                  slot_sliders);

  complexImage_out = complexImage.clone();
  while(true){
    // cria a matriz com as componentes do filtro e junta
    // ambas em uma matriz multicanal complexa
    planos.clear();
    planos.push_back(filter_tmp);
    planos.push_back(filter_tmp);
    merge(planos, filter);

    // aplica o filtro frequencial
    mulSpectrums(complexImage, filter,complexImage_out,0);
    // troca novamente os quadrantes
    deslocaDFT(complexImage_out);
    // calcula a DFT inversa
    idft(complexImage_out, complexImage_out);
    // limpa o array de planos
    planos.clear();
    // separa as partes real e imaginaria da
    // imagem filtrada
    split(complexImage_out, planos);
    cv::exp(planos[0], planos[0]);
    planos[0] -= cv::Scalar::all(1);

    // normaliza a parte real para exibicao
    normalize(planos[0], planos[0], 0, 1, CV_MINMAX);

    imshow(mainWindow, filter_tmp);
    imshow("Resultado", planos[0]);
    waitKey(10);
  }

  return 0;
}

//cada slider eh mapeado de 0-100% em um range de valores
//fixos para cada uma das variaveis.
// (foi uma maneira simples que achei para controlar as variaveis com sliders de valores inteiros)
void slot_sliders(int, void*){
  calcFilter(filter_tmp, percent_GamaL/100.0,
                     (percent_GamaH/100.0)*8+2,
                     (percentD0/100.0)*d_max,
                     (percent_C/100.0)*4+1);
}

void calcFilter(Mat& H, float gamaL, float gamaH, float D0, float c){
  system("clear");
  std::cout << "Gama L:"<< gamaL << '\n';
  std::cout << "Gama H:"<< gamaH << '\n';
  std::cout << "D0:"<< D0 << '\n';
  std::cout << "C:"<< c << '\n';

  int N = H.size().width;
  int M = H.size().height;

  //Macro de calculo de distancia, com relacao ao ponto central da imagem
  #define D(u,v) sqrt( ((u)-M/2)*((u)-M/2) + ((v)-N/2)*((v)-N/2) )

  for(int u = 0; u < M; u++){
    for(int v = 0; v < N; v++){
        H.at<float>(u,v) = (gamaH - gamaL)*(1 - exp(-c*(D(u,v)*D(u,v))/(D0*D0))) + gamaL;
    }
  }
}

7. Efeito Pontilhista com detecção de bordas(Canny)

Em processo de documentação, desculpe…​

porDoSol
Figura 14. Entrada
Pontilhismo porDoSol
Figura 15. Resultado

Clique aqui pra ver o código completo

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

using namespace std;
using namespace cv;

#define STEP 7
#define JITTER 3
#define RAIO_MAX 2

//funcao que desenha circulos de raio deteminados
//nas bordas destacadas pelo algoritmo de canny
//usando T1 e T2 = 3*T1, como limites infeior e superior
//respectivamente
//Os circulos sao desenhados em ordem aleatoria, para evitar efeitos padronizados
//e as cores dos circulos sao versoes levemente diferentes das tonalidades originais
//para dar efeito de erro humano
void pontilhar(const Mat &imgIn, Mat &imgOut,uint raio, uint T1);

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

  Mat image, points, canny_img;

  uint width, height;
  uint x, y;

  image= imread(argv[1]);
  imshow("original", image);

  srand(time(0));

  if(!image.data){
    cout << "nao abriu" << argv[1] << 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(uint i=0; i<xrange.size(); i++){
    xrange[i]= xrange[i]*STEP+STEP/2;
  }

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

  points =  Mat(height, width, CV_8UC3, Scalar::all(255));

  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;

      Vec3b val = image.at<Vec3b>(x,y);
      if(x >= height || y >= width)val = Vec3b(255,255,255);

      circle(points,
             cv::Point(y,x),
             RAIO_MAX+(rand()%2),
             val,
             -1,
             CV_AA);
    }
  }

  //3 tipos de tracos sao aplicados aqui
  //a medida que o limite inferior eh menor, mais bordas sao detectadas
  //e essas bordas serao "pintadas" com "pinceis mais grossos"
  //e para as bordas de menor limiar, "pinceis mais finos" sao usados
  pontilhar(image, points, 3, 1);
  pontilhar(image, points, 2, 20);
  pontilhar(image, points, 1, 80);

  std::string nomeArq = std::string(argv[1]);
  nomeArq = "Pontilhismo_"+nomeArq;
  imwrite(nomeArq, points);
  std::cout << "Resultado salvo em: " << nomeArq << '\n';
  return 0;
}

void pontilhar(const Mat &imgIn, Mat &imgOut, uint raio, uint T1){
  std::vector<int> xrange(imgIn.rows);
  std::vector<int> yrange(imgIn.cols);
  cv::Mat canny_img;
  uint x, y;

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

  //Destacando as bordas com o filtro de Canny
  cvtColor(imgIn, canny_img, CV_BGR2GRAY);
  Canny(canny_img, canny_img, T1, 3*T1);

  random_shuffle(xrange.begin(), xrange.end());
  for(auto i : xrange){
    random_shuffle(yrange.begin(), yrange.end());
    for(auto j : yrange){
      if(canny_img.at<uint8_t>(i,j) == 255){
        x = i+rand()%(4*raio)-1.5*raio + 1;
        y = j+rand()%(4*raio)-1.5*raio + 1;
        circle(imgOut,
          cv::Point(y,x),
          raio,
          imgIn.at<Vec3b>(i,j)*(1+(rand()%3)/10.0),  //realiza pequenas mudanças nas tonalidades originais, para dar efeito de erro humano
          -1,
          CV_AA);
      }
    }
  }
}

8. Quantização vetorial com k-means

Em processo de documentação, desculpe…​

porDoSol
Figura 16. Entrada
kmeans
Figura 17. Resultado

Clique aqui pra ver o código completo

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

using namespace cv;

int main( int argc, char** argv ){
  int nClusters = 10;
  Mat rotulos;
  int nRodadas = 10;
  Mat centros;
  Mat resultados[nRodadas];

  if(argc!=2)exit(0);

  Mat img = imread( argv[1], CV_LOAD_IMAGE_COLOR);

  Mat samples(img.rows * img.cols, 3, CV_32F);

  //preenche o vetor de amostras
  for( int y = 0; y < img.rows; y++ ){
    for( int x = 0; x < img.cols; x++ ){
      for( int z = 0; z < 3; z++){
        samples.at<float>(y + x*img.rows, z) = img.at<Vec3b>(y,x)[z];
	  }
	 }
  }

  //Realiza nRodadas de calculo de kmeans, cada uma, muito provavelmente, retornara
  //um valor diferente para cada media represntativa, pra cada uma das nClusters classes
  for(uint i = 0; i < nRodadas; i++)
  {
    kmeans(samples,
      nClusters,
      rotulos,
      TermCriteria(CV_TERMCRIT_ITER|CV_TERMCRIT_EPS, 10000, 0.0001),
      1,
      KMEANS_RANDOM_CENTERS,
      centros );

      Mat rotulada( img.size(), img.type() );
      for( int y = 0; y < img.rows; y++ ){
        for( int x = 0; x < img.cols; x++ ){
          int indice = rotulos.at<int>(y + x*img.rows,0);
          rotulada.at<Vec3b>(y,x)[0] = (uchar) centros.at<float>(indice, 0);
          rotulada.at<Vec3b>(y,x)[1] = (uchar) centros.at<float>(indice, 1);
          rotulada.at<Vec3b>(y,x)[2] = (uchar) centros.at<float>(indice, 2);
        }
      }
      //salva no vetor resultados, para posteriormente virarem um gif
      rotulada.copyTo(resultados[i]);
  }
  //salva cada iteraca em um arquivo png
  //para posteriormente virarem um gif
  //com a ferramenta imageMagick por meio do
  //do programa via terminal, convert.
  for(uint i = 0; i < nRodadas; i++)
    imwrite("rodada-"+std::to_string(i)+".png", resultados[i]);
}

9. Rastreamento de Objetos por Cor e Teste de Desempenho

Link Para Apresentação no Google presentation.

Diagramas PDI OpenCv
Figura 18. Diagrama em blocos do processamento feito em OpenCv
Diagramas PDI Poti
Figura 19. Diagrama em blocos do processamento feito pela equipe Poti
Demonstração

Download do código completo: trackingColor.cpp

Clique aqui pra ver o código completo Da implementação em OpenCV

#include <iostream>
#include <fstream>
#include <string>
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"

#include <system_tools.h>

using namespace cv;
using namespace std;

#define NUM_COLORS 3


struct RANGE{
  RANGE( ){
      inf = cv::Scalar(179, 255,255);
      sup = cv::Scalar(179, 255,255);
  }

  RANGE( cv::Scalar sInf, cv::Scalar sSup){
      inf = sInf;
      sup = sSup;
  }
  cv::Scalar inf;
  cv::Scalar sup;
};

static const cv::Vec3b PxLARANJA(0,127,255);
static const cv::Vec3b PxAZUL(255, 0, 0);
static const cv::Vec3b PxAMARELO(0,255,255);
static const cv::Vec3b PxVERDE(0,255,0);
static const cv::Vec3b PxMAGENTA(255, 0, 255);
static const cv::Vec3b PxCIANO(255,255, 0);
static const cv::Vec3b PxPRETO(0,0,0);
static const cv::Vec3b PxBRANCO(255,255,255);

static const cv::Vec3b COLORS[] = { PxLARANJA, PxAZUL, PxAMARELO, PxVERDE, PxMAGENTA, PxCIANO};

static const RANGE AZUL_range = RANGE( cv::Scalar(91, 233, 77),  cv::Scalar(165, 255, 200));
static const RANGE LARANJA_range = RANGE( cv::Scalar(0, 148, 61),  cv::Scalar(25, 255, 255));
static const RANGE AMARELO_range = RANGE( cv::Scalar(91, 233, 77),  cv::Scalar(165, 255, 200));

static  RANGE COLORS_RANGE[NUM_COLORS];

void readFile(){
  std::ifstream in("../calibracao.cal");
  int colorIndex;
  int Hmin,Smin,Vmin;
  int Hmax,Smax,Vmax;

  if(!in.is_open()){
    std::cerr << "Erro abrir arquivo de calibracao" << '\n';
    exit(1);
  }

  for(int c = 0; c < NUM_COLORS; c++)
  {
    in >> Hmin;
    in >> Smin;
    in >> Vmin;

    in >> Hmax;
    in >> Smax;
    in >> Vmax;

    COLORS_RANGE[c] = RANGE(cv::Scalar(Hmin, Smin, Vmin), cv::Scalar(Hmax, Smax, Vmax));
  }
  in.close();
};

 int main( int argc, char** argv )
 {
    VideoCapture cap(argc > 1 ? atoi(argv[1]) : 0);

    if ( !cap.isOpened() )  // if not success, exit program
    {
         cout << "Cannot open the web cam" << endl;
         return -1;
    }
    readFile();//carrega os limires de HSV estabelecidos no programa de calibracação

  Mat segment;
  double dt_process;
  double dt_BGR2HSV;

    while (true)
    {
      Mat imgOriginal;

       if (!cap.read(imgOriginal))
      {
           cout << "Erro na captura!" << endl;
           break;
      }

    dt_BGR2HSV = tools::clock();
    Mat imgHSV;
    cvtColor(imgOriginal, imgHSV, COLOR_BGR2HSV);
    dt_BGR2HSV = tools::clock() - dt_BGR2HSV;

    dt_process = tools::clock();
    for(uint color = 0; color < NUM_COLORS; color++){
      Mat imgThresholded;

      inRange(imgHSV, COLORS_RANGE[color].inf, COLORS_RANGE[color].sup, imgThresholded); //Threshold
      //operacao morfologia de abertura, para remover ruidos de formato
      erode(imgThresholded, imgThresholded, getStructuringElement(MORPH_ELLIPSE, Size(5, 5)) );
      dilate( imgThresholded, imgThresholded, getStructuringElement(MORPH_ELLIPSE, Size(5, 5)) );

      //filtro morfologico, fechamento (para preencher espacos vazios dos objetos)
      dilate(imgThresholded, imgThresholded, getStructuringElement(MORPH_ELLIPSE, Size(5, 5)) );
      erode(imgThresholded, imgThresholded, getStructuringElement(MORPH_ELLIPSE, Size(5, 5)) );

      if(color == 0)segment = imgThresholded.clone();
      else segment = segment + imgThresholded;

      //calcula dos momentos da imagem segmentada
      Moments oMoments = moments(imgThresholded);

      //uso dos momentos espacais para o calculo do centroide
      double dM01 = oMoments.m01; //momento em torno de x
      double dM10 = oMoments.m10; //momento em torno de y
      double dArea = oMoments.m00;//area
      dt_process = tools::clock() - dt_process;

      imshow(to_string(color),  imgThresholded);
      //considera apenas regioes com mais de 1000 pixels
      if (dArea > 1000)
      {
        //Calculo do Centro da regiao
        int posX = dM10 / dArea;
        int posY = dM01 / dArea;

        circle(imgOriginal,
          cv::Point(posX, posY),
          6,
          COLORS[color],  //realiza pequenas mudanças nas tonalidades originais, para dar efeito de erro humano
          -1,
          CV_AA);
        }
      }

    imshow("Thresholded Image", segment); //show the thresholded image
    imshow("Original", imgOriginal); //show the original image

    std::cout << "Tempo de Processsamento Total:" << dt_process + dt_BGR2HSV << 's' <<'\n';

    if (waitKey(30) == 27)//para sair com a tecla  ESC
    {
        cout << "Programa Finalizado" << endl;
        break;
    }
  }
     return 0;
  }

Link Para o código da Equipe Poti de futebol de Robôs.

10. Bibliografia

  • Rafael Gonzalez. 'Processamento Digital de Imagens'. Addison-Wesley. 1990. 2 ed.