package fr.unistra.pelican.algorithms.segmentation.superpixel; import java.util.ArrayList; import java.util.Arrays; import fr.unistra.pelican.Algorithm; import fr.unistra.pelican.AlgorithmException; import fr.unistra.pelican.DoubleImage; import fr.unistra.pelican.Image; import fr.unistra.pelican.IntegerImage; import fr.unistra.pelican.algorithms.conversion.RGBToXYZ; import fr.unistra.pelican.algorithms.conversion.XYZToLAB; import fr.unistra.pelican.algorithms.morphology.vectorial.gradient.MultispectralEuclideanGradient; import fr.unistra.pelican.util.morphology.FlatStructuringElement2D; /** * Implementation of SLIC superpixels * * Radhakrishna Achanta, Appu Shaji, Kevin Smith, Aurelien Lucchi, Pascal Fua, and Sabine Süsstrunk, * SLIC Superpixels Compared to State-of-the-art Superpixel Methods, * IEEE Transactions on Pattern Analysis and Machine Intelligence, vol. 34, num. 11, p. 2274 - 2282, May 2012. * * @author Jonathan Weber */ public class SLIC extends Algorithm { public Image inputImage; public int numberOfSuperpixels; //Between 1 and 40, low value to respect boundary, high value to respect compactness public double m=10; public IntegerImage superpixels; public SLIC() { super(); super.inputs="inputImage,numberOfSuperpixels"; super.options="m"; super.outputs = "superpixels"; } @Override public void launch() throws AlgorithmException { int xDim = inputImage.xdim; int yDim = inputImage.ydim; // Convert image to Lab color space Image lab = XYZToLAB.exec(RGBToXYZ.exec(inputImage)); // Perform gradient on RGB image Image gradient = MultispectralEuclideanGradient.exec(inputImage, FlatStructuringElement2D.createSquareFlatStructuringElement(3)); //Compute step value int step = (int) Math.round(Math.sqrt(((double)gradient.size())/numberOfSuperpixels)); //Initiate cluster ArrayList<Cluster> clusters = new ArrayList<Cluster>(); for(int y=step/2;y<yDim;y+=step) for(int x=step/2;x<xDim;x+=step) { clusters.add(new Cluster(lab.getPixelXYBDouble(x, y, 0),lab.getPixelXYBDouble(x, y, 1),lab.getPixelXYBDouble(x, y, 2),x,y)); } //Move cluster center to lowest gradient in 3x3 neighbourhood for(Cluster c : clusters) { int lowestGradient=Integer.MAX_VALUE; int bestX=0; int bestY=0; for(int y=c.y-1;y<=c.y+1;y++) for(int x=c.x-1;x<=c.x+1;x++) { if(gradient.getPixelXYByte(x, y)<lowestGradient) { lowestGradient=gradient.getPixelXYByte(x, y); bestX=x; bestY=y; } } c.x=bestX; c.y=bestY; c.l=lab.getPixelXYBDouble(bestX, bestY, 0); c.a=lab.getPixelXYBDouble(bestX, bestY, 1); c.b=lab.getPixelXYBDouble(bestX, bestY, 2); } // initialize label and distance image IntegerImage label = new IntegerImage(xDim,yDim,1,1,1); label.fill(-1); DoubleImage distance = new DoubleImage(xDim,yDim,1,1,1); distance.fill(Double.MAX_VALUE); double error=0; int loop=0; do { loop++; System.out.println("Loop "+loop+" started !"); // Pixel assignment to cluster for(int k=0;k<clusters.size();k++) { Cluster c = clusters.get(k); int yMin=Math.max(0, c.y-step); int yMax=Math.min(yDim-1, c.y+step); int xMin=Math.max(0, c.x-step); int xMax=Math.min(xDim-1, c.x+step); for(int y=yMin;y<=yMax;y++) for(int x=xMin;x<=xMax;x++) { double l=lab.getPixelXYBDouble(x,y,0); double a=lab.getPixelXYBDouble(x,y,1); double b=lab.getPixelXYBDouble(x,y,2); double dc=Math.sqrt((l-c.l)*(l-c.l)+(a-c.a)*(a-c.a)+(b-c.b)*(b-c.b)); double ds=Math.sqrt((x-c.x)*(x-c.x)+(y-c.y)*(y-c.y)); double d = Math.sqrt(dc*dc+((ds/step)*(ds/step))*(m*m)); if(d<distance.getPixelXYDouble(x, y)) { distance.setPixelXYDouble(x,y,d); label.setPixelXYInt(x, y, k); } } } // Cluster center update and error computation error=0; int[][] newClusterCenter = new int [clusters.size()][2]; for(int[] val : newClusterCenter) { val[0]=0; val[1]=0; } int[] newClusterPixelCount = new int[clusters.size()]; Arrays.fill(newClusterPixelCount, 0); for(int y=0;y<yDim;y++) for(int x=0;x<xDim;x++) { int labelValue = label.getPixelXYInt(x,y); if(labelValue!=-1) { newClusterCenter[labelValue][0]+=x; newClusterCenter[labelValue][1]+=y; newClusterPixelCount[labelValue]++; } } for(int i=0;i<newClusterCenter.length;i++) { int newX = Math.round(((float) newClusterCenter[i][0])/newClusterPixelCount[i]); int newY = Math.round(((float) newClusterCenter[i][1])/newClusterPixelCount[i]); error+=Math.sqrt((newX-clusters.get(i).x)*(newX-clusters.get(i).x)+(newY-clusters.get(i).y)*(newY-clusters.get(i).y)); clusters.get(i).x=newX; clusters.get(i).y=newY; clusters.get(i).l=lab.getPixelXYBDouble(newX, newY, 0); clusters.get(i).a=lab.getPixelXYBDouble(newX, newY, 1); clusters.get(i).b=lab.getPixelXYBDouble(newX, newY, 2); } System.out.println("Loop "+loop+" done ! Residual error : "+error); } while (error!=0); //Segmentation result superpixels = label; } /** * @param inputImage image to compute * @param numberOfSuperpixels desired number of superpixels * @return SLIC superpixels image */ public static IntegerImage exec(Image inputImage, int numberOfSuperpixels) { return (IntegerImage) new SLIC().process(inputImage, numberOfSuperpixels); } /** * @param inputImage image to compute * @param numberOfSuperpixels desired number of superpixels * @param m compactness parameter * @return SLIC superpixels image */ public static IntegerImage exec(Image inputImage, int numberOfSuperpixels, double m) { return (IntegerImage) new SLIC().process(inputImage, numberOfSuperpixels,m); } private class Cluster { public double l; public double a; public double b; public int x; public int y; public Cluster(double l, double a, double b, int x, int y) { this.l=l; this.a=a; this.b=b; this.x=x; this.y=y; } } }