miércoles, 18 de septiembre de 2013

Java: crear histograma acumulado. Procesamiento digital de imágenes IV.

En esta entrada vamos a ver cómo crear el histograma acumulado de una imagen (en su día, ya lo hicimos en VB.NET). Lo primero que tenemos que saber es qué es el histograma acumulado de una imagen. El histograma acumulado de una imagen no es más que contar, para cada canal RGB, el número de píxeles que hay de cada componente. Por ejemplo, para el canal rojo, el número de píxeles que tienen valor 255.
Hecha esta pequeña introducción vamos al lío..
Lo primero es crear un nuevo proyecto en nuestro IDE favorito, en este caso voy a utilizar NetBeans. Y vamos a añadir 3 clases. Las dos primeras serán Histograma y DibujarHistograma, y la tercera será nuestro formulario (JFrame Form), es decir, nuestra parte visual de la aplicación, a la que llamaremos Formulario. A continuación vamos a ir viendo cada clase:

Histograma
En esta clase será donde creemos la matriz que almacene nuestro histograma. La función principal se va a denominar histograma y será la encargada de crear nuestros histogramas a partir de un BufferedImage que recibirá como parámetros. Esta función devolverá una matriz de enteros de dos dimensiones (int[5][256]), siendo la primera dimensión el canal del histograma ([0][] -> rojo, [1][] -> verde, [2][] -> azul, [3][] -> alfa, [4][] -> escala de grises) y la segunda el valor de ese píxel (va de 0 a 255 [256]). Si por ejemplo queremos saber el número de píxeles que tiene una imagen para el valor 100 del canal verde, los índices serían [1][100]. El código fuente de la clase es el siguiente:

package histograma;

import java.awt.Color;
import java.awt.image.BufferedImage;

/**
 *
 * @author Luis Marcos
 */
public class Histograma {
    
    /**
     * Calculamos la media de una variable Color
     * @param color Color del cual se quiere obtener la media
     * @return entero con el valor de la media
     */
    private int calcularMedia(Color color){
        int mediaColor;
        mediaColor=(int)((color.getRed()+color.getGreen()+color.getBlue())/3);
        return mediaColor;
    }
    
    /**
     * Devuelve el histograma de la imagen.
     * @param imagen BufferedImagen de la cual se quiere obtener el histograma
     * @return Devuelve una variable int[5][256], donde el primer campo[0] corresponde
     * al canal Rojo, [1]=verde [2]=azul [3]=alfa [4]=escala grises
     */
    public int[][] histograma(BufferedImage imagen){
        Color colorAuxiliar;
        /*Creamos la variable que contendrá el histograma
        El primer campo [0], almacenará el histograma Rojo
        [1]=verde [2]=azul [3]=alfa [4]=escala grises*/
        int histogramaReturn[][]=new int[5][256];
        //Recorremos la imagen
        for( int i = 0; i < imagen.getWidth(); i++ ){
            for( int j = 0; j < imagen.getHeight(); j++ ){
                //Obtenemos color del píxel actual
                colorAuxiliar=new Color(imagen.getRGB(i, j));
                //Sumamos una unidad en la fila roja [0], 
                //en la columna del color rojo obtenido
                histogramaReturn[0][colorAuxiliar.getRed()]+=1;
                histogramaReturn[1][colorAuxiliar.getGreen()]+=1;
                histogramaReturn[2][colorAuxiliar.getBlue()]+=1;
                histogramaReturn[3][colorAuxiliar.getAlpha()]+=1;
                histogramaReturn[4][calcularMedia(colorAuxiliar)]+=1;
            }
        }
        return histogramaReturn;
    }
}

DibujarHistograma
En esta clase vamos a dibujar el histograma como un gráfico de barras, a partir de los histogramas obtenidos con la clase anterior. Para poder dibujar gráficos de barras nos vamos a servir de un par de librerías libres (y muy potentes) que nos van a hacer la vida muy fácil. Estas librerías se llaman jfreechart y jcommon, las podemos encontrar en la página oficial o desde aquí. En concreto, para este proyecto se ha utilizado la versión 1.0.15 de jfreechart y la 1.0.20 de jcommon. Una vez las tenemos descargadas, las tenemos que agregar a nuestro proyecto, para ello, basta con hacer clic con el botón derecho en nuestro proyecto y pulsar en Propiedades.

Agregar librerías

Una vez hecho esto, nos aparecerá la ventana de propiedades del proyecto y en la parte de la izquierda nos dirigimos a la sección Librerías. Por último, pulsamos en añadir JAR/Carpeta, buscamos nuestras dos librerías y las añadimos. Sólo queda aceptar.

Agregando librerías

Ahora sí, ya tenemos todos dispuesto para dibujar nuestros histogramas. El método que los dibujará se llama crearHistograma y recibe como parámetros, una matriz de una dimensión (que será la que contenga el histograma de un canal), un JPanel que será donde se dibuje el histograma y el color del cual se quiere dibujar el histograma. Como vemos, es un método, con lo que no devuelve nada, ya que el JPanel que le pasemos como parámetro será donde se dibuje nuestro gráfico de barras. El código fuente es el siguiente:

package histograma;

import java.awt.Color;
import javax.swing.JPanel;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.BarRenderer;
import org.jfree.data.category.DefaultCategoryDataset;

/**
 *
 * @author Luis Marcos
 */
public class DibujarGrafico {
      /**
     * Crea un gráfico de barras y lo asigna al JPanel recibido
     * @param histograma histograma de frecuencias (int[256]).
     * @param jPanelHistograma JPanel donde el histograma será dibujado
     * @param colorBarras color de cuál será dibujado el histograma
     */
    public void crearHistograma(int[] histograma,JPanel jPanelHistograma,Color colorBarras) {
        //Creamos el dataSet y añadimos el histograma
        DefaultCategoryDataset dataset = new DefaultCategoryDataset();
        String serie = "Number of píxels";
        for (int i = 0; i < histograma.length; i++){
            dataset.addValue(histograma[i], serie, "" + i);
        }
        //Creamos el chart
        JFreeChart chart = ChartFactory.createBarChart("Frequency Histogram", null, null,
                                    dataset, PlotOrientation.VERTICAL, true, true, false);
        //Modificamos el diseño del chart
        CategoryPlot plot = (CategoryPlot) chart.getPlot();
        BarRenderer renderer = (BarRenderer) plot.getRenderer();
        renderer.setSeriesPaint(0, colorBarras);
        chart.setAntiAlias(true);
        chart.setBackgroundPaint(new Color(214, 217, 223)); 
        jPanelHistograma.removeAll();
        jPanelHistograma.repaint();
        jPanelHistograma.setLayout(new java.awt.BorderLayout());
        jPanelHistograma.add(new ChartPanel(chart));
        jPanelHistograma.validate();    
    }
}

Formulario
El siguiente paso es crear la parte visual de nuestra aplicación, para ello (como vimos) agregamos un JFrame Form y le damos el siguiente aspecto:

Aspecto formulario

Donde los recuadros (vacíos) negros, son JPanel donde se pintarán los histogramas. Ahora sólo queda hacer doble clic sobre el botón Dibujar histogramas, para invocar el evento clic del botón, y añadimos el siguiente código:

    private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {                                         
        try {
            //LEEMOS LA IMAGEN
            Image imagen=ImageIO.read(new File(jTextField1.getText()));
            //establecemos la imagen como imagen en el jLabel
            jLabel1_Imagen.setIcon(new ImageIcon(imagen));
            //CREAMOS EL HISTOGRAMAS
            Histograma ObjHistograma=new Histograma();
            int[][] histograma=ObjHistograma.histograma((BufferedImage)imagen);
            //DIBUJAMOS LOS HISTOGRAMAS
            DibujarGrafico ObjDibujaHisto=new DibujarGrafico();
            for (int i = 0; i < 5; i++) {
                //extraemos un canal del histograma 
                int[] histogramaCanal=new int[256];
                System.arraycopy(histograma[i], 0, histogramaCanal, 0, histograma[i].length);
                //Dibujamos en el panel
                switch(i){
                    case 0:
                        ObjDibujaHisto.crearHistograma(histogramaCanal, jPanel_rojo, Color.red);
                        break;
                    case 1:
                        ObjDibujaHisto.crearHistograma(histogramaCanal, jPanel_verde, Color.green);
                        break;
                    case 2:
                        ObjDibujaHisto.crearHistograma(histogramaCanal, jPanel_azul, Color.blue);
                        break;
                    case 3:
                        ObjDibujaHisto.crearHistograma(histogramaCanal, jPanel_alfa, Color.black);
                        break;
                    case 4:
                        ObjDibujaHisto.crearHistograma(histogramaCanal, JPanel_Gris, Color.gray);
                        break;
                }
            }
           
        } catch (Exception e) {
            JOptionPane.showMessageDialog(null, "No se pudo cargar la imagen", "Error", JOptionPane.ERROR_MESSAGE);
        }
    }     

Eso sí, que no se nos olvide agregar las referencias:

import java.awt.Color;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JOptionPane;

El resultado final de nuestra aplicación sería este:

Resultado final histogramas

Como hemos podido ver, código facilito (como siempre digo, se podría hacer mucho mejor) y resultados bastante buenos. Cualquier duda/crítica/sugerencia en los comentarios. Podéis descargar el código fuente aquí:

4 comentarios:

  1. excelente, solo tengo una duda o mejor dicho una pregunta, se puede hacer un buscador de imágenes con otra imagen para buscar los coincidentes, algo así como el buscador de imágenes de google pero con imágenes y no con palabras ...

    ResponderEliminar
    Respuestas
    1. Hola.
      Claro que se podría hacer, no obstante, Google ya lo hace. Si vas al buscador de Google imágenes, puedes subir una foto y busca fotos similares.
      de todas maneras, el comparar imágenes, no es algo trivial y queda fuera del alcance de este humildes blog ;)
      un saludo

      Eliminar
  2. Gracias amigo es una muy buena pagina de referencia me ayudo mucho

    ResponderEliminar
  3. Muy buena!! la verdad es que me salvaste la tarea!!

    ResponderEliminar