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.sandbox; 031 032import java.awt.GraphicsDevice; 033import java.awt.GraphicsEnvironment; 034import java.awt.event.KeyEvent; 035import java.awt.event.KeyListener; 036import java.awt.event.MouseEvent; 037import java.io.IOException; 038import java.util.ArrayList; 039import java.util.List; 040 041import javax.swing.JFrame; 042import javax.swing.event.MouseInputListener; 043 044import org.openimaj.image.DisplayUtilities; 045import org.openimaj.image.FImage; 046import org.openimaj.image.MBFImage; 047import org.openimaj.image.colour.ColourSpace; 048import org.openimaj.image.colour.RGBColour; 049import org.openimaj.image.colour.Transforms; 050import org.openimaj.image.connectedcomponent.ConnectedComponentLabeler; 051import org.openimaj.image.pixel.ConnectedComponent; 052import org.openimaj.image.pixel.Pixel; 053import org.openimaj.image.pixel.PixelSet; 054import org.openimaj.math.geometry.line.Line2d; 055import org.openimaj.math.geometry.point.Point2d; 056import org.openimaj.math.geometry.point.Point2dImpl; 057import org.openimaj.math.geometry.shape.Circle; 058import org.openimaj.math.geometry.transforms.HomographyModel; 059import org.openimaj.util.pair.IndependentPair; 060import org.openimaj.util.pair.Pair; 061import org.openimaj.video.VideoDisplay; 062import org.openimaj.video.VideoDisplayListener; 063import org.openimaj.video.capture.VideoCapture; 064 065public class DigitalWhiteboard implements VideoDisplayListener<MBFImage>, MouseInputListener, KeyListener { 066 private VideoCapture capture; 067 private VideoDisplay<MBFImage> display; 068 private JFrame drawingFrame; 069 private MBFImage drawingPanel; 070 private Runnable drawingUpdater; 071 private ConnectedComponentLabeler labeler; 072 List<MBFImage> learningFrames = new ArrayList<MBFImage>(); 073 // private HistogramPixelModel model = null; 074 private MODE mode = MODE.NONE; 075 private HomographyModel homography = null; 076 private List<Pair<Point2d>> homographyPoints = new ArrayList<Pair<Point2d>>(); 077 private List<IndependentPair<String, Point2d>> calibrationPoints = new ArrayList<IndependentPair<String, Point2d>>(); 078 private int calibrationPointIndex; 079 private Point2dImpl previousPoint; 080 private double screenDiagonal; 081 082 enum MODE { 083 MODEL, SEARCHING, NONE, LINE_CONSTRUCTING; 084 } 085 086 public DigitalWhiteboard() throws IOException { 087 088 System.out.println(VideoCapture.getVideoDevices()); 089 capture = new VideoCapture(320, 240, VideoCapture.getVideoDevices().get(1)); 090 final JFrame screen = DisplayUtilities.makeFrame("Video"); 091 display = VideoDisplay.createVideoDisplay(capture, screen); 092 display.addVideoListener(this); 093 display.displayMode(true); 094 display.getScreen().addKeyListener(this); 095 // GraphicsDevice device = 096 // GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); 097 final GraphicsDevice device = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()[1]; 098 drawingPanel = new MBFImage(device.getDisplayMode().getWidth(), device.getDisplayMode().getHeight(), 099 ColourSpace.RGB); 100 // drawingPanel = new MBFImage(640,480,ColourSpace.RGB); 101 drawingPanel.fill(RGBColour.WHITE); 102 drawingFrame = DisplayUtilities.display(drawingPanel); 103 drawingFrame.setBounds(640, 0, drawingPanel.getWidth(), drawingPanel.getHeight()); 104 drawingFrame.addKeyListener(this); 105 // drawingFrame.setUndecorated(true); 106 drawingFrame.setIgnoreRepaint(true); 107 drawingFrame.setResizable(false); 108 // drawingFrame.setVisible(false); 109 // drawingFrame.setExtendedState(JFrame.MAXIMIZED_BOTH); 110 // device.setFullScreenWindow(drawingFrame); 111 // GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()[0].setFullScreenWindow(screen); 112 113 // screenDiagonal = 114 // Math.sqrt(Math.pow(display.getScreen().getWidth()/2,2) + 115 // Math.pow(display.getScreen().getHeight()/2,2)); 116 screenDiagonal = 50; 117 118 drawingUpdater = new Runnable() { 119 120 @Override 121 public void run() { 122 while (true) { 123 // drawingPanel = new MBFImage(640,480,ColourSpace.RGB); 124 try { 125 drawWhiteboard(drawingPanel); 126 DisplayUtilities.display(drawingPanel, drawingFrame); 127 128 Thread.sleep(1000 / 30); 129 } catch (final InterruptedException e) { 130 // TODO Auto-generated catch block 131 e.printStackTrace(); 132 } 133 } 134 } 135 136 }; 137 final Thread t = new Thread(drawingUpdater); 138 t.start(); 139 140 labeler = new ConnectedComponentLabeler(ConnectedComponent.ConnectMode.CONNECT_4); 141 142 calibrationPoints.add(new IndependentPair<String, Point2d>("TOP LEFT", new Point2dImpl(20, 20))); 143 calibrationPoints.add(new IndependentPair<String, Point2d>("TOP RIGHT", new Point2dImpl( 144 drawingPanel.getWidth() - 20, 20))); 145 calibrationPoints.add(new IndependentPair<String, Point2d>("BOTTOM LEFT", new Point2dImpl(20, drawingPanel 146 .getHeight() - 20))); 147 calibrationPoints.add(new IndependentPair<String, Point2d>("BOTTOM RIGHT", new Point2dImpl(drawingPanel 148 .getWidth() - 20, drawingPanel.getHeight() - 20))); 149 calibrationPointIndex = 0; 150 } 151 152 private synchronized void drawWhiteboard(MBFImage drawingPanel) { 153 // drawingPanel.fill(RGBColour.WHITE); 154 if (mode == MODE.MODEL || this.calibrationPointIndex < this.calibrationPoints.size()) { 155 drawingPanel.fill(RGBColour.WHITE); 156 final Point2d waitingForPoint = this.calibrationPoints.get(calibrationPointIndex).secondObject(); 157 drawingPanel.createRenderer().drawShape(new Circle(waitingForPoint.getX(), waitingForPoint.getY(), 10), 158 RGBColour.RED); 159 } 160 161 } 162 163 @Override 164 public void afterUpdate(VideoDisplay<MBFImage> display) { 165 // TODO Auto-generated method stub 166 167 } 168 169 @Override 170 public synchronized void beforeUpdate(MBFImage frame) { 171 172 final FImage greyFramePart = Transforms.calculateIntensityNTSC(frame); 173 greyFramePart.threshold(0.99f); 174 // greyFramePart.processInplace(new AdaptiveLocalThresholdMean(5)); 175 // greyFramePart.processInplace(new OtsuThreshold()); 176 // FImage greyFramePart = frame.getBand(0); 177 final MBFImage greyFrame = new MBFImage(new FImage[] { greyFramePart.clone(), greyFramePart.clone(), 178 greyFramePart.clone() }); 179 if (mode == MODE.MODEL) { 180 final List<ConnectedComponent> labels = labeler.findComponents(greyFramePart); 181 if (labels.size() > 0) { 182 final PixelSet c = largestLabel(labels); 183 final Pixel centroid = c.calculateCentroidPixel(); 184 int distance = -1; 185 if (this.homographyPoints.size() != 0) { 186 distance = distance(homographyPoints.get(homographyPoints.size() - 1).firstObject(), centroid); 187 } 188 if (this.homographyPoints.size() == 0 || distance > screenDiagonal) { 189 System.out.println("Point found at: " + centroid); 190 System.out.println("Distance was: " + distance); 191 final IndependentPair<String, Point2d> calibration = this.calibrationPoints 192 .get(this.calibrationPointIndex); 193 System.out.println("Adding point for: " + calibration.firstObject()); 194 this.homographyPoints.add(new Pair<Point2d>(centroid, calibration.secondObject())); 195 this.calibrationPointIndex++; 196 if (this.calibrationPointIndex >= this.calibrationPoints.size()) { 197 this.homography.estimate(homographyPoints); 198 this.mode = MODE.SEARCHING; 199 drawingPanel.fill(RGBColour.WHITE); 200 } 201 else { 202 System.out.println("CURRENTLY EXPECTING POINT: " 203 + this.calibrationPoints.get(this.calibrationPointIndex).firstObject()); 204 } 205 } 206 207 } 208 } 209 else if (mode == MODE.SEARCHING) { 210 System.out.println("TOTALLY SEARHCING"); 211 final List<ConnectedComponent> labels = labeler.findComponents(greyFramePart); 212 if (labels.size() > 0) { 213 this.mode = MODE.LINE_CONSTRUCTING; 214 final PixelSet c = largestLabel(labels); 215 final Point2dImpl actualPoint = findActualPoint(c); 216 drawingPanel.createRenderer().drawShapeFilled(new Circle((int) actualPoint.x, (int) actualPoint.y, 5), 217 RGBColour.BLACK); 218 previousPoint = actualPoint; 219 } 220 } 221 else if (mode == MODE.LINE_CONSTRUCTING) { 222 System.out.println("TOTALLY LINE DRAWING"); 223 final List<ConnectedComponent> labels = labeler.findComponents(greyFramePart); 224 if (labels.size() > 0) { 225 final PixelSet c = largestLabel(labels); 226 final Point2dImpl actualPoint = findActualPoint(c); 227 drawingPanel.createRenderer().drawLine(new Line2d(previousPoint, actualPoint), 5, RGBColour.BLACK); 228 previousPoint = actualPoint; 229 } 230 else { 231 mode = MODE.SEARCHING; 232 previousPoint = null; 233 } 234 } 235 frame.internalAssign(greyFrame); 236 } 237 238 private Point2dImpl findActualPoint(PixelSet c) { 239 final double[] centroidDouble = c.calculateCentroid(); 240 final Point2dImpl centroid = new Point2dImpl((float) centroidDouble[0], (float) centroidDouble[1]); 241 final Point2dImpl actualPoint = centroid.transform(this.homography.getTransform()); 242 return actualPoint; 243 } 244 245 private PixelSet largestLabel(List<ConnectedComponent> labels) { 246 int max = 0; 247 PixelSet r = null; 248 for (final PixelSet c : labels) { 249 if (c.getPixels().size() > max) 250 { 251 max = c.getPixels().size(); 252 r = c; 253 } 254 } 255 return r; 256 } 257 258 private int distance(Point2d p1, Point2d p2) { 259 final double dx = p1.getX() - p2.getX(); 260 final double dy = p1.getY() - p2.getY(); 261 return (int) Math.sqrt(dx * dx + dy * dy); 262 } 263 264 public static void main(String args[]) throws IOException { 265 new DigitalWhiteboard(); 266 } 267 268 @Override 269 public void mouseClicked(MouseEvent arg0) { 270 271 } 272 273 @Override 274 public void mouseEntered(MouseEvent arg0) { 275 // TODO Auto-generated method stub 276 277 } 278 279 @Override 280 public void mouseExited(MouseEvent arg0) { 281 // TODO Auto-generated method stub 282 283 } 284 285 @Override 286 public void mousePressed(MouseEvent arg0) { 287 // TODO Auto-generated method stub 288 289 } 290 291 @Override 292 public void mouseReleased(MouseEvent arg0) { 293 // TODO Auto-generated method stub 294 295 } 296 297 @Override 298 public void mouseDragged(MouseEvent arg0) { 299 // TODO Auto-generated method stub 300 301 } 302 303 @Override 304 public void mouseMoved(MouseEvent arg0) { 305 // TODO Auto-generated method stub 306 307 } 308 309 @Override 310 public void keyPressed(KeyEvent arg0) { 311 // TODO Auto-generated method stub 312 313 } 314 315 @Override 316 public void keyReleased(KeyEvent arg0) { 317 // TODO Auto-generated method stub 318 319 } 320 321 @Override 322 public void keyTyped(KeyEvent event) { 323 System.out.println("Got a key"); 324 if (event.getKeyChar() == 'c') { 325 drawingPanel.fill(RGBColour.WHITE); 326 System.out.println("Modelling mode started"); 327 this.mode = MODE.MODEL; 328 this.calibrationPointIndex = 0; 329 homographyPoints.clear(); 330 this.homography = new HomographyModel(); 331 332 System.out.println("CURRENTLY EXPECTING POINT: " 333 + this.calibrationPoints.get(this.calibrationPointIndex).firstObject()); 334 335 } 336 if (event.getKeyChar() == 'd' && this.homographyPoints.size() > 4) { 337 338 } 339 } 340}