/*
* ImageProcessor.java
*
* Created on October 13, 2007, 1:26 AM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
package taller3;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.imageio.ImageIO;
import coder.HuffmanCoder;
/**
*
* @author Juli�n Klas
*/
public class ImageProcessor {
public final static int RED_CHANNEL = 0;
public final static int GREEN_CHANNEL = 1;
public final static int BLUE_CHANNEL = 2;
public final static int INTENSITY_CHANNEL = 3;
public final static int RGB_CHANNEL = 4;
private String DEFAULT_FILENAME="c:\\mercadoliebre.jpg";
private BufferedImage img = null;
int[] grayHistogram = new int[256];
int[][] rgbHistogram = new int[3][];
/**
* Carga una imagen a partir de un archivo
*
* @param filename nombre del archivo que contiene la imagen
* @throws IOException
*/
public void loadImage(String filename) throws IOException{
if(filename==null) filename=DEFAULT_FILENAME;
img=ImageIO.read(new File(filename));
}
public List<Color> getColorMatchList(List<Color> places, int buckets, float tolerance) {
buildColorHistogram(buckets);
List<Color> result = new ArrayList<Color>();
List<Pair<Color,Float>> intermediate = new ArrayList<Pair<Color,Float>>();
for (Color color : places) {
intermediate.add(new Pair<Color,Float>(color,getSimilitude(color, buckets, tolerance)));
}
Collections.sort(intermediate,new Comparator<Pair<Color,Float>>() {
public int compare(Pair<Color, Float> o1, Pair<Color, Float> o2) {
return o1.getSecond().compareTo(o2.getSecond());
}
});
for (Pair<Color, Float> pair : intermediate) {
result.add(pair.getFirst());
}
return result;
}
// retorna la masa alrededor de mas menos 10% de los buckets
private Float getSimilitude(Color color, int buckets,float tolerance) {
int intColor = color.getRGB();
char redIndex = (char)(((intColor >> 16) & 0xFF) * (float)(buckets - 1) / 255.0f);
char greenIndex = (char)(((intColor >> 8) & 0xFF) * (float)(buckets - 1) / 255.0f);
char blueIndex = (char)(((intColor >> 0) & 0xFF) * (float)(buckets - 1) / 255.0f);
float mass = 0;
int desplazamiento = Math.min(1,(int)Math.round(buckets*(1-tolerance)));
if(desplazamiento % 2 == 0) desplazamiento++;
for (int i = -Math.max(0,desplazamiento); i <= Math.min(desplazamiento,255); i++) {
mass += rgbHistogram[0][redIndex+i]+rgbHistogram[1][greenIndex+i]+rgbHistogram[2][blueIndex+i];
}
return mass;
}
/**
* Determina si el robot est� por colisionar con una pared
*
* @param bandColor color de la banda inferior del laberinto
* @param tolerance cuenta m�nima de los colores del color de la banda en el histograma
* para considerar una colisi�n
* @param histogramBuckets n�mero de divisiones en el histograma
* @return
*/
public boolean isCollision(int[][] bandColors, float tolerance, int histogramBuckets) {
buildColorHistogram(histogramBuckets);
for (int i = 0; i < bandColors[0].length; i++) {
int bandColorRedIndex = (int)(bandColors[0][i]*((float)histogramBuckets/255));
int bandColorGreenIndex = (int)(bandColors[1][i]*((float)histogramBuckets/255));
int bandColorBlueIndex = (int)(bandColors[2][i]*((float)histogramBuckets/255));
if( rgbHistogram[0][bandColorRedIndex] < tolerance &&
rgbHistogram[1][bandColorGreenIndex] < tolerance &&
rgbHistogram[2][bandColorBlueIndex] < tolerance )return true;
}
return false;
}
/**
* Crea un histograma de 3 canales (RGB)
* @param buckets n�mero de divisiones en el histograma
*/
private void buildColorHistogram(int buckets) {
rgbHistogram[0]=new int[buckets];
rgbHistogram[1]=new int[buckets];
rgbHistogram[2]=new int[buckets];
int width = img.getWidth();
int height = img.getHeight();
for(int i=0; i< width ; i++)
for(int j=0; j< height; j++)
{
int color = img.getRGB(i, j);
char redIndex = (char)(((color >> 16) & 0xFF) * (float)(buckets - 1) / 255.0f);
char greenIndex = (char)(((color >> 8) & 0xFF) * (float)(buckets - 1) / 255.0f);
char blueIndex = (char)(((color >> 0) & 0xFF) * (float)(buckets - 1) / 255.0f);
rgbHistogram[0][redIndex]++;
rgbHistogram[1][greenIndex]++;
rgbHistogram[2][blueIndex]++;
}
}
/**
* Crea un histograma de intensidades de gris
*/
private void buildIntensityHistogram()
{
for(int i=0; i< img.getWidth(); i++)
for(int j=0; j< img.getHeight(); j++)
{
int intensity=getIntensity(i,j);
grayHistogram[intensity]++;
}
}
public void saveHistogram(String filename) throws IOException
{
File f=new File(filename);
BufferedWriter bw=new BufferedWriter(new FileWriter(f));
for(int k=0; k<grayHistogram.length; k++){
bw.write(Integer.toString(grayHistogram[k]));
bw.write(";");
}
bw.close();
}
public void equalize(int channel) {
switch(channel) {
case RED_CHANNEL: if(rgbHistogram == null) buildColorHistogram(255);
equalizeSingleChannel(rgbHistogram[0],RED_CHANNEL);
break;
case GREEN_CHANNEL: if(rgbHistogram == null) buildColorHistogram(255);
equalizeSingleChannel(rgbHistogram[1],GREEN_CHANNEL);
break;
case BLUE_CHANNEL: if(rgbHistogram == null) buildColorHistogram(255);
equalizeSingleChannel(rgbHistogram[2],BLUE_CHANNEL);
break;
case INTENSITY_CHANNEL: if(grayHistogram == null) buildIntensityHistogram();
equalizeSingleChannel(grayHistogram,INTENSITY_CHANNEL);
break;
case RGB_CHANNEL: buildColorHistogram(256);
equalize(RED_CHANNEL);
equalize(GREEN_CHANNEL);
equalize(BLUE_CHANNEL);
break;
}
}
private void equalizeSingleChannel(int[] currentChannel, int channel) {
/* 1) n�mero total de colores */
int totalColors=img.getWidth()*img.getHeight();
/* 2) limiteInferior = 5% de la masa a la izquierda */
int limiteInferior=0;
int limiteSuperior=255;
int colorMass = 0;
while(limiteInferior<256 && colorMass < totalColors*0.005)
{
colorMass+=currentChannel[limiteInferior];
limiteInferior++;
}
colorMass=0;
while(limiteSuperior>=0 && colorMass < totalColors*0.05)
{
colorMass+=currentChannel[limiteSuperior];
limiteSuperior--;
}
if(limiteSuperior<=limiteInferior || (limiteSuperior == 255 && limiteInferior==0))
{
logInfo("No se pudo encontrar la masa critica.");
}
else
{
logInfo("Limite inferior: "+limiteInferior+" ; LimiteSuperior: "+limiteSuperior);
int maxValue=255;
float k = (float)maxValue / (limiteSuperior-limiteInferior) ;
float offset = - ((float)maxValue / (limiteSuperior-limiteInferior))*limiteInferior;
for(int i=0; i<img.getWidth(); i++)
for(int j=0; j<img.getHeight(); j++)
{
int color = (channel != INTENSITY_CHANNEL) ? img.getRGB(i, j) : getIntensity(i,j);
int newColor = color;
char actualIndex = 0;
int shifting = 0;
int colorMask = 0;
switch(channel) {
case RED_CHANNEL: shifting = 16; colorMask = 0x00FF0000; break;
case GREEN_CHANNEL: shifting = 8; colorMask = 0x0000FF00; break;
case BLUE_CHANNEL: shifting = 0; colorMask = 0x000000FF; break;
}
actualIndex = (char)(((color >> shifting) & 0xFF));
int newIndex=actualIndex;
newIndex=(int)(k*actualIndex+offset);
if(actualIndex<limiteInferior)
{
newIndex = 0;
}
else if(actualIndex>limiteSuperior){
newIndex = 255;
}
if(channel != INTENSITY_CHANNEL) {
newColor = (newColor & ~colorMask) | ((int)newIndex<<shifting & colorMask);
} else {
newColor =((newIndex << 8) + (newIndex << 16) + (newIndex << 24))+newIndex;
}
img.setRGB(i,j,newColor);
}
}
}
/**
* Retorna la intensidad de gris de una cordenada de la imagen
* @param x posici�n sobre el eje horizontal
* @param y posici�n sobre el eje vertical
* @return intensida de gris en el punto especificado
*/
private int getIntensity(int x, int y) {
Color color=new Color(img.getRGB(x,y));
int out = (int)(((float)color.getRed()*0.3) + ((float)color.getGreen() * 0.59) + ((float)color.getBlue() * 0.11));
return out;
}
/**
* Guarda esta imagen como archivo JPG
* @param filename nombre del archivo
* @throws IOException
*/
void saveImage(String filename) throws IOException {
/*ImageWriteParam iwparam = new JPEGImageWriteParam(Locale.getDefault());
iwparam.setCompressionMode(ImageWriteParam.MODE_DEFAULT);*/
ImageIO.write(img,"jpeg",new File(filename));
}
public void colorMovingAverage() {
float matrix[][]={{0.025f,0.05f,0.025f},{0.05f,0.7f,0.05f},{0.025f,0.05f,0.025f}};
int anchoImagen = img.getWidth()-1;
int altoImagen = img.getHeight()-1;
for (int color = 0; color < 3; color++) {
for(int i=1; i<anchoImagen; i++)
for(int j=1; j<altoImagen; j++)
{
char temp=0;
temp += (int)(getSingleChannelColor(img.getRGB(i-1, j-1), color)*matrix[0][0]);
temp += (int)(getSingleChannelColor(img.getRGB(i-1, j ), color)*matrix[0][1]);
temp += (int)(getSingleChannelColor(img.getRGB(i-1, j+1), color)*matrix[0][2]);
temp += (int)(getSingleChannelColor(img.getRGB(i , j-1), color)*matrix[1][0]);
temp += (int)(getSingleChannelColor(img.getRGB(i , j ), color)*matrix[1][1]);
temp += (int)(getSingleChannelColor(img.getRGB(i ,j+1), color)*matrix[1][2]);
temp += (int)(getSingleChannelColor(img.getRGB(i+1, j-1), color)*matrix[2][0]);
temp += (int)(getSingleChannelColor(img.getRGB(i+1, j ), color)*matrix[2][1]);
temp += (int)(getSingleChannelColor(img.getRGB(i+1, j+1), color)*matrix[2][2]);
int shifting = 0;
int colorMask = 0;
switch(color) {
case RED_CHANNEL: shifting = 16; colorMask = 0x00FF0000; break;
case GREEN_CHANNEL: shifting = 8; colorMask = 0x0000FF00; break;
case BLUE_CHANNEL: shifting = 0; colorMask = 0x000000FF; break;
}
int newColor = img.getRGB(i, j);
newColor = (newColor & ~colorMask) | ((int)temp<<shifting & colorMask);
img.setRGB(i,j,newColor);
}
}
}
/**
* Genera un efecto de desenfoque sobre la imagen
*/
public void movingAverage()
{
float matrix[][]={{0.025f,0.05f,0.025f},{0.05f,0.7f,0.05f},{0.025f,0.05f,0.025f}};
int temp;
for(int i=1; i<img.getWidth()-1; i++)
for(int j=1; j<img.getHeight()-1; j++)
{
temp=0;
temp += (int)(getIntensity(i-1,j-1)*matrix[0][0]);
temp += (int)(getIntensity(i-1,j) *matrix[0][1]);
temp += (int)(getIntensity(i-1,j+1)*matrix[0][2]);
temp += (int)(getIntensity(i ,j-1)*matrix[1][0]);
temp += (int)(getIntensity(i ,j )*matrix[1][1]);
temp += (int)(getIntensity(i ,j+1)*matrix[1][2]);
temp += (int)(getIntensity(i+1,j-1)*matrix[2][0]);
temp += (int)(getIntensity(i+1,j )*matrix[2][1]);
temp += (int)(getIntensity(i+1,j+1)*matrix[2][2]);
temp=((temp << 8) + (temp << 16) + (temp << 24))+temp;
img.setRGB(i,j,temp);
}
}
/**
* Reemplaza la imagen por una representaci�n de sus bordes
* @param sensibility nivel de sensibilidad para encontrar un borde
*/
public void findBorder(float sensibility)
{
float matrixX[][]={{-1f,0f,+1f},{-2f,0f,2f},{-1f,0f,1f}};
float matrixY[][]={{1f,2f,1f},{0f,0f,0f},{-1f,-2f,-1f}};
if(sensibility<0) sensibility*=-1;
int tempX,tempY;
int finalColor;
Raster raster=img.getData();
BufferedImage copy=new BufferedImage(img.getWidth(),img.getHeight(),img.getType());
copy.setData(raster);
for(int i=1; i<img.getWidth()-1; i++)
for(int j=1; j<img.getHeight()-1; j++)
{
tempX=0;
tempY=0;
/** X **/
tempX += (int)(getIntensity(i-1,j-1)*matrixX[0][0]);
tempX += (int)(getIntensity(i-1,j) *matrixX[0][1]);
tempX += (int)(getIntensity(i-1,j+1)*matrixX[0][2]);
tempX += (int)(getIntensity(i ,j-1)*matrixX[1][0]);
tempX += (int)(getIntensity(i ,j )*matrixX[1][1]);
tempX += (int)(getIntensity(i ,j+1)*matrixX[1][2]);
tempX += (int)(getIntensity(i+1,j-1)*matrixX[2][0]);
tempX += (int)(getIntensity(i+1,j )*matrixX[2][1]);
tempX += (int)(getIntensity(i+1,j+1)*matrixX[2][2]);
/** Y **/
tempY += (int)(getIntensity(i-1,j-1)*matrixY[0][0]);
tempY += (int)(getIntensity(i-1,j) *matrixY[0][1]);
tempY += (int)(getIntensity(i-1,j+1)*matrixY[0][2]);
tempY += (int)(getIntensity(i ,j-1)*matrixY[1][0]);
tempY += (int)(getIntensity(i ,j )*matrixY[1][1]);
tempY += (int)(getIntensity(i ,j+1)*matrixY[1][2]);
tempY += (int)(getIntensity(i+1,j-1)*matrixY[2][0]);
tempY += (int)(getIntensity(i+1,j )*matrixY[2][1]);
tempY += (int)(getIntensity(i+1,j+1)*matrixY[2][2]);
float sum=(float)Math.sqrt(tempX*tempX+tempY*tempY);
if(sum>sensibility){
finalColor=(int)(0);
finalColor=((finalColor << 8) + (finalColor << 16) + (finalColor << 24))+finalColor;
copy.setRGB(i,j,finalColor);
}
else{
finalColor=(int)(255);
finalColor=((finalColor << 8) + (finalColor << 16) + (finalColor << 24))+finalColor;
copy.setRGB(i,j,finalColor);
}
}
raster=copy.getData();
img.setData(raster);
}
/**
* Calcula la superposicion entre dos objetos en base a cu�n rodeado
* est� el objeto1 por el objeto2
*
* @param object1 colores que representan al objeto1 en canales RGB
* @param object2 colores que representan al objeto2 en canales RGB
* @param tolerance par�metro para regular la sensibilidad del algoritmo
* @return nivel superposici�n de objeto1 y objeto2
*/
public float calcularSuperposicion(char[][] object1, char[][] object2, float tolerance, float centroidTolerance) {
int[] boundingBox = getBoundingBox(object1,centroidTolerance);
int x0 = boundingBox[0], y0 = boundingBox[1],x1 = boundingBox[2],y1 = boundingBox[3];
int colorMassInBox = getColorMassInRegion(object2,boundingBox,tolerance);
int totalMass = (int)Math.round((float)(y1-y0) * (float)(x1-x0));
return (float)colorMassInBox / (float) totalMass;
}
/**
* Obtiene la masa absoluta de colores de un objeto en una region
*
* @param object matriz con las componentes de color del objeto en cada canal
* @param boundingBox region de la imagen donde se encuentra el objeto (x0,y0,x1,y1)
* @param tolerance nivel de precisi�n a la hora de matchear colores (0 hasta 1)
* @return el n�mero absoluto de colores que matchearon
*/
private int getColorMassInRegion(char[][] object, int[] boundingBox,float tolerance) {
int x0 = boundingBox[0], y0 = boundingBox[1],x1 = boundingBox[2],y1 = boundingBox[3];
int matches = 0;
for (int i = x0; i < x1; i++) {
for (int j = y0; j < y1; j++) {
int actualColor = img.getRGB(i, j);
if(isColorMatch(object,actualColor,tolerance)) {
matches ++;
}
}
}
if(matches == 0) return 0;
else return matches;
}
/**
* Obtiene estad�sticamente las coordenadas de un rect�ngulo que encierra al objeto.
*
* No hay garant�as de que el cuadrado encierre efectivamente al objeto.
*
* @param object colores RGB que definen al objeto
* @param centroidTolerance nivel de exactitud requerido por el algoritmo (0 m�s exacto, 1 menos exacto)
* @return coordenadas del rect�ngulo que encierra al objeto
*/
private int[] getBoundingBox(char[][] object, float centroidTolerance) {
int anchoImagen = img.getWidth(), altoImagen = img.getHeight();
int centroidX = 0, centroidY = 0;
int matches = 0;
long varX = 0, varY = 0;
for (int i = 0; i < anchoImagen; i++) {
for (int j = 0; j < altoImagen; j++) {
int actualColor = img.getRGB(i, j);
if(isColorMatch(object,actualColor,centroidTolerance)) {
centroidX += i;
centroidY += j;
matches ++;
}
}
}
if (matches == 0) return new int[] {0,0,0,0};
centroidX = Math.round((float)centroidX / (float)matches);
centroidY = Math.round((float)centroidY / (float)matches);
for (int i = 0; i < anchoImagen; i++) {
for (int j = 0; j < altoImagen; j++) {
int actualColor = img.getRGB(i, j);
if(isColorMatch(object,actualColor,centroidTolerance)) {
varX += Math.pow(i-centroidX,2);
varY += Math.pow(j-centroidY,2);
}
}
}
int desvioX = (int)Math.round(Math.sqrt((double)varX/(double)(matches-1)));
int desvioY = (int)Math.round(Math.sqrt((double)varY/(double)(matches-1)));
// debug, pinta un cuadro negro
// for (int i = Math.max(0,centroidX - (int)Math.round(2.5*desvioX)); i < Math.min(centroidX + (int)Math.round(2.5*desvioX),anchoImagen); i++) {
// for (int j = Math.max(0,centroidY - (int)Math.round(2.5*desvioY)); j < Math.min(centroidY + (int)Math.round(2.5*desvioY),anchoImagen); j++) {
// img.setRGB(i, j, 0);
// }
// }
// x0,y0,x1,y1
return new int[] {
Math.max(0,centroidX - (int)Math.round(2.5*desvioX)),
Math.max(0,centroidY - (int)Math.round(2.5*desvioY)),
Math.min(centroidX + (int)Math.round(2.5*desvioX),anchoImagen),
Math.min(centroidY + (int)Math.round(2.5*desvioY),anchoImagen)
};
}
/**
* Determina si hay coincidencia entre un color y una lista de colores RGB
* con la tolerancia especificada
*
* @param object lista de colores RGB del objeto
* @param actualColor color que se quiere saber si pertenece al objeto
* @param tolerance nivel de tolerancia entre 0 y 1
* @return true si actualColor est� en object, false en caso contrario
*/
private boolean isColorMatch(char[][] object, int actualColor, float tolerance) {
int actualRed = getSingleChannelColor(actualColor, RED_CHANNEL);
int actualGreen = getSingleChannelColor(actualColor, GREEN_CHANNEL);
int actualBlue = getSingleChannelColor(actualColor, BLUE_CHANNEL);
for (int i = 0; i < object[0].length; i++) {
int minRed = Math.round(object[RED_CHANNEL][i]-256*tolerance);
int maxRed = Math.round(object[RED_CHANNEL][i]+256*tolerance);
int minGreen = Math.round(object[GREEN_CHANNEL][i]-256*tolerance);
int maxGreen = Math.round(object[GREEN_CHANNEL][i]+256*tolerance);
int minBlue = Math.round(object[BLUE_CHANNEL][i]-256*tolerance);
int maxBlue = Math.round(object[BLUE_CHANNEL][i]+256*tolerance);
if( actualRed > minRed && actualRed < maxRed &&
actualGreen > minGreen && actualGreen < maxGreen &&
actualBlue > minBlue && actualBlue < maxBlue ) {
return true;
}
}
return false;
}
/**
* Obtiene la entrop�a de la imagen a partir del histograma de grises
* @return la entropia de la imagen en escala de grises
*/
public float getEntropy(){
this.buildIntensityHistogram();
float entropy=0;
float sum = img.getWidth()*img.getHeight();
for (int i = 0; i < grayHistogram.length; i++) {
float probability = (((float)grayHistogram[i])/sum);
if(probability>0) {
entropy+= (-probability*Math.log(probability)/Math.log(2));
}
}
return entropy;
}
public BufferedImage getImg() {
return img;
}
public void setImg(BufferedImage img) {
this.img = img;
}
public float getHuffAvg() {
this.buildIntensityHistogram();
HuffmanCoder coder=new HuffmanCoder();
coder.buildTreeFromFrequency(grayHistogram);
return coder.getAverageBits();
}
private void logInfo(String info) {
System.out.println(info);
}
private char getSingleChannelColor(int color, int channel) {
switch(channel) {
case RED_CHANNEL: return (char)(((color >> 16) & 0xFF));
case GREEN_CHANNEL: return (char)(((color >> 8) & 0xFF));
case BLUE_CHANNEL: return (char)(((color >> 0) & 0xFF));
}
return 0;
}
}