package utils.images;
import ij.ImagePlus;
import ij.gui.Wand;
import ij.measure.Measurements;
import ij.measure.ResultsTable;
import ij.plugin.filter.ParticleAnalyzer;
import ij.process.ByteProcessor;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.awt.image.DirectColorModel;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import javax.imageio.ImageIO;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.PaletteData;
import org.eclipse.swt.graphics.RGB;
/**
* La clase <code>ImageUtils</code> est� constituida por un conjunto de
* herramientas para procesamiento de im�genes. Una de estas funcionalidades
* es el reconocimiento de objetos seg�n color, lo cual es intensamente
* utilizado por los sistemas de posicionamiento basados en im�genes.<br>
* Tambi�n permite la conversi�n de im�genes entre los formatos AWT y SWT.
*
*/
public class ImageUtils
{
// Constantes p�blicas
public static final int NORMAL_ACCURACY = 1;
public static final int HIGH_ACCURACY = 2;
// Constantes privadas
private static final int MARGIN = 30; // TODO: ajustar
private static final int BINARY_THRESHOLD = 150;
// Atributos
private ParticleAnalyzer analizer;
private ResultsTable rt;
private RecognizedShape[] recognizedShapes;
private ImagePlus bwImage;
private ByteProcessor bwProcessor;
private BufferedImage sceneImage;
private int[] pixels;
private Graphics2D gc;
private boolean firstTime;
/**
* Crea una nueva instancia <code>ImageUtils</code> para
* procesamiento de im�genes.
*/
public ImageUtils()
{
firstTime = true;
}
private void buildAnalizer(int min, int max)
{
// Se construye el analizador
rt = new ResultsTable();
analizer = new ParticleAnalyzer(
ParticleAnalyzer.SHOW_NONE | ParticleAnalyzer.FLOOD_FILL | ParticleAnalyzer.RECORD_STARTS,
Measurements.CENTROID | Measurements.AREA | Measurements.RECT,
rt,
min,
max);
}
/**
* Establece la imagen a la cual se le aplicar�n los procesamientos.
* @param image imagen AWT a procesar.
*/
public synchronized void setImage(Image image)
{
if ( firstTime )
{
int width = image.getWidth(null);
int height = image.getHeight(null);
pixels = new int[ width * height ];
sceneImage = new BufferedImage( width, height, BufferedImage.TYPE_INT_RGB);
pixels = ((DataBufferInt)sceneImage.getRaster().getDataBuffer()).getData();
gc = sceneImage.createGraphics();
bwImage = new ImagePlus(null, bwProcessor = new ByteProcessor(width,height));
//bwImage.trimProcessor();
firstTime = false;
}
gc.drawImage(image, 0, 0, null);
}
private synchronized void extractColor(Color c) throws ImageUtilsException
{
if ( sceneImage == null || bwProcessor == null )
throw new ImageUtilsException("There is not a loaded image.");
int auxR, auxG, auxB;
int offset, i;
int r = c.getRed();
int g = c.getGreen();
int b = c.getBlue();
int width = sceneImage.getWidth(null);
int height = sceneImage.getHeight(null);
byte[] bwPixels = (byte[])bwProcessor.getPixels();
for (int y=0; y<height; y++)
{
offset = y*width;
for (int x=0; x<width; x++)
{
i = offset + x;
auxR = (pixels[i] & 0x00FF0000) >> 16;
auxG = (pixels[i] & 0x0000FF00) >> 8;
auxB = pixels[i] & 0x000000FF;
if ( (auxR < r-MARGIN) || (auxR > r+MARGIN) ||
(auxG < g-MARGIN) || (auxG > g+MARGIN) ||
(auxB < b-MARGIN) || (auxB > b+MARGIN) )
bwPixels[i] |= 0xFF; // blanco
else
bwPixels[i] &= 0x00; // negro
}
}
}
/**
* Reconoce en la imagen cargada con <code>setImage()</code> las figuras
* del color indicado por par�metro, cuyas �reas est�n comprendidas entre
* <code>min</code> y <code>max</code>. Almacena un arreglo con informaci�n
* de cada figura reconocida (centroide, �rea, v�rtices).<br>
* Permite especificar la precisi�n con la que se har� el procesamiento
* (en computo intensivo o tiempo real, utilizar el nivel normal para no
* penalizar el desempe�o).
* @param r componente rojo.
* @param g componente verde.
* @param b componente azul.
* @param min tama�o m�nimo.
* @param max tama�o m�ximo.
* @param accuracy {@link #NORMAL_ACCURACY} o {@link #HIGH_ACCURACY}.
* @throws ImageUtilsException si se produce alg�n error en el proceso.
*/
public synchronized RecognizedShape[] recognizeShapes(int r, int g, int b, int min, int max, int accuracy) throws ImageUtilsException
{
// Etapa de preprocesamiento
Color color = new Color(r,g,b);
extractColor( color );
if ( accuracy == HIGH_ACCURACY )
{
// Si se utiliza "alta precisi�n", entonces se aplica
// un filtrado ("median filter") que permite hacer un
// mejor reconocimiento de �tems, a costa de un mayor
// tiempo de procesamiento.
int i,j;
bwImage.trimProcessor();
bwProcessor.medianFilter();
bwProcessor.threshold(BINARY_THRESHOLD);
// Se aplica una correcci�n a la imagen, para quitar el borde negro
// delgado agregado por el filtro medianFilter().
// TODO: ver c�mo solucionar esto de una forma "correcta"
int w = bwImage.getWidth();
int h = bwImage.getHeight();
for (j=0; j<w; j++)
{
bwProcessor.putPixel(j, 0 , 255);
bwProcessor.putPixel(j, h-1, 255);
}
for (i=0; i<h; i++)
{
bwProcessor.putPixel(0, i, 255);
bwProcessor.putPixel(w-1, i, 255);
}
}
// Etapa de an�lisis de la im�gen preprocesada
buildAnalizer(min, max);
rt.reset();
if ( !analizer.analyze(bwImage) )
throw new ImageUtilsException("The image couldn�t be analyzed.");
int xColumn = rt.getColumnIndex("X");
int yColumn = rt.getColumnIndex("Y");
int bxColumn = rt.getColumnIndex("BX");
int byColumn = rt.getColumnIndex("BY");
int xStartColumn = rt.getColumnIndex("XStart");
int yStartColumn = rt.getColumnIndex("YStart");
int areaColumn = rt.getColumnIndex("Area");
float xCentroid[] = rt.getColumn( xColumn );
float yCentroid[] = rt.getColumn( yColumn );
float bx[] = rt.getColumn( bxColumn );
float by[] = rt.getColumn( byColumn );
float xStarts[] = rt.getColumn( xStartColumn );
float yStarts[] = rt.getColumn( yStartColumn );
float areas[] = rt.getColumn( areaColumn );
if ( xCentroid == null ||
yCentroid == null ||
bx == null ||
by == null ||
areas == null )
throw new ImageUtilsException("The results table couldn't be read.");
recognizedShapes = new RecognizedShape[ xCentroid.length ];
// Aplicar "Wand Tool"
Wand wandTool = new Wand( bwImage.getProcessor() );
for (int i=0; i<xCentroid.length; i++)
{
wandTool.autoOutline( (int)xStarts[i] , (int)yStarts[i] );
recognizedShapes[i] = new RecognizedShape( xCentroid[i] ,
yCentroid[i] ,
bx[i] ,
by[i] ,
areas[i] ,
color ,
wandTool.xpoints,
wandTool.ypoints,
wandTool.npoints
);
}
return recognizedShapes;
}
/**
* Retorna el conjunto de figuras (objetos) reconocidos por
* {@link #recognizedShapes}.
* @return figuras reconocidas.
*/
public synchronized RecognizedShape[] getRecognizedShapes()
{
return recognizedShapes;
}
/**
* Convierte una imagen AWT en otra imagen SWT.
* @param bufferedImage imagen AWT.
* @return imagen SWT.
*/
public static ImageData convertToSWT(BufferedImage bufferedImage)
{
if (bufferedImage.getColorModel() instanceof DirectColorModel)
{
DirectColorModel colorModel = (DirectColorModel) bufferedImage.getColorModel();
PaletteData palette = new PaletteData( colorModel.getRedMask(),
colorModel.getGreenMask(),
colorModel.getBlueMask());
ImageData data = new ImageData( bufferedImage.getWidth(),
bufferedImage.getHeight(),
colorModel.getPixelSize(),
palette);
WritableRaster raster = bufferedImage.getRaster();
int[] pixelArray = new int[3];
RGB rgb = new RGB(0,0,0);
for (int y = 0; y < data.height; y++)
{
for (int x = 0; x < data.width; x++)
{
raster.getPixel(x, y, pixelArray);
rgb.red = pixelArray[0];
rgb.green = pixelArray[1];
rgb.blue = pixelArray[2];
data.setPixel(x, y, palette.getPixel( rgb ) );
}
}
return data;
}
return null;
}
/**
* Almacena una imagen AWT en un archivo en disco. Este m�todo
* suele utilizarse para fines de <i>debugging</i>, ya que no
* presenta un buen desempe�o.
* @param image imagen AWT.
* @param name nombre del archivo en disco.
*/
public static void saveImage(Image image, String name)
{
if ( image == null )
return;
int w = image.getWidth(null);
int h = image.getHeight(null);
BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = bi.createGraphics();
g2.drawImage(image, 0, 0, null);
g2.dispose();
try
{
ImageIO.write(bi, "jpg", new File(name));
}
catch(IOException e)
{ }
}
/**
* Retorna el color promedio del �rea definida por el extremo superior
* izquierdo en <i>(x,y)</i>, y de ancho y alto <i>w</i> y <i>h</i>,
* respectivamente.
* @param id imagen SWT.
* @param x coordenada x del extremo superior izquierdo del �rea.
* @param y coordenada y del extremo superior izquierdo del �rea.
* @param w ancho del �rea.
* @param h alto del �rea.
* @return componentes RGB del color promedio.
*/
public static int[] getAverageColor(ImageData id, int x, int y, int w, int h)
{
int[] pixline = new int[w];
int[] pixels = new int[w*h];
int k = 0;
for (int line=y; line<y+h; line++)
{
id.getPixels(x, line, w, pixline, 0);
for (int pix=0; pix<pixline.length; pix++, k++)
pixels[k] = pixline[pix];
}
// Se busca la mediana
Arrays.sort( pixels );
RGB rgb = id.palette.getRGB( pixels[pixels.length/2] );
return new int[] { rgb.red , rgb.green , rgb.blue };
}
}