View Javadoc

1   /**
2    * Copyright (c) 2011, The University of Southampton and the individual contributors.
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without modification,
6    * are permitted provided that the following conditions are met:
7    *
8    *   * 	Redistributions of source code must retain the above copyright notice,
9    * 	this list of conditions and the following disclaimer.
10   *
11   *   *	Redistributions in binary form must reproduce the above copyright notice,
12   * 	this list of conditions and the following disclaimer in the documentation
13   * 	and/or other materials provided with the distribution.
14   *
15   *   *	Neither the name of the University of Southampton nor the names of its
16   * 	contributors may be used to endorse or promote products derived from this
17   * 	software without specific prior written permission.
18   *
19   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
23   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26   * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29   */
30  package org.openimaj.image.processing.convolution;
31  
32  import org.openimaj.image.FImage;
33  import org.openimaj.image.processor.SinglebandImageProcessor;
34  import org.openimaj.math.matrix.MatrixUtils;
35  
36  import Jama.SingularValueDecomposition;
37  
38  /**
39   * Base class for implementation of classes that perform convolution operations
40   * on {@link FImage}s as a @link{SinglebandImageProcessor}, with the kernel
41   * itself formed from and @link{FImage}.
42   *
43   * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
44   */
45  public class FConvolution implements SinglebandImageProcessor<Float, FImage> {
46  	/** The kernel */
47  	public FImage kernel;
48  
49  	private ConvolveMode mode;
50  
51  	interface ConvolveMode {
52  		public void convolve(FImage f);
53  
54  		class OneD implements ConvolveMode {
55  			private float[] kernel;
56  			private boolean rowMode;
57  
58  			OneD(FImage image) {
59  				if (image.height == 1) {
60  					this.rowMode = true;
61  					this.kernel = image.pixels[0];
62  
63  				} else {
64  					this.rowMode = false;
65  					this.kernel = new float[image.height];
66  					for (int i = 0; i < image.height; i++)
67  						this.kernel[i] = image.pixels[i][0];
68  				}
69  			}
70  
71  			@Override
72  			public void convolve(FImage f) {
73  				if (this.rowMode)
74  					FImageConvolveSeparable.convolveHorizontal(f, kernel);
75  				else
76  					FImageConvolveSeparable.convolveVertical(f, kernel);
77  			}
78  
79  		}
80  
81  		class Separable implements ConvolveMode {
82  			private float[] row;
83  			private float[] col;
84  
85  			Separable(SingularValueDecomposition svd) {
86  
87  				final int nrows = svd.getU().getRowDimension();
88  
89  				this.row = new float[nrows];
90  				this.col = new float[nrows];
91  
92  				final float factor = (float) Math.sqrt(svd.getS().get(0, 0));
93  				for (int i = 0; i < nrows; i++) {
94  					this.row[i] = (float) svd.getU().get(i, 0) * factor;
95  					this.col[i] = (float) svd.getV().get(i, 0) * factor;
96  				}
97  			}
98  
99  			@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 }