03/12/2008
Complex Linear Diffusion Filter A Java Implementation
JosĂŠ Iguelmar Miranda
José Iguelmar Miranda
1
José Iguelmar Miranda
Complex Linear Diffusion Filter A Java Implementation
Introduction The aim of this article is to present the Java implementation of the complex linear diffusion filter for edge detection (Gilboa et al., 2004). Edge detection is one of the most common processes when analyzing digital images, counting with a great variety of algorithms. Edges define objects contour. They are transitions regions in a digital image and generally define the borders between the object and its background. This means that if one can detect the edges, then objects and some of their features can be measured, like area, perimeter and shape. This is why the edge detection process is qualified as an essential tool in image analysis. Edge detection is part of a major procedure, known as segmentation – the identification of regions inside an image. There are two techniques in digital image processing that deal with edges: edge detection and edge enhancement. Technically, edge detection aims to localize the pixels in the borders of an object, while edge enhancement increases the contrast between edges and background, making them more visible (Parker, 1997). In practice, both terms are used with the same subject, because the majority of the algorithms for edge detection assign to the pixels in the borders values that make them more visible.
Theoretical Aspects There are some possible definitions for edges, applicable in differing situations. The most common and generic is ideal step border (Fig. 1a). In this one dimension example, the edge represents the gray level change in a specific localization. The bigger the gray level difference in the transition zone, the easier to detect edges. But, in real world, complexity happens. For instance, the quantization process realized by a satellite, the scene sampling not always can make a precise match between real object edges and the pixels that will represent them. Commonly, happens what is shown in Fig. 1b.
2
JosĂŠ Iguelmar Miranda
Figure 1a. Ideal border example.
Figure 1b. Ideal border example. An additional difficulty has to do with the ubiquitous problem of noise. Due to some factors, like sun light intensity, camera model and lens, satellite movement, air temperature, atmospheric effects, among others, it is unlike that two pixels with the same gray level in the ground will have the same gray level in the digital image. Noises are random and systematic. Random noises are only characterized by a statistical distribution. Systematic noises are easier to detect and to remove. The net effect of noises in the image is to produce a random variation in the gray level values of the pixels, such that the ideal edge shown in Fig. 1 is not found in real images.
3ď‚—
JosĂŠ Iguelmar Miranda
That said, it is not possible to ignore the presence of random noise in images. The problem is that this noise cannot be identified and measured precisely, since one cannot differentiate its contribution in the pixels gray level values. Happily, sometimes, the random noise can be characterized by its effect in the image, expressed as a probability distribution with specific values of mean and standard deviation (Parker, 1997). So, before working with an image, it is necessary to filter this kind of noise, normally using an edge detection process. Since a border is defined as a change in the gray level, an operator that is sensible to this change has a task to detect edge. Generally, edge operators can be classified in three groups: (1) those based on partial derivatives, approximated by differences in the discrete case, whose task is to identify spots where there are great intensity changes; (2) those that model the border with a small dimension kernel; and (3) those that use mathematical models for the borders, using partial differential equations, or diffusion models, that search for the maxima and minima of a function. This is the one used here.
The Complex Diffusion Filter Gilboa et al. (2004) developed the complex linear diffusion filter based on the idea that in several fields of physics and engineering one could expand the real domain to the complex domain, even that all used values were in the real domain. Examples of this procedure can be found in the use of Fourier transforms and wavelets in digital image processing, which use complex values. Actually, wavelets are not, necessarily, complex transforms. The cited authors used complex numbers and generalized the idea of modeling filters with partial differential equations, used to build diffusion models. Diffusion models are known in mathematic as boundary values problems, represented by a partial or ordinary differential equation, given with boundary values or conditions, to asseverate unique solution, although it may have infinity solutions. Boundary values specify values of the function or its derivatives in the boundaries (borders) of the region where the equation is defined. Diffusion models can be linear or isotropic and non linear or anisotropic. This paper presents the liner model, as proposed by Gilboa et al. (2004).
4ď‚—
José Iguelmar Miranda
There is a kind of transformation in image processing called scale-space, which consists in the extraction of spatial information from an image considering a set of scales, moving from details in specific areas to wide features all over the image1. This transformation works as filters applied successively in differing scales of the image (Schowengerdt, 1997). Gilboa et al. (2004) generalized the use of this transformation to the complex domain, combining the diffusion partial differential equation with a free model of Schrödinger’s equation. Complex diffusion analysis shows that the generalized diffusion presents properties of the direct and inverse diffusion. The basal point of the authors’ study, theoretically and numerically supported, is that the imaginary part works as an edge detector, similar to the second derivative in the time scale, whose effect is to smooth the image, when the complex diffusion coefficient, c in this work, comes close to the real axis (Fig. 2).
Figure 2. Exponential and rectangle form of a complex number. The canonical partial differential equation of the linear scale-space that satisfies both methods (function maximum or minimum) is:
The reader can find more on this subject in the document “Anisotropic Diffusion Model for Edge Detection: a Java Implementation”, available in Scribd site.
1
5
José Iguelmar Miranda
It = cΔI, t > 0; x ∈ ℜ. I(x; 0) = I0,
I0 ∈ ℜ; c, I ∈ C (complex).
Where It is the “perfect” image describing the real scene; I0 is the observed or initial image, with some degradation, due to noise; c is the complex diffusion coefficient and Δ is the function’s Laplacian. As the shown equation is a partial differential, the expression It = cΔI indicates the boundary condition and I(x; 0) = I0 indicates the initial condition. The complex diffusion coefficient can be written as that c, in this case, is restricted to a real positive value (θ = 0) (Fig. 2).
, being
The scale-space approach is a multiple resolution technique established to analyze the image structure. The distributed information over all the scales is generated as the solution I(x, y; t) to the linear heat equation. This way, critical points and edges are eliminated from all scales, allowing analysis of the scene in full. A problem with this approach is that important structures features, like borders, are smoothed and blurred along the process, as the processed image change in time. As a consequence, the zero-crossing path of the second derivative, which indicates the borders localization, varies from scale to scale. To avoid this problem, Perona & Malik (1990), proposed an anisotropic process, where the diffusion occurs according to an adapted variable, the diffusion coefficient, in order to reduce the smoothing effect close to the borders (see foot note 1). Gilboa et al. (2004), anticipated an additional generalization for the linear and non linear scale-space, that became special cases of a more general theory about complex diffusion processes. These processes are found in quantum physics, electromagnetism and optics. Gilboa et al. (2004) used as a model the Schrödinger’s equation2, which depends on time. In the simplest case of a particle without spin, subject to an external field, it has the following formulation: (1)
Erwin Rudolf Josef Alexander Schrödinger, Austrian mathematician, published his revolutionary work about the mechanics of waves in a series of six articles in 1926. Mechanics of waves was the second formulation of quantum theory, being the first the Heisenberg’s matrices mechanics. Due to his contributions to the quantum mechanics, he received the Nobel in 1933.
2
6
José Iguelmar Miranda
Where Ψ = Ψ(x, t) is the wave’s function of a quantum particle depending on the time t, with mass m, is the Planck’s constant, V(x) is the external field potential, and i .
Additional details about the use of this equation can be found in Gilboa et al.
(2004). The right side of (1) is called Schrödinger’s operator and is interpreted as the energy operator of the particles. The first term is the kinetic energy and the second term is the potential energy. One of the authors’ conclusions was that a basic solution of equation (1) was a plane wave, whose behavior is found in the complex flow. In their work, the authors used the equation with potential energy equal to zero (no external field) but with kinetic energy complex and non-linear. In order to use the complex diffusion operator, the authors considered the following partial differential equation along with the initial and boundary values: It = cIxx, t > 0, x ∈ ℜ, c ∈ C (Boundary value) I(x; 0) = I0,
I0 ∈ ℜ, I ∈ C.
(2)
(Initial value)
This equation unifies the linear diffusion equation for c ∈ ℜ and the Schrödinger’s free equation, i.e., c ∈ C and V(x) ≡ 0. The fundamental complex solution h(x; t) must satisfy the relationship: I(x; t) = I0*h(x; t)
(3)
Where * means convolution. Equation (3) means that the “perfect” image, (I), can be obtained through a series of convolutions in time (t) from the initial image (I0). The goal of the convolution series was to eliminate noise problems. Additionally, equation (3) is the generic formula to filter an image, one of the edge detection subjects. There are several ways in defining h. The authors’ contribution was to present a complex fundamental solution for h(x; t) given by: (4) Where:
When θ → 0, the imaginary part can be considered as the second derivative, smoothing the initial image, factored by θ and the time t. Equation (4), with one dimension, can be generalized to ℜn.
7
José Iguelmar Miranda
The application of this solution is related to the definition of the behavior of small values of θ (the angle in the polar coordinate space), obtained by separating the real and imaginary parts of the signal or image, I = IR + iII and of the diffusion coefficient c = cR + icI, in a set of two equations: (5)
Where cR = cosθ, cI = sinθ (Fig. 2). The authors suggested the value θ = π/1000, as solution to equation implementation, value used in the Java program.
The Java Implementation Java language is an alternative for implementation of applications not only for the web, but for others uses. The facility to run Java applications in whatever operating system is a strong appeal to use Java. At least, this advantage motivated the present work. In the following sections, the reader is presented with the whole source code of the application. First, there is the claim for the code use. /********************************************************************* ComplexDiffusionFilter.java – implements the complex diffusion filter. WRITTEN BY: José Iguelmar Miranda & João Camargo Neto. DATE:
February 2007
DOCUMENTATION: See http://www.cnptia.embrapa.br/files/ct75.pdf (Portuguese) Copyright (c) 2007 Embrapa Informática Agropecuária PERMISSION TO COPY: This program is free software, under the GNU General Public License (GPL); permission to use, copy and modify this software and its documentation for NON-COMMERCIAL purposes is granted, without fee, provided that an acknowledgement to the authors, José Iguelmar Miranda & João Camargo Neto, at www.cnptia.embrapa.br, appears in all copies. Embrapa Informática Agropecuária makes no representations about the suitability or fitness of the software for any or for a particular purpose. Embrapa Informática Agropecuária shall not be liable for any damages suffered as a result of using, modifying or distributing this software or its derivatives. For a copy of GNU General Public License, write to: Free Software Foundation, Inc.,
8
José Iguelmar Miranda
59 Temple Street, Suite 330, Boston, MA 02111-1307 USA. ********************************************************************* Description: This program implements the complex diffusion filter. Reference paper: GILBOA, G.; SOCHE, N.; ZEEVI, Y. Y. Image enhancement and denoising by complex diffusion processes. IEEE Transactions on Pattern Analysis and Machine Intelligence. 26(8):1020-1036, 2004. This program uses Complex.java and MatrizComplexa.java; see below. Input: 1. Input image. 2. Iteration: how many times to filter the source image. It means time, in equations (3). Output: JFrame with source, imaginary, and real images. Informações do CVS: $Source$: $Revision$: $Date$: *********************************************************************/ // Generic packages import java.io.File; import java.io.IOException; // AWT import import import import
packages java.awt.image.WritableRaster; java.awt.image.BufferedImage; java.awt.image.Raster; java.awt.GridLayout;
// Swing packages for graphical interface import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.ImageIcon; import javax.swing.JScrollPane; // Immediate mode reading - J2SE 1.4+ import javax.imageio.ImageIO; public class ComplexDiffusionFilter extends JFrame { // Phase angle for diffusion coefficient
9
JosĂŠ Iguelmar Miranda
double theta = Math.PI/1000; public static void main(String args[]) { int iteration = 0; // Is the parameters list ok? if (args.length != 2) { String msga = "Usage: java -cp . ComplexDiffusionFilter "; String msgb = "<image> <iterations>"; System.out.println(msga + msgb); System.exit(0); } // Show JFrame decorated by Swing JFrame.setDefaultLookAndFeelDecorated(true); long eq_time = System.currentTimeMillis(); try { iteration = Integer.parseInt(args[1]); } catch (NumberFormatException e) { String st = "Parameter 'iteration' not valid"; System.out.println(st); System.exit(0); } if (iteration <= 0) { System.out.println("Invalid 'iteration' value. Must be > 0"); } System.out.println("\nComplex Diffusion Filter - Parameters:\n"); String st = "\t#iterations: "; System.out.println(st + iteration); ComplexDiffusionFilter c = new ComplexDiffusionFilter(args[0], iteration); eq_time = System.currentTimeMillis() - eq_time; String msg = "Comples filter: expended time "; System.out.println(msg + eq_time + " milisseg."); // Close application clicking on "close" c.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); c.setVisible(true); } public ComplexDiffusionFilter(String aFile, int iteration) { BufferedImage src = null, dest = null, dest1 = null; int w, h, tipo, limiarR, pixels[][];
10 ď&#x201A;&#x2014;
José Iguelmar Miranda
int[][] bordas; Complex limiarJ; JLabel img1, img2, img3; // Does the image exist? File file = new File(aFile); try { src = ImageIO.read(file); } catch(Exception e) { System.out.println("Image '" + aFile + "' does not exist."); System.exit(0); } // JFrame title setTitle("Complex Diffusion: " + file.getName()); w = src.getWidth(); h = src.getHeight(); // Linear method // Return the imaginary component. limiarJ = new Complex(1, theta); // limiar = K = 1 (Gilboa) => return the real component. limiarR = 1; pixels = readPixels(src); // Show imaginary factor of the complex image. bordas = complexDiffusion(pixels, iteration, limiarJ); dest = criaImagem(bordas); // Show real factor of the complex image. bordas = complexDiffusion(pixels, iteration, limiarR); dest1 = criaImagem(bordas); // Define gridLayout 1 x 3 getContentPane().setLayout(new GridLayout(1, 3)); img1 = new JLabel(new ImageIcon(src)); img2 = new JLabel(new ImageIcon(dest)); img3 = new JLabel(new ImageIcon(dest1)); getContentPane().add(new JScrollPane(img1)); getContentPane().add(new JScrollPane(img2)); getContentPane().add(new JScrollPane(img3)); setSize(3*w, h); } ..private int[][] readPixels(BufferedImage src) { int w, h, pixels[][] = null; w = src.getWidth();
11
JosĂŠ Iguelmar Miranda
h = src.getHeight(); pixels = new int[w][h]; Raster srcR = src.getRaster(); for (int i = 0; i < h; i++) for (int j = 0; j < w; j++) { pixels[j][i] = srcR.getSample(j, i, 0); } return pixels; } // Complex diffusion: return imaginary component private int[][] complexDiffusion(int[][] in, int iterations, Complex limiar){ int h, w, bfi[][]; double dt, bufferMatriz[][]; w = in.length; h = in[0].length; bfi = new int[w][h]; dt = 0.2; // Define objects MatrizComplexa MatrizComplexa m1ComplexaJ = new MatrizComplexa m1ComplexaN = new MatrizComplexa m1ComplexaS = new MatrizComplexa m1ComplexaL = new MatrizComplexa m1ComplexaO = new
from the image MatrizComplexa(in); MatrizComplexa(in); MatrizComplexa(in); MatrizComplexa(in); MatrizComplexa(in);
System.out.print("\nComplex diffusion: imaginary component\n"); System.out.println("Calculating..."); for (int i = 0; i < iterations; i++) { m1ComplexaN.gradiente(1); m1ComplexaS.gradiente(2); m1ComplexaL.gradiente(3); m1ComplexaO.gradiente(4); System.out.print("."); m1ComplexaN.multiplica(limiar); m1ComplexaS.multiplica(limiar); m1ComplexaL.multiplica(limiar); m1ComplexaO.multiplica(limiar); m1ComplexaN.soma(m1ComplexaS); m1ComplexaO.soma(m1ComplexaL); m1ComplexaN.soma(m1ComplexaO); m1ComplexaN.multiplica(dt);
12 ď&#x201A;&#x2014;
JosĂŠ Iguelmar Miranda
m1ComplexaJ.soma(m1ComplexaN); m1ComplexaN.copia(m1ComplexaJ); m1ComplexaS.copia(m1ComplexaJ); m1ComplexaL.copia(m1ComplexaJ); m1ComplexaO.copia(m1ComplexaJ); } bufferMatriz = m1ComplexaJ.retornaParteImaginaria(); for(int lin = 0; lin < h; lin++) { for(int col = 0; col < w; col++) { bfi[col][lin] = (int) (128.0 + bufferMatriz[col][lin]/theta); } } // free storage area m1ComplexaN = null; m1ComplexaS = null; m1ComplexaL = null; m1ComplexaO = null; m1ComplexaJ = null; System.out.println("\n"); return bfi; } // Complex diffusion: return real component private int[][] complexDiffusion(int[][] in, int iterations, int limiar){ int h, w, bfi[][]; double dt; double[][] bufferMatriz; w = in.length; h = in[0].length; bfi = new int[w][h]; dt = 0.2; // Define objects MatrizComplexa MatrizComplexa m1ComplexaJ = new MatrizComplexa m1ComplexaN = new MatrizComplexa m1ComplexaS = new MatrizComplexa m1ComplexaL = new MatrizComplexa m1ComplexaO = new
from the image MatrizComplexa(in); MatrizComplexa(in); MatrizComplexa(in); MatrizComplexa(in); MatrizComplexa(in);
System.out.print("\nComplex diffusion: real component\n"); System.out.println("Calculating..."); for (int i = 0; i < iterations; i++) { m1ComplexaN.gradiente(1);
13 ď&#x201A;&#x2014;
José Iguelmar Miranda
m1ComplexaS.gradiente(2); m1ComplexaL.gradiente(3); m1ComplexaO.gradiente(4); System.out.print("."); m1ComplexaN.multiplica((double) m1ComplexaS.multiplica((double) m1ComplexaL.multiplica((double) m1ComplexaO.multiplica((double)
limiar); limiar); limiar); limiar);
m1ComplexaN.soma(m1ComplexaS); m1ComplexaO.soma(m1ComplexaL); m1ComplexaN.soma(m1ComplexaO); m1ComplexaN.multiplica(dt); m1ComplexaJ.soma(m1ComplexaN); m1ComplexaN.copia(m1ComplexaJ); m1ComplexaS.copia(m1ComplexaJ); m1ComplexaL.copia(m1ComplexaJ); m1ComplexaO.copia(m1ComplexaJ); } bufferMatriz = m1ComplexaJ.retornaParteReal(); for(int lin = 0; lin < h; lin++) { for(int col = 0; col < w; col++) { bfi[col][lin] = (int) Math.abs(bufferMatriz[col][lin]); } } // Free storage area m1ComplexaN = null; m1ComplexaS = null; m1ComplexaL = null; m1ComplexaO = null; m1ComplexaJ = null; System.out.println("\n"); return bfi; } private BufferedImage criaImagem(int[][] in) { int w, h, tipo, pixels[], ind = 0; BufferedImage dest = null; w = in.length; h = in[0].length; pixels = new int[w*h]; tipo = BufferedImage.TYPE_BYTE_GRAY; dest = new BufferedImage(w, h, tipo);
14
José Iguelmar Miranda
WritableRaster destWR = dest.getRaster(); for(int lin = 0; lin < h; lin++) for(int col = 0; col < w; col++) pixels[ind++] = in[col][lin]; destWR.setPixels(0, 0, w, h, pixels); return dest; } }
The complex diffusion filter constructor is defined with two parameters:
1. aFile: file name; the image to be filtered. 2. Iteration: number of iterations; equivalent to the number of convolutions in time t (equation 3). The constructor reads the source image, defines the variables used by the algorithm, and calls the complex diffusion process. It returns the imaginary and real components of the original image, which are displayed in a JFrame. The method that actually implements the complex diffusion is designed with two signatures (polymorphism): private int[][] complexDiffusion(int[][] in, int iterations, Complex limiar){ … }
And private int[][] complexDiffusion(int[][] in, int iterations, int limiar){… }
The first method returns the imaginary component of the complex number and the second method returns the real part. You can choose to show only one or another component, depending on the method called. Whatever the case chosen, the called method gets the image in a new format, as a bidimensional integer matrix, the number of iterations needed, and the threshold 15
José Iguelmar Miranda
value, defined as a complex or integer number. As a result, the called method returns the image convolved in another bidimensional, integer matrix. The pixels values of the returned image are computed according to the asked complex number. To return the complex component: bfi[col][lin] = (int) (128.0 + bufferMatriz[col][lin]/theta);
Where theta is defined as π/1000. In order to return the real component: bfi[col][lin] = (int) Math.abs(bufferMatriz[col][lin]);
The heart of the method resides on the loop, according to the iterations parameter that defines how many times the image must be filtered. Equation (5) is implemented inside this loop. The class ComplexDiffusionFilter depends on another class, MatrizComplexa, as shown below:
/********************************************************************* MatrizComplexa.java – implements the complex matrix class. WRITTEN BY: José Iguelmar Miranda & João Camargo Neto. DATE:
February 2007
Copyright (c) 2007 Embrapa Informática Agropecuária PERMISSION TO COPY: This program is free software, under the GNU General Public License (GPL); permission to use, copy and modify this software and its documentation for NON-COMMERCIAL purposes is granted, without fee, provided that an acknowledgement to the authors, José Iguelmar Miranda & João Camargo Neto, at www.cnptia.embrapa.br, appears in all copies. Embrapa Informática Agropecuária makes no representations about the suitability or fitness of the software for any or for a particular purpose. Embrapa Informática Agropecuária shall not be liable for any damages suffered as a result of using, modifying or distributing this software or its derivatives. For a copy of GNU General Public License, write to: Free Software Foundation, Inc., 59 Temple Street, Suite 330, Boston, MA 02111-1307 USA. ********************************************************************* Description: This program implements the class for dealing with complex matrix. Informações do CVS:
16
JosĂŠ Iguelmar Miranda
$Source$: $Revision$: $Date$: *********************************************************************/ // Generic package import java.text.DecimalFormat; public class MatrizComplexa { // ATTRIBUTES public Complex[][] MComplexa; private int col, lin; // CLASS CONSTRUCTORS public MatrizComplexa(int c, int l){ this.col = c; this.lin = l; this.MComplexa = new Complex[col][lin]; for(int j = 0; j < lin; j++){ for(int i = 0; i < col; i++){ this.MComplexa[i][j] = new Complex(0.0, 0.0); } } } // Transform an integer matrix into a complex values matrix, // where the integer value is the real part and has zero // as imaginary value; public MatrizComplexa(int[][] a){ col = a.length; lin = a[0].length; this.MComplexa = new Complex[col][lin]; for(int j = 0; j < lin; j++){ for(int i = 0; i < col; i++){ this.MComplexa[i][j] = new Complex((double) a[i][j], 0.0); } } } // PUBLIC METHODS public int retornaNumeroLinhas(){ return lin; } public int retornaNumeroColunas(){ return col; } public Complex[][] retornaMatriz(){ return MComplexa; } public void subtrai(MatrizComplexa b){ double fatorI, // imaginary factor fatorR; // real factor
17 ď&#x201A;&#x2014;
JosĂŠ Iguelmar Miranda
for(int j = 0; j < lin; j++){ for(int i = 0; i < col; i++){ fatorR = this.MComplexa[i][j].re - b.MComplexa[i][j].re; fatorI = this.MComplexa[i][j].im - b.MComplexa[i][j].im; this.MComplexa[i][j].re = fatorR; this.MComplexa[i][j].im = fatorI; } } } public void soma(MatrizComplexa b){ double fatorI, // imaginary factor fatorR; // real factor for(int j = 0; j < lin; j++){ for(int i = 0; i < col; i++){ fatorR = this.MComplexa[i][j].re + b.MComplexa[i][j].re; fatorI = this.MComplexa[i][j].im + b.MComplexa[i][j].im; this.MComplexa[i][j].re = fatorR; this.MComplexa[i][j].im = fatorI; } } } public void somaElemento(Complex b){ double fatorI, // imaginary factor fatorR; // real factor for(int j = 0; j < lin; j++){ for(int i = 0; i < col; i++){ fatorR = this.MComplexa[i][j].re + b.re; fatorI = this.MComplexa[i][j].im + b.im; this.MComplexa[i][j].re = fatorR; this.MComplexa[i][j].im = fatorI; } } } public void atribuiElemento(Complex b){ for(int j = 0; j < lin; j++){ for(int i = 0; i < col; i++){ this.MComplexa[i][j].re = b.re; this.MComplexa[i][j].im = b.im; } } } public void somaElemento(double b){ double fatorI, // imaginary factor fatorR; // real factor for(int j = 0; j < lin; j++){
18 ď&#x201A;&#x2014;
José Iguelmar Miranda
for(int i = 0; i < col; i++){ fatorR = this.MComplexa[i][j].re + b; fatorI = this.MComplexa[i][j].im + b; this.MComplexa[i][j].re = fatorR; this.MComplexa[i][j].im = fatorI; } } } public void multiplica(MatrizComplexa b){ double fatorI, // imaginary factor fatorR; // real factor for(int j = 0; j < lin; j++){ for(int i = 0; i < col; i++){ fatorR = this.MComplexa[i][j].re* this.MComplexa[i][j].im* fatorI = this.MComplexa[i][j].re* this.MComplexa[i][j].im* this.MComplexa[i][j].re = fatorR; this.MComplexa[i][j].im = fatorI; } }
b.MComplexa[i][j].re b.MComplexa[i][j].im; b.MComplexa[i][j].im + b.MComplexa[i][j].re;
} public void multiplica(double b){ double fatorI, // imaginary factor fatorR; // real factor for(int j = 0; j < lin; j++){ for(int i = 0; i < col; i++){ fatorR = this.MComplexa[i][j].re* b; fatorI = this.MComplexa[i][j].im* b; this.MComplexa[i][j].re = fatorR; this.MComplexa[i][j].im = fatorI; } } } public void multiplica(Complex b){ double fatorI, // imaginary factor fatorR; // real factor for(int j = 0; j < lin; j++){ for(int i = 0; i < col; i++){ fatorR = this.MComplexa[i][j].re* this.MComplexa[i][j].im* fatorI = this.MComplexa[i][j].re* this.MComplexa[i][j].im* this.MComplexa[i][j].re = fatorR; this.MComplexa[i][j].im = fatorI; } }
b.re – b.im; b.im + b.re;
}
19
José Iguelmar Miranda
public double[][] abs(){ double[][] absVec = new double[col][lin]; for(int j = 0; j < lin; j++) for(int i = 0; i < col; i++) absVec[i][j] = this.MComplexa[i][j].abs(); return absVec; } public void divide(double b){ double fatorI, // imaginary factor fatorR; // real factor for(int j = 0; j < lin; j++){ for(int i = 0; i < col; i++){ fatorR = this.MComplexa[i][j].re/ b; fatorI = this.MComplexa[i][j].im/ b; this.MComplexa[i][j].re = fatorR; this.MComplexa[i][j].im = fatorI; } } } public void divideElemento(MatrizComplexa b){ double fatorI, // imaginary factor fatorR; // real factor for(int j = 0; j < lin; j++){ for(int i = 0; i < col; i++){ //this.MComplexa[i][j].divide(b.MComplexa[i][j]); fatorR = this.MComplexa[i][j].re/b.MComplexa[i][j].re; fatorI = this.MComplexa[i][j].im/b.MComplexa[i][j].im; this.MComplexa[i][j].re = fatorR; this.MComplexa[i][j].im = fatorI; } } } public void divideElementoPorMatriz(Complex c,MatrizComplexa b){ for(int j = 0; j < lin; j++){ for(int i = 0; i < col; i++){ this.MComplexa[i][j].re = c.re/b.MComplexa[i][j].im; this.MComplexa[i][j].im = c.im/b.MComplexa[i][j].im; } } } public void copia(MatrizComplexa b){ for(int j = 0; j < lin; j++){ for(int i = 0; i < col; i++){ this.MComplexa[i][j].re = b.MComplexa[i][j].re; this.MComplexa[i][j].im = b.MComplexa[i][j].im; }
20
José Iguelmar Miranda
} } public double[][] retornaParteReal(){ double[][] mcReal; Complex[][] a = this.MComplexa; mcReal = new double[col][lin]; for(int j = 0; j < lin; j++){ for(int i = 0; i < col; i++){ mcReal[i][j] = a[i][j].re; } } return mcReal; } public double[][] retornaParteImaginaria(){ double[][] mcImaginario; mcImaginario = new double[col][lin]; for(int j = 0; j < lin; j++){ for(int i = 0; i < col; i++){ mcImaginario[i][j] = this.MComplexa[i][j].im; } } return mcImaginario; } public void retornaParteImaginaria(MatrizComplexa b){ for(int j = 0; j < lin; j++){ for(int i = 0; i < col; i++){ this.MComplexa[i][j].re = 0.0; this.MComplexa[i][j].im = b.MComplexa[i][j].im; } } } public void elevaQuadrado(){ double fatorI, // imaginary factor fatorR; // real factor for(int j = 0; j < lin; j++){ for(int i = 0; i < col; i++){ fatorR = this.MComplexa[i][j].re*this.MComplexa[i][j].re; fatorI = this.MComplexa[i][j].im*this.MComplexa[i][j].im; this.MComplexa[i][j].re = fatorR; this.MComplexa[i][j].im = fatorI; } } } public void inverteElemento(){ double fatorI, // imaginary factor fatorR; // real factor
21
José Iguelmar Miranda
fatorR2, fatorI2; for(int j = 0; j < lin; j++){ for(int i = 0; i < col; i++){ fatorR = this.MComplexa[i][j].re; fatorI = 1.0/this.MComplexa[i][j].im; fatorR2 = fatorR*fatorR; fatorI2 = fatorI*fatorI; this.MComplexa[i][j].re = fatorR/(fatorR2+fatorI2); this.MComplexa[i][j].im = -fatorI/(fatorR2+fatorI2); } } } public void somaElementoReal(double valor){ double fatorR; // real factor for(int j = 0; j < lin; j++){ for(int i = 0; i < col; i++){ fatorR = this.MComplexa[i][j].re + valor; this.MComplexa[i][j].re = fatorR; } } } public void somaElementoImaginario(double valor){ double fatorI; // imaginary factor for(int j = 0; j < lin; j++){ for(int i = 0; i < col; i++){ fatorI = this.MComplexa[i][j].im + valor; this.MComplexa[i][j].im = fatorI; } } } // Print the matrix with ‘decimals’ decimal precision public void imprimeMatriz(int decimais) { String padrao = "#."; for (int i = 0; i < decimais; i++) padrao += "#"; DecimalFormat formato = null; formato = new DecimalFormat(padrao); String elemento; System.out.println(); if (this.MComplexa == null){ System.out.println("Empty Matrix."); } else { for (int j = 0; j < lin; j++){ for (int i = 0; i < col; i++){ elemento = formato.format(this.MComplexa[i][j].re); System.out.print(elemento+ " + "); elemento = formato.format(this.MComplexa[i][j].im); System.out.print(elemento+ "i ");
22
José Iguelmar Miranda
} System.out.println(); } } } // Print the matrix with ‘decimals’ decimal precision public void imprimeReal(int decimais) { String padrao = "#."; for (int i = 0; i < decimais; i++) padrao += "#"; DecimalFormat formato = null; formato = new DecimalFormat(padrao); String elemento; System.out.println(); if (this.MComplexa == null){ System.out.println("Empty Matrix."); } else { for (int j = 0; j < lin; j++){ for (int i = 0; i < col; i++){ elemento = formato.format(this.MComplexa[i][j].re); System.out.print(elemento+ "\t"); } System.out.println(); } } } // Print the matrix with ‘decimals’ decimal precision public void imprimeImaginario(int decimais) { String padrao = "#."; for (int i = 0; i < decimais; i++) padrao += "#"; DecimalFormat formato = null; formato = new DecimalFormat(padrao); String elemento; System.out.println(); if (this.MComplexa == null){ System.out.println("Empty Matrix."); } else { for (int j = 0; j < lin; j++){ for (int i = 0; i < col; i++){ elemento = formato.format(this.MComplexa[i][j].im); System.out.print(elemento+ "i\t"); } System.out.println(); } } } // Gradient: parameters // dir = 1 ==> North // dir = 2 ==> South
23
José Iguelmar Miranda
// dir = 3 ==> East // dir = 4 ==> West public void gradiente(int dir){ double pReA, // actual real pixel pReG, // real pixel in gradient direction pImA, // actual imaginary pixel pImG; // imaginary pixel in gradient direction switch(dir){ case 1: // North for(int i = 0; i < col; i++){ for(int j = lin - 1; j > 0; j--){ pReA = this.MComplexa[i][j].re; pReG = this.MComplexa[i][j-1].re; pImA = this.MComplexa[i][j].im; pImG = this.MComplexa[i][j-1].im; this.MComplexa[i][j].re = pReG - pReA; this.MComplexa[i][j].im = pImG - pImA; } } for(int i = 0; i < col; i++){ this.MComplexa[i][0].re = 0.0; this.MComplexa[i][0].im = 0.0; } break; case 2: // South for(int i = 0; i < col; i++){ for(int j = 0; j < lin - 1; j++){ pReA = this.MComplexa[i][j].re; pReG = this.MComplexa[i][j+1].re; pImA = this.MComplexa[i][j].im; pImG = this.MComplexa[i][j+1].im; this.MComplexa[i][j].re = pReG - pReA; this.MComplexa[i][j].im = pImG - pImA; } } for(int i = 0; i < col; i++){ this.MComplexa[i][lin-1].re = 0.0; this.MComplexa[i][lin-1].im = 0.0; } break; case 3: // East for(int j = 0; j < lin; j++){ for(int i = 0; i < col - 1; i++){ pReA = this.MComplexa[i][j].re; pReG = this.MComplexa[i+1][j].re; pImA = this.MComplexa[i][j].im; pImG = this.MComplexa[i+1][j].im; this.MComplexa[i][j].re = pReG - pReA; this.MComplexa[i][j].im = pImG - pImA; } } for(int j = 0; j < lin; j++){ this.MComplexa[col-1][j].re = 0.0; this.MComplexa[col-1][j].im = 0.0; } break;
24
JosĂŠ Iguelmar Miranda
case 4: // West for(int j = 0; j < lin; j++){ for(int i = col - 1; i > 0; i--){ pReA = this.MComplexa[i][j].re; pReG = this.MComplexa[i-1][j].re; pImA = this.MComplexa[i][j].im; pImG = this.MComplexa[i-1][j].im; this.MComplexa[i][j].re = pReG - pReA; this.MComplexa[i][j].im = pImG - pImA; } } for(int j = 0; j < lin; j++){ this.MComplexa[0][j].re = 0.0; this.MComplexa[0][j].im = 0.0; } break; } } }
As seen in the above method, it calls for the Complex class, implemented as:
public class Complex { public double re; // the real part public double im; // the imaginary part // Create a new object with the given real and imaginary parts public Complex(double real, double imag) { this.re = real; this.im = imag; } public Complex() { this.re = 0; this.im = 0; } // Return a string representation of the invoking object public String toString() { return re + " + " + im + "i"; } // Return |this| public double abs() { return Math.sqrt(re*re + im*im); } // Return a new object whose value is (this + b) public Complex plus(Complex b) { Complex a = this; // invoking object double real = a.re + b.re; double imag = a.im + b.im;
25 ď&#x201A;&#x2014;
JosĂŠ Iguelmar Miranda
Complex sum = new Complex(real, imag); return sum; } // Return a new object whose value is (this - b) public Complex minus(Complex b) { Complex a = this; double real = a.re - b.re; double imag = a.im - b.im; Complex diff = new Complex(real, imag); return diff; } // Return a new object whose value public Complex times(Complex b) { Complex a = this; double real = a.re * b.re - a.im double imag = a.re * b.im + a.im Complex prod = new Complex(real, return prod; }
is (this * b) * b.im; * b.re; imag);
// Return a new object whose value is (this * alpha) public Complex times(double alpha) { return new Complex(alpha * re, alpha * im); } public Complex divide(double b){ Complex a = this; // invoking object double real, imag; if (b != 0.0) { real = a.re/b; imag = a.im/b; } else { String str = "Complex::divide(double b) -> division by zero."; System.out.println(str); return new Complex(0.0, 0.0); } return new Complex(real, imag); } public void divide(Complex b){ Complex a = this; // invoking object double real = a.re*b.re+a.im*b.im; double imag = a.im*b.re-a.re*b.im; double denominador = b.re*b.re+b.im*b.im; if (denominador != 0.0) { a.re = real/denominador; a.im = imag/denominador; } else { String str = "Complex::divide(Complex b) -> division by zero."; System.out.println(str); a.re = real/1; a.im = imag/1;
26 ď&#x201A;&#x2014;
José Iguelmar Miranda
} } // Return a new object whose value is the conjugate of this public Complex conjugate() { return new Complex(re, -im); } }
Case Study Fig. 3 shows the result of the linear complex diffusion filter. The real part outputted by the filter is shown in the upper portion of the picture. The first image represents zero iterations, that is to say, it is the original image. As the number of iterations increases, the image becomes more blurred, due to the fact that it has its edges smoothed continuously by the algorithm. The continuous effect of this smoothing process tends to difficult the edges identification, as seen in the picture.
Figure 3. Linear complex diffusion (θ = π/1000). Image values after 0, 1, 5 and 25 iterations. From left: real part (above) and imaginary (below). In the lower portion of the picture, appear the equivalent imaginary parts of the filtered image. In opposition to what happens with the real part, which is constantly smoothed, loosing borders characteristics, the filter in the imaginary part keeps the characteristics of the original image, while enhancing its edges, as proposed by the filter.
27
José Iguelmar Miranda
Please note: if you intend to use this material in any publication, please give credits to: MIRANDA, J. I.; CAMARGO NETO, Filtro de difusão linear complexa para detecção de bordas: implementação Java. Campinas: Embrapa Informática Agropecuária, 2006. Páginas: 5 (Embrapa Informática Agropecuária. Comunicado Técnico, 75). Available in: <http://www.cnptia.embrapa.br/files/ct75.pdf>. FOR ANY additional information, please contact: jose.ig.miranda@gmail.com
Bibliography GILBOA, G.; SOCHE, N.; ZEEVI, Y. Y. Image enhancement and denoising by complex diffusion processes. IEEE Transactions on Pattern Analysis and Machine Intelligence. 26(8):1020-1036, 2004.
PARKER, J.R. Algorithms for image processing and computer vision. New York, NY: John Wiley & Sons, 1997. 417 p.
PERONA, P.; MALIK, J. Scale-space and edge detection using anisotropic diffusion. IEEE Transactions on Patterns Analysis and Machine Intelligence, 12(7):629-639, 1990.
SCHOWENGERDT, R. A. Remote sensing, models and methods for image processing. San Diego, CA: Academic Press, 1997. 522p.
28