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.demos.image;
31  
32  import java.awt.event.MouseEvent;
33  import java.awt.event.MouseListener;
34  import java.awt.event.MouseMotionListener;
35  import java.io.IOException;
36  import java.util.ArrayList;
37  import java.util.List;
38  
39  import javax.swing.JFrame;
40  
41  import org.openimaj.demos.Demo;
42  import org.openimaj.image.DisplayUtilities;
43  import org.openimaj.image.MBFImage;
44  import org.openimaj.image.colour.ColourSpace;
45  import org.openimaj.image.colour.RGBColour;
46  import org.openimaj.image.colour.Transforms;
47  import org.openimaj.image.feature.local.interest.HarrisIPD;
48  import org.openimaj.image.processing.convolution.FGaussianConvolve;
49  import org.openimaj.image.processing.resize.ResizeProcessor;
50  import org.openimaj.image.processing.transform.ProjectionProcessor;
51  import org.openimaj.image.renderer.MBFImageRenderer;
52  import org.openimaj.math.geometry.line.Line2d;
53  import org.openimaj.math.geometry.point.Point2d;
54  import org.openimaj.math.geometry.point.Point2dImpl;
55  import org.openimaj.math.geometry.shape.Ellipse;
56  import org.openimaj.math.geometry.shape.EllipseUtilities;
57  import org.openimaj.math.geometry.shape.Rectangle;
58  import org.openimaj.math.geometry.shape.Shape;
59  import org.openimaj.math.geometry.transforms.TransformUtilities;
60  import org.openimaj.util.pair.Pair;
61  
62  import Jama.EigenvalueDecomposition;
63  import Jama.Matrix;
64  
65  /**
66   * Demonstrate the second moments
67   * 
68   * @author Sina Samangeooi (ss@ecs.soton.ac.uk)
69   * 
70   */
71  @Demo(
72  		author = "Sina Samangeooi",
73  		description = "Demonstrates the second moment extractor in an interactive"
74  				+ " way. Move the mouse over the edges of the box in the first image "
75  				+ "and the moments are displayed in the other images.",
76  		keywords = {
77  				"image", "moments" },
78  		title = "Second Moment Visualiser",
79  		icon = "/org/openimaj/demos/icons/image/moment-icon.png")
80  public class SecondMomentVisualiser implements MouseListener, MouseMotionListener {
81  	private MBFImage image;
82  	private HarrisIPD ipd;
83  	private Point2d drawPoint = null;
84  	private double derivscale;
85  	private JFrame projectFrame;
86  	private ResizeProcessor resizeProject;
87  	private List<Ellipse> ellipses;
88  	private List<Pair<Line2d>> lines;
89  	private Matrix transformMatrix;
90  	private JFrame mouseFrame;
91  	private int windowSize;
92  	private int featureWindowSize;
93  	private JFrame featureFrame;
94  	private double visFactor = 4;
95  
96  	/**
97  	 * Construct the demo
98  	 * 
99  	 * @throws IOException
100 	 */
101 	public SecondMomentVisualiser() throws IOException {
102 		// image = ImageUtilities.readMBF(
103 		// SecondMomentVisualiser.class.getResourceAsStream("/org/openimaj/image/data/square_rot.png")
104 		// );
105 		image = new MBFImage(400, 400, ColourSpace.RGB);
106 		image.fill(RGBColour.WHITE);
107 		final Shape shapeToDraw = new Rectangle(100, 100, 200, 200)
108 				.transform(TransformUtilities.rotationMatrixAboutPoint(
109 						Math.PI / 4, 200, 200));
110 		// Shape shapeToDraw = new Rectangle(100,100,200,200);
111 		// Shape shapeToDraw = new Circle(200f,200f,100f);
112 		image.createRenderer().drawShapeFilled(shapeToDraw, RGBColour.BLACK);
113 		derivscale = 5;
114 		ipd = new HarrisIPD((float) derivscale, (float) derivscale * 2);
115 		ipd.findInterestPoints(Transforms.calculateIntensityNTSC(image));
116 
117 		class Updater implements Runnable {
118 
119 			private SecondMomentVisualiser frame;
120 
121 			Updater(SecondMomentVisualiser frame) {
122 				this.frame = frame;
123 			}
124 
125 			@Override
126 			public void run() {
127 				while (true) {
128 					frame.draw();
129 					try {
130 						Thread.sleep(1000 / 30);
131 					} catch (final InterruptedException e) {
132 					}
133 				}
134 			}
135 		}
136 		image = image.process(new FGaussianConvolve(5));
137 
138 		this.mouseFrame = DisplayUtilities.displaySimple(image.clone());
139 
140 		this.mouseFrame.getContentPane().addMouseListener(this);
141 		this.mouseFrame.getContentPane().addMouseMotionListener(this);
142 
143 		projectFrame = DisplayUtilities.display(image.clone());
144 		projectFrame.setBounds(image.getWidth(), 0, image.getWidth(),
145 				image.getHeight());
146 		featureFrame = DisplayUtilities.display(image.clone());
147 		featureFrame.setBounds(image.getWidth() * 2, 0, image.getWidth(),
148 				image.getHeight());
149 		ellipses = new ArrayList<Ellipse>();
150 		lines = new ArrayList<Pair<Line2d>>();
151 		resizeProject = new ResizeProcessor(256, 256);
152 		final Thread t = new Thread(new Updater(this));
153 		t.start();
154 
155 	}
156 
157 	/**
158 	 * Draw
159 	 */
160 	public synchronized void draw() {
161 		final MBFImage toDraw = image.clone();
162 		final MBFImageRenderer renderer = toDraw.createRenderer();
163 
164 		if (this.drawPoint != null)
165 			renderer.drawPoint(this.drawPoint, RGBColour.RED, 3);
166 
167 		for (final Ellipse ellipse : ellipses) {
168 			renderer.drawShape(ellipse, 1, RGBColour.GREEN);
169 		}
170 		for (final Pair<Line2d> line : lines) {
171 			renderer.drawLine(line.firstObject(), 3, RGBColour.BLUE);
172 			renderer.drawLine(line.secondObject(), 3, RGBColour.RED);
173 		}
174 		if (this.transformMatrix != null) {
175 			try {
176 
177 				final ProjectionProcessor<Float[], MBFImage> pp = new ProjectionProcessor<Float[], MBFImage>();
178 				pp.setMatrix(this.transformMatrix);
179 				this.image.accumulateWith(pp);
180 				final MBFImage patch = pp.performProjection(-windowSize,
181 						windowSize, -windowSize, windowSize,
182 						RGBColour.RED);
183 				if (patch.getWidth() > 0 && patch.getHeight() > 0) {
184 					DisplayUtilities.display(patch.process(this.resizeProject),
185 							this.projectFrame);
186 					DisplayUtilities.display(
187 							patch.extractCenter(this.featureWindowSize,
188 									this.featureWindowSize).process(
189 									this.resizeProject), this.featureFrame);
190 
191 				}
192 			} catch (final Exception e) {
193 				e.printStackTrace();
194 			}
195 
196 		}
197 
198 		DisplayUtilities.display(toDraw.clone(), this.mouseFrame);
199 	}
200 
201 	private synchronized void setEBowl() {
202 		final Matrix secondMoments = ipd.getSecondMomentsAt(
203 				(int) this.drawPoint.getX(), (int) this.drawPoint.getY());
204 		// System.out.println(secondMoments.det());
205 		// secondMoments = secondMoments.times(1/secondMoments.det());
206 		// System.out.println(secondMoments.det());
207 		this.ellipses.clear();
208 		this.lines.clear();
209 		try {
210 			getBowlEllipse(secondMoments);
211 		} catch (final Exception e) {
212 			e.printStackTrace();
213 		}
214 	}
215 
216 	private void getBowlEllipse(Matrix secondMoments) {
217 		double rotation = 0;
218 		double d1 = 0, d2 = 0;
219 		if (secondMoments.det() == 0)
220 			return;
221 		// If [u v] M [u v]' = E(u,v)
222 		// THEN
223 		// [u v] (M / E(u,v)) [u v]' = 1
224 		// THEN you can go ahead and do the eigen decomp s.t.
225 		// (M / E(u,v)) = R' D R
226 		// where R is the rotation and D is the size of the ellipse
227 		// double divFactor = 1/E;
228 		final Matrix noblur = new Matrix(new double[][] {
229 				{
230 						ipd.lxmxblur.getPixel((int) this.drawPoint.getX(),
231 								(int) this.drawPoint.getY()),
232 						ipd.lxmyblur.getPixel((int) this.drawPoint.getX(),
233 								(int) this.drawPoint.getY()) },
234 				{
235 						ipd.lxmyblur.getPixel((int) this.drawPoint.getX(),
236 								(int) this.drawPoint.getY()),
237 						ipd.lxmxblur.getPixel((int) this.drawPoint.getX(),
238 								(int) this.drawPoint.getY()) } });
239 		System.out.println("NO BLUR SECOND MOMENTS MATRIX");
240 		noblur.print(5, 5);
241 		System.out.println("det is: " + noblur.det());
242 		if (noblur.det() < 0.00001)
243 			return;
244 
245 		final double divFactor = 1 / Math.sqrt(secondMoments.det());
246 		final double scaleFctor = derivscale;
247 		final EigenvalueDecomposition rdr = secondMoments.times(divFactor).eig();
248 		secondMoments.times(divFactor).print(5, 5);
249 
250 		System.out.println("D1(before)= " + rdr.getD().get(0, 0));
251 		System.out.println("D2(before) = " + rdr.getD().get(1, 1));
252 
253 		if (rdr.getD().get(0, 0) == 0)
254 			d1 = 0;
255 		else
256 			d1 = 1.0 / Math.sqrt(rdr.getD().get(0, 0));
257 		// d1 = Math.sqrt(rdr.getD().get(0,0));
258 		if (rdr.getD().get(1, 1) == 0)
259 			d2 = 0;
260 		else
261 			d2 = 1.0 / Math.sqrt(rdr.getD().get(1, 1));
262 		// d2 = Math.sqrt(rdr.getD().get(1,1));
263 
264 		final double scaleCorrectedD1 = d1 * scaleFctor * visFactor;
265 		final double scaleCorrectedD2 = d2 * scaleFctor * visFactor;
266 
267 		final Matrix eigenMatrix = rdr.getV();
268 		System.out.println("D1 = " + d1);
269 		System.out.println("D2 = " + d2);
270 		eigenMatrix.print(5, 5);
271 
272 		rotation = Math.atan2(eigenMatrix.get(1, 0), eigenMatrix.get(0, 0));
273 		final Ellipse ellipseToAdd = EllipseUtilities.ellipseFromEquation(
274 				this.drawPoint.getX(), // center x
275 				this.drawPoint.getY(), // center y
276 				scaleCorrectedD1, // semi-major axis
277 				scaleCorrectedD2, // semi-minor axis
278 				rotation// rotation
279 				);
280 		ellipses.add(ellipseToAdd);
281 
282 		if (d1 != 0 && d2 != 0) {
283 			this.windowSize = (int) (scaleFctor * d1 / d2) / 2;
284 			this.featureWindowSize = (int) scaleFctor;
285 			if (this.windowSize > 256)
286 				this.windowSize = 256;
287 			// this.transformMatrix = affineIPDTransformMatrix(secondMoments);
288 			// this.transformMatrix =
289 			// secondMomentsTransformMatrix(secondMoments);
290 			// this.transformMatrix =
291 			// usingEllipseTransformMatrix(d1,d2,rotation);
292 			this.transformMatrix = ellipseToAdd
293 					.transformMatrix()
294 					.times(TransformUtilities.scaleMatrix(1 / scaleFctor,
295 							1 / scaleFctor)).inverse();
296 			for (final double d : transformMatrix.getRowPackedCopy())
297 				if (d == Double.NaN) {
298 					this.transformMatrix = null;
299 					break;
300 				}
301 		} else {
302 			transformMatrix = null;
303 		}
304 		if (transformMatrix != null) {
305 			System.out.println("Transform matrix:");
306 			transformMatrix.print(5, 5);
307 		}
308 
309 		final Line2d major = Line2d.lineFromRotation((int) this.drawPoint.getX(),
310 				(int) this.drawPoint.getY(), (float) rotation,
311 				(int) scaleCorrectedD1);
312 		final Line2d minor = Line2d.lineFromRotation((int) this.drawPoint.getX(),
313 				(int) this.drawPoint.getY(), (float) (rotation + Math.PI / 2),
314 				(int) scaleCorrectedD2);
315 		lines.add(new Pair<Line2d>(major, minor));
316 	}
317 
318 	// private Matrix usingEllipseTransformMatrix(double major, double minor,
319 	// double rotation){
320 	// Matrix rotate = TransformUtilities.rotationMatrix(rotation);
321 	// Matrix scale = TransformUtilities.scaleMatrix(major, minor);
322 	// Matrix translation =
323 	// TransformUtilities.translateMatrix(this.drawPoint.getX(),
324 	// this.drawPoint.getY());
325 	// // Matrix transformMatrix = scale.times(translation).times(rotation);
326 	// Matrix transformMatrix = translation.times(rotate.times(scale));
327 	// return transformMatrix.inverse();
328 	// }
329 	//
330 	// private Matrix secondMomentsTransformMatrix(Matrix secondMoments) {
331 	// secondMoments = secondMoments.times(1/Math.sqrt(secondMoments.det()));
332 	// Matrix eigenMatrix = MatrixUtils.sqrt(secondMoments).inverse(); // This
333 	// is simply the rotation (eig vectors) and the scaling by the semi major
334 	// and semi minor axis (eig values)
335 	// // eigenMatrix = eigenMatrix.inverse();
336 	// Matrix transformMatrix = new Matrix(new double[][]{
337 	// {eigenMatrix.get(0, 0),eigenMatrix.get(0, 1),this.drawPoint.getX()},
338 	// {eigenMatrix.get(1, 0),eigenMatrix.get(1, 1),this.drawPoint.getY()},
339 	// {0,0,1},
340 	// });
341 	// return transformMatrix.inverse();
342 	// }
343 	//
344 	//
345 	// private Matrix affineIPDTransformMatrix(Matrix secondMoments) {
346 	// Matrix covar =
347 	// AbstractIPD.InterestPointData.getCovarianceMatrix(secondMoments);
348 	// Matrix sqrt = MatrixUtils.sqrt(covar);
349 	// Matrix transform = new Matrix(new double[][]{
350 	// {sqrt.get(0, 0),sqrt.get(0,1),this.drawPoint.getX()},
351 	// {sqrt.get(1, 0),sqrt.get(1,1),this.drawPoint.getY()},
352 	// {0,0,1},
353 	// });
354 	// return transform.inverse();
355 	// }
356 
357 	@Override
358 	public void mouseClicked(MouseEvent event) {
359 		drawPoint = new Point2dImpl(event.getX(), event.getY());
360 		if (this.drawPoint != null) {
361 			setEBowl();
362 		}
363 	}
364 
365 	@Override
366 	public void mouseEntered(MouseEvent event) {
367 
368 	}
369 
370 	@Override
371 	public void mouseExited(MouseEvent event) {
372 
373 	}
374 
375 	@Override
376 	public void mousePressed(MouseEvent event) {
377 
378 	}
379 
380 	@Override
381 	public void mouseReleased(MouseEvent event) {
382 
383 	}
384 
385 	@Override
386 	public void mouseDragged(MouseEvent e) {
387 
388 	}
389 
390 	@Override
391 	public void mouseMoved(MouseEvent e) {
392 		drawPoint = new Point2dImpl(e.getX(), e.getY());
393 		if (this.drawPoint != null) {
394 			setEBowl();
395 		}
396 	}
397 
398 	/**
399 	 * The main method
400 	 * 
401 	 * @param args
402 	 *            ignored
403 	 * @throws IOException
404 	 */
405 	public static void main(String args[]) throws IOException {
406 		new SecondMomentVisualiser();
407 	}
408 }