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.hardware.kinect; 031 032import java.util.ArrayList; 033import java.util.List; 034 035import org.bridj.Pointer; 036import org.bridj.ValuedEnum; 037import org.openimaj.hardware.kinect.freenect.freenect_raw_tilt_state; 038import org.openimaj.hardware.kinect.freenect.freenect_registration; 039import org.openimaj.hardware.kinect.freenect.libfreenectLibrary; 040import org.openimaj.hardware.kinect.freenect.libfreenectLibrary.freenect_context; 041import org.openimaj.hardware.kinect.freenect.libfreenectLibrary.freenect_device; 042import org.openimaj.hardware.kinect.freenect.libfreenectLibrary.freenect_led_options; 043import org.openimaj.hardware.kinect.freenect.libfreenectLibrary.freenect_tilt_status_code; 044import org.openimaj.image.FImage; 045import org.openimaj.video.VideoDisplay; 046 047/** 048 * The event thread for Freenect 049 * 050 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 051 */ 052class EventThread extends Thread { 053 private volatile boolean alive = true; 054 055 public EventThread() { 056 setDaemon(true); 057 setName("FreenectEventThread"); 058 } 059 060 public void kill() { 061 this.alive = false; 062 } 063 064 @Override 065 public void run() { 066 while (alive) { 067 libfreenectLibrary.freenect_process_events(KinectController.CONTEXT); 068 } 069 } 070}; 071 072/** 073 * The OpenIMAJ Kinect Interface. 074 * 075 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 076 */ 077public class KinectController { 078 private static final int DEPTH_X_RES = 640; 079 private static final int DEPTH_Y_RES = 480; 080 protected volatile static Pointer<freenect_context> CONTEXT; 081 protected volatile static EventThread EVENT_THREAD; 082 protected volatile static List<KinectController> ACTIVE_CONTROLLERS = new ArrayList<KinectController>(); 083 084 protected Pointer<freenect_device> device; 085 086 /** 087 * The RGB or IR video stream 088 */ 089 public KinectStream<?> videoStream; 090 091 /** 092 * The depth stream 093 */ 094 public KinectDepthStream depthStream; 095 096 /** 097 * Construct with given device 098 * 099 * @param deviceId 100 * the device id 101 * @throws KinectException 102 */ 103 public KinectController(int deviceId) throws KinectException { 104 this(deviceId, false, false); 105 } 106 107 /** 108 * Construct with the first device in the given mode. 109 * 110 * @param irmode 111 * if true then the camera is set to IR mode; otherwise its in 112 * RGB mode 113 * @throws KinectException 114 */ 115 public KinectController(boolean irmode) throws KinectException { 116 this(0, irmode, false); 117 } 118 119 /** 120 * Default constructor. Uses the first device in RGB mode. 121 * 122 * @throws KinectException 123 */ 124 public KinectController() throws KinectException { 125 this(0, false, false); 126 } 127 128 /** 129 * Construct with the given device and mode. 130 * 131 * @param deviceId 132 * the device identifier. 0 for the first one. 133 * @param irmode 134 * whether to use infra-red mode or rgb mode. 135 * @param registeredDepthMode 136 * whether to register the depth image. If true, depth 137 * measurements are in millimeters. 138 * @throws KinectException 139 */ 140 public KinectController(int deviceId, boolean irmode, boolean registeredDepthMode) throws KinectException { 141 // init the context and start thread if necessary 142 init(); 143 144 final int cd = connectedDevices(); 145 if (deviceId >= cd || deviceId < 0) { 146 throw new IllegalArgumentException("Invalid device id"); 147 } 148 149 ACTIVE_CONTROLLERS.add(this); 150 151 // init device 152 final Pointer<Pointer<freenect_device>> devicePtr = Pointer.pointerToPointer(null); 153 libfreenectLibrary.freenect_open_device(CONTEXT, devicePtr, deviceId); 154 device = devicePtr.get(); 155 156 // setup listeners 157 if (irmode) 158 videoStream = new KinectIRVideoStream(this); 159 else 160 videoStream = new KinectRGBVideoStream(this); 161 depthStream = new KinectDepthStream(this, registeredDepthMode); 162 } 163 164 @Override 165 public void finalize() { 166 close(); 167 } 168 169 /** 170 * Init the freenect library. This only has to be done once. 171 * 172 * @throws KinectException 173 */ 174 private static synchronized void init() throws KinectException { 175 if (KinectController.CONTEXT == null) { 176 final Pointer<Pointer<freenect_context>> ctxPointer = Pointer.pointerToPointer(null); 177 libfreenectLibrary.freenect_init(ctxPointer, Pointer.NULL); 178 179 if (ctxPointer == null) 180 throw new KinectException("Unable to initialise libfreenect."); 181 182 CONTEXT = ctxPointer.get(); 183 184 if (CONTEXT == null) 185 throw new KinectException("Unable to initialise libfreenect."); 186 187 if (libfreenectLibrary.freenect_num_devices(CONTEXT) == 0) { 188 libfreenectLibrary.freenect_shutdown(CONTEXT); 189 throw new KinectException("Unable to initialise libfreenect; No devices found."); 190 } 191 192 EVENT_THREAD = new EventThread(); 193 EVENT_THREAD.start(); 194 195 // turn off the devices on shutdown 196 Runtime.getRuntime().addShutdownHook(new Thread() { 197 @Override 198 public synchronized void run() { 199 shutdownFreenect(); 200 } 201 }); 202 } 203 } 204 205 /** 206 * Completely shutdown the context. This turns off all cameras. The context 207 * will be restarted upon creation of a new KinectController. 208 */ 209 public synchronized static void shutdownFreenect() { 210 while (ACTIVE_CONTROLLERS.size() > 0) { 211 ACTIVE_CONTROLLERS.get(0).close(); 212 } 213 214 if (EVENT_THREAD != null) { 215 EVENT_THREAD.kill(); 216 } 217 218 if (CONTEXT != null) 219 libfreenectLibrary.freenect_shutdown(CONTEXT); 220 221 CONTEXT = null; 222 EVENT_THREAD = null; 223 } 224 225 /** 226 * Switch the main camera between InfraRed and RGB modes. 227 * 228 * @param irmode 229 * if true, then switches to IR mode, otherwise switches to RGB 230 * mode. 231 */ 232 public void setIRMode(boolean irmode) { 233 if (device == null) 234 return; 235 236 if (irmode) { 237 if (!(videoStream instanceof KinectIRVideoStream)) { 238 videoStream.stop(); 239 videoStream = new KinectIRVideoStream(this); 240 } 241 } else { 242 if (!(videoStream instanceof KinectRGBVideoStream)) { 243 videoStream.stop(); 244 videoStream = new KinectRGBVideoStream(this); 245 } 246 } 247 } 248 249 /** 250 * Set whether depth should be registered 251 * 252 * @param rdepth 253 * if true, then switches to depth registered mode, otherwise 254 * depth is not registered 255 */ 256 public void setRegisteredDepth(boolean rdepth) { 257 if (device == null) 258 return; 259 260 if (depthStream.registered != rdepth) { 261 depthStream.stop(); 262 depthStream = new KinectDepthStream(this, rdepth); 263 } 264 } 265 266 /** 267 * Get the number of connected devices. 268 * 269 * @return the number of devices connected. 270 * @throws KinectException 271 */ 272 public static synchronized int connectedDevices() throws KinectException { 273 init(); 274 return libfreenectLibrary.freenect_num_devices(CONTEXT); 275 } 276 277 /** 278 * Close the device. Closing an already closed device has no effect. 279 */ 280 public synchronized void close() { 281 if (device == null) 282 return; 283 284 videoStream.stop(); 285 depthStream.stop(); 286 libfreenectLibrary.freenect_close_device(device); 287 device = null; 288 289 ACTIVE_CONTROLLERS.remove(this); 290 } 291 292 /** 293 * Get the current angle in degrees 294 * 295 * @return the angle or 0 if the device is closed 296 */ 297 public synchronized double getTilt() { 298 if (device == null) 299 return 0; 300 301 libfreenectLibrary.freenect_update_tilt_state(device); 302 final Pointer<freenect_raw_tilt_state> state = libfreenectLibrary.freenect_get_tilt_state(device); 303 return libfreenectLibrary.freenect_get_tilt_degs(state); 304 } 305 306 /** 307 * Set the tilt to the given angle in degrees 308 * 309 * @param angle 310 * the angle 311 */ 312 public synchronized void setTilt(double angle) { 313 if (device == null) 314 return; 315 316 if (angle < -30) 317 angle = -30; 318 if (angle > 30) 319 angle = 30; 320 libfreenectLibrary.freenect_set_tilt_degs(device, angle); 321 } 322 323 /** 324 * Determine the current tilt status of the device 325 * 326 * @return the tilt status or null if the device is closed 327 */ 328 public synchronized KinectTiltStatus getTiltStatus() { 329 if (device == null) 330 return null; 331 332 libfreenectLibrary.freenect_update_tilt_state(device); 333 final Pointer<freenect_raw_tilt_state> state = libfreenectLibrary.freenect_get_tilt_state(device); 334 final ValuedEnum<freenect_tilt_status_code> v = libfreenectLibrary.freenect_get_tilt_status(state); 335 336 for (final freenect_tilt_status_code c : freenect_tilt_status_code.values()) 337 if (c.value == v.value()) 338 return KinectTiltStatus.valueOf(c.name()); 339 340 return null; 341 } 342 343 /** 344 * Set the device status LEDs 345 * 346 * @param option 347 * the LED status to set 348 */ 349 public synchronized void setLED(KinectLEDMode option) { 350 if (device == null) 351 return; 352 353 final ValuedEnum<freenect_led_options> o = freenect_led_options.valueOf(option.name()); 354 libfreenectLibrary.freenect_set_led(device, o); 355 } 356 357 /** 358 * Sets the LEDs to blink red and yellow for five seconds before resetting 359 * to green. 360 */ 361 public synchronized void identify() { 362 if (device == null) 363 return; 364 365 setLED(KinectLEDMode.LED_BLINK_RED_YELLOW); 366 try { 367 Thread.sleep(5000); 368 } catch (final InterruptedException e) { 369 } 370 setLED(KinectLEDMode.LED_GREEN); 371 } 372 373 /** 374 * Get the current acceleration values from the device 375 * 376 * @return the acceleration or null if the device is closed 377 */ 378 public synchronized KinectAcceleration getAcceleration() { 379 if (device == null) 380 return null; 381 382 final Pointer<Double> px = Pointer.pointerToDouble(0); 383 final Pointer<Double> py = Pointer.pointerToDouble(0); 384 final Pointer<Double> pz = Pointer.pointerToDouble(0); 385 386 libfreenectLibrary.freenect_update_tilt_state(device); 387 final Pointer<freenect_raw_tilt_state> state = libfreenectLibrary.freenect_get_tilt_state(device); 388 libfreenectLibrary.freenect_get_mks_accel(state, px, py, pz); 389 390 return new KinectAcceleration(px.getDouble(), py.getDouble(), pz.getDouble()); 391 } 392 393 /** 394 * Compute the world coordinates from the pixel location and registered 395 * depth. 396 * 397 * @param x 398 * pixel x-ordinate 399 * @param y 400 * pixel y-ordinate 401 * @param depth 402 * the depth 403 * @return the (x,y,z) coordinate in world space 404 */ 405 public double[] cameraToWorld(int x, int y, int depth) { 406 final Pointer<Double> wx = Pointer.allocateDouble(); 407 final Pointer<Double> wy = Pointer.allocateDouble(); 408 libfreenectLibrary.freenect_camera_to_world(device, x, y, depth, wx, wy); 409 410 return new double[] { wx.get(), wy.get(), depth }; 411 } 412 413 /** 414 * Compute the scaling factor for computing world coordinates. 415 * 416 * @see #cameraToWorld(int, int, int, double) 417 * 418 * @return the scaling factor 419 */ 420 public double computeScalingFactor() { 421 // Struct-by-value isn't currently working in bridj. When it is 422 // we can do: 423 // freenect_registration regInfo = 424 // libfreenectLibrary.freenect_copy_registration(device); 425 // double ref_pix_size = 426 // regInfo.zero_plane_info().reference_pixel_size(); 427 // double ref_distance = regInfo.zero_plane_info().reference_distance(); 428 // return 2 * ref_pix_size / ref_distance; 429 430 // for now we can work around by calculating the factor from a projected 431 // point 432 final int x = (DEPTH_X_RES / 2) + 1; 433 final double[] xyz = cameraToWorld(x, 0, 1000); 434 return xyz[0] / 1000.0; 435 } 436 437 /** 438 * Compute the world coordinates from the pixel location and registered 439 * depth. This method requires you pre-compute the constant scaling factor 440 * using {@link #computeScalingFactor()}, but it should be faster than using 441 * {@link #cameraToWorld(int, int, int)}. 442 * 443 * @param x 444 * pixel x-ordinate 445 * @param y 446 * pixel y-ordinate 447 * @param depth 448 * the depth 449 * @param factor 450 * the scaling factor 451 * @return the (x,y,z) coordinate in world space 452 */ 453 public float[] cameraToWorld(int x, int y, int depth, double factor) { 454 final float[] pt = new float[3]; 455 pt[0] = (float) ((x - DEPTH_X_RES / 2) * factor * depth); 456 pt[1] = (float) ((y - DEPTH_Y_RES / 2) * factor * depth); 457 pt[2] = depth; 458 return pt; 459 } 460 461 /** 462 * Compute the world coordinates from the pixel location and registered 463 * depth. This method requires you pre-compute the constant scaling factor 464 * using {@link #computeScalingFactor()}, but it should be faster than using 465 * {@link #cameraToWorld(int, int, int)}. 466 * 467 * This method is the same as {@link #cameraToWorld(int, int, int, double)}, 468 * but reuses the point array for efficiency. 469 * 470 * @param x 471 * pixel x-ordinate 472 * @param y 473 * pixel y-ordinate 474 * @param depth 475 * the depth 476 * @param factor 477 * @param pt 478 * the point to write to 479 * @return the (x,y,z) coordinate in world space 480 */ 481 public float[] cameraToWorld(int x, int y, int depth, double factor, float[] pt) { 482 pt[0] = (float) ((x - DEPTH_X_RES / 2) * factor * depth); 483 pt[1] = (float) ((y - DEPTH_Y_RES / 2) * factor * depth); 484 pt[2] = depth; 485 return pt; 486 } 487 488 /** 489 * Compute the world coordinates from the pixel locations in the given 490 * registered depth image. This method basically gives you a fully 491 * registered point cloud. 492 * 493 * @param regDepth 494 * the registered depth image 495 * @param startx 496 * the starting x-ordinate in the image 497 * @param stopx 498 * the stopping x-ordinate in the image 499 * @param stepx 500 * the number of pixels in the x direction to skip between 501 * samples 502 * @param starty 503 * the starting y-ordinate in the image 504 * @param stopy 505 * the stopping y-ordinate in the image 506 * @param stepy 507 * the number of pixels in the y direction to skip between 508 * samples 509 * @return an array of 3d vectors 510 */ 511 public float[][] cameraToWorld(FImage regDepth, int startx, int stopx, int stepx, int starty, int stopy, int stepy) { 512 final freenect_registration regInfo = libfreenectLibrary.freenect_copy_registration(device); 513 final double ref_pix_size = regInfo.zero_plane_info().reference_pixel_size(); 514 final double ref_distance = regInfo.zero_plane_info().reference_distance(); 515 516 final int nx = (stopx - startx) / stepx; 517 final int ny = (stopy - starty) / stepy; 518 final float[][] points = new float[nx * ny][3]; 519 final float[][] depths = regDepth.pixels; 520 final double factor = 2 * ref_pix_size / ref_distance; 521 522 for (int i = 0, y = starty; y < stopy; y += stepy) { 523 for (int x = startx; x < stopx; x += stepx) { 524 final float depth = depths[y][x]; 525 526 points[i][0] = (float) ((x - DEPTH_X_RES / 2) * factor * depth); 527 points[i][1] = (float) ((y - DEPTH_Y_RES / 2) * factor * depth); 528 points[i][2] = depth; 529 } 530 } 531 532 return points; 533 } 534 535 /** 536 * Test 537 * 538 * @param args 539 * @throws KinectException 540 */ 541 public static void main(String[] args) throws KinectException { 542 VideoDisplay.createVideoDisplay(new KinectController(0).videoStream); 543 } 544}