Detección de colores de una imágen mediante K-means (JAVA)

Aquí os dejo la 1a práctica de Inteligencia Artificial 1, una detección de colores dominantes de una imágen mediante k-means con versión línea de comandos y con GUI y todo.

Leed las notas antes que nada.







NOTAS:
 .  CUIDADO:
/!   El archivo "swing-layout-1.0.jar" es ESENCIAL para que funcione la GUI, pues es la API de NetBeans para
^^^   hacer ventanas. No se ha de borrar o automaticamente no se podrá ni compilar ni ejecutar la práctica.



Para las tareas de ejecución y compilado de la práctica se han incluído sendos scripts, tanto .sh para operativos UNIX-like como .bat para operativos de Micro$soft.

Compilación Linux:
        CLASSPATH=".:swing-layout-1.0.jar" javac *.java

Ejecución Linux:
        CLASSPATH=".:swing-layout-1.0.jar" java Menu1

Compilación Windows:
        set CLASSPATH=".;swing-layout-1.0.jar"
        javac *.java

Ejecución Windows:
        set CLASSPATH=".;swing-layout-1.0.jar"
        java Menu1

Al ejecutar la práctica aparece una GUI.

También se puede ejecutar el k-menas sin la GUI, mediante la clase detecta.java, en cuyo caso no necesitaremos el .jar adjunto. La sintaxis es:
java detecta ruta_de_la_imagen numero_de_colores

Además, si ponemos cualquier cosa como 3er argumento se ofrecerá muchísima información del proceso.




kmeans.java:

import java.io.*;
import java.util.*;
 
 
 
public class kmeans {


    /** Numero de clusters */
    private int k;

    /** Debug por consola */
    boolean debug=false;

    /** Paleta final, en las mismas posiciones que los clusters */
    int[] paleta;

    /** Array de clusters */
    private int[] clusters;


    /** Numero de iteraciones (log) */
    private int nIterations;

    /** Vector de pixeles */
    private int[] pixeles;

    /** Tamanyo del vector de pixeles **/
    private int tamanyo;

    /** Asignacion a cluster de los pixeles; sirve para sacar la imagen recompuesta **/
    private int[] asignacion;

    /**
    * Constructor kmeans: kmeans( k, img, tamanyo, debug)
    *   k = numero de clases a encontrar
    *   img = pixeles de la imagen en un int[]
    *   tamanyo = tamanyo de la imagen en pixeles, igual que tamanyo de img
    *   debug = bool, activa el debug
    */
    public kmeans(int k, int[] pixeles, int tamanyo, boolean debug) {
        int ii=0;
        this.debug = debug;
        this.k = k;
        this.nIterations = 0;
        this.tamanyo = tamanyo;
        this.clusters = new int[k];
        this.paleta = new int[k];
        this.asignacion = new int[this.tamanyo];
        this.pixeles = new int[tamanyo];
        /** Inicializamos los clusters con los primeros k pixeles no repetidos **/
        for( int i=0; i < tamanyo; i++) this.pixeles[i] = (pixeles[i] & 0x00FFFFFF);

        /** Cogemos los 1os k colores diferentes como iniciales de las clases **/
        int cluster=0;
        for( int i=0; i<this.tamanyo && cluster < this.k; i++) {
            boolean continuar=true;
            for( ii = 0; ii < cluster && continuar == true; ii++) if( this.pixeles[i] == this.clusters[ii]) continuar=false;
            if( continuar == true ) {
                this.clusters[cluster] = this.pixeles[i];
                if(debug) System.out.println("Cluster inicial " + cluster + ", pixel " + i + ": rojo=" + ( (this.clusters[cluster] & 0xFF0000) / 0x10000) +"; verde=" + ( (this.clusters[cluster] & 0x00FF00) / 0x100) + " azul=" + (this.clusters[cluster] & 0x0000FF) );
                cluster++;
                }
            }
       runkmeans();
       } // fin del kmeans()


    /**
     * Runs the k-means algorithm over the data set
     */
    private void runkmeans() {
        int[] clustersant=new int[this.k];
        int[] clusterstmpR = new int[this.k];
        int[] clusterstmpG = new int[this.k];
        int[] clusterstmpB = new int[this.k];
        int[] clusterstmpn = new int[this.k];
        int distanciamin,dtmp;
        int pixeli,clusteri,indexmin;
        boolean mismocluster;

        do {
            mismocluster = true;
            /**
            Para cada pixel encontramos su cluster
            **/
            for(pixeli=0; pixeli < this.tamanyo; pixeli++) {
                distanciamin=distancia(this.clusters[0],this.pixeles[pixeli]);
                indexmin=0;
                /**
                Para todos los clusters, calculamos la distáncia y escogemos
                la minima como cluster al que pertenece ese pixel
                **/
                for( clusteri=0; clusteri<this.k; clusteri++) {
                    dtmp=distancia(this.pixeles[pixeli],this.clusters[clusteri]);
                    if( dtmp < distanciamin ) {
                        distanciamin = dtmp;
                        indexmin = clusteri;
                        }
                    }
                this.asignacion[pixeli] = indexmin;
                } // fin de analisis de pixeles
            /**
            Ponemos a 0 el acumulador temporal...
            **/
            for(clusteri=0; clusteri<this.k; clusteri++){
                clusterstmpR[clusteri]=0;
                clusterstmpG[clusteri]=0;
                clusterstmpB[clusteri]=0;
                clusterstmpn[clusteri]=0;
                }
            /**
            Para los pixeles reagrupados, ahora encontramos el nuevo 'centro de
            gravedad', que sera la nueva ubicacion del cluster
            Para ello usamos unos acumuladores donde iremos sumando
            **/
            for(pixeli=0; pixeli<this.tamanyo; pixeli++){
                clusterstmpR[this.asignacion[pixeli]] += (this.pixeles[pixeli] & 0x00FF0000) / 0x10000;
                clusterstmpG[this.asignacion[pixeli]] += (this.pixeles[pixeli] & 0x0000FF00) / 0x100;
                clusterstmpB[this.asignacion[pixeli]] += (this.pixeles[pixeli] & 0x000000FF);
                clusterstmpn[this.asignacion[pixeli]]++;
                }
            for(clusteri=0; clusteri<this.k; clusteri++){
                if( clusterstmpn[clusteri] > 0 ) {
                    clusterstmpR[clusteri]=clusterstmpR[clusteri]/clusterstmpn[clusteri];
                    clusterstmpG[clusteri]=clusterstmpG[clusteri]/clusterstmpn[clusteri];
                    clusterstmpB[clusteri]=clusterstmpB[clusteri]/clusterstmpn[clusteri];
                    clustersant[clusteri] = this.clusters[clusteri];
                    this.clusters[clusteri] = clusterstmpR[clusteri]*0x10000 + clusterstmpG[clusteri]*0x100 + clusterstmpB[clusteri];
                    if( clusters[clusteri] != clustersant[clusteri] ) mismocluster = false;
                    }
                  else System.out.println("/! Cluster "+clusteri+" con 0 pixeles!");
                }

            if( debug ) {
                System.out.println("> Iteracion " + nIterations );
                for( int ii = 0; ii < this.k ; ii++)  System.out.println("   -cluster " + ii + ": rojo=" + ( (this.clusters[ii] & 0xFF0000) / 0x10000) +"; verde=" + ( (this.clusters[ii] & 0x00FF00) / 0x100) + " azul=" + (this.clusters[ii] & 0x0000FF) );
                }

            this.nIterations++;
            } while ( !mismocluster );
        if( debug ) System.out.println("-- usadas " + this.nIterations + " iteraciones--");
        } // end of runkmeans()

    /**
    * Para ahorrar tiempo de computo calculamos la distancia al cuadrado
    * Esto no afecta a las comparaciones, que es lo que nos interesa
    * Asi nos ahorramos hacer la raiz cuadrada
    */
    private int distancia(int puntoa, int puntob) {
        int resultR = ( (puntoa & 0x00FF0000) - (puntob & 0x00FF0000) ) / 0x10000;
        int resultG = ( (puntoa & 0x0000FF00) - (puntob & 0x0000FF00) ) / 0x100;
        int resultB = (puntoa & 0x000000FF) - (puntob & 0x000000FF);
        return (resultR*resultR + resultG*resultG + resultB*resultB);
        } // fin de distancia()

    /**
    * Retorna el valor de k, el numero de clusters
    */
    public int getK() {
        return this.k;
        } // end of getK()

    /**
    * Retorna los clusters en un array de int's
    */
    public int[] getClusters() {
        return this.clusters;
        } // end of getCluster()


    /**
    * Retorna la imagen transformada por kmeans a 'k' colores cualquiera.
    */
    public int[] getkImage() {
        int i=0;
        int[] ret=new int[this.tamanyo];
        for( i=0; i<this.tamanyo; i++) ret[i]= 0xFF000000 + this.clusters[this.asignacion[i]];
        return ret;
        }


    /**
    * Retorna la imagen transformada por kmeans a 'k' colores (max) de la paleta.
    */
    public void setPaleta(int[] paleta, int l) {
        int distanciamin,indexmin,dtmp,clusteri,paletai;
        /**
        * Para todos los clusters, calculamos la distáncia a la paleta dada
        * y escogemos la minima como color al que pertenece ese cluster
        */
        for( clusteri=0; clusteri<this.k; clusteri++) {
            distanciamin=distancia(this.clusters[clusteri],paleta[0]);
            indexmin=0;
            for( paletai=0; paletai < l; paletai++) {
                dtmp=distancia(paleta[paletai],this.clusters[clusteri]);
                if( dtmp < distanciamin ) {
                    distanciamin = dtmp;
                    indexmin = paletai;
                    }
                }
            this.paleta[clusteri] = paleta[indexmin];
            }
        }


    /**
    * Retorna la imagen transformada por kmeans a 'k' colores (max) de la paleta
    */
    public int[] getkImagep() {
        int[] ret=new int[this.tamanyo];
        for( int i=0; i<this.tamanyo; i++) ret[i]= 0xFF000000 | this.paleta[this.asignacion[i]];
        return ret;
        }

    /**
    * Retorna la paleta que usa la imagen transformada con el kmeans (k colores de los 11)
    */
    public int[] getPaleta() {
        return this.paleta;
        }



    } // final de la calse kmeans






detecta.java:

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;

class detecta extends Frame {
  Image imagenFuente;                 // Imagen cargada del disco
  int iniAncho;
  int iniAlto;
  // Imagen modificada
  // Valores del borde para el objeto contenedor
  int insetArriba;
  int insetIzqda;
 
  // Método de control del programa
  public static void main( String[] args ) {
    // Se instancia un objeto de esta clase
    detecta obj = new detecta(args);
    }

  // Constructor de la clase
  public detecta(String[] args) {
    kmeans kluster;
    int k = Integer.parseInt(args[1]);
    boolean debug=false;
    if( args.length > 2 ) debug = true;
    // Se carga la imagen desde el fichero que se indique, que se
    // supone situado en el directorio actual del disco duro
    imagenFuente = Toolkit.getDefaultToolkit().getImage( args[0] );

    // Se utilzia un objeto MediaTracker para bloquear la tarea hasta
    // que la imagen se haya cargado o hayan transcurrido 10 segundos
    // desde que se inicia la carga
    MediaTracker tracker = new MediaTracker( this );
    tracker.addImage( imagenFuente,1 );

    try {
      if( !tracker.waitForID( 1,10000 ) ) {
        System.out.println( "Error en la carga de la imagen" );
        System.exit( 1 );
      }
    } catch( InterruptedException e ) {
      System.out.println( e );
      }
   
    // La imagen ya está cargada, así que se establece la anchura y
    // altura de la ventana, para que sus dimensiones se adecúen a la
    // imagen
    iniAncho = imagenFuente.getWidth( this );
    iniAlto = imagenFuente.getHeight( this );
    // Se hace visible el Frame
    this.setVisible( true );
   
    //Get and store inset data for the Frame object so
    // that it can be easily avoided.
    insetArriba = this.getInsets().top;
    insetIzqda = this.getInsets().left;

    // Se usan las dimensiones de insets y el tamaño de la imagen
    // fuente para establecer el tamaño total del Frame. La altura se
    // hace doble, para que se puedan presentar la imagen original y
    // debajo de ella, la imagen modificada. Exactamente no es doble,
    // para permitir que la imagen modificada se superponga un poco
    // sobre la original.   
    this.setSize( insetIzqda+iniAncho,insetArriba+iniAlto );
    this.setTitle( "IA1 - Practica 1" );
    this.setBackground( Color.black );

    // Se declara un array para guardar la representación de la imagen
    // en pixels individuales
    int[] pix = new int[iniAncho * iniAlto];
    // Se convierte la "imagenFuente" a representación numérica que
    // corresponde a sus pixels, de forma que se puedan manipular
    // Esto hay que colocarlo en un bloque try-catch, porque tenemos
    // que estar prevenidos par recoger las excepciones de tipo
    // "InterrruptedException" que puede lanzar el método grabPixels()
    try {
      // Se instancia un objeto de tipo PixelGrabber, pasándole como
      // parámetro el array de pixels en donde queremos guardar la
      // representación numérica de la imagen que vamos manipular
      PixelGrabber pgObj = new PixelGrabber( imagenFuente,
        0,0,iniAncho,iniAlto,pix,0,iniAncho );

      // Se invoca ahora el método grabPixels() sobre el objeto de tipo
      // PixelGrabber que se acaba de instanciar, para la imagen se
      // convierta en un array de pixels. también se comprueba que el
      // proceso se realiza satisfactoriamente
      if( pgObj.grabPixels() &&
        ( (pgObj.getStatus() & ImageObserver.ALLBITS ) != 0 ) ) {
        for( int i=0; i < (iniAncho*iniAlto); i++ ) {
          pix[i] = pix[i] & 0xFFFFFFFF;
          }
        }
      else {
        System.out.println( "Problemas al descomponer la imagen" );
        }
    } catch( InterruptedException e ) {
      System.out.println( e );
      }
/**
* Constructor kmeans: kmeans( k, img, tamanyo, debug)
*   k = numero de clases a encontrar
*   img = pixeles de la imagen en un int[]
*   tamanyo = tamanyo de la imagen en pixeles, igual que tamanyo de img
*   debug = bool, activa el debug
*/
    kluster=new kmeans(k,pix,(iniAncho*iniAlto),debug);
    int[] colores=kluster.getClusters();
    if(debug) for( int i=0; i < k; i++) System.out.println("Color k-means " + i + ": rojo=" + ( (colores[i] & 0xFF0000) / 0x10000) +"; verde=" + ( (colores[i] & 0x00FF00) / 0x100) + " azul=" + (colores[i] & 0x0000FF) );

    // Ahora se utiliza el método createImage() para obtener una nueva
    // imagen a partir del array de pixels que hemos alterado
    imagenFuente = this.createImage( new MemoryImageSource(iniAncho,iniAlto,kluster.getkImage(),0,iniAncho ) );

    // Se llama directamente al método que va a presentar la imagen
    this.repaint();

    /**
    * Ahora adaptamos la imágen a la nueva paleta, la del ejercicio.
    */
    int[] paleta = new int[11];
    paleta[0] = Color.white.getRGB() & 0x00FFFFFF;
    paleta[1] = Color.gray.getRGB() & 0x00FFFFFF;
    paleta[2] = Color.black.getRGB() & 0x00FFFFFF;
    paleta[3] = Color.red.getRGB() & 0x00FFFFFF;
    paleta[4] = Color.green.getRGB() & 0x00FFFFFF;
    paleta[5] = Color.blue.getRGB() & 0x00FFFFFF;
    paleta[6] = Color.yellow.getRGB() & 0x00FFFFFF;
    paleta[7] = Color.orange.getRGB() & 0x00FFFFFF;
    paleta[8] = 0xA52A2A; // brown
    paleta[9] = 0x800080; // purple
    paleta[10] = Color.pink.getRGB() & 0x00FFFFFF;

    kluster=new kmeans(k,pix,(iniAncho*iniAlto),false);

    kluster.setPaleta(paleta,11);
    colores = kluster.getPaleta();
    for( int i=0; i<k; i++) System.out.println("Color k-paleta " + i + ": rojo=" + ( (colores[i] & 0xFF0000) / 0x10000) +"; verde=" + ( (colores[i] & 0x00FF00) / 0x100) + " azul=" + (colores[i] & 0x0000FF) );

    imagenFuente = this.createImage( new MemoryImageSource(iniAncho,iniAlto,kluster.getkImagep(),0,iniAncho ) );

    // Se llama directamente al método que va a presentar la imagen
    this.repaint();

    System.out.println("----fin-----");


    // Clase anidada que permite terminar la ejecución de la animación
    this.addWindowListener(
      // Definición de la clase anónima para controlar el cierre de
      // la ventana
      new WindowAdapter() {
        public void windowClosing( WindowEvent evt ) {
          // Se concluye el programa
          System.exit( 0 );
        }
      }
    );
  }
 
  // Sobrecargamos el método paint() para presentar las dos imágenes
  // del ejemplo, la original leida del fichero, y la modificada que se
  // ha creado tras la manipulación de los pixels de la original
  public void paint( Graphics g ) {
    if( imagenFuente != null ) {
      g.drawImage( imagenFuente,insetIzqda,insetArriba,this );
      }
    }
  }



    Editado por Daniel el 22/11/2007 a las 06:21:57h.


    Editado por Daniel el 18/12/2007 a las 11:28:11h.

Menu1.java:


/*
 * NewApplication.java
 *
 * Created on 26 de octubre de 2006, 14:23
 *
 * MEJORAS PENDIENTES:
 *      - Control de errores al abrir una imagen.
 *      - Restringir el tamaño de la ventana
 */

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import javax.swing.*;
   
   
/**
 *
 * @author  David
 */
public class Menu1 extends javax.swing.JFrame {
   
    Image imagenFuente;
    Image imagenResultado;
    int iniAncho, iniAlto;
    kmeans kluster;
   
    /** Creates new form NewApplication */
    public Menu1() {
        initComponents();
    }
   
    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
    private void initComponents() {
        jLabel1 = new javax.swing.JLabel();
        jLabel2 = new javax.swing.JLabel();
        jPanel1 = new javax.swing.JPanel();
        jCheckBox7 = new javax.swing.JCheckBox();
        jCheckBox1 = new javax.swing.JCheckBox();
        jCheckBox2 = new javax.swing.JCheckBox();
        jCheckBox8 = new javax.swing.JCheckBox();
        jCheckBox3 = new javax.swing.JCheckBox();
        jCheckBox9 = new javax.swing.JCheckBox();
        jCheckBox4 = new javax.swing.JCheckBox();
        jCheckBox10 = new javax.swing.JCheckBox();
        jCheckBox5 = new javax.swing.JCheckBox();
        jCheckBox11 = new javax.swing.JCheckBox();
        jCheckBox6 = new javax.swing.JCheckBox();
        jToolBar1 = new javax.swing.JToolBar();
        BotonAbrir = new javax.swing.JButton();
        BotonProcesar = new javax.swing.JButton();
        jPanel2 = new javax.swing.JPanel();
        ComboNClases = new javax.swing.JComboBox();
        jLabel3 = new javax.swing.JLabel();
        menuBar = new javax.swing.JMenuBar();
        fileMenu = new javax.swing.JMenu();
        openMenuItem = new javax.swing.JMenuItem();
        saveMenuItem = new javax.swing.JMenuItem();
        saveAsMenuItem = new javax.swing.JMenuItem();
        exitMenuItem = new javax.swing.JMenuItem();
        procesarMenu = new javax.swing.JMenu();
        ProcesaMenuItem = new javax.swing.JMenuItem();
        helpMenu = new javax.swing.JMenu();
        contentsMenuItem = new javax.swing.JMenuItem();
        aboutMenuItem = new javax.swing.JMenuItem();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("Obtener colores predominantes");
        jLabel1.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        jLabel1.setBorder(javax.swing.BorderFactory.createTitledBorder("Imagen Original"));
        jLabel1.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);

        jLabel2.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        jLabel2.setBorder(javax.swing.BorderFactory.createTitledBorder(null, "Imagen resultado", javax.swing.border.TitledBorder.LEADING, javax.swing.border.TitledBorder.DEFAULT_POSITION));

        jPanel1.setBorder(javax.swing.BorderFactory.createTitledBorder("Colores predominantes"));
        jCheckBox7.setBackground(java.awt.Color.yellow);
        jCheckBox7.setText("Yellow");
        jCheckBox7.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 0, 0, 0));
        jCheckBox7.setEnabled(false);
        jCheckBox7.setMargin(new java.awt.Insets(0, 0, 0, 0));

        jCheckBox1.setBackground(java.awt.Color.white);
        jCheckBox1.setText("White");
        jCheckBox1.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 0, 0, 0));
        jCheckBox1.setEnabled(false);
        jCheckBox1.setMargin(new java.awt.Insets(0, 0, 0, 0));

        jCheckBox2.setBackground(java.awt.Color.gray);
        jCheckBox2.setText("Gray");
        jCheckBox2.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 0, 0, 0));
        jCheckBox2.setEnabled(false);
        jCheckBox2.setMargin(new java.awt.Insets(0, 0, 0, 0));

        jCheckBox8.setBackground(java.awt.Color.orange);
        jCheckBox8.setText("Orange");
        jCheckBox8.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 0, 0, 0));
        jCheckBox8.setEnabled(false);
        jCheckBox8.setMargin(new java.awt.Insets(0, 0, 0, 0));

        jCheckBox3.setBackground(java.awt.Color.black);
        jCheckBox3.setForeground(java.awt.Color.white);
        jCheckBox3.setText("Black");
        jCheckBox3.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 0, 0, 0));
        jCheckBox3.setEnabled(false);
        jCheckBox3.setMargin(new java.awt.Insets(0, 0, 0, 0));

        jCheckBox9.setBackground(new java.awt.Color(102, 0, 0));
        jCheckBox9.setText("Brown");
        jCheckBox9.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 0, 0, 0));
        jCheckBox9.setEnabled(false);
        jCheckBox9.setMargin(new java.awt.Insets(0, 0, 0, 0));

        jCheckBox4.setBackground(java.awt.Color.red);
        jCheckBox4.setText("Red");
        jCheckBox4.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 0, 0, 0));
        jCheckBox4.setEnabled(false);
        jCheckBox4.setMargin(new java.awt.Insets(0, 0, 0, 0));

        jCheckBox10.setBackground(new java.awt.Color(153, 0, 153));
        jCheckBox10.setText("Purple");
        jCheckBox10.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 0, 0, 0));
        jCheckBox10.setEnabled(false);
        jCheckBox10.setMargin(new java.awt.Insets(0, 0, 0, 0));

        jCheckBox5.setBackground(java.awt.Color.green);
        jCheckBox5.setText("Green");
        jCheckBox5.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 0, 0, 0));
        jCheckBox5.setEnabled(false);
        jCheckBox5.setMargin(new java.awt.Insets(0, 0, 0, 0));

        jCheckBox11.setBackground(java.awt.Color.pink);
        jCheckBox11.setText("Pink");
        jCheckBox11.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 0, 0, 0));
        jCheckBox11.setEnabled(false);
        jCheckBox11.setMargin(new java.awt.Insets(0, 0, 0, 0));

        jCheckBox6.setBackground(java.awt.Color.blue);
        jCheckBox6.setText("Blue");
        jCheckBox6.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 0, 0, 0));
        jCheckBox6.setEnabled(false);
        jCheckBox6.setMargin(new java.awt.Insets(0, 0, 0, 0));

        org.jdesktop.layout.GroupLayout jPanel1Layout = new org.jdesktop.layout.GroupLayout(jPanel1);
        jPanel1.setLayout(jPanel1Layout);
        jPanel1Layout.setHorizontalGroup(
            jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(jPanel1Layout.createSequentialGroup()
                .add(jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                    .add(jCheckBox1)
                    .add(jCheckBox7))
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                    .add(jCheckBox2)
                    .add(jCheckBox8))
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                    .add(jCheckBox9)
                    .add(jCheckBox3))
                .add(13, 13, 13)
                .add(jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                    .add(jCheckBox10)
                    .add(jCheckBox4))
                .add(19, 19, 19)
                .add(jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                    .add(jPanel1Layout.createSequentialGroup()
                        .add(jCheckBox5)
                        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                        .add(jCheckBox6))
                    .add(jCheckBox11))
                .addContainerGap(org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
        jPanel1Layout.setVerticalGroup(
            jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(jPanel1Layout.createSequentialGroup()
                .addContainerGap()
                .add(jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING)
                    .add(jPanel1Layout.createSequentialGroup()
                        .add(jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                            .add(jCheckBox5)
                            .add(jCheckBox3)
                            .add(jCheckBox4)
                            .add(jCheckBox6))
                        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                        .add(jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                            .add(jCheckBox9)
                            .add(jCheckBox10)
                            .add(jCheckBox11)))
                    .add(jPanel1Layout.createSequentialGroup()
                        .add(jCheckBox2)
                        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                        .add(jCheckBox8))
                    .add(jPanel1Layout.createSequentialGroup()
                        .add(jCheckBox1)
                        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                        .add(jCheckBox7)))
                .addContainerGap(org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        BotonAbrir.setText("Abrir");
        BotonAbrir.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                BotonAbrirActionPerformed(evt);
            }
        });

        jToolBar1.add(BotonAbrir);

        BotonProcesar.setText("Procesar Imagen");
        BotonProcesar.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                BotonProcesarActionPerformed(evt);
            }
        });

        jToolBar1.add(BotonProcesar);

        jPanel2.setBorder(javax.swing.BorderFactory.createTitledBorder("Configuraci\u00f3n"));
        ComboNClases.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "2", "3", "4", "5", "6", "7", "8", "9" }));

        jLabel3.setText("N\u00famero de clases");

        org.jdesktop.layout.GroupLayout jPanel2Layout = new org.jdesktop.layout.GroupLayout(jPanel2);
        jPanel2.setLayout(jPanel2Layout);
        jPanel2Layout.setHorizontalGroup(
            jPanel2Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(jPanel2Layout.createSequentialGroup()
                .addContainerGap()
                .add(jLabel3)
                .add(14, 14, 14)
                .add(ComboNClases, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                .addContainerGap(200, Short.MAX_VALUE))
        );
        jPanel2Layout.setVerticalGroup(
            jPanel2Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(jPanel2Layout.createSequentialGroup()
                .addContainerGap()
                .add(jPanel2Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                    .add(jLabel3)
                    .add(ComboNClases, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
                .addContainerGap(25, Short.MAX_VALUE))
        );

        fileMenu.setText("Archivo");
        openMenuItem.setText("Abrir");
        openMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                openMenuItemActionPerformed(evt);
            }
        });

        fileMenu.add(openMenuItem);

        saveMenuItem.setText("Guardar");
        saveMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                saveMenuItemActionPerformed(evt);
            }
        });

        fileMenu.add(saveMenuItem);

        saveAsMenuItem.setText("Guardar como...");
        fileMenu.add(saveAsMenuItem);

        exitMenuItem.setText("Salir");
        exitMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                exitMenuItemActionPerformed(evt);
            }
        });

        fileMenu.add(exitMenuItem);

        menuBar.add(fileMenu);

        procesarMenu.setText("Procesar");
        ProcesaMenuItem.setText("Procesa");
        ProcesaMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                ProcesaMenuItemActionPerformed(evt);
            }
        });

        procesarMenu.add(ProcesaMenuItem);

        menuBar.add(procesarMenu);

        helpMenu.setText("Ayuda");
        contentsMenuItem.setText("Ayuda");
        helpMenu.add(contentsMenuItem);

        aboutMenuItem.setText("Sobre");
        helpMenu.add(aboutMenuItem);

        menuBar.add(helpMenu);

        setJMenuBar(menuBar);

        org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(layout.createSequentialGroup()
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING, false)
                    .add(org.jdesktop.layout.GroupLayout.LEADING, jToolBar1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .add(org.jdesktop.layout.GroupLayout.LEADING, layout.createSequentialGroup()
                        .addContainerGap()
                        .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING, false)
                            .add(org.jdesktop.layout.GroupLayout.TRAILING, jLabel1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                            .add(org.jdesktop.layout.GroupLayout.TRAILING, jPanel1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))))
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                    .add(jPanel2, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .add(jLabel2, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 354, Short.MAX_VALUE))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(layout.createSequentialGroup()
                .add(jToolBar1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 25, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING)
                    .add(org.jdesktop.layout.GroupLayout.LEADING, jLabel2, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 266, Short.MAX_VALUE)
                    .add(org.jdesktop.layout.GroupLayout.LEADING, jLabel1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 266, Short.MAX_VALUE))
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING, false)
                    .add(jPanel2, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .add(jPanel1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addContainerGap())
        );
        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void saveMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_saveMenuItemActionPerformed
        // Abrimos el cuadro de dialogo de guardar fichero
        JFileChooser fc = new JFileChooser();
        int returnVal = fc.showOpenDialog(Menu1.this);
       
        // Si se selecciona un fichero
        if (returnVal == JFileChooser.APPROVE_OPTION) {
       
        }
    }//GEN-LAST:event_saveMenuItemActionPerformed

    private void BotonProcesarActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_BotonProcesarActionPerformed
        // Llamamos a la función de procesar
        this.ProcesaMenuItemActionPerformed(evt);
    }//GEN-LAST:event_BotonProcesarActionPerformed

    private void ProcesaMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_ProcesaMenuItemActionPerformed
        // Comprobamos que la imagen esté cargada
        if (imagenFuente==null) {
            JOptionPane.showMessageDialog( this, "Error al procesar la imagen. Imagen no cargada.", "Error al procesar la imagen.", JOptionPane.ERROR_MESSAGE);
            return;
        }
       
        // Deseleccionamos los colores activados
        deselecciona();
       
        // Leemos el numero de clases seleccionadas por el usuario
        int NClases = Integer.parseInt(this.ComboNClases.getSelectedItem().toString());

        // Convertimos la imagen en un array de pixels
          // Creamos la matriz en memoria
        int[] pix = new int[iniAncho * iniAlto];
        try {
            // Creamos un objeto grabador de pixeles pasandole la matriz y la imagen
          PixelGrabber pgObj = new PixelGrabber( imagenFuente,
            0,0,iniAncho,iniAlto,pix,0,iniAncho );
            // Llamamos al método de grabar los pixeles
          if( pgObj.grabPixels() && ( (pgObj.getStatus() & ImageObserver.ALLBITS ) != 0 ) ) {
            for( int i=0; i < (iniAncho*iniAlto); i++ ) {pix[i] = pix[i] & 0xFFFFFFFF;}
          }
          else {
            JOptionPane.showMessageDialog( this, "Error al descomponer la imagen.", "Error al procesar la imagen.", JOptionPane.ERROR_MESSAGE);
            return;
          }
        } catch( InterruptedException e ) {
          JOptionPane.showMessageDialog( this, "Error al descomponer la imagen."+e, "Error al procesar la imagen.", JOptionPane.ERROR_MESSAGE);
          return;
        }
   
        // Definimos la paleta de los colores basicos
        int[] paleta = new int[11];
        paleta[0] = Color.white.getRGB() & 0x00FFFFFF;
        paleta[1] = Color.gray.getRGB() & 0x00FFFFFF;
        paleta[2] = Color.black.getRGB() & 0x00FFFFFF;
        paleta[3] = Color.red.getRGB() & 0x00FFFFFF;
        paleta[4] = Color.green.getRGB() & 0x00FFFFFF;
        paleta[5] = Color.blue.getRGB() & 0x00FFFFFF;
        paleta[6] = Color.yellow.getRGB() & 0x00FFFFFF;
        paleta[7] = Color.orange.getRGB() & 0x00FFFFFF;
        paleta[8] = 0xA52A2A; // brown
        paleta[9] = 0x800080; // purple
        paleta[10] = Color.pink.getRGB() & 0x00FFFFFF;
        // Le pasamos el array de pixels y el numero de clusters al kmeans       
        kluster=new kmeans(NClases, pix, (iniAncho*iniAlto), true);
        // Le pasamos la paleta de colores basicos a encontrar
        kluster.setPaleta(paleta,11);
        // Obtenemos los colores caracteristicos
        int[] colores = kluster.getPaleta();
        // Mostramos la lista de colores
        for( int i=0; i<NClases; i++) System.out.println("Color k-paleta " + i + ": rojo=" + ( (colores[i] & 0xFF0000) / 0x10000) +"; verde=" + ( (colores[i] & 0x00FF00) / 0x100) + " azul=" + (colores[i] & 0x0000FF) );
        for( int i=0; i<NClases; i++) {
            if (colores[i] == paleta[0]) this.jCheckBox1.setSelected(true);
            if (colores[i] == paleta[1]) this.jCheckBox2.setSelected(true);
            if (colores[i] == paleta[2]) this.jCheckBox3.setSelected(true);
            if (colores[i] == paleta[3]) this.jCheckBox4.setSelected(true);
            if (colores[i] == paleta[4]) this.jCheckBox5.setSelected(true);
            if (colores[i] == paleta[5]) this.jCheckBox6.setSelected(true);
            if (colores[i] == paleta[6]) this.jCheckBox7.setSelected(true);
            if (colores[i] == paleta[7]) this.jCheckBox8.setSelected(true);
            if (colores[i] == paleta[8]) this.jCheckBox9.setSelected(true);
            if (colores[i] == paleta[9]) this.jCheckBox10.setSelected(true);
            if (colores[i] == paleta[10]) this.jCheckBox11.setSelected(true);
        }
       
        // Mostramos la imagen procesada
        imagenResultado = this.createImage(new MemoryImageSource(iniAncho,iniAlto,kluster.getkImagep(),0,iniAncho ) );       
          // Escalamos la imagen
        Image imagenEscalada = imagenResultado.getScaledInstance(-(this.jLabel2.getWidth()-50), this.jLabel2.getHeight()-50, 1);
          // Mostramos la imagen en pantalla
        ImageIcon IconoImagen = new ImageIcon(imagenEscalada);
        this.jLabel2.setIcon(IconoImagen);
    }//GEN-LAST:event_ProcesaMenuItemActionPerformed

    private void BotonAbrirActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_BotonAbrirActionPerformed
        // Llamamos a la función de abrir fichero
        openMenuItemActionPerformed(evt);
    }//GEN-LAST:event_BotonAbrirActionPerformed

    private void deselecciona() {
            this.jCheckBox1.setSelected(false);
            this.jCheckBox2.setSelected(false);
            this.jCheckBox3.setSelected(false);
            this.jCheckBox4.setSelected(false);
            this.jCheckBox5.setSelected(false);
            this.jCheckBox6.setSelected(false);
            this.jCheckBox7.setSelected(false);
            this.jCheckBox8.setSelected(false);
            this.jCheckBox9.setSelected(false);
            this.jCheckBox10.setSelected(false);
            this.jCheckBox11.setSelected(false);
    }
   
    private void openMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_openMenuItemActionPerformed
        // Abrimos el cuadro de dialogo de abrir fichero
        JFileChooser fc = new JFileChooser();
        int returnVal = fc.showOpenDialog(Menu1.this);
       
        // Si se selecciona un fichero
        if (returnVal == JFileChooser.APPROVE_OPTION) {
            // Abrimos el fichero
            File file = fc.getSelectedFile();
            // Leemos la imagen
            imagenFuente = Toolkit.getDefaultToolkit().getImage(file.getAbsolutePath());
            // Bloqueamos la tarea hasta que la imagen se haya cargado
            MediaTracker tracker = new MediaTracker( this );
            tracker.addImage( imagenFuente,1 );
            try {
              if( !tracker.waitForID( 1,10000 ) ) {
                System.out.println( "Error en la carga de la imagen" );
                System.exit( 1 );
              }
            } catch( InterruptedException e ) {
              System.out.println( e );
            }
            iniAncho = imagenFuente.getWidth(this);
            iniAlto = imagenFuente.getHeight(this);           

            // Escalamos la imagen
            Image imagenEscalada = imagenFuente.getScaledInstance(-(this.jLabel1.getWidth()-50), this.jLabel1.getHeight()-50, 1);
            // Mostramos la imagen en pantalla
            ImageIcon IconoImagen = new ImageIcon(imagenEscalada);
            this.jLabel1.setIcon(IconoImagen);
            // Eliminamos la imagen procesada
            this.imagenResultado = null;
            this.jLabel2.setIcon(null);
            // Deseleccionamos los colores que puedan estar marcados
            deselecciona();
        }
    }//GEN-LAST:event_openMenuItemActionPerformed

   
    private void exitMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exitMenuItemActionPerformed
        System.exit(0);
    }//GEN-LAST:event_exitMenuItemActionPerformed
   
    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new Menu1().setVisible(true);
            }
        });
    }
   
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton BotonAbrir;
    private javax.swing.JButton BotonProcesar;
    private javax.swing.JComboBox ComboNClases;
    private javax.swing.JMenuItem ProcesaMenuItem;
    private javax.swing.JMenuItem aboutMenuItem;
    private javax.swing.JMenuItem contentsMenuItem;
    private javax.swing.JMenuItem exitMenuItem;
    private javax.swing.JMenu fileMenu;
    private javax.swing.JMenu helpMenu;
    private javax.swing.JCheckBox jCheckBox1;
    private javax.swing.JCheckBox jCheckBox10;
    private javax.swing.JCheckBox jCheckBox11;
    private javax.swing.JCheckBox jCheckBox2;
    private javax.swing.JCheckBox jCheckBox3;
    private javax.swing.JCheckBox jCheckBox4;
    private javax.swing.JCheckBox jCheckBox5;
    private javax.swing.JCheckBox jCheckBox6;
    private javax.swing.JCheckBox jCheckBox7;
    private javax.swing.JCheckBox jCheckBox8;
    private javax.swing.JCheckBox jCheckBox9;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JLabel jLabel3;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JPanel jPanel2;
    private javax.swing.JToolBar jToolBar1;
    private javax.swing.JMenuBar menuBar;
    private javax.swing.JMenuItem openMenuItem;
    private javax.swing.JMenu procesarMenu;
    private javax.swing.JMenuItem saveAsMenuItem;
    private javax.swing.JMenuItem saveMenuItem;
    // End of variables declaration//GEN-END:variables
   
}