Filtros Espaciais e Processamento de Imagens em Java
Filtro de Média dos 4 Vizinhos (Máscara 3x3)
Máscara espacial 3×3 que realiza a média dos quatro vizinhos mais próximos, excluindo o ponto central em si:
public ImagemAccess CalculaMedia(ImagemAccess img) {
int linha = img.getHeight();
int coluna = img.getWidth();
double vizinho[][] = new double[3][3];
ImagemAccess img_media = new Image(coluna, linha);
for (int l = 0; l < linha; l++) {
for (int c = 0; c < coluna; c++) {
img.getNeighborhood(coluna, linha, vizinho); /* faz espelhamento sozinho */
double pixel = (vizinho[0][1] + vizinho[1][0] + vizinho[1][2] + vizinho[2][1]) / 4;
img_media.putPixel(coluna, linha, pixel);
}
}
return img_media;
}Filtro Butterworth no Domínio da Frequência
public static ImageAccess ButterworthFourier(ImageAccess f, double cut) {
int nx = input.getWidth();
int ny = input.getHeight();
ImageAccess output = new ImageAccess(nx, ny);
ImageAccess Real = new ImageAccess(nx, ny);
ImageAccess g = new ImageAccess(nx, ny);
int r = 0;
double h = 0.0;
for (int x = 0; x < nx; x++) {
for (int y = 0; y < ny; y++) {
r = (int) Math.sqrt(x * x + y * y);
h = 1 / (1 + Math.pow((r / cut), 2));
g.putPixel(x, y, h);
}
}
FFT.doFFT(real, imag);
ImageAccess newReal = new ImageAccess(nx, ny);
for (int x = 0; x < nx; x++) {
for (int y = 0; y < ny; y++) {
double pixel = real.getPixel(x, y) * g.getPixel(x, y);
newReal.putPixel(x, y, pixel);
}
}
output = FFT.FFTinverse(real, imag);
return output;
}Filtro de Média Móvel Separável 3x3
/* No caso de querer as condições de espelhamento */
static public ImageAccess doMovingAverage3_Sep(ImageAccess input) {
int nx = input.getWidth();
int ny = input.getHeight();
ImagemAccess oux = new ImagemAccess(nx, ny);
ImagemAccess out = new ImagemAccess(nx, ny);
double colin[] = new double[ny];
double colout[] = new double[ny];
double rowin[] = new double[nx];
double rowout[] = new double[nx];
for (int y = 0; y < ny; y++) {
input.getRow(y, rowin);
doAverage3(rowin, rowout);
oux.putRow(y, rowout);
}
for (int x = 0; x < nx; x++) {
oux.getColumn(x, colin);
doAverage3(colin, colout);
out.putColumn(x, colout);
}
return out;
}
static private void doAverage3(double vin[], double vout[]) {
int n = vin.length;
vout[0] = (vin[0] + 2 * vin[1]) / 3.0;
for (int k = 1; k < n - 1; k++) {
vout[k] = (vin[k - 1] + vin[k] + vin[k + 1]) / 3.0;
}
vout[n - 1] = (vin[n - 1] + 2 * vin[n - 2]) / 3.0;
}Operações de Histograma e Realce
Cálculo do Histograma
// Calcula histograma
public void run(ImageProcessor ip) {
int[] H = new int[65536];
int w = ip.getWidth();
int h = ip.getHeight();
for (int v = 0; v < h; v++) {
for (int u = 0; u < w; u++) {
int i = ip.getPixel(u, v);
H[i] = H[i] + 1;
}
}
}Reescalonamento de Contraste
// Reescalona
static public ImageAccess rescale(ImageAccess input) {
int nx = input.getWidth();
int ny = input.getHeight();
double max = input.getMaximum();
double min = input.getMinimum();
ImageAccess output = new ImageAccess(nx, ny);
double alpha = 255.0 / (max - min);
double beta = min;
output = input;
output.subtract(beta);
output.multiply(alpha);
return output;
}Algoritmo de Equalização de Histograma
// Algoritmo de equalização de histograma
public void run(imageProcessor ip) {
int w = ip.getWidth();
int h = ip.getHeight();
int M = w * h; // Número total de pixels da imagem
int K = 256; // Número de níveis de intensidade
// Calcula histograma cumulativo
int[] H = ip.getHistogram();
for (int j = 1; j < H.length; j++) {
H[j] = H[j - 1] + H[j];
}
// Equalização da imagem
for (int v = 0; v < h; v++) {
for (int u = 0; u < w; u++) {
int a = ip.get(u, v);
int b = H[a] * (K - 1) / M;
ip.set(u, v, b);
}
}
}Histograma Normalizado
// Histograma normalizado
public Double[] getNormalizedHistogram(ImageAccess img) {
int ny = img.getWidth();
int nx = img.getHeight();
ImagemAccess output = new ImagemAccess(nx, ny);
ImagemAccess out = new ImagemAccess(nx, ny);
int l = 256;
double histog[] = new double[l];
double max = img.getMaximum();
double min = img.getMinimum();
for (int x = 0; x < nx; x++) {
for (int y = 0; y < ny; y++) {
double pixel = img.getPixel(x, y);
pixel = (255 * (pixel - min) / (max - min));
output.putPixel(x, y, pixel);
}
}
for (int x = 0; x < nx; x++) {
for (int y = 0; y < ny; y++) {
histog[(int) output.getPixel(x, y)]++;
}
}
for (int i = 0; i < l; i++) {
histog[i] = histog[i] / (nx + ny);
}
return histog;
}Condições de Fronteira em Filtros Espaciais
Em filtros espaciais bidimensionais, para que servem as condições de fronteira?
As condições de fronteira servem para evitar que valores nulos (null) ou indefinidos de pixels sejam considerados durante a aplicação dos filtros nas bordas da imagem.
Quais as funções de fronteira mais usadas?
- Atribuição de constante: Situação na qual os valores de pixels externos à fronteira da imagem recebem um valor constante pré-determinado (por exemplo, 0).
- Replicação de borda: Atribuição dos valores dos primeiros pixels adjacentes à fronteira para além dela.
- Espelhamento (reflexão periódica): Situação na qual os valores de pixels externos à fronteira recebem os valores contidos nos pixels finais do lado oposto ou refletidos, simulando uma imagem enrolada sobre si mesma (toroide).
Separabilidade da Convolução Bidimensional
A propriedade de separabilidade da convolução é definida matematicamente da seguinte forma:
Seja a convolução linear dada por:
I′(i, j) = ∑∑ I(u - i, v - j) · H(i, j)
Ou na forma matricial: I′ = I ∗ H
De acordo com as propriedades da convolução, em que uma convolução pode resultar de n convoluções sucessivas:
H = H1 ∗ H2 ∗ H3 ∗ ... ∗ Hn
Logo:
I′ = I ∗ H = I ∗ (H1 ∗ H2 ∗ H3 ... ∗ Hn)
I′ = I ∗ H = (...((I ∗ H1) ∗ H2) ∗ H3)... ∗ Hn
Desta forma, demonstra-se que a convolução 2D pode ser efetuada separadamente:
I′ = (I ∗ Hx) ∗ Hy, onde o resultado independe da ordem de execução de Hx e Hy.
Em que essa propriedade pode ajudar em filtros espaciais e por quê?
Resposta: A propriedade da separabilidade é responsável pela otimização e redução do custo computacional dos algoritmos de convolução em imagens. A execução de um filtro separável passa de uma complexidade O(N² · M²) para O(2 · N · M²), sendo significativamente mais rápida e eficiente que a versão 2D não-separável.
Como implementar uma versão separável de um filtro espacial?
Resposta: De acordo com as propriedades da convolução 2D, um filtro separável pode ser aplicado executando-se primeiro todas as operações unidimensionais em um sentido horizontal (I ∗ Hx) e, em seguida, aplicando uma segunda convolução unidimensional vertical sobre esse resultado intermediário ((I ∗ Hx) ∗ Hy).
// Pseudo-código de convolução separável:
function SeparableFilter(imagem i)
para toda linha fazer:
convoluir i com Hx
// com o resultado da operação acima:
para toda coluna fazer:
convoluir resultado com Hy
fim de SeparableFilter