I was researching on how to calculate an Optimal Threshold for ImageJ and found this explanation of the Otsu Thresholding, which I thought made perfect sense for me to use.
I've struggled with implementing it though, and after some thought I found a mistake with the way the weight and mean were calculated, and it now finds the optimal threshold of 77, which for the coin image looks good to me since it almost completely separates the background from the coins (and you'd be able to automatically count the coins, or measure them for size, ectr)
new coin image with optimal threshold
it also seems to work pretty well with this image, even though it has varying intensities of light: rice image with varying intensities
I'm quite happy with my solution found, but if you have any feedback or can find something else, that'd be great! This homework was tough but I learned a lot from it :)
public float calculateMeanFG(int[] histogram, int t) {
float sumI = 0;
int total = 0;
//cumulate the histogram for < 256
for (int i = t; i < 256; i++) {
sumI += histogram[i] * i;
total = i;
}
return sumI / total;
}
public float calculateMeanBG(int[] histogram, int t) {
float sumI = 0;
//cumulate the histogram for < t
for (int i = 0; i < t; i++) {
sumI += histogram[i] * i;
}
return sumI;
}
public float calculateWeightFG(int[] histogram, int t, int total) {
int sum = 0;
for (int i = t; i < 256; i++) {
sum += histogram[i];
}
return sum / total;
}
public int[] getHistogram(ImageProcessor ip, int height, int width) {
byte[] outP = ((byte[]) ip.getPixels()).clone();
int[][] inDataArr = new int[width][height];
int[] histogram = new int[256];
int idx = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
// fill in values
inDataArr[x][y] = outP[idx];
if (inDataArr[x][y] < 0) {
inDataArr[x][y] += 256;
} // if
histogram[inDataArr[x][y]] += 1; // count grayscale occurrences
idx++;
} // for x
} // for y
return histogram;
}
public int[][] convergeOptThresh(int[][] imgArr, int width, int height) {
int BG_VAL = 0;
int FG_VAL = 255;
int[] histogram = getHistogram(ip, height, width);
// total number of pixels
int total = imgArr.length;
// cumulative hist
float sum = 0;
for (int i = 0; i < 256; i++)
sum += i * histogram[i];
float sumBG = 0; // sum background
float weightBG = 0;
float weightFG = 0;
float varMax = 0;
int threshold = 0;
for (int t = 0; t < 256; t++) {
weightBG = calculateMeanBG(histogram, t);
weightBG /= total;
weightFG = calculateWeightFG(histogram, t, total);
if ((int)weightFG == 0)
break;
sumBG += (float) (t * histogram[t]);
float meanBG = sumBG / t;
float meanFG = calculateMeanFG(histogram, t);
// calculate between class variance
float varBetween = weightBG * weightFG * (meanBG - meanFG) * (meanBG - meanFG);
// check if new max found
if (varBetween > varMax) {
varMax = varBetween;
threshold = t;
}
}
IJ.log("optimal threshold: " + threshold);
int[][] retArr = new int[width][height];
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
if (imgArr[x][y] <= threshold) {
retArr[x][y] = BG_VAL;
} else {
retArr[x][y] = FG_VAL;
}
}
}
return retArr;
}