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&apos;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}