package pl.wroc.pwr.imagechannel.skeletonization; import pl.wroc.pwr.IOperator; import pl.wroc.pwr.imagechannel.IImageChannel; import pl.wroc.pwr.imagechannel.basic.CopyChannel; import pl.wroc.pwr.imagechannel.model.ImageChannel; /** * Thinning is a morphological operation that is used to remove selected foreground pixels from binary * images, somewhat like erosion or opening. It can be used for several applications, but is * particularly useful for skeletonization. In this mode it is commonly used to tidy up the output of * edge detectors by reducing all lines to single pixel thickness. Thinning is normally only applied * to binary images, and produces another binary image as output. The thinning operation is related to * the hit-and-miss transform, and so it is helpful to have an understanding of that operator before * reading on. * * http://homepages.inf.ed.ac.uk/rbf/HIPR2/thin.htm * * Implemented according to Biomedical Image Analysis, page 548-549 */ public class FastParallelThinning implements IOperator { private static final long serialVersionUID = -4016066669227307063L; private IImageChannel input; private float threshold; private IImageChannel deletion; private IImageChannel result; public FastParallelThinning(IImageChannel input) { this(input, 0.5f); } public FastParallelThinning(IImageChannel input, float threshold) { this.input = input; this.threshold = threshold; //System.out.println("FastParallelThinning on " + this.input + " with threshold " + this.threshold); //$NON-NLS-1$ //$NON-NLS-2$ } private boolean makeDecissionOnPairForS(int [] p, int i, int j) { return (p[i] == 0)&&(p[j] == 1); } private int calculateS(int [] p) { int S = 0; if (makeDecissionOnPairForS(p, 2, 3)) S++; if (makeDecissionOnPairForS(p, 3, 4)) S++; if (makeDecissionOnPairForS(p, 4, 5)) S++; if (makeDecissionOnPairForS(p, 5, 6)) S++; if (makeDecissionOnPairForS(p, 6, 7)) S++; if (makeDecissionOnPairForS(p, 7, 8)) S++; if (makeDecissionOnPairForS(p, 8, 9)) S++; if (makeDecissionOnPairForS(p, 9, 2)) S++; return S; } private boolean makeDecissionForS(int step, int S, int [] p) { if (S == 1) { if (step == 1) { if (p[2] * p[4] * p[6] == 0) { if (p[4] * p[6] * p[8] == 0) { return true; } } } else { //Do the same as Step 1 above replacing the conditions (c) and (d) if (p[2] * p[4] * p[8] == 0) { if (p[2] * p[6] * p[8] == 0) { return true; } } } } return false; } private boolean makeDecissionForN(int [] p) { int N = p[2] + p[3] + p[4] + p[5] + p[6] + p[7] + p[8] + p[9]; return (N >= 2)&&(N <= 6); } private boolean makeDecissionForP(int [] p) { return (p[2] == 0)||(p[3] == 0)||(p[4] == 0)||(p[5] == 0)|| (p[6] == 0)||(p[7] == 0)||(p[8] == 0)||(p[9] == 0); } private int checkThresholdForPixelDelta(int x, int y) { return (this.result.getValue(x, y) <= this.threshold) ? 0 : 1; } private boolean makeDecissionForPixel(int step, int x, int y, int [] p) { if (this.result.getValue(x, y) > this.threshold) { p[2] = this.checkThresholdForPixelDelta(x, y - 1); p[3] = this.checkThresholdForPixelDelta(x + 1, y - 1); p[4] = this.checkThresholdForPixelDelta(x + 1, y); p[5] = this.checkThresholdForPixelDelta(x + 1, y + 1); p[6] = this.checkThresholdForPixelDelta(x, y + 1); p[7] = this.checkThresholdForPixelDelta(x - 1, y + 1); p[8] = this.checkThresholdForPixelDelta(x - 1, y); p[9] = this.checkThresholdForPixelDelta(x - 1, y - 1); if (this.makeDecissionForP(p)) { if (this.makeDecissionForN(p)) { int S = this.calculateS(p); return this.makeDecissionForS(step, S, p); } } } return false; } private boolean checkImageForDeletion(int step, int [] p ) { boolean repetition = false; for (int x = 1; x < this.result.getXSize() - 1; x++) { for (int y = 1; y < this.result.getYSize() - 1; y++) { if (this.makeDecissionForPixel(step, x, y, p)) { repetition = true; this.deletion.setValue(x, y, 1); } } } if (repetition) { for (int x = 1; x < this.result.getXSize() - 1; x++) { for (int y = 1; y < this.result.getYSize() - 1; y++) { if (this.deletion.getValue(x, y) == 1) { this.result.setValue(x, y, 0); } } } } return repetition; } public IImageChannel apply() { this.result = new ImageChannel(this.input.getSize()); this.deletion = new ImageChannel(this.input.getSize()); this.result = new CopyChannel(this.result, this.input).apply(); boolean repeat = true; int[] p = new int[10]; while (repeat) { boolean continue1 = this.checkImageForDeletion(1, p); boolean continue2 = this.checkImageForDeletion(2, p); repeat = continue1 || continue2; } return this.result; } }