com.github.ojil.algorithm.Gray8OtsuThreshold Maven / Gradle / Ivy
Show all versions of ojil-core Show documentation
package com.github.ojil.algorithm;
import com.github.ojil.core.Error;
import com.github.ojil.core.Gray8Image;
import com.github.ojil.core.Image;
import com.github.ojil.core.PipelineStage;
/**
* Choose and apply a threshold using Otsu's algorithm, which searches for a
* threshold value based on best separation of the histogram into two
* components.
*
* Algorithm from
* http://homepages.inf.ed.ac.uk/rbf/CVonline/LOCAL_COPIES/MORSE/threshold.pdf
*
* @author webb
*
*/
public class Gray8OtsuThreshold extends PipelineStage {
private boolean mbSmaller;
private int mnAdjustFactor;
private int mnCountBelow, mnCountAbove;
/**
* Create new instances of Gray8OtsuThreshold, specifying whether pixels
* less than the threshold value will be considered to be "on"
* (bSmaller==true) or "off" (bSmaller==false).
*
* @param bSmaller if bSmaller is true the direction of the threshold is
* chosen so that the smaller number of pixels will be set on (Byte.MAX_VALUE)
* and the reverse others.
*/
public Gray8OtsuThreshold(boolean bSmaller, int nAdjustFactor) {
this.mbSmaller = bSmaller;
this.mnAdjustFactor = nAdjustFactor;
}
/**
* Compute the Ostu threshold on an input Gray8Image and apply it, replacing
* the input. The result is a Gray8Image with all pixels set to
* Byte.MIN_VALUE or Byte.MAX_VALUE.
*
* @param imageInput
* the input Gray8Image. This image is overwritten on output.
*/
public void push(Image imageInput) throws Error {
if (!(imageInput instanceof Gray8Image)) {
throw new Error(Error.PACKAGE.ALGORITHM,
ErrorCodes.IMAGE_NOT_GRAY8IMAGE, imageInput.toString(),
null, null);
}
Gray8Image g8i = (Gray8Image) imageInput;
/* compute histogram */
Integer[] rnHistogram = Gray8Hist.computeHistogram(g8i);
/* calculate Otsu threshold */
int nThresh = calculateOtsuThreshold(rnHistogram);
// determine whether small pixel values should get set on (bWithin = true)
// or not
boolean bWithin = this.mbSmaller == (this.mnCountBelow < this.mnCountAbove);
if (bWithin) {
nThresh = (nThresh * this.mnAdjustFactor) / 256;
} else {
nThresh = 256 - ((256 - nThresh) * this.mnAdjustFactor) / 256;
}
// if (bWithin) {
// nThresh = (nThresh * 256) / this.mnBias;
// } else {
// nThresh = (nThresh * this.mnBias) / 256;
// }
// Gray8Threshold test is on byte value so we adjust
// appropriately. The histogram value runs from 0-256 so
// we have to offset it by Byte.MIN_VALUE.
// Gray8Threshold replaces its input.
Gray8Threshold g8t = new Gray8Threshold(nThresh + Byte.MIN_VALUE,
bWithin);
g8t.push(imageInput);
super.setOutput(g8t.getFront());
}
/**
* Calculate the Otsu threshold. Also compute the number of pixels
* below and above the threshold value.
*
* @param rnHistogram
* the input histogram, or any array with similar structure
* @return an integer from 0-rnHistogram.length-1 which divides the array
* into two maximally disjoint components, as computed by Otsu's
* algorithm
*/
public int calculateOtsuThreshold(Integer[] rnHistogram) {
int nBelow = 0;
int nPixelSum = 0;
int nAbove = 0;
for (int i = 0; i < rnHistogram.length; i++) {
nAbove += rnHistogram[i];
nPixelSum += rnHistogram[i] * i;
}
// lSumBelow and lSumAbove are the sum of all pixel values above
// and below the threshold
long lSumBelow = 0;
long lSumAbove = nPixelSum;
int nBestThreshold = 0;
long lBestSeparation = Long.MIN_VALUE;
for (int i = 0; i < rnHistogram.length; i++) {
// new count of pixels above and below the threshold
nBelow = nBelow + rnHistogram[i];
nAbove = nAbove - rnHistogram[i];
// compute new means above and below threshold
lSumBelow += rnHistogram[i] * i;
int nMeanBelow, nMeanAbove;
if (nBelow != 0) {
nMeanBelow = (int) (lSumBelow / nBelow);
} else {
nMeanBelow = 0;
}
lSumAbove -= rnHistogram[i] * i;
if (nAbove != 0) {
nMeanAbove = (int) (lSumAbove / nAbove);
} else {
nMeanAbove = 0;
}
// compute variance with this separation (see article above)
long lVariance = nBelow * nAbove * (long) (nMeanAbove - nMeanBelow)
* (long) (nMeanAbove - nMeanBelow);
// if this separation is better than previous update
if (lVariance > lBestSeparation) {
lBestSeparation = lVariance;
nBestThreshold = i;
this.mnCountBelow = nBelow;
this.mnCountAbove = nAbove;
}
}
return nBestThreshold;
}
}