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; 031 032import org.openimaj.image.FImage; 033import org.openimaj.image.processor.SinglebandImageProcessor; 034import org.openimaj.math.matrix.MatrixUtils; 035 036import Jama.SingularValueDecomposition; 037 038/** 039 * Base class for implementation of classes that perform convolution operations 040 * on {@link FImage}s as a @link{SinglebandImageProcessor}, with the kernel 041 * itself formed from and @link{FImage}. 042 * 043 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 044 */ 045public class FConvolution implements SinglebandImageProcessor<Float, FImage> { 046 /** The kernel */ 047 public FImage kernel; 048 049 private ConvolveMode mode; 050 051 interface ConvolveMode { 052 public void convolve(FImage f); 053 054 class OneD implements ConvolveMode { 055 private float[] kernel; 056 private boolean rowMode; 057 058 OneD(FImage image) { 059 if (image.height == 1) { 060 this.rowMode = true; 061 this.kernel = image.pixels[0]; 062 063 } else { 064 this.rowMode = false; 065 this.kernel = new float[image.height]; 066 for (int i = 0; i < image.height; i++) 067 this.kernel[i] = image.pixels[i][0]; 068 } 069 } 070 071 @Override 072 public void convolve(FImage f) { 073 if (this.rowMode) 074 FImageConvolveSeparable.convolveHorizontal(f, kernel); 075 else 076 FImageConvolveSeparable.convolveVertical(f, kernel); 077 } 078 079 } 080 081 class Separable implements ConvolveMode { 082 private float[] row; 083 private float[] col; 084 085 Separable(SingularValueDecomposition svd) { 086 087 final int nrows = svd.getU().getRowDimension(); 088 089 this.row = new float[nrows]; 090 this.col = new float[nrows]; 091 092 final float factor = (float) Math.sqrt(svd.getS().get(0, 0)); 093 for (int i = 0; i < nrows; i++) { 094 this.row[i] = (float) svd.getU().get(i, 0) * factor; 095 this.col[i] = (float) svd.getV().get(i, 0) * factor; 096 } 097 } 098 099 @Override 100 public void convolve(FImage f) { 101 FImageConvolveSeparable.convolveHorizontal(f, col); 102 FImageConvolveSeparable.convolveVertical(f, row); 103 } 104 } 105 106 class BruteForce implements ConvolveMode { 107 protected FImage kernel; 108 109 BruteForce(FImage kernel) { 110 this.kernel = kernel; 111 } 112 113 @Override 114 public void convolve(FImage image) { 115 final int kh = kernel.height; 116 final int kw = kernel.width; 117 final int hh = kh / 2; 118 final int hw = kw / 2; 119 final FImage clone = image.newInstance(image.width, image.height); 120 for (int y = hh; y < image.height - (kh - hh); y++) { 121 for (int x = hw; x < image.width - (kw - hw); x++) { 122 float sum = 0; 123 for (int j = 0, jj = kh - 1; j < kh; j++, jj--) { 124 for (int i = 0, ii = kw - 1; i < kw; i++, ii--) { 125 final int rx = x + i - hw; 126 final int ry = y + j - hh; 127 128 sum += image.pixels[ry][rx] * kernel.pixels[jj][ii]; 129 } 130 } 131 clone.pixels[y][x] = sum; 132 } 133 } 134 image.internalAssign(clone); 135 } 136 } 137 } 138 139 /** 140 * Construct the convolution operator with the given kernel 141 * 142 * @param kernel 143 * the kernel 144 */ 145 public FConvolution(FImage kernel) { 146 this.kernel = kernel; 147 setup(false); 148 } 149 150 /** 151 * Construct the convolution operator with the given kernel 152 * 153 * @param kernel 154 * the kernel 155 */ 156 public FConvolution(float[][] kernel) { 157 this.kernel = new FImage(kernel); 158 setup(false); 159 } 160 161 /** 162 * Set brute-force convolution; disables kernel separation and other 163 * optimisations. 164 * 165 * @param brute 166 */ 167 public void setBruteForce(boolean brute) { 168 setup(brute); 169 } 170 171 private void setup(boolean brute) { 172 if (brute) { 173 this.mode = new ConvolveMode.BruteForce(this.kernel); 174 return; 175 } 176 if (this.kernel.width == 1 || this.kernel.height == 1) { 177 this.mode = new ConvolveMode.OneD(kernel); 178 } else { 179 final SingularValueDecomposition svd = new SingularValueDecomposition( 180 MatrixUtils.matrixFromFloat(this.kernel.pixels)); 181 if (svd.rank() == 1) 182 this.mode = new ConvolveMode.Separable(svd); 183 else 184 this.mode = new ConvolveMode.BruteForce(this.kernel); 185 } 186 } 187 188 /* 189 * (non-Javadoc) 190 * 191 * @see org.openimaj.image.processor.ImageProcessor#processImage(org.openimaj 192 * .image.Image) 193 */ 194 @Override 195 public void processImage(FImage image) { 196 mode.convolve(image); 197 } 198 199 /** 200 * Return the kernel response at the x,y in the given image. 201 * 202 * This method will throw an array index out of bounds if x,y requests pixels 203 * outside the image bounds 204 * 205 * @param x 206 * @param y 207 * @param image 208 * @return the kernel response at the given coordinates 209 */ 210 public float responseAt(int x, int y, FImage image) { 211 float sum = 0; 212 final int kh = kernel.height; 213 final int kw = kernel.width; 214 final int hh = kh / 2; 215 final int hw = kw / 2; 216 217 for (int j = 0, jj = kh - 1; j < kh; j++, jj--) { 218 for (int i = 0, ii = kw - 1; i < kw; i++, ii--) { 219 final int rx = x + i - hw; 220 final int ry = y + j - hh; 221 222 sum += image.pixels[ry][rx] * kernel.pixels[jj][ii]; 223 } 224 } 225 return sum; 226 } 227}