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}