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.saliency;
031
032import java.util.Arrays;
033
034import org.openimaj.citation.annotation.Reference;
035import org.openimaj.citation.annotation.ReferenceType;
036import org.openimaj.image.FImage;
037import org.openimaj.image.processing.convolution.AverageBoxFilter;
038import org.openimaj.image.processing.convolution.FConvolution;
039
040/**
041 * Construct a map that shows the "focus" of each pixel. A value of 0 in the
042 * output corresponds to a sharp pixel, whilst higher values correspond to more
043 * blurred pixels.
044 *
045 * Algorithm based on: Yiwen Luo and Xiaoou Tang. 2008. Photo and Video Quality
046 * Evaluation: Focusing on the Subject. In Proceedings of the 10th European
047 * Conference on Computer Vision: Part III (ECCV '08), David Forsyth, Philip
048 * Torr, and Andrew Zisserman (Eds.). Springer-Verlag, Berlin, Heidelberg,
049 * 386-399. DOI=10.1007/978-3-540-88690-7_29
050 * http://dx.doi.org/10.1007/978-3-540-88690-7_29
051 *
052 * Note that this is not scale invariant - you will get different results with
053 * different sized images...
054 *
055 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
056 */
057@Reference(
058                type = ReferenceType.Inproceedings,
059                author = { "Luo, Yiwen", "Tang, Xiaoou" },
060                title = "Photo and Video Quality Evaluation: Focusing on the Subject",
061                year = "2008",
062                booktitle = "Proceedings of the 10th European Conference on Computer Vision: Part III",
063                pages = { "386", "", "399" },
064                url = "http://dx.doi.org/10.1007/978-3-540-88690-7_29",
065                publisher = "Springer-Verlag",
066                series = "ECCV '08",
067                customData = {
068                                "isbn", "978-3-540-88689-1",
069                                "location", "Marseille, France",
070                                "numpages", "14",
071                                "doi", "10.1007/978-3-540-88690-7_29",
072                                "acmid", "1478204",
073                                "address", "Berlin, Heidelberg"
074                })
075public class DepthOfFieldEstimator implements SaliencyMapGenerator<FImage> {
076        private static FConvolution DX_FILTER = new FConvolution(new float[][] { { 1, -1 } });
077        private static FConvolution DY_FILTER = new FConvolution(new float[][] { { 1 }, { -1 } });
078
079        protected int maxKernelSize = 50;
080        protected int kernelSizeStep = 1;
081        protected int nbins = 41;
082
083        protected int windowSize = 3;
084
085        protected float[][] xHistograms;
086        protected float[][] yHistograms;
087        private FImage map;
088
089        /**
090         * Construct with the given parameters.
091         * 
092         * @param maxKernelSize
093         *            Maximum kernel size.
094         * @param kernelSizeStep
095         *            Kernel step size.
096         * @param nbins
097         *            Number of bins.
098         * @param windowSize
099         *            window size.
100         */
101        public DepthOfFieldEstimator(int maxKernelSize, int kernelSizeStep, int nbins, int windowSize) {
102                this.maxKernelSize = maxKernelSize;
103                this.kernelSizeStep = kernelSizeStep;
104                this.nbins = nbins;
105                this.windowSize = windowSize;
106                this.xHistograms = new float[maxKernelSize / kernelSizeStep][nbins];
107                this.yHistograms = new float[maxKernelSize / kernelSizeStep][nbins];
108        }
109
110        /**
111         * Construct with the default values (max kernel size = 50, step size = 1,
112         * 41 bins, window size of 3).
113         */
114        public DepthOfFieldEstimator() {
115                this.xHistograms = new float[maxKernelSize / kernelSizeStep][nbins];
116                this.yHistograms = new float[maxKernelSize / kernelSizeStep][nbins];
117        }
118
119        protected void clearHistograms() {
120                for (final float[] h : xHistograms)
121                        Arrays.fill(h, 0);
122
123                for (final float[] h : yHistograms)
124                        Arrays.fill(h, 0);
125        }
126
127        /*
128         * (non-Javadoc)
129         * 
130         * @see
131         * org.openimaj.image.processor.ImageProcessor#processImage(org.openimaj.
132         * image.Image)
133         */
134        @Override
135        public void analyseImage(FImage image) {
136                clearHistograms();
137
138                for (int i = 0, j = 0; i < maxKernelSize; i += kernelSizeStep, j++) {
139                        final FImage blurred = image.process(new AverageBoxFilter(i + 1, i + 1));
140                        final FImage dx = blurred.process(DX_FILTER);
141                        final FImage dy = blurred.process(DY_FILTER);
142
143                        makeLogHistogram(xHistograms[j], dx);
144                        makeLogHistogram(yHistograms[j], dy);
145                }
146
147                final FImage dx = image.process(DX_FILTER);
148                final FImage dy = image.process(DY_FILTER);
149                map = new FImage(image.width, image.height);
150                for (int y = 0; y < image.height; y++) {
151                        for (int x = 0; x < image.width; x++) {
152                                if (x == 0 || y == 0 || x == image.width - 1 || y == image.height - 1) {
153                                        map.pixels[y][x] = maxKernelSize;
154                                } else {
155                                        int bestModel = 0;
156                                        double bestLL = calculatedLogLikelihood(x, y, dx, dy, 0);
157
158                                        for (int i = 1, j = 1; i < maxKernelSize; i += kernelSizeStep, j++) {
159                                                final double newLL = calculatedLogLikelihood(x, y, dx, dy, j);
160
161                                                if (newLL > bestLL) {
162                                                        bestLL = newLL;
163                                                        bestModel = i;
164                                                }
165                                        }
166
167                                        map.pixels[y][x] = bestModel;
168                                }
169                        }
170                }
171        }
172
173        private double calculatedLogLikelihood(int x, int y, FImage dx, FImage dy, int level) {
174                final int border = windowSize / 2;
175
176                double LL = 0;
177                for (int j = y - border; j <= y + border; j++) {
178                        for (int i = x - border; i <= x + border; i++) {
179                                final float vx = (dx.pixels[j][i] + 1) / 2;
180                                int bx = (int) (vx * nbins);
181                                if (bx >= nbins)
182                                        bx--;
183
184                                final float vy = (dy.pixels[j][i] + 1) / 2;
185                                int by = (int) (vy * nbins);
186                                if (by >= nbins)
187                                        by--;
188
189                                LL += xHistograms[level][bx] + yHistograms[level][by];
190                        }
191                }
192                return LL;
193        }
194
195        private void makeLogHistogram(float[] h, FImage im) {
196                int sum = 0;
197                for (int y = 0; y < im.height; y++) {
198                        for (int x = 0; x < im.width; x++) {
199                                final float v = (im.pixels[y][x] + 1) / 2; // norm to 0..1
200
201                                int bin = (int) (v * nbins);
202                                if (bin >= nbins)
203                                        bin--;
204
205                                h[bin]++;
206                                sum++;
207                        }
208                }
209
210                for (int i = 0; i < nbins; i++) {
211                        if (h[i] == 0)
212                                h[i] = 0.00000001f; // a really small value for smoothing
213
214                        h[i] = (float) Math.log(h[i] / (double) sum);
215                }
216        }
217
218        @Override
219        public FImage getSaliencyMap() {
220                return map;
221        }
222}