1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
|
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.util.LinkedList;
import millie.automation.Automatable;
import millie.plugins.PluginInfo;
import millie.plugins.appimage.GenericAppImagePlugin;
import millie.plugins.core.blur.FastGaussianBlurPlugin;
import millie.plugins.parameters.CheckBoxParameter;
import millie.plugins.parameters.IntSliderParameter;
import millie.se.operator.GrayOperator;
import millie.se.operator.GrayOperator.SelectedColor;
/**
* Canny Filter (edge detector)
*
* @author Xavier Philippeau
*
*/
@PluginInfo(name="Détection de Canny", category="new", description="Détection de Canny")
public class CannyFilterPlugin extends GenericAppImagePlugin implements Automatable {
private int width=0, height=0;
public CannyFilterPlugin() {
setReinitializable(true);
setLongProcessing(true);
addParameter(new IntSliderParameter("radius", "Sampling radius", 0, 10, 2));
addParameter(new IntSliderParameter("low", "Hysteresis low value (%)", 0, 100, 30));
addParameter(new IntSliderParameter("high", "Hysteresis high value (%)", 0, 100, 60));
addParameter(new CheckBoxParameter("logscale", "Use Log-Scale", true));
}
@Override
public BufferedImage filter() throws Exception {
int radius = getIntValue("radius");
int lowvalue = getIntValue("low");
int highvalue = getIntValue("high");
boolean logscale = getBooleanValue("logscale");
if (lowvalue>highvalue)
throw new IllegalArgumentException("Low > High");
// input image
BufferedImage input = this.getInputImage();
this.width = input.getWidth();
this.height = input.getHeight();
// convert to graylevel if needed
int numBands = input.getRaster().getNumBands();
if (numBands>1) {
GrayOperator grayOp = new GrayOperator(SelectedColor.ALL);
input = grayOp.compute(input);
}
// blur input image (if requested)
if (radius>0) {
FastGaussianBlurPlugin fgblur = new FastGaussianBlurPlugin();
fgblur.getParameter("rayon").setValue(radius);
fgblur.setInputImage( input );
input = fgblur.filter();
}
// Canny detection
BufferedImage output = new BufferedImage(input.getWidth(), input.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
canny(input.getRaster(), output.getRaster(), 0, lowvalue, highvalue, logscale);
return output;
}
/**
* Compute the local gradient
*
* @param raster Image raster
* @param x x coord of the computation (int)
* @param y y coord of the computation (int)
* @return norme and value of the gradient
*/
private double[] gradient(Raster raster, int x, int y, int band) {
// limit to image dimension
if (x<0) x=0; else if (x>=width) x=width-1;
if (y<0) y=0; else if (y>=height) y=height-1;
int px = x - 1; // previous x
int nx = x + 1; // next x
int py = y - 1; // previous y
int ny = y + 1; // next y
// limit to image dimension
if (px < 0) px = 0;
if (nx >= width) nx = width - 1;
if (py < 0) py = 0;
if (ny >= height) ny = height - 1;
// Intesity of the 8 neighbors
int Ipp = raster.getSample( px,py, band);
int Icp = raster.getSample( x,py, band);
int Inp = raster.getSample( nx,py, band);
int Ipc = raster.getSample( px, y, band);
int Inc = raster.getSample( nx, y, band);
int Ipn = raster.getSample( px,ny, band);
int Icn = raster.getSample( x,ny, band);
int Inn = raster.getSample( nx,ny, band);
// Local gradient
double gradx = ((Inc-Ipc)*2 + (Inn-Ipp) + (Inp-Ipn))/4.0; // horizontal + 2 diagonals
double grady = ((Icn-Icp)*2 + (Inn-Ipp) + (Ipn-Inp))/4.0; // vertical + 2 diagonals
if (gradx==0 && grady==0)
return new double[] { 0, 0, 0 };
double norme = Math.sqrt(gradx*gradx+grady*grady);
return new double[] { norme, gradx/norme, grady/norme };
}
/**
* Compute the norm of the local gradient (subpixel with bilinear interpolation)
*
* @param gradient gradient map
* @param x x coord of the computation (double)
* @param y y coord of the computation (double)
* @return interpolated value of the norm of the gradient
*/
private double gradientNorm(double[][][] gradient, double x, double y) {
// fractional part of coordinates
double wx = x - (int) x;
double wy = y - (int) y;
// limit to image dimension
int cx = (int) x, cy = (int) y;
if (cx < 0) cx = 0; else if (cx >= width) cx = width - 1;
if (cy < 0) cy = 0; else if (cy >= height) cy = height - 1;
int nx = cx + 1, ny = cy + 1;
if (nx >= width) nx = width - 1;
if (ny >= height) ny = height - 1;
// norm of the 4 neighbours
double Gpp = gradient[cx][cy][0];
double Gnp = gradient[nx][cy][0];
double Gpn = gradient[cx][ny][0];
double Gnn = gradient[nx][ny][0];
// bilinear interpolation
double wpp=(1-wx)*(1-wy), wnp= wx*(1-wy), wpn=(1-wx)*wy, wnn=wx*wy;
double norm = wpp*Gpp + wnp*Gnp + wpn*Gpn + wnn*Gnn;
return norm;
}
/**
* compute if a position is a local maxima
*
* @param c Image map
* @param x x coord of the computation
* @param y y coord of the computation
* @return true if position is a local maxima
*/
private boolean isLocalMaxima(double[][][] gradient, int x, int y) {
// gradient at current position
double[] grad = gradient[x][y];
// gradient direction
double gx = grad[1]; //Math.cos( angle );
double gy = grad[2]; //Math.sin( angle );
// gradient value at next position in the gradient direction
double nx = x + gx;
double ny = y + gy;
double gradn = gradientNorm(gradient,nx,ny);
// gradient value at previous position in the gradient direction
double px = x - gx;
double py = y - gy;
double gradp = gradientNorm(gradient,px,py);
// is the current gradient value a local maxima ?
if (grad[0]>gradn && grad[0]>=gradp) return true;
return false;
}
/**
* @param input Input image raster
* @param output Output image raster
* @param band band of the raster to process
* @param lowThreshold Hysteresis low value (0...100%)
* @param highThreshold Hysteresis hight value (0...100%)
* @param logscale use log-scale
*/
private void canny(Raster input, WritableRaster output, int band, int lowThreshold, int highThreshold, boolean logscale) {
int LOWSTATE=92, HIGHSTATE=255;
// list of pixels above highThreshold
LinkedList<int[]> highpixels = new LinkedList<int[]>();
// precompute gradient
double[][][] gradient = new double[width][height][];
for (int y=0; y<height; y++)
for (int x=0; x<width; x++)
gradient[x][y] = gradient(input, x, y, band);
// low/high intesity value of gradient
double glow = gradient[0][0][0], ghigh = glow;
for (int y=0; y<height; y++)
for (int x=0; x<width; x++) {
if (gradient[x][y][0]<glow) glow=gradient[x][y][0];
if (gradient[x][y][0]>ghigh) ghigh=gradient[x][y][0];
}
// rescale values of gradient in [0,100]
for (int y=0; y<height; y++)
for (int x=0; x<width; x++) {
double d = (gradient[x][y][0]-glow)/(ghigh-glow);
// use log-scale if requested
if (logscale)
gradient[x][y][0] = 100.0*Math.max(0 , 1.16*Math.pow(d, 0.333)-0.16);
else
gradient[x][y][0] = 100.0*d;
}
// gradient thresholding
for (int y=0; y<height; y++) {
for (int x=0; x<width; x++) {
// gradient intesity
double g = gradient[x][y][0];
// low-threshold -> not an edge
if (g<lowThreshold) continue;
// not a local maxima -> not an edge
if (!isLocalMaxima(gradient, x, y)) continue;
// high-threshold -> is an edge
if (g>highThreshold) {
output.setSample(x, y, band, HIGHSTATE);
highpixels.addLast(new int[]{x,y});
continue;
}
// between thresholds -> "unknown state" (depends of neighbors)
output.setSample(x, y, band, LOWSTATE);
}
}
// edge continuation
int[] dx8 = new int[] {-1, 0, 1, 1, 1, 0,-1,-1};
int[] dy8 = new int[] {-1,-1,-1, 0, 1, 1, 1, 0};
// analyze 8 neighboors of each pixels in HIGHSTATE
while(!highpixels.isEmpty()) {
int[] pixel = highpixels.removeFirst();
int x=pixel[0], y=pixel[1];
// move pixels from LOWSTATE to HIGHSTATE
for(int k=0;k<8;k++) {
if (safeGetSample(output, x+dx8[k], y+dy8[k], band)==LOWSTATE) {
output.setSample(x+dx8[k], y+dy8[k], band, HIGHSTATE);
highpixels.addLast(new int[]{x+dx8[k], y+dy8[k]});
}
}
}
// keep only pixels in HIGHSTATE
for (int y=0; y<height; y++)
for (int x=0; x<width; x++)
if (output.getSample(x, y, band)!=HIGHSTATE)
output.setSample(x,y,band,0);
else
output.setSample(x,y,band,(int)(gradient[x][y][0]*2.55));
}
private static int safeGetSample(Raster raster, int x, int y, int band) {
if(x<0 || x>=raster.getWidth() ||y<0 || y>=raster.getHeight())
return 0;
return raster.getSample(x,y,band);
}
} |
Partager