domingo, 25 de agosto de 2013

Java: abrir imagen, leer píxeles y pasar a escala de grises. Procesamiento digital de imágenes I.

Hoy vamos a empezar con Java. En esta primera entrada lo que veremos será cómo abrir una imagen y mostrarla, y cómo leer los valores de los píxeles y pasarlos a escala de grises.
Lo primero que vamos a ver es qué herramienta utilizar, en este caso vamos a descargar NetBeans, un IDE bastante completo y gratuito. Accedemos a la página:
www.oracle.com/technetwork/es/java/javase/downloads/index.html y descargamos el jdk (Java Development Kit) junto con NetBeans.

Web de descarga

Una vez lo hemos descargado e instalado procedemos a abrir el IDE y creamos un nuevo proyecto. Para ello hacemos clic en File>New Project y seleccionamos Java Application.

Nuevo proyecto

Le damos a siguiente (Next) y proporcionamos un nombre a nuestro proyecto, en este caso, cargarImagen y por último pulsamos el botón Finish.

Nombre del proyecto

Una vez realizados estos pasos, se nos carga la clase inicial de nuestro proyecto, podemos ver que es el punto de partida, ya que tiene el método main. Como nuestra aplicación va a tener interfaz (GUI) vamos a eliminar esta clase para agregar un jFrame Form (el equivalente a Windows Form en Visual Studio).

Eliminar clase inicial

Para crear este nuevo jFrame (que será el inicio de nuestra aplicación), basta con hacer clic con el botón derecho encima de nuestro paquete fuente, y se selecciona New>JFrame Form. A este formulario le vamos a llamar Principal.

Añadir nuevo JFrame

Y con estos sencillos pasos, ya tenemos nuestro formulario que contendrá una imagen y dos botones, uno para transformar a escala de grises y otro para abrir la imagen. Para agregar una imagen no hay un contenedor específico, como podía ser PictureBox en Visual Studio, sino que vamos a asignar una imagen a un jLabel, en concreto en su propiedad Icon. El resultado de nuestro formulario es el siguiente:

Formulario principal

A continuación, vamos a crear una clase que será la que recuperará la imagen desde archivo y contendrá la función que transforme la imagen a escala de grises. Para ello, hacemos de nuevo clic con el botón derecho encima de nuestro paquete fuente, y seleccionamos New>Java class.

Añadir clase

A esta nueva clase le llamaremos, ProcesamientoImagen. Y vamos a incluir el siguiente código fuente:

package cargarimagen;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
import javax.swing.JFileChooser;
import javax.swing.filechooser.FileNameExtensionFilter;

/**
 *
 * @author Luis
 */
public class ProcesamientoImagen {
    
    //Imagen actual que se ha cargado
    private BufferedImage imageActual;
    
    //Método que devuelve una imagen abierta desde archivo
    //Retorna un objeto BufferedImagen
    public BufferedImage abrirImagen(){
        //Creamos la variable que será devuelta (la creamos como null)
        BufferedImage bmp=null;
        //Creamos un nuevo cuadro de diálogo para seleccionar imagen
        JFileChooser selector=new JFileChooser();
        //Le damos un título
        selector.setDialogTitle("Seleccione una imagen");
        //Filtramos los tipos de archivos
        FileNameExtensionFilter filtroImagen = new FileNameExtensionFilter("JPG & GIF & BMP", "jpg", "gif", "bmp");
        selector.setFileFilter(filtroImagen);
        //Abrimos el cuadro de diálog
        int flag=selector.showOpenDialog(null);
        //Comprobamos que pulse en aceptar
        if(flag==JFileChooser.APPROVE_OPTION){
            try {
                //Devuelve el fichero seleccionado
                File imagenSeleccionada=selector.getSelectedFile();
                //Asignamos a la variable bmp la imagen leida
                bmp = ImageIO.read(imagenSeleccionada);
            } catch (Exception e) {
            }
                 
        }
        //Asignamos la imagen cargada a la propiedad imageActual
        imageActual=bmp;
        //Retornamos el valor
        return bmp;
    }
    
    public BufferedImage escalaGrises(){
        //Variables que almacenarán los píxeles
        int mediaPixel,colorSRGB;
        Color colorAux;
                
        //Recorremos la imagen píxel a píxel
        for( int i = 0; i < imageActual.getWidth(); i++ ){
            for( int j = 0; j < imageActual.getHeight(); j++ ){
                //Almacenamos el color del píxel
                colorAux=new Color(this.imageActual.getRGB(i, j));
                //Calculamos la media de los tres canales (rojo, verde, azul)
                mediaPixel=(int)((colorAux.getRed()+colorAux.getGreen()+colorAux.getBlue())/3);
                //Cambiamos a formato sRGB
                colorSRGB=(mediaPixel << 16) | (mediaPixel << 8) | mediaPixel;
                //Asignamos el nuevo valor al BufferedImage
                imageActual.setRGB(i, j,colorSRGB);
            }
        }
        //Retornamos la imagen
        return imageActual;
    }
}

Simplemente lo que hace esta clase es abrir una imagen (devolviendo una variable del tipo BufferedImage) y pasar esa imagen a escala de grises (también devolviendo un BufferedImage). Ahora lo que tenemos que hacer es, en nuestro formulario, hacer doble clic sobre el botón de abrir imagen y pasar a escala de grises para gestionar el evento de clic. Una vez realizado esto, incluimos el siguiente código:

//Código para cargar imagen
    private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {                                         
        //Transformamos el BifferedImagen a ImageIcon para poder mostrarlo en el jLabel1
        jLabel1.setIcon(new ImageIcon(ObjProcesamiento.abrirImagen()));
    }                                        
    //Código para pasar a escala de grises
    private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {                                         
        //Transformamos el BifferedImagen a ImageIcon para poder mostrarlo en el jLabel1
        jLabel1.setIcon(new ImageIcon(ObjProcesamiento.escalaGrises()));
    }           

El resultado final de la aplicación es el siguiente:

Resultado final

Como hemos visto, no es algo demasiado complejo. Seguramente se pueda hacer todo de una forma más elegante, pero bueno, son mis primeros pasos en Java. Poco a poco iremos viendo más cosas y seguramente las haremos de otra forma. Cualquier duda o crítica, en los comentarios ;)
Podéis descargar el código fuente aquí:

46 comentarios:

  1. Muchisimas gracias!!!, casi lloro!!
    Jajajaa muy buen aporte, gracias!!!

    ResponderEliminar
  2. Hola buenas tardes!
    Quisiera saber cómo conocer
    el valor del rgb de una imagen insertada a
    partir de un filechooser?
    :(

    ResponderEliminar
    Respuestas
    1. Este comentario ha sido eliminado por el autor.

      Eliminar
    2. En la propia entrada se hace lo que comentas. Fíjate, una vez que tienes la imagen como BufferedImage, puedes obtener el valor de un píxel así:
      colorAux=new Color(this.imageActual.getRGB(i, j));
      donde i es la fila y j la columna. Es decir, para obtener el color del píxel superior izquierdo de una imagen sería:
      colorAux=new Color(this.imageActual.getRGB(0,0));
      Si quieres recorrer toda la imagen:
      //Recorremos la imagen píxel a píxel
      for( int i = 0; i < imageActual.getWidth(); i++ ){
      for( int j = 0; j < imageActual.getHeight(); j++ ){
      //Almacenamos el color del píxel
      colorAux=new Color(this.imageActual.getRGB(i, j));
      }
      }
      Un saludo!

      Eliminar
  3. Pero, si solicitan que al aparecer la imagen
    el usuario seleccione las coordenadas y aparezcan los valores
    de RGB en cajas de texto ?

    ResponderEliminar
    Respuestas
    1. Justo como te dije.
      Color colorAux=new Color(this.imageActual.getRGB(i, j));
      donde i es la fila y j la columna.
      Un saludo

      Eliminar
  4. Buenas tardes.. cómo se realiza la llamada a un metodo ( de tipo BifferedImagen ) desde el evento de un botón gracias!

    ResponderEliminar
    Respuestas
    1. Hola.
      Echa un vistazo a esta entrada http://algoimagen.blogspot.com.es/2013/09/java-crear-aplicacion-para.html

      Eliminar
  5. Hola,
    Estoy tratando de implementar este código con una imagen formato PGM, y da error de java.lang.NullPointerException, Qué podría hacer?

    ResponderEliminar
  6. Que tal,

    Estoy tratando de implementar este código con una imagen formato PGM, y da error de java.lang.NullPointerException, Qué podría hacer?

    ResponderEliminar
    Respuestas
    1. Diseña la clase que adsorba imágenes de tipo PGM...

      Eliminar
  7. hola, tuve un problema con el ultimo codigo, al copiar:
    jLabel1.setIcon(new ImageIcon(ObjProcesamiento.abrirImagen()));
    en ImageIcon y ObjProcesamiento me dice cannot find symbol

    ResponderEliminar
  8. hola gracias por la ayuda, una pregunta si quiero restablecer de
    nuevo los colores como le puedo hacer
    espero tu respuesta gracia

    ResponderEliminar
  9. Ah excelente aporte, pero me gustaría saber como le harías para pasar de escalas de grises a la foto normal

    ResponderEliminar
  10. Buenas noches como puedo regresar al color original, cual es el codigo que se utilizaria

    ResponderEliminar
  11. Hola buenas tardes
    como podria hacerle si quiero que cambie por ejemplo todos los pixeles blancos a verdes,y me diera un resultado tipo andy warhol.

    ResponderEliminar
    Respuestas
    1. Muy simple. Sólo tienes que hacer un condicional que verifique que el color es blanco (las tres componentes RGB sean 255) y asignar ese píxel al color verde.
      //Almacenamos el color del píxel
      colorAux=new Color(this.imageActual.getRGB(i, j));
      //Comprobamos si el color es blanco
      if(colorAux.getRed()==255 && colorAux.getGreen()==255 && colorAux.getBlue()==255){
      //Cambiamos a formato sRGB el color verde
      colorSRGB=(0 << 16) | (255 << 8) | 0;
      }
      Échale un vistazo a esto: http://www.uv.es/gpoei/eng/Pfc_web/generalidades/pseudocolor/pseudocolor.htm
      Un saludo

      Eliminar
  12. hola, tuve un problema con el ultimo codigo, al copiar:
    jLabel1.setIcon(new ImageIcon(ObjProcesamiento.abrirImagen()));
    en ImageIcon y ObjProcesamiento me dice cannot find symbol
    gracias espero tu ayuda

    ResponderEliminar
    Respuestas
    1. yo tuve el miso problema me podrias ayudar ??? como solucionaste ese error??

      Eliminar
  13. Gracias amigo... de mucha ayuda...

    ResponderEliminar
  14. Hola muy buen aporte mi estimado,vas por buen camino em el mundo de java ,mi duda es la sigueinte estoy leyendo sobre int_argb , necessito que hace significa esse entero y con el barra (OR logico) y las "<<" que estas especificando hacer ya que no encuentro la explicacion detallada de esse procedimento , agradeceria tu respuesta.

    ResponderEliminar
    Respuestas
    1. Hola Renato,
      es aritmética binaria. De lo que se trata es de pasar un color en sistema ARGB a RGB
      http://es.wikipedia.org/wiki/Espacio_de_color_sRGB
      http://es.wikipedia.org/wiki/RGB
      http://stackoverflow.com/questions/2615522/java-bufferedimage-getting-red-green-and-blue-individually

      Eliminar
  15. Hola yo estoy intentando implementar tu codigo soy nueva en java y tengo problemas con estas lineas de codigo: jLabel1.setIcon(new ImageIcon(ObjProcesamiento.abrirImagen())); y jLabel1.setIcon(new ImageIcon(ObjProcesamiento.escalaGrises())); me marca un error en ObjProcesamiento como puedo solucionar este problema??? Agradeceria tu respuesta!!!!!

    ResponderEliminar
    Respuestas
    1. El problema es que al iniciar el codigo en "Principal" no se declara Procesamiento,
      copia este código al inicio y listo, problema resuelto ahora deberá ejecutar sin contratiempos.

      ------------------------------------------------
      package cargarimagen;

      import java.awt.Image;
      import java.awt.image.BufferedImage;
      import javax.swing.ImageIcon;

      /**
      *
      * @author Luis
      */
      public class Principal extends javax.swing.JFrame {
      ProcesamientoImagen ObjProcesamiento=new ProcesamientoImagen();
      ----------------------------------

      Eliminar
  16. Que tal. Muchas Gracias por la explicación pero tengo una duda con respecto al código.
    ¿Que es lo que hace esta linea?
    colorSRGB=(mediaPixel << 16) | (mediaPixel << 8) | mediaPixel;
    ¿Esa instruccion cambia en dado caso que en lugar del promedio use
    "(max(r,g,b)+min(r,g,b))/2"?

    ResponderEliminar
  17. Lo que hace es pasar un píxel de sistema RGB a SRGB, por lo tanto esa línea no la tienes que cambiar.
    Primero calculas el promedio como dices ("(max(r,g,b)+min(r,g,b))/2") y con el dato que te resulte (entiendo que la imagen que resulte será en escala de grises), calculas el valor en sistema SRGB. Algo así sería:

    promedio=(max(r,g,b)+min(r,g,b))/2;
    //Cambiamos a formato sRGB
    colorSRGB=(mediaPixel << 16) | (mediaPixel << 8) | mediaPixel;
    //Asignamos el nuevo valor al BufferedImage
    imageActual.setRGB(i, j,colorSRGB);
    Un saludo

    ResponderEliminar
  18. Hola Luis.
    no he podido entender que hace técnicamente la siguiente línea:
    //Cambiamos a formato sRGB
    colorSRGB=(mediaPixel << 16) | (mediaPixel << 8) | mediaPixel;

    entiendo más o menos lo de desplazamiento y los OR pero no se que hace realmente en este caso.
    de antemano gracias

    ResponderEliminar
  19. Primero que nada gracias por el tuto, ahora mi pregunta como puedo hacerle para rayar la imagen y con otro boton exportar ?? espero tu respuesta amigo gracias y saludos

    ResponderEliminar
  20. muchas gracias por el post, fue de mucha ayuda, saludos!!

    ResponderEliminar
  21. inicialmente me salía error jLabel1.setIcon(new ImageIcon(ObjProcesamiento.abrirImagen())); y jLabel1.setIcon(new ImageIcon(ObjProcesamiento.escalaGrises()));
    lo solucioné creando un objeto de la clase procesamientoimagen, pero luego al correrlo abre la imagen, pero no pasa a escala de grises, sale:
    Exception in thread "main" java.lang.NullPointerException at cargar.imagen.pkg2.procesamientoimagen.escalaGrises(procesamientoimagen.java:56)

    cómo puedo solucionarlo???

    ResponderEliminar
  22. Muchas gracias por tu post
    un pregunta
    ¿Como puedo pasar la imagen de la escala de grises a blanco y negro?

    ResponderEliminar
    Respuestas
    1. Puedes poner un tresholding (un valor podría ser 127) y si pasa de ese valor lo pones a 0 o sino a 255, creo que así seria.

      Eliminar
  23. hola, estoy haciendo un codigo para poner marca de agua a una imagen...
    podrian ayudarme?

    ResponderEliminar
  24. Y si quiero leer una imagen .pgm como le hago?

    ResponderEliminar
    Respuestas
    1. hola, yo estoy trabajando con imagen pgm Y para leer lo tengo de esta manera.. checalo aver si te sirve


      Scanner sc = new Scanner(new File("./foto.pgm"));

      //se obtiene el tamaño de la imagen
      String type = sc.next();

      int columns = sc.nextInt();
      int rows = sc.nextInt();
      int val_max = sc.nextInt();

      Eliminar
  25. Buenas tardes, una pregunta...es posible que me muestre en un JPanel los bytes que va leyendo de la imagen o algo parecido? y como le haria?
    Saludos!

    ResponderEliminar
  26. buena noche, deseo saber como guardar la imagen que contengo dentro del label en un archivo y que me guarde con el filtro, me puede ayudar

    ResponderEliminar
  27. Excelente post a seguir con la lección II...

    ResponderEliminar
  28. Hola una pregunta, quisiera saber para que es esta linea de codigo...
    colorSRGB=(mediaPixel << 16) | (mediaPixel << 8) | mediaPixel;
    Es acaso para asignar los tres canales de RGB de la imagen? y otra pregunta, porque los valores 16 y 8? son acaso parametros apara asignar? Gracias :D

    ResponderEliminar
  29. Hola colega una pregunta como puedo hacer para que me muestre el nombre de la imagen y su ubicación y de la misma manera al hacer clic me muestre las coordenadas de los pixeles. De antemano gracias.

    ResponderEliminar
  30. Muy buen aporte, me fue de gran ayuda. Ahora, quería preguntarle si es posible un algoritmo que me permita contar los objetos que contenga una imagen... en java. Saludos

    ResponderEliminar
  31. Hola estoy tratando de Leer una imagen TIFF (Geotiff) con java, pero por alguna razón no me funciona JAI.creater, los valores de los píxeles son float de 64 bits no se si ese sea el problema.

    ResponderEliminar
  32. HOLA, NECESITO ALGO PARESIDO A LA TUYO PERO QUE PUEDA MANEJAR UN SCANNER, ES DECIR NECESITO HACER UN SOFTWARE CON JAVA, QUE ME DE CONEXION A UN SCANNER

    ResponderEliminar
  33. hola, como harias para hacer mediciones dentro de una imagen, como el largo y ancho de una puerta o ventana dentro de foto.

    ResponderEliminar