Tratamiento de imágenes I: Escala de grises

10 comentarios

Tratamiento de imágenes

Con la llegada del fin de verano es posible que tengamos fotografías de las vacaciones, celebraciones o cualquier tipo de evento que se realiza para disfrutar del buen tiempo. A continuación iniciaremos una serie de números a modo de talleres prácticos para hablar del tratamiento de imágenes y fotografías.

A lo largo de esta serie iremos poniendo ejemplos y prácticas en Java que podréis ejecutar vosotros mismos. No hay un motivo definitivo por la elección de este lenguaje. Los mismos algoritmos se podrían realizar utilizando cualquier otro lenguaje. En este primer número vamos a tratar algunos fundamentos básicos sobre las imágenes.

Formatos

Nos podemos encontrar multitud de formatos para el almacenamiento de ficheros. Los más antiguos y rudimentarios son los mapas de bits como por ejemplo el formato .bmp. En este formato se almacena la información sin comprimir por lo que suelen ser muy grandes respecto a otros formatos. Como ventajas son más fáciles de tratar pues no precisan de un algoritmo de descompresión pudiendo acceder a los puntos directamente.

Existen otros formatos como gif, jpg, png o tiff que comprimen este mapa de bits y estaban orientados a ciertas necesidades. El formato que vamos a utilizar para realizar estas prácticas será el formato jpg ya que es bastante aconsejable para fotografías. Pero siempre hay que tener el cuenta que el tratamiento de la imagen se debe realizar sobre un mapa de bits descomprimido. Por ello los pasos que se suelen realizar son descomprimir el fichero, tratar el fichero y almacenar nuevamente comprimido.

Colores

Una imagen, a groso modo, se trata de una matriz de puntos. Siempre que deseemos acceder a un punto concreto deberemos utilizar una coordenada (x, y). Un punto contiene una serie de bits para representar el color. Esta cantidad de bits puede variar según la calidad de la imagen.

En la actualidad se suelen utilizar 24bits para fotografías en el que cada color primario (rojo, verde y azul) está representado por 8 bits. Puede darse el caso de que veamos opciones de imágenes a 32bits, pero en ese caso se añade un nuevo canal llamado alfa que representa la transparencia del punto.

Combinando estos colores primarios obtenemos todas las posibles tonalidades de colores. Cada color primario se representa con 1 byte así que el rango de valores posibles es entre 0 y 255. Cuando queremos ausencia de color pondremos el color 0 y si queremos que se pinte ese color en su tonalidad más fuerte pondremos 255.

También podemos obtener colores derivados a partir de combinaciones de tonalidades de los colores primarios. Por ejemplo, si aplicamos tonalidad máxima en rojo y en verde se obtiene el amarillo. El blanco se obtiene con la tonalidad máxima en los tres colores primarios y el negro con su ausencia.

colores

Primera práctica – Filtro gris.

De momento en este primer post solo deseamos establecer unas bases para poder crear filtros más complejos en futuros números. Así que en este número escribiremos el código que abre todos los ficheros de un directorio, los procese con un filtro sencillo y los almacene en otro directorio.

El filtro sencillo que vamos a desarrollar es poner la imagen con tonalidades grises. Anteriormente comentamos que se podían crear derivados de colores utilizando los colores primarios. Para el caso de los grises se obtienen poniendo la misma tonalidad de colores primarios. De esa forma podemos obtener 256 tonalidades de grises, desde el color negro al color blanco.

Para empezar nuestra práctica vamos a crear un interface para el tratamiento de imágenes de manera que podremos crear diferentes filtros y almacenarlos para ejecutarlos de manera consecutiva. Aquí crearemos nuestro primer fichero:

package org.gbd.fotos;
import java.awt.image.BufferedImage;
public interface Filtro {
	public abstract BufferedImage filtrar(BufferedImage bi);
}

El método filtrar será el encargado de ejecutar el filtro correspondiente. Este método recibe una imagen (BufferedImage) y devuelve una copia con el filtro aplicado. Hemos elegido BufferedImage por permitir de manera más sencilla a cada punto mediante coordenadas x,y.

A continuación vamos a crear un filtro que convierta una imagen en color a escala de grises. Para obtener la escala de grises debemos obtener de cada punto una tonalidad idéntica para asociarla a los tres colores primarios. En nuestro caso para obtener valor único vamos a realizar la media de las tres tonalidades de la imagen origen. Aquí tenéis el algoritmo.

package org.gbd.fotos;
import java.awt.Color;
import java.awt.GraphicsEnvironment;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
public class FiltroGris implements Filtro{
	public BufferedImage filtrar(BufferedImage bi){
		//Crea una copia del mismo tamaño que la imagen
		BufferedImage biDestino = 
			GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration()
			   .createCompatibleImage(bi.getWidth(), bi.getHeight(), Transparency.OPAQUE);
		//Recorre las imagenes y obtiene el color de la imagen original y la almacena en el destino
		for (int x=0;x < bi.getWidth();x++){
			for (int y=0;y < bi.getHeight();y++){
				//Obtiene el color
				Color c1=new Color(bi.getRGB(x, y));
				//Calcula la media de tonalidades
				int med=(c1.getRed()+c1.getGreen()+c1.getBlue())/3;
				//Almacena el color en la imagen destino
				biDestino.setRGB(x, y, new Color(med,med,med).getRGB());
			}
		}
		return biDestino;
	}
}

En el anterior algoritmo crea una imagen de las mismas dimensiones que la imagen original. A continuación recorre las imágenes y obtiene la media de tonos de cada punto almacenando esta media en todos los tonos de la segunda imagen.

Grises

Aplicar el filtro en serie

En ocasiones puede que queramos aplicar un filtro en diferentes imágenes ya que quizá la cámara estaba sacando las fotos de una manera determinada y deseamos corregir todas las fotos que ha realizado. Ejecutar la aplicación foto a foto puede llegar a ser un incordio para el usuario. Por todo ello vamos a desarrollar un algoritmo que lea los ficheros de una carpeta y los almacene en otra aplicando los filtros que deseemos.

import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import org.gbd.fotos.Filtro;
import org.gbd.fotos.FiltroGris;
public class Main {
	public static void main(String[] args) {
		if (args.length==0){
			System.out.println("No ha introducido ningún parámetro. Sintaxis:");
			System.out.println("java Main < carpeta > ");
			System.exit(-1);
		}
		String rutaOrigen=args[0];
		File dirOrigen=new File(rutaOrigen);
		//Comprueba que sea un directorio
		if (!dirOrigen.isDirectory()){
			System.out.println("La ruta enviada no es un directorio");
			System.exit(-1);
		}
		//Crea el directorio destino
		File dirDestino=new File(rutaOrigen+"/modificado");
		if (!dirDestino.exists()){
			dirDestino.mkdir();
		}
		//Recorre los ficheros
		File ficheros[]=dirOrigen.listFiles();
		for (File fic:ficheros){			
			//Si no es directorio
			if (!fic.isDirectory()){			
				try{
					//Carga la imagen
					Image ima=Toolkit.getDefaultToolkit().getImage(fic.getAbsolutePath());
					ima = new ImageIcon(ima).getImage(); //Para poder utilizar el getWidth y getHeight y asegurarse la carga de la imagen
					//Creación BufferedImage con la imagen
					BufferedImage bi =
					GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration()
										.createCompatibleImage(ima.getWidth(null), ima.getHeight(null), Transparency.OPAQUE);
					bi.getGraphics().drawImage(ima, 0, 0, null);
					// Almacenar los filtros que deseamos aplicar
					ArrayList < Filtro > alFiltros=new ArrayList< Filtro >();
					alFiltros.add(new FiltroGris());
					// Ejecutar los filtros
					for (Filtro f:alFiltros){
						bi=f.filtrar(bi);
					}
					// Guardar la imagen final en la carpeta destino
					try{
						ImageIO.write(bi, "jpg", new File(dirDestino + "/" + fic.getName())); 
						System.out.println("Foto finalizada " + fic.getName());
					}catch(IOException e){
						System.out.println("Error al procesar foto");
						e.printStackTrace();
					}
				}catch(Exception e){
					System.out.println(fic.getAbsolutePath() + " no tratado");
				}
			}
		}
	}
}

Inicialmente realiza la lectura de los ficheros que contiene el directorio indicado como parámetro y genera la carpeta destino si no existe. A continuación almacenamos todos los filtros que queremos aplicar en un ArrayList y los ejecutamos. Finalmente almacena el resultado en la carpeta destino con el mismo nombre.

Conclusión

En este primer número hemos visto algunos fundamentos básicos sobre los colores y hemos creado un filtro sencillo que convierte la imagen a escala de grises. También hemos creado la base para aplicar una serie de filtros sobre todos los ficheros de una carpeta. En futuros números crearemos filtros más complejos para tratar nuestras fotografías.

Anunciate aquí
Anunciate aquí
Anunciate aquí

¿Quieres saber más?

Artículos

Artículos relacionados que probablemente también te interesen

Ver más

Respuestas

Preguntas sobre este tema que ha contestado la comunidad

+ Deja tu comentario

Comentarios

  • 1

    Avatar de acerswap !

    Muy bueno el tutorial para los que no tenemos mucha idea, sabia que los grises era poniendo valores identicos para los tres colores, pero no habia caido nunca en que usando la media de los valores se hacia la escala de grises. Ahora que lo se, supongo que lo de los canales de cada color que aparece en los editores de imagenes es lo mismo, pero en vez de usar la media se usa solo el valor del color correspondiente.

    Un post muy interesante, ¡seguid asi!

  • 2

    Avatar de Jose Juan !

    (Hago este comentario desde el agradecimiento al autor [Jorge Rubira] por su esfuerzo, del que todos los días me aprovecho)

    Cuando he leído el título y el primer párrafo, pensaba que era un artículo para "grafers" (lo cual, en mi opinión, no procede), luego que leí el segundo párrafo creí que se trataba de "Informática Gráfica" (lo cual, en mi opinión, procede).

    Pero el contenido del artículo me ha parecido un pupurrí de objetos y métodos de Java para tratamiento de imágenes que, para nada ayudan a comprender la esencia del tratamiento de la imagen.

    Es decir:

    1. el código está formado en su mayoría por elementos totalmente ajenos al propio procesamiento del que se supone se habla. ¿No sería mejor una explicación sin código o un pseudocódigo y así nos ahorramos el GraphicsEnvironment .getLocalGraphicsEnvironment() .getDefaultScreenDevice() .getDefaultConfiguration() .createCompatibleImage?.

    2. el tratamiento de la imagen para pasarla a gris no es correcto, pues (al menos si se quiere mantener la percepción visual) no debe hacerse una media de las componentes (ver aquí para más detalles). Por ejemplo:

        int med=(30*c1.getRed()+59*c1.getGreen()+11*c1.getBlue())/100;

    3. el tratamiento en lotes se sale (creo yo) del objetivo del artículo y (advirtiendo que daba igual el lenguaje) es brutal (por bruto), cuando un simple script (ej. bash) consigue lo mismo (no hay, parece, motivo para hacerlo dentro del mismo programa).

    En fin, creo que la iniciativa del artículo es genial (para variar un poco) pero la puesta en práctica se queda un poco coja.

    Por supuesto es sólo mi opinión.

    -- editado por última vez a las 15:35

  • Respondiendo a #2:
  • 4

    Avatar de acerswap !

    En la Wikipedia hay un enlace muy interesante: http://gimp-savvy.com/BOOK/index.html?node54.html

  • Respondiendo a #2:
  • 7

    Avatar de Pakirri !

    El hecho de que ete blog sea un poco mas técnico no implica que no se pueda hacer tutoriales totalmente completos y funcionales... Es un gran aporte para usuario noveles y hasta de nivel intermedio, si quieres aportes puramente técnicos y depurados o aplicaciones mas específicas pues busca en foros o en libros especializados...

  • 3

    !
    | 1 estrellas

    ¡Súper interesante! De aquí se puede ir construyendo una serie hasta llegar a los muy complejos e interesantes Computer Graphics.

    En otro orden de ideas, ¿han pensado en Biología Molecular Computacional o algo de Algoritmos Genéticos?

  • 5

    Avatar de calandras !

    Yo tuve que hacer un trabajo en la universidad en el que tenía que trabajar con imágenes. Empezé haciendo pruebas con BufferedImage de java pero era bastante complicado así que seguí buscando y encontré imageJ. Os sugiero que lo tengais en cuenta. También trabaja en java, pero tiene muchos problemas ya resueltos y además se pueden añadir plugins hechos por ti. Yo en un fin de semana logré hacer el trabajo gracias a imageJ.

  • 6

    Avatar de sayjo !
    sayjo | 1 estrellas

    Justamente mi próxima practica en la Superior es sobre esto, aunque lo ando haciendo en Java y C#, no he encontrado como leer ya en escala de grises el valor en Byte, por ejemplo, si el pixel es blanco, que obtenga un 0, si es completamente negro un 255, alguien que me ayude?

  • Respondiendo a #6:
  • 8

    interesante

    Avatar de Jose Juan !

    Es al revés, si es 0 es negro, y si es blanco es 255.

    Aunque bueno, igual por corregirte e intentar ayudarte también me ponen negativos. Que me da igual (sólo hay que ver mi perfil), pero empieza a ser exasperante que nadie me lo justifique...

    Suerte!

  • 9

    Avatar de bigbossnake !

    Probe su Programa con eclipse y me borro todos lo iconos del escritorio a que creen que se deba.

  • 10

    Avatar de ogrosx !
    ogrosx | 1 estrellas

    El formato bmp si puede tener compresion. Este formato viene de os/2, paso a windows y se añadieron caracteristica. Juraria que en windows 3.0 podia tener run-length encoding (rle), con posterioridad se han admitido mas tipos de compresion. En la actualidad creo que hay 5 versiones de cabeceras, resumiendo puede tener (copy-paste en ingles):

    BI_RGB: Uncompressed image. BI_RLE8: 8-bpp image using run-length encoding compression. Bottom-up DIB only. BI_RLE4: 4-bpp image using run-length encoding compression. Bottom-up DIB only. BI_BITFIELDS: 16-, 32-bpp uncompressed image, three-bit masks provided to specify how to extract RGB components from a pixel. BI_JPEG: Pixel array is an embedded image in JPEG format. Windows 98/2000 only. BI_PNG: Pixel array is an embedded image in PNG format. Windows 98/2000 only.

    Saludos

Escribir un comentario

Para hacer un comentario es necesario que te identifiques: ENTRA o conéctate con Facebook Connect

Anunciate aquí

WSL Weblogs SL