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.demos.image; 031 032import java.awt.Container; 033import java.awt.GridLayout; 034import java.awt.event.ActionEvent; 035import java.awt.event.ActionListener; 036import java.io.File; 037import java.io.IOException; 038import java.util.Hashtable; 039 040import javax.swing.BorderFactory; 041import javax.swing.JFrame; 042import javax.swing.JLabel; 043import javax.swing.JPanel; 044import javax.swing.JSlider; 045import javax.swing.JTextField; 046import javax.swing.SpringLayout; 047import javax.swing.event.ChangeEvent; 048import javax.swing.event.ChangeListener; 049 050import org.openimaj.demos.Demo; 051import org.openimaj.image.DisplayUtilities; 052import org.openimaj.image.ImageUtilities; 053import org.openimaj.image.MBFImage; 054import org.openimaj.image.colour.RGBColour; 055import org.openimaj.math.geometry.line.Line2d; 056import org.openimaj.math.geometry.point.Point2d; 057import org.openimaj.math.geometry.point.Point2dImpl; 058 059/** 060 * Radial distortion demo 061 * 062 * @author Sina Samangooei (ss@ecs.soton.ac.uk) 063 * 064 */ 065@Demo(author = "Sina Samangooei", description = "Demonstrates the radial distortion correction image " 066 + "processor and allows the parameters to be changed interactively " 067 + "and see the results.", keywords = { "image", "warp", "radial", 068 "distortion" }, title = "Radial Distortion Calibrator", icon = "/org/openimaj/demos/icons/image/radial-icon.png") 069public class RadialDistortionCalibrator { 070 private static final int SLIDER_MAX = 1000; 071 private MBFImage outImage; 072 private MBFImage image; 073 private int midX; 074 private int midY; 075 private float alphaY, betaY; 076 private float alphaX, betaX; 077 private JFrame outFrame; 078 private int origMidX; 079 private int origMidY; 080 081 /** 082 * Construct the demo with the given image 083 * @param image the image 084 */ 085 public RadialDistortionCalibrator(MBFImage image) { 086 int padding = 200; 087 this.outImage = image.newInstance(image.getWidth() + padding, image.getHeight() + padding); 088 this.image = image; 089 this.midX = outImage.getWidth() / 2; 090 this.midY = outImage.getHeight() / 2; 091 this.origMidX = image.getWidth() / 2; 092 this.origMidY = image.getHeight() / 2; 093 this.alphaX = 0.02f; 094 this.alphaY = 0.04f; 095 this.betaX = 0.02f; 096 this.betaY = 0.04f; 097 regenAndDisplay(); 098 createControlWindow(); 099 } 100 101 private void createControlWindow() { 102 JFrame control = new JFrame(); 103 control.setBounds(this.outFrame.getWidth(), 0, 700, 400); 104 Container cpane = control.getContentPane(); 105 cpane.setLayout(new GridLayout(4, 1)); 106 Container alphaXSlider = createSlider(new AlphaXChanger()); 107 Container alphaYSlider = createSlider(new AlphaYChanger()); 108 Container betaXSlider = createSlider(new BetaXChanger()); 109 Container betaYSlider = createSlider(new BetaYChanger()); 110 cpane.add(alphaXSlider); 111 cpane.add(betaXSlider); 112 cpane.add(alphaYSlider); 113 cpane.add(betaYSlider); 114 115 control.setVisible(true); 116 } 117 118 abstract class Changer implements ChangeListener, ActionListener { 119 JTextField text = null; 120 JSlider slider = null; 121 122 public abstract String getName(); 123 124 public abstract boolean setNewValue(float value); 125 126 public float min() { 127 return -1f; 128 } 129 130 public float max() { 131 return 1f; 132 } 133 134 public float range() { 135 return max() - min(); 136 } 137 138 public abstract float def(); 139 140 @Override 141 public void stateChanged(ChangeEvent e) { 142 JSlider source = (JSlider) e.getSource(); 143 if (!source.getValueIsAdjusting()) { 144 int val = (int) source.getValue(); 145 float prop = (float) val / (float) SLIDER_MAX; 146 float toSet = min() + range() * prop; 147 if (setNewValue(toSet)) { 148 text.setText(toSet + ""); 149 } 150 } 151 } 152 153 @Override 154 public void actionPerformed(ActionEvent e) { 155 JTextField field = (JTextField) e.getSource(); 156 float toSet = Float.parseFloat(field.getText()); 157 if (setNewValue(toSet)) { 158 slider.setValue((int) (SLIDER_MAX * (toSet - min()) / range())); 159 } 160 } 161 162 } 163 164 class AlphaXChanger extends Changer { 165 @Override 166 public String getName() { 167 return "alpha X"; 168 } 169 170 @Override 171 public float def() { 172 return alphaX; 173 } 174 175 @Override 176 public boolean setNewValue(float value) { 177 boolean change = value != alphaX; 178 if (change) { 179 alphaX = value; 180 regenAndDisplay(); 181 } 182 return change; 183 } 184 185 } 186 187 class AlphaYChanger extends Changer { 188 @Override 189 public String getName() { 190 return "alpha Y"; 191 } 192 193 @Override 194 public float def() { 195 return alphaY; 196 } 197 198 @Override 199 public boolean setNewValue(float value) { 200 boolean change = value != alphaY; 201 if (change) { 202 alphaY = value; 203 regenAndDisplay(); 204 } 205 return change; 206 } 207 } 208 209 class BetaXChanger extends Changer { 210 @Override 211 public String getName() { 212 return "beta X"; 213 } 214 215 @Override 216 public float def() { 217 return betaX; 218 } 219 220 @Override 221 public boolean setNewValue(float value) { 222 boolean change = value != betaX; 223 if (change) { 224 betaX = value; 225 regenAndDisplay(); 226 } 227 return change; 228 } 229 230 } 231 232 class BetaYChanger extends Changer { 233 @Override 234 public String getName() { 235 return "beta Y"; 236 } 237 238 @Override 239 public float def() { 240 return betaY; 241 } 242 243 @Override 244 public boolean setNewValue(float value) { 245 boolean change = value != betaY; 246 if (change) { 247 betaY = value; 248 regenAndDisplay(); 249 } 250 return change; 251 } 252 } 253 254 private Container createSlider(Changer changer) { 255 JSlider slider = new JSlider(JSlider.HORIZONTAL, 0, SLIDER_MAX, 256 (int) (SLIDER_MAX * ((changer.def() - changer.min()) / changer 257 .range()))); 258 Hashtable<Integer, JLabel> labelTable = new Hashtable<Integer, JLabel>(); 259 labelTable.put(new Integer(0), new JLabel("" + changer.min())); 260 labelTable.put(new Integer(SLIDER_MAX), new JLabel("" + changer.max())); 261 for (float i = 1; i < 10f; i++) { 262 float prop = (i / 10f); 263 String s = String.format("%.2f", (changer.min() + changer.range() 264 * prop)); 265 labelTable.put(new Integer((int) (prop * SLIDER_MAX)), 266 new JLabel(s)); 267 } 268 slider.setLabelTable(labelTable); 269 slider.setPaintLabels(true); 270 slider.setBorder(BorderFactory.createTitledBorder(changer.getName())); 271 slider.addChangeListener(changer); 272 JPanel sliderHolder = new JPanel(); 273 SpringLayout layout = new SpringLayout(); 274 sliderHolder.setLayout(layout); 275 sliderHolder.add(slider); 276 JTextField text = new JTextField("" + changer.def(), 10); 277 text.addActionListener(changer); 278 sliderHolder.add(text); 279 layout.putConstraint(SpringLayout.WEST, slider, 5, SpringLayout.WEST, 280 sliderHolder); 281 layout.putConstraint(SpringLayout.WEST, text, 5, SpringLayout.EAST, 282 slider); 283 layout.putConstraint(SpringLayout.EAST, sliderHolder, 5, 284 SpringLayout.EAST, text); 285 layout.putConstraint(SpringLayout.WEST, sliderHolder, 10, 286 SpringLayout.WEST, slider); 287 changer.slider = slider; 288 changer.text = text; 289 return sliderHolder; 290 } 291 292 private Point2d getDistortedPoint(Point2d point) { 293 // this pixel relative to the padding 294 float paddingX = point.getX(); 295 float paddingY = point.getY(); 296 // Normalise x and y such that they are in a -1 to 1 range 297 float normX = (paddingX - midX) / (image.getWidth() / 2.0f); 298 float normY = (paddingY - midY) / (image.getHeight() / 2.0f); 299 300 float radius2 = normX * normX + normY * normY; 301 float radius4 = radius2 * radius2; 302 303 float xRatio = normX / (1 - alphaX * radius2 - betaX * radius4); 304 float yRatio = normY / (1 - alphaY * radius2 - betaY * radius4); 305 306 float radiusRatio2 = xRatio * xRatio + yRatio * yRatio; 307 float radiusRatio4 = radiusRatio2 * radiusRatio2; 308 309 float normDistortedX = normX 310 / (1 - alphaX * radiusRatio2 - betaX * radiusRatio4); 311 float normDistortedY = normY 312 / (1 - alphaY * radiusRatio2 - betaY * radiusRatio4); 313 314 float distortedX = ((1 + normDistortedX) / 2) * image.getWidth(); 315 float distortedY = ((1 + normDistortedY) / 2) * image.getHeight(); 316 return new Point2dImpl(distortedX, distortedY); 317 } 318 319 /** 320 * Compute the transformed point for a given point 321 * @param point the input point 322 * @return the transformed point 323 */ 324 public Point2d getUndistortedPoint(Point2d point) { 325 // this pixel relative to the padding 326 float x = point.getX(); 327 float y = point.getY(); 328 // Normalise x and y such that they are in a -1 to 1 range 329 float normX = (x - origMidX) / (outImage.getWidth() / 2.0f); 330 float normY = (y - origMidY) / (outImage.getHeight() / 2.0f); 331 332 float radius2 = normX * normX + normY * normY; 333 float radius4 = radius2 * radius2; 334 335 float normundistortedX = normX - alphaX * normX * radius2 - betaX 336 * normX * radius4; 337 float normundistortedY = normY - alphaY * normY * radius2 - betaY 338 * normY * radius4; 339 340 float undistortedX = ((1 + normundistortedX) / 2) * outImage.getWidth(); 341 float undistortedY = ((1 + normundistortedY) / 2) 342 * outImage.getHeight(); 343 return new Point2dImpl(undistortedX, undistortedY); 344 } 345 346 private void regenAndDisplay() { 347 double sumDistance = 0; 348 for (float y = 0; y < outImage.getHeight(); y++) { 349 for (float x = 0; x < outImage.getWidth(); x++) { 350 Point2dImpl point = new Point2dImpl(x, y); 351 Point2d distorted = getDistortedPoint(point); 352 353 if (image.getBounds().isInside(distorted)) { 354 Point2d undistorted = getUndistortedPoint(distorted); 355 sumDistance += new Line2d(point, undistorted) 356 .calculateLength(); 357 } 358 359 outImage.setPixel((int) x, (int) y, image.getPixelInterp( 360 distorted.getX(), distorted.getY(), RGBColour.BLACK)); 361 } 362 } 363 System.out.println("Sum difference: " + sumDistance); 364 365 if (this.outFrame == null) { 366 outFrame = DisplayUtilities.display(outImage); 367 } else { 368 DisplayUtilities.display(outImage, outFrame); 369 } 370 } 371 372 /** 373 * The main method. 374 * @param args path to an image; or empty 375 * @throws IOException 376 */ 377 public static void main(String args[]) throws IOException { 378 if (args.length == 0) 379 new RadialDistortionCalibrator( 380 ImageUtilities.readMBF(RadialDistortionCalibrator.class 381 .getResourceAsStream("/org/openimaj/demos/image/35smm_original.jpg"))); 382 else 383 new RadialDistortionCalibrator(ImageUtilities.readMBF(new File( 384 args[0]))); 385 } 386}