-
Notifications
You must be signed in to change notification settings - Fork 1
Home
Thales Castro <thalescast@gmail.com>
Brevik Rafael < brevik_3@yahoo.com.br>
O filtro homomórfico controla as componentes de alta e baixa frequências de uma imagem, iluminação e refletância, de modo a separá-las através da aplicação do logaritmo ao modelo iluminação-refletância. Dessa maneira, aplica-se a transformada de fourier a cada componente, separadamente, e um filtro adequado às características que se deseja obter na imagem. Na sequência é aplicado ao resultado filtrado a transformada inversa e o cálculo da exponencial, de modo a formar a imagem de saída. O código que segue abaixo executa os passos para a aplicação do filtro homomórfico.
#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#define RADIUS 20
using namespace cv;
using namespace std;
// 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 , char**){
VideoCapture cap;
Mat imaginaryInput, complexImage, multsp;
Mat padded, filter, mag;
Mat image, imagegray, tmp;
Mat_<float> realInput, zeros;
vector<Mat> planos;
// habilita/desabilita ruido
int noise=0;
// frequencia do ruido
int freq=10;
// ganho inicial do ruido
float gain=1;
// valor do ruido
float mean;
// guarda tecla capturada
char key;
// valores ideais dos tamanhos da imagem
// para calculo da DFT
int dft_M, dft_N;
// abre a câmera default
cap.open(0);
if(!cap.isOpened())
return -1;
// captura uma imagem para recuperar as
// informacoes de gravação
cap >> 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 função de transferência (filtro frequencial) deve ter o
// mesmo tamanho e tipo da matriz complexa
filter = complexImage.clone();
// cria uma matriz temporária para criar as componentes real
// e imaginaria do filtro ideal
tmp = Mat(dft_M, dft_N, CV_32F);
float d;
int yh, yhMax=20, yl, ylMax=20, d0=5, d0Max=50, c, cMax=20;
char *nome = "window";
// prepara o filtro passa-baixas ideal
namedWindow(nome, CV_WINDOW_AUTOSIZE);
createTrackbar("Gama H: ",nome, &yh, yhMax);
createTrackbar("Gama L: ",nome, &yl, ylMax);
createTrackbar("D0: ",nome, &d0, d0Max);
createTrackbar("c: ",nome, &c, cMax);
// cria a matriz com as componentes do filtro e junta
// ambas em uma matriz multicanal complexa
Mat comps[]= {tmp, tmp};
for(;;){
cap >> image;
for(int i=0; i<dft_M; i++){
for(int j=0; j<dft_N; j++){
d = pow(pow(i - dft_M/2, 2) + pow(j - dft_N/2, 2), 0.5);
tmp.at<float>(i,j) = (yh - yl)*(1 - exp(-1*c*(pow(d,2)/pow(d0,2)))) + yl;// (yh - yl)*(1 - exp(-c*(pow(d, 2)/pow(d0,2)))) + yl;
}
}
comps[0] = tmp;
comps[1] = tmp;
merge(comps, 2, filter);
cvtColor(image, imagegray, CV_BGR2GRAY);
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);
// 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);
for (int i = 0; i < dft_M; i++){
for (int j = 0; j < dft_N;j++){
complexImage.at<char>(i,j) = log(complexImage.at<char>(i,j)+1);
}
}
// calcula o dft
dft(complexImage, complexImage);
// realiza a troca de quadrantes
deslocaDFT(complexImage);
// aplica o filtro frequencial
mulSpectrums(complexImage,filter,complexImage,0);
// limpa o array de planos
planos.clear();
// separa as partes real e imaginaria para modifica-las
split(complexImage, planos);
// usa o valor medio do espectro para dosar o ruido
mean = abs(planos[0].at<float> (dft_M/2,dft_N/2));
// insere ruido coerente, se habilitado
if(noise){
// F(u,v) recebe ganho proporcional a F(0,0)
planos[0].at<float>(dft_M/2 +freq, dft_N/2 +freq) +=
gain*mean;
planos[1].at<float>(dft_M/2 +freq, dft_N/2 +freq) +=
gain*mean;
// F*(-u,-v) = F(u,v)
planos[0].at<float>(dft_M/2 -freq, dft_N/2 -freq) =
planos[0].at<float>(dft_M/2 +freq, dft_N/2 +freq);
planos[1].at<float>(dft_M/2 -freq, dft_N/2 -freq) =
-planos[1].at<float>(dft_M/2 +freq, dft_N/2 +freq);
}
// recompoe os planos em uma unica matriz complexa
merge(planos, complexImage);
// troca novamente os quadrantes
deslocaDFT(complexImage);
// calcula a DFT inversa
idft(complexImage, complexImage);
for (int i = 0; i < dft_M; i++){
for (int j = 0; j < dft_N;j++){
complexImage.at<char>(i,j) = exp((complexImage.at<char>(i,j)));
}
}
// limpa o array de planos
planos.clear();
// separa as partes real e imaginaria da
// imagem filtrada
split(complexImage, planos);
// normaliza a parte real para exibicao
normalize(planos[0], planos[0], 0, 1, CV_MINMAX);
imshow(nome, planos[0]);
key = (char) waitKey(10);
if( key == 27 ) break; // esc pressed!
switch(key){
// aumenta a frequencia do ruido
case 'q':
freq=freq+1;
if(freq > dft_M/2-1)
freq = dft_M/2-1;
break;
// diminui a frequencia do ruido
case 'a':
freq=freq-1;
if(freq < 1)
freq = 1;
break;
// amplifica o ruido
case 'x':
gain += 0.1;
break;
// atenua o ruido
case 'z':
gain -= 0.1;
if(gain < 0)
gain=0;
break;
// insere/remove ruido
case 'e':
noise=!noise;
break;
}
}
return 0;
}Começamos essa tarefa por introduzir, resumidamente, o filtro de Canny ou o detector de bordas de Canny. Trata-se de um algoritmo que utiliza os conceitos de convolução digital e de cálculo do gradiente. A princípio, Canny buscava um algoritmo ótimo que, como provado, conseguiu, porém conseguiu para os três critérios que ele estabeleceu. O primeiro critério estabelecido por Canny foi o de que as bordas encontradas devem ser o mais próximas possíveis das verdadeiras, ou seja, ter baixa taxa de erro. O segundo foi que a distância entre um ponto marcado como uma borda e o centro da borda verdadeira deve ser a menor possível. E o último foi que o número de máximos locais retornados por uma borda deve ser mínimo. O filtro de Canny consiste em suavizar a imagem por um filtro Gaussiano, obter a magnitude do gradiente, bem como os ângulos da imagem, aplicar a supressão de não máximos, que tem por finalidade afinar as cristas da imagem e a limiarização por histeresse que se utiliza de dois limiares.
Esta tarefa une os conceitos utilizados pelo detector de bordas de Canny e a técnica de Pontilhismo, que é uma técnica de pintura em que pequenas manchas, círculos ou pontos são introduzidos na imagem.
Tendo como base os dois códigos disponibilizados no site do professor, canny.cpp e pontilhismo.cpp, o código foi implementado mesclando as funcionalidades de cada um deles. Foi criada uma função para a implementação do pontilhismo, seguindo a mesma ideia do código referência, onde a função circle, disponibilizada pelo OpenCV, traça círculos de raios especificados pelo usuário. Vale lembrar que antes de tudo, o filtro de Canny é aplicado utilizando a função que o próprio OpenCV fornece, Canny, que recebe a imagem de interesse, a matriz onde o resultado do filtro será escrito e os limiares. Por último, vale destacar que pelo filtro retornar uma imagem com bordas, em um fundo escuro, fez-se necessário que cada pixel fosse diminuído por 255, fazendo com que a imagem ficasse clara. O código bem como as imagens original e processadas. Vale lembrar que para maiores informações das funções utilizadas aqui, basta dar uma olhada no site do professor
int main(int argc, char const *argv[]) {
#include "opencv2/imgproc/imgproc_c.h"
#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 5
#define JITTER 3
#define RAIO 2
Mat image, border, points;
int width, height, gray;
void pontilhismo(void);
int main(int argc, char** argv){
image= imread(argv[1], IMREAD_COLOR/*IMREAD_GRAYSCALE*/);
namedWindow("canny",1);
/*----Filtro de Canny----*/
Canny(image, border, 5/*top_slider*/, 3*5/*3*top_slider*/);
width=border.size().width;
height=border.size().height;
//Para que a imagem deixe de ser preta
for(int i=0; i < height;i++){
for(int j=0; j< width;j++){
border.at<uchar>(i,j) = 255 - border.at<uchar>(i,j);
}
}
pontilhismo();
return 0;
}
/*-------Função que aplica o pontilhismo-------*/
void pontilhismo(void){
vector<int> yrange;
vector<int> xrange;
int x, y;
width=border.size().width;
height=border.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_8U, Scalar(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;
gray = border.at<uchar>(x,y);
/*----Aplicação dos círculos nos pixels----*/
circle(points,
cv::Point(y,x),
RAIO,
CV_RGB(gray,gray,gray),
-1,
CV_AA);
}
}
imwrite("cannypoints.jpg", points);
}