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.convolution.filterbank; 031 032import org.openimaj.feature.FloatFV; 033import org.openimaj.image.FImage; 034import org.openimaj.image.analyser.ImageAnalyser; 035import org.openimaj.image.processing.algorithm.FourierTransform; 036import org.openimaj.image.processing.convolution.FConvolution; 037 038import edu.emory.mathcs.jtransforms.fft.FloatFFT_2D; 039 040/** 041 * A FilterBank is a set of convolution filters which can be applied to an 042 * image. The filterbank allows a response vector of the filter at each pixel in 043 * the image to be generated. Convolution is performed in the fourier domain for 044 * efficiency (the fft's of the filters are cached, and the fft of the image 045 * only has to be performed once for all convolutions) 046 * 047 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 048 * 049 */ 050public abstract class FilterBank implements ImageAnalyser<FImage> { 051 private FConvolution[] filters; 052 protected FImage[] responses; 053 054 private FloatFFT_2D fft; 055 private float[][][] preparedFilters; 056 private float[][] tmpImage; 057 private int paddingX; 058 private int paddingY; 059 060 protected FilterBank(FConvolution[] filters) { 061 this.filters = filters; 062 063 int maxWidth = 0; 064 int maxHeight = 0; 065 for (int i = 0; i < filters.length; i++) { 066 maxWidth = Math.max(maxWidth, filters[i].kernel.width); 067 maxHeight = Math.max(maxHeight, filters[i].kernel.height); 068 } 069 this.paddingX = (int) Math.ceil(maxWidth / 2); 070 this.paddingY = (int) Math.ceil(maxHeight / 2); 071 } 072 073 /* 074 * (non-Javadoc) 075 * 076 * @see org.openimaj.image.processor.ImageAnalyser#analyseImage(org.openimaj 077 * .image.Image) 078 */ 079 @Override 080 public void analyseImage(FImage in) { 081 responses = new FImage[filters.length]; 082 083 final FImage image = in.padding(paddingX, paddingY); 084 final int cols = image.getCols(); 085 final int rows = image.getRows(); 086 087 if (fft == null || preparedFilters == null || preparedFilters[0].length != rows 088 || preparedFilters[0][0].length != 2 * cols) 089 { 090 fft = new FloatFFT_2D(rows, cols); 091 preparedFilters = new float[filters.length][][]; 092 tmpImage = new float[rows][cols * 2]; 093 094 for (int i = 0; i < preparedFilters.length; i++) { 095 final float[][] preparedKernel = FourierTransform.prepareData(filters[i].kernel, rows, cols, false); 096 fft.complexForward(preparedKernel); 097 preparedFilters[i] = preparedKernel; 098 } 099 } 100 101 final float[][] preparedImage = FourierTransform.prepareData(image.pixels, rows, cols, false); 102 fft.complexForward(preparedImage); 103 104 for (int i = 0; i < preparedFilters.length; i++) { 105 responses[i] = convolve(cols, rows, preparedImage, preparedFilters[i]); 106 responses[i] = responses[i].extractROI(2 * paddingX, 2 * paddingY, 107 responses[i].width - 2 * paddingX, 108 responses[i].height - 2 * paddingY); 109 } 110 } 111 112 private FImage 113 convolve(final int cols, final int rows, final float[][] preparedImage, final float[][] preparedFilter) 114 { 115 for (int y = 0; y < rows; y++) { 116 for (int x = 0; x < cols; x++) { 117 final float reImage = preparedImage[y][x * 2]; 118 final float imImage = preparedImage[y][1 + x * 2]; 119 120 final float reKernel = preparedFilter[y][x * 2]; 121 final float imKernel = preparedFilter[y][1 + x * 2]; 122 123 final float re = reImage * reKernel - imImage * imKernel; 124 final float im = reImage * imKernel + imImage * reKernel; 125 126 tmpImage[y][x * 2] = re; 127 tmpImage[y][1 + x * 2] = im; 128 } 129 } 130 131 fft.complexInverse(tmpImage, true); 132 133 final FImage out = new FImage(cols, rows); 134 FourierTransform.unprepareData(tmpImage, out, false); 135 return out; 136 } 137 138 /** 139 * Get the response images for the image analysed with 140 * {@link #analyseImage(FImage)}. 141 * 142 * @return the filter responses. 143 */ 144 public FImage[] getResponseImages() { 145 return responses; 146 } 147 148 /** 149 * Get the response vector for a given pixel. 150 * 151 * @param x 152 * the x-ordinate 153 * @param y 154 * the y-ordinate 155 * @return the response vector 156 */ 157 public float[] getResponse(int x, int y) { 158 final float[] response = new float[responses.length]; 159 160 for (int i = 0; i < response.length; i++) 161 response[i] = responses[i].getPixelNative(x, y); 162 163 return response; 164 } 165 166 /** 167 * Get the response vector for a given pixel as a {@link FloatFV}. 168 * 169 * @param x 170 * the x-ordinate 171 * @param y 172 * the y-ordinate 173 * @return the response vector 174 */ 175 public FloatFV getResponseFV(int x, int y) { 176 return new FloatFV(getResponse(x, y)); 177 } 178 179 /** 180 * Create an image to visualise the filters in the bank. Assumes that all 181 * the filters are the same size. Filters are normalised and displayed in a 182 * grid. 183 * 184 * @param numFiltersX 185 * number of filters to display per row 186 * @return a visualisation of the filters 187 */ 188 public FImage renderFilters(int numFiltersX) { 189 final int border = 4; 190 final int numFiltersY = (int) Math.ceil((double) filters.length / numFiltersX); 191 final int w = (border + filters[0].kernel.width); 192 final int width = w * (numFiltersX) + border; 193 final int h = (border + filters[0].kernel.height); 194 final int height = h * (numFiltersY) + border; 195 196 final FImage image = new FImage(width, height); 197 image.fill(1f); 198 199 int count = 0; 200 for (int j = 0; j < numFiltersY; j++) 201 for (int i = 0; i < numFiltersX && count < filters.length; i++) 202 image.drawImage(filters[count++].kernel.clone().normalise(), w * i + border, h * j + border); 203 204 return image; 205 } 206 207 /** 208 * Build an array of responses for every pixel. The response for each pixel 209 * is added in scan order (left-right, top-bottom). 210 * 211 * @return the responses for each pixel. 212 */ 213 public float[][] getResponses() { 214 final int width = this.responses[0].width; 215 final int height = this.responses[0].height; 216 217 final float[][] resp = new float[width * height][this.responses.length]; 218 219 for (int i = 0; i < responses.length; i++) { 220 for (int y = 0; y < responses[0].height; y++) { 221 for (int x = 0; x < responses[0].width; x++) { 222 resp[x + width * y][i] = responses[i].pixels[y][x]; 223 } 224 } 225 } 226 227 return resp; 228 } 229}