1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 package org.openimaj.demos.image;
31
32 import java.awt.Container;
33 import java.awt.GridLayout;
34 import java.awt.event.ActionEvent;
35 import java.awt.event.ActionListener;
36 import java.io.File;
37 import java.io.IOException;
38 import java.util.Hashtable;
39
40 import javax.swing.BorderFactory;
41 import javax.swing.JFrame;
42 import javax.swing.JLabel;
43 import javax.swing.JPanel;
44 import javax.swing.JSlider;
45 import javax.swing.JTextField;
46 import javax.swing.SpringLayout;
47 import javax.swing.event.ChangeEvent;
48 import javax.swing.event.ChangeListener;
49
50 import org.openimaj.demos.Demo;
51 import org.openimaj.image.DisplayUtilities;
52 import org.openimaj.image.ImageUtilities;
53 import org.openimaj.image.MBFImage;
54 import org.openimaj.image.colour.RGBColour;
55 import org.openimaj.math.geometry.line.Line2d;
56 import org.openimaj.math.geometry.point.Point2d;
57 import org.openimaj.math.geometry.point.Point2dImpl;
58
59
60
61
62
63
64
65 @Demo(author = "Sina Samangooei", description = "Demonstrates the radial distortion correction image "
66 + "processor and allows the parameters to be changed interactively "
67 + "and see the results.", keywords = { "image", "warp", "radial",
68 "distortion" }, title = "Radial Distortion Calibrator", icon = "/org/openimaj/demos/icons/image/radial-icon.png")
69 public class RadialDistortionCalibrator {
70 private static final int SLIDER_MAX = 1000;
71 private MBFImage outImage;
72 private MBFImage image;
73 private int midX;
74 private int midY;
75 private float alphaY, betaY;
76 private float alphaX, betaX;
77 private JFrame outFrame;
78 private int origMidX;
79 private int origMidY;
80
81
82
83
84
85 public RadialDistortionCalibrator(MBFImage image) {
86 int padding = 200;
87 this.outImage = image.newInstance(image.getWidth() + padding, image.getHeight() + padding);
88 this.image = image;
89 this.midX = outImage.getWidth() / 2;
90 this.midY = outImage.getHeight() / 2;
91 this.origMidX = image.getWidth() / 2;
92 this.origMidY = image.getHeight() / 2;
93 this.alphaX = 0.02f;
94 this.alphaY = 0.04f;
95 this.betaX = 0.02f;
96 this.betaY = 0.04f;
97 regenAndDisplay();
98 createControlWindow();
99 }
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
294 float paddingX = point.getX();
295 float paddingY = point.getY();
296
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
321
322
323
324 public Point2d getUndistortedPoint(Point2d point) {
325
326 float x = point.getX();
327 float y = point.getY();
328
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
374
375
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 }