001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package org.openimaj.image.processing.threshold; 031 032import org.openimaj.citation.annotation.Reference; 033import org.openimaj.citation.annotation.ReferenceType; 034import org.openimaj.image.FImage; 035import org.openimaj.image.processor.ImageProcessor; 036import org.openimaj.util.array.ArrayUtils; 037 038/** 039 * Otsu's adaptive thresholding algorithm. 040 * 041 * @see <a 042 * href="http://en.wikipedia.org/wiki/Otsu's_method">http://en.wikipedia.org/wiki/Otsu's_method</a> 043 * 044 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 045 */ 046@Reference( 047 type = ReferenceType.Article, 048 author = { "Nobuyuki Otsu" }, 049 title = "A Threshold Selection Method from Gray-Level Histograms", 050 year = "1979", 051 journal = "Systems, Man and Cybernetics, IEEE Transactions on", 052 pages = { "62", "66" }, 053 number = "1", 054 volume = "9", 055 customData = { 056 "keywords", 057 "Displays;Gaussian distribution;Histograms;Least squares approximation;Marine vehicles;Q measurement;Radar tracking;Sea measurements;Surveillance;Target tracking", 058 "doi", "10.1109/TSMC.1979.4310076", 059 "ISSN", "0018-9472" 060 }) 061public class OtsuThreshold implements ImageProcessor<FImage> { 062 private static final int DEFAULT_NUM_BINS = 256; 063 int numBins = DEFAULT_NUM_BINS; 064 065 /** 066 * Default constructor 067 */ 068 public OtsuThreshold() { 069 070 } 071 072 /** 073 * Construct with the given number of histogram bins 074 * 075 * @param numBins 076 * the number of histogram bins 077 */ 078 public OtsuThreshold(int numBins) { 079 this.numBins = numBins; 080 } 081 082 protected static int[] makeHistogram(FImage fimg, int numBins) { 083 final int[] histData = new int[numBins]; 084 085 // Calculate histogram 086 for (int r = 0; r < fimg.height; r++) { 087 for (int c = 0; c < fimg.width; c++) { 088 final int h = (int) (fimg.pixels[r][c] * (numBins - 1)); 089 histData[h]++; 090 } 091 } 092 093 return histData; 094 } 095 096 protected static int[] makeHistogram(float[] data, int numBins, float min, float max) { 097 final int[] histData = new int[numBins]; 098 099 // Calculate histogram 100 for (int c = 0; c < data.length; c++) { 101 final float d = (data[c] - min) / (max - min); 102 final int h = (int) (d * (numBins - 1)); 103 histData[h]++; 104 } 105 106 return histData; 107 } 108 109 /** 110 * Estimate the threshold for the given image. 111 * 112 * @param img 113 * the image 114 * @param numBins 115 * the number of histogram bins 116 * @return the estimated threshold 117 */ 118 public static float calculateThreshold(FImage img, int numBins) { 119 final int[] histData = makeHistogram(img, numBins); 120 121 // Total number of pixels 122 final int total = img.getWidth() * img.getHeight(); 123 124 return computeThresholdFromHistogram(histData, total); 125 } 126 127 /** 128 * Estimate the threshold for the given data. 129 * <p> 130 * Internally, the data will be min-max normalised before the histogram is 131 * built, and the specified number of bins will cover the entire 132 * <code>max-min</code> range. The returned threshold will have 133 * <code>min</code> added to it to return it to the original range. 134 * 135 * @param data 136 * the data 137 * @param numBins 138 * the number of histogram bins 139 * @return the estimated threshold 140 */ 141 public static float calculateThreshold(float[] data, int numBins) { 142 final float min = ArrayUtils.minValue(data); 143 final float max = ArrayUtils.maxValue(data); 144 final int[] histData = makeHistogram(data, numBins, min, max); 145 146 return computeThresholdFromHistogram(histData, data.length) + min; 147 } 148 149 /** 150 * Estimate the threshold for the given histogram. 151 * 152 * @param histData 153 * the histogram 154 * @param total 155 * the total number of items in the histogram 156 * @return the estimated threshold 157 */ 158 public static float computeThresholdFromHistogram(int[] histData, int total) { 159 final int numBins = histData.length; 160 float sum = 0; 161 for (int t = 0; t < numBins; t++) 162 sum += t * histData[t]; 163 164 float sumB = 0; 165 int wB = 0; 166 int wF = 0; 167 168 float varMax = 0; 169 float threshold = 0; 170 171 for (int t = 0; t < numBins; t++) { 172 wB += histData[t]; // Weight Background 173 if (wB == 0) 174 continue; 175 176 wF = total - wB; // Weight Foreground 177 if (wF == 0) 178 break; 179 180 sumB += (t * histData[t]); 181 182 final float mB = sumB / wB; // Mean Background 183 final float mF = (sum - sumB) / wF; // Mean Foreground 184 185 // Calculate Between Class Variance 186 final float varBetween = (float) wB * (float) wF * (mB - mF) * (mB - mF); 187 188 // Check if new maximum found 189 if (varBetween > varMax) { 190 varMax = varBetween; 191 threshold = t; 192 } 193 } 194 195 return threshold / (numBins - 1); 196 } 197 198 @Override 199 public void processImage(FImage image) { 200 final float threshold = calculateThreshold(image, numBins); 201 202 image.threshold(threshold); 203 } 204}