/*
* Copyright 2005, 2009 Cosmin Basca.
* e-mail: cosmin.basca@gmail.com
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1
* of the License, or (at your option) any later version.
*
* Please see COPYING for the complete licence.
*/
package robo.vision;
import java.awt.Rectangle;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import javax.media.jai.ImageLayout;
import javax.media.jai.UntiledOpImage;
@SuppressWarnings("unchecked")
public class StentifordThinningOpImage extends UntiledOpImage
{
private int width;
@SuppressWarnings("unused")
private int heigth;
private final int WHITE = 255;
@SuppressWarnings("unused")
private static final int BLACK = 0;
public StentifordThinningOpImage(RenderedImage source, ImageLayout layout)
{
super(source, null, layout);
}
protected void computeImage(Raster[] srcarr, WritableRaster dst, Rectangle destRect)
{
Raster src = srcarr[0];
this.width = src.getWidth();
this.height = src.getHeight();
this.thinning(src,dst);
}
// Return the k indexed neighbour of a given pixel (x, y).
// The k indexed neighbour table is given as below:
// 4 3 2
// 5 0 1
// 6 7 8
private int neighbour(int x, int y, int k,Raster src)
{
assert (x>=0 && x<width && y>=0 && y<height && k>=0 && k<=8): "x ="+x+" y ="+y+" k ="+k;
switch(k)
{
case 0:
break;
case 1:
x++;
break;
case 2:
x++;
y--;
break;
case 3:
y--;
break;
case 4:
x--;
y--;
break;
case 5:
x--;
break;
case 6:
x--;
y++;
break;
case 7:
y++;
break;
case 8:
x++;
y++;
break;
}
if(x<0) x = 0;
else if(x>=width) x = width-1;
if(y<0) y = 0;
else if(y>=height) y = height - 1;
//return src.getSample(x, y, 0);
return this.get(src,x,y,0);
}
private int connectivity(int x, int y,Raster src)
{
assert (x>=0 && x<width && y>=0 && y<height): "x ="+x+" y ="+y;
// As in paper http://www.eng.fiu.edu/me/robotics/elib/am_st_fiu_ppr_2000.pdf,
// 0 is object value (foreground) and 1 is background value. This is opposite
// from the binarize operation in JAI where equation
// dst(x, y) = src(x, y) >= threshold ? 1 : 0;
// is used. Hence, we will just invert the value to fit JAI.
int p1 = neighbour(x, y, 1,src)==1?0:1;
int p2 = neighbour(x, y, 2,src)==1?0:1;
int p3 = neighbour(x, y, 3,src)==1?0:1;
int p4 = neighbour(x, y, 4,src)==1?0:1;
int p5 = neighbour(x, y, 5,src)==1?0:1;
int p6 = neighbour(x, y, 6,src)==1?0:1;
int p7 = neighbour(x, y, 7,src)==1?0:1;
int p8 = neighbour(x, y, 8,src)==1?0:1;
return p1*(1-p2*p3) + p3*(1-p4*p5) + p5*(1-p6*p7) + p7*(1-p8*p1);
}
private boolean endPoint(int x, int y,Raster src)
{
assert (x>=0 && x<width && y>=0 && y<height): "x ="+x+" y ="+y;
//int pixel = src.getSample(x, y, 0);
int pixel = this.get(src,x,y,0);
int count = 0;
if(pixel == neighbour(x, y, 1,src)) count++;
if(pixel == neighbour(x, y, 2,src)) count++;
if(pixel == neighbour(x, y, 3,src)) count++;
if(pixel == neighbour(x, y, 4,src)) count++;
if(pixel == neighbour(x, y, 5,src)) count++;
if(pixel == neighbour(x, y, 6,src)) count++;
if(pixel == neighbour(x, y, 7,src)) count++;
if(pixel == neighbour(x, y, 8,src)) count++;
return count == 1;
}
private void thinning(Raster src,WritableRaster dst)
{
int[] foregroundNeighbour = {7,1,3,5};
int[] backgroundNeighbour = {3,5,7,1};
boolean[][] m = new boolean[width][height];
boolean remain = true;
for(int i=0; i<m.length; i++)
{
for(int j=0; j<m[i].length; j++)
{
m[i][j] = false; // uneraseable
}
}
while (remain)
{
remain = false;
for(int k=0; k<foregroundNeighbour.length; k++)
{
// We are not interest at the image boundary.
for(int y=1; y<height-1; y++)
{
for(int x=1; x<width-1; x++)
{
//int p = src.getSample(x, y, 0);
int p = get(src,x,y,0);
//if(p == COLOR_FOREGROUND)
if(p == 1)
{
int p1 = neighbour(x, y, foregroundNeighbour[k],src);
int p2 = neighbour(x, y, backgroundNeighbour[k],src);
// Pattern matched.
//if(p1 == COLOR_FOREGROUND && p2 == COLOR_BACKGROUND)
if(p1 == 1 && p2 == 0)
{
if(!endPoint(x, y,src) && connectivity(x, y,src) == 1)
{
// Mark for deletion.
m[x][y] = true; // eraseable
remain = true;
}
}
}
}// for(int x=1; x<width-1; x++)
}// for(int y=1; y<height-1; y++)
// We are not interest at the image boundary.
for(int y=1; y<height-1; y++)
{
for(int x=1; x<width-1; x++)
{
if(m[x][y]) // is eraseable
{
//System.out.println("DELETE POINT AT "+x+" "+y);
//src.setSample(x, y, 0, COLOR_BACKGROUND);
this.setBlack(dst,x,y);
m[x][y] = false;
}
else
{
this.setWhite(dst,x,y);
}
}
}
}// for(int k=0; k<foregroundNeighbour.length; k++)
}// while (remain)
}
private void setWhite(WritableRaster dst,int i,int j)
{
dst.setSample(i,j,0,255);
dst.setSample(i,j,1,255);
dst.setSample(i,j,2,255);
}
private void setBlack(WritableRaster dst,int i,int j)
{
dst.setSample(i,j,0,0);
dst.setSample(i,j,1,0);
dst.setSample(i,j,2,0);
}
private int get(Raster src,int x,int y,int b)
{
return (src.getSample(x,y,b) == this.WHITE ) ? 1 : 0;
}
}