/* * Copyright Inria and Bordeaux University. * Author Jeremy Laviole. jeremy.laviole@inria.fr * PapAR project is the open-source version of the * PapARt project. License is LGPLv3, distributed with the sources. * This project can also distributed with standard commercial * licence for closed-sources projects. */ package fr.inria.papart.procam.camera; /** * * @author jeremylaviole */ import fr.inria.papart.procam.MarkerBoard; import fr.inria.papart.procam.ProjectiveDeviceP; import fr.inria.papart.procam.Utils; import fr.inria.papart.procam.camera.CamImage; import fr.inria.papart.procam.camera.CamImageColor; import fr.inria.papart.procam.camera.CamImageGray; import org.bytedeco.javacpp.opencv_core.CvMat; import org.bytedeco.javacpp.opencv_core.IplImage; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.Semaphore; import java.util.logging.Level; import java.util.logging.Logger; import processing.core.PApplet; import processing.core.PConstants; import processing.core.PImage; import processing.core.PMatrix3D; import processing.core.PVector; public abstract class Camera implements PConstants { public static Camera INVALID_CAMERA = new CameraOpenCV(-1); // Images protected IplImage currentImage, copyUndist; protected CamImage camImage = null; public enum Type { OPENCV, PROCESSING, OPEN_KINECT, FLY_CAPTURE } public enum PixelFormat { RGB, BGR, ARGB, RGBA, GRAY, DEPTH_KINECT_MM } protected PixelFormat format; // Parameters // One or the other. protected String cameraDescription = null; protected int systemNumber = -1; protected int width, height; protected int frameRate; protected boolean trackSheets = false; private boolean isClosing = false; protected boolean isConnected = false; private boolean undistort = false; // Properties files private String calibrationFile = null; // Properties (instanciated) protected ProjectiveDeviceP pdp = null; protected PMatrix3D camIntrinsicsP3D; // Instance variables protected PApplet parent = null; private List<MarkerBoard> sheets = null; protected final Semaphore sheetsSemaphore = new Semaphore(1); // ARToolkit protected String calibrationARToolkit; private CameraThread thread = null; abstract public void start(); public PImage getImage() { return getPImage(); } public abstract PImage getPImage(); protected void checkParameters() { if (width == 0 || height == 0) { throw new RuntimeException("Camera: Width or Height are 0, set them or load a calibration."); } if (this.parent == null) { throw new RuntimeException("Camera: the parent (current applet) is not set."); } if (this.systemNumber == -1 && cameraDescription == null) { throw new RuntimeException("Camera: initalization failed"); } } public void setCalibration(String fileName) { try { this.calibrationFile = fileName; pdp = ProjectiveDeviceP.loadCameraDevice(parent, fileName); camIntrinsicsP3D = pdp.getIntrinsics(); this.width = pdp.getWidth(); this.height = pdp.getHeight(); this.undistort = pdp.handleDistorsions(); } catch (Exception e) { e.printStackTrace(); System.err.println("Camera: error reading the calibration " + pdp + "file" + fileName + " \n" + e); } } public PImage getPImageCopy() { PImage out = parent.createImage(this.width, this.height, RGB); Utils.IplImageToPImage(currentImage, out); return out; } /** * Works if the source is IPLImage ?. * * @param context * @return */ public PImage getPImageCopy(PApplet context) { PImage out = context.createImage(this.width, this.height, RGB); Utils.IplImageToPImage(currentImage, out); return out; } public PImage getPImageCopyTo(PImage out) { Utils.IplImageToPImage(currentImage, this.format, out); return out; } /** * Description of the camera, the number if using OpenCV or OpenKinect, and * a name or file if using Processing. * * @param description */ public void setCameraDevice(String description) { this.cameraDescription = description; } protected String getCameraDevice() { return this.cameraDescription; } public void setParent(PApplet applet) { this.parent = applet; } public void setSystemNumber(int systemNumber) { this.systemNumber = systemNumber; } public void setSize(int width, int height) { // TODO: error handling while running. this.width = width; this.height = height; } public void setFrameRate(int frameRate) { this.frameRate = frameRate; } public int width() { return width; } public int height() { return height; } public int getFrameRate() { return this.frameRate; } public boolean isUndistort() { return undistort; } public void setUndistort(boolean undistort) { this.undistort = undistort; } public boolean isCalibrated() { return this.calibrationFile != null; } public String getCalibrationFile() { return this.calibrationFile; } /** * Initialize the tracking with ARToolkit plus. * * @param calibrationARToolkit */ public void initMarkerDetection(String calibrationARToolkit) { // Marker Detection and view this.calibrationARToolkit = calibrationARToolkit; this.sheets = Collections.synchronizedList(new ArrayList<MarkerBoard>()); } public String getCalibrationARToolkit() { return calibrationARToolkit; } /** * Add a markerboard to track with this camera. * * @param sheet */ public void trackMarkerBoard(MarkerBoard sheet) { sheet.addTracker(parent, this); try { sheetsSemaphore.acquire(); this.sheets.add(sheet); sheetsSemaphore.release(); } catch (InterruptedException ex) { Logger.getLogger(Camera.class.getName()).log(Level.SEVERE, null, ex); } catch (NullPointerException e) { throw new RuntimeException("Marker detection not initialized."); } } /** * If the video is threaded, this sets if the tracking is on or not. * * @param auto automatic Tag detection: ON if true. */ public void trackSheets(boolean auto) { this.trackSheets = auto; if (thread != null) { thread.setCompute(auto); } else { System.err.println("Camera: Error AutoCompute only if threaded."); } } public boolean tracks(MarkerBoard board) { return this.sheets.contains(board); } public List<MarkerBoard> getTrackedSheets() { return this.sheets; } /** * It makes the camera update continuously. */ public void setThread() { if (thread == null) { thread = new CameraThread(this); thread.setCompute(this.trackSheets); thread.start(); } else { System.err.println("Camera: Error Thread already launched"); } } /** * Stops the update thread. */ public void stopThread() { if (thread != null) { thread.stopThread(); thread = null; } } public boolean useThread() { return thread != null; } public void forceCurrentImage(IplImage img) { updateCurrentImage(img); } /** * Update the current Image, from the specific grabber, lens distorsions are * handled here. * * @param img */ protected void updateCurrentImage(IplImage img) { if (undistort) { if (pdp == null || !pdp.handleDistorsions()) { System.err.println("I cannot distort the image for processing. The " + "calibration did not contain information. "); return; } if (copyUndist == null) { copyUndist = img.clone(); } // Workaround for crash when the java program is closing // to avoid native code to continue to run... if (isClosing()) { return; } pdp.getDevice().undistort(img, copyUndist); currentImage = copyUndist; } else { currentImage = img; } } /** * Check the memory allocation of the CamImage. */ protected void checkCamImage() { if (camImage == null) { if (this.isPixelFormatGray()) { camImage = new CamImageGray(parent, width(), height()); } if (this.isPixelFormatColor()) { camImage = new CamImageColor(parent, width(), height()); } } } protected boolean isPixelFormatGray() { PixelFormat pixelFormat = getPixelFormat(); return pixelFormat == PixelFormat.GRAY || pixelFormat == PixelFormat.DEPTH_KINECT_MM; } protected boolean isPixelFormatColor() { PixelFormat pixelFormat = getPixelFormat(); return pixelFormat == PixelFormat.ARGB || pixelFormat == PixelFormat.BGR || pixelFormat == PixelFormat.RGB || pixelFormat == PixelFormat.RGBA; } // Public API public abstract void grab(); public IplImage getIplImage() { return currentImage; } public ProjectiveDeviceP getProjectiveDevice() { return this.pdp; } public abstract void close(); protected void setClosing() { this.isClosing = true; this.stopThread(); } public boolean isClosing() { return isClosing || !isConnected; } public PixelFormat getPixelFormat() { return format; } public void setPixelFormat(PixelFormat format) { this.format = format; } /** * To use instead of getCamViewpoint * * @param point * @return */ public PVector getViewPoint(PVector point) { return getCamViewPoint(point); } /** * Gets the 2D location in the image of a 3D point. TODO: undistort ? * * @param pt 3D point seen by the camera. * @return 2D location of the 3D point in the image. */ @Deprecated public PVector getCamViewPoint(PVector pt) { PVector tmp = new PVector(); camIntrinsicsP3D.mult(new PVector(pt.x, pt.y, pt.z), tmp); //TODO: lens distorsion ? return new PVector(tmp.x / tmp.z, tmp.y / tmp.z); } private CvMat internalParams = null; @Deprecated public PMatrix3D estimateOrientation(PVector[] objectPoints, PVector[] imagePoints) { return pdp.estimateOrientation(objectPoints, imagePoints); } static public void convertARParams(PApplet parent, String calibrationFile, String calibrationARtoolkit) { convertARParams(parent, calibrationFile, calibrationARtoolkit, 0, 0); } static public void convertARParams(PApplet parent, String calibrationFile, String calibrationARtoolkit, int width, int height) { try { // ARToolkit Plus 2.1.1 // fr.inria.papart.procam.Utils.convertARParam(parent, calibrationYAML, calibrationData, width, height); // ARToolkit Plus 2.3.0 fr.inria.papart.procam.Utils.convertARParam2(parent, calibrationFile, calibrationARtoolkit); } catch (Exception e) { PApplet.println("Conversion error. " + e); } } }