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