import static com.googlecode.javacv.jna.cxcore.*; import static com.googlecode.javacv.jna.cv.*; import static com.googlecode.javacv.jna.highgui.*; import static com.googlecode.javacv.jna.cvaux.*; import com.googlecode.javacv.*; //use these for javacv-20100730 or earlier //import name.audet.samuel.javacv.jna.cxcore.*; //import static name.audet.samuel.javacv.jna.cxcore.*; //import static name.audet.samuel.javacv.jna.cv.*; //import static name.audet.samuel.javacv.jna.highgui.*; //import static name.audet.samuel.javacv.jna.cvaux.*; //import name.audet.samuel.javacv.*; import java.text.DecimalFormat; /** * This is a translation to Java of the C++ CvBase class (cvbase.cpp, * cvbase.hpp). * * Opens a display window and reads input frames from either a camera or video * file. Processes each frame (no operation by default) and displays the * results. * * Provides basic functions to pause/unpause the live input, emit debugging * information for selected frames, and to save both raw capture and processed * images. * * See CvDemo.java for a simple usage example and build instructions. * * You can use this class as a base for your own work. Just create a subclass * that overrides the necessary functions. The main place you probably need to * do this is the process() function. Also, the various *Ext*() functions are * hooks that mostly do nothing in the base class, but are convenient places * for you to insert your own code in a subclass (but note that you can also * override even non-Ext functions, if needed). * * If you do use this class, it is best if you do not modify it. I.e., use the * file CvBase.java as you download it, with no modifications. That way if it * becomes necessary for us to supply an updated version of the files * (e.g. with bugfixes) you will be able to just easily drop in the * replacement, rather than having to merge it line-by-line with your code. * * @author vona **/ class CvBase { /** Default input specification (use next available camera). **/ public static final String DEF_INPUT = "-1"; /** Default width and height for camera input. **/ public static final int DEF_CAM_W = 640, DEF_CAM_H = 480; /** Default maximum frames per second. **/ public static final float DEF_MAX_FPS = 10.0f; /** Default minimum inter-frame delay in ms. **/ public static final int DEF_MIN_DELAY_MS = 5; /** Default application name. **/ public static String DEF_APPNAME = "cvbase"; /** Number formatter. **/ public static final DecimalFormat FMT = new DecimalFormat() { { setMaximumFractionDigits(3); } }; /** Format a number for printing. **/ public static final String fmt(double d) { return FMT.format(d); } /** The JavaCV capture structure. **/ protected CvCapture cap = null; /** Maximum frames per second. **/ protected float maxFPS = DEF_MAX_FPS; /** Minimum inter-frame delay. **/ protected int minDelayMS = DEF_MIN_DELAY_MS; /** Application name. **/ protected String appname = DEF_APPNAME; /** The file save image, if any (freed by destructor iff allocated). **/ protected IplImage saveImage = null; /** Most recently captured image, if any (do not mutate or free). **/ protected IplImage capImage = null; /** Most recently processed image, if any (do not free). **/ protected IplImage procImage = null; /** Most recent captured frame number. **/ protected int frameN = -1; /** Milliseconds per OpenCV clock tick. **/ protected double msPerTick = (1.0/1000.0)*(1.0/cvGetTickFrequency()); //cvGetTickFrequency() returns //ticks per microsecond /** Whether processing is currently paused. **/ protected boolean paused = false; /** Whether debug is requested for next frame. **/ protected boolean dbg = false; /** Current time in milliseconds according to OpenCV clock. **/ protected double nowMS() { return cvGetTickCount()*msPerTick; } /** Display command line help. **/ protected void cmdHelp() { boolean cio = camIndexOptional(); System.out.println(appname+" -?|-h -- display this help"); System.out.println(appname+" "+(cio?"[":"")+"I [W H]"+(cio?"]":"")+ " "+cmdHelpExtParams()+ "-- use cv cam index I (def -1) [at WxH, def "+ DEF_CAM_W+"x"+DEF_CAM_H+"]"); System.out.println(appname+" path "+cmdHelpExtParams()+ "-- read frames from video file at path"); cmdHelpExt(); } /** Whether the camera index is optional on the command line. **/ protected boolean camIndexOptional() { return true; } /** Display extra command line help, for subclasses. **/ protected void cmdHelpExt() {} /** Extra command line parameters, for subclasses. **/ protected String cmdHelpExtParams() { return ""; } /** Display GUI help. **/ protected void guiHelp() { System.out.println("h -- display this help"); System.out.println("q,ESC -- quit"); System.out.println("SPACE -- toggle run/pause"); System.out.println("g -- debug next frame"); System.out.println("c -- save last captured image"); System.out.println("p -- save last processed image"); guiHelpExt(); } /** Display extra GUI help, for subclasses. **/ protected void guiHelpExt() {} /** * Process the given frame. * * Default impl is identity. * * Note that the passed image may not be mutated. So to do any significant * processing, you will need to allocate your own return image. It is * typically best to do this once for the first frame, store the results in * subclass instance variables, and then deallocate any allocated space in * the subclass destructor. * * All frames are guaranteed to have the same dimensions and pixel format. * * This will be called for every iteration of mainLoop(), even while paused. * You may use the various instance fields (#frameN, #capImage, #paused, * #dbg, etc) to determine what operations to perform. Of course, when * writing a subclass, you may also add your own fields. * * @return the processed image, null will cause image display to be skipped * for this frame **/ protected IplImage process(IplImage frame) { return frame; } /** * Handle keypresses. * * @return true to continue processing frames, false to end program **/ protected boolean handleKey(int code) { switch (code) { case 'h': { guiHelp(); break; } case 27: case 'q': return false; case ' ': { paused = !paused; break; } case 'g': { dbg = true; break; } case 'c': { if (capImage == null) { System.err.println("no capture image"); break; } String fn = appname+"-c"+frameN+".png"; System.err.println("saving capture image to "+fn); if (!save(fn, capImage)) System.err.println("error saving image"); break; } case 'p': { if (procImage == null) {System.err.println("no processed image"); break;} String fn = appname+"-p"+frameN+".png"; System.out.println("saving processed image to "+fn); if (!save(fn, procImage)) System.out.println("error saving image"); break; } default: return handleKeyExt(code); } return true; } /** * Keypresses not handled by the default implementation of handleKey() are * passed here. * * Default impl just prints the keycode. * * Overriding this is one way that subclasses can handle extra keypresses. **/ protected boolean handleKeyExt(int code) { System.out.println("unhandled keycode "+code); return true; } /** * Save an image to file using the OpenCV API. * * @return true on success. **/ boolean save(String filename, IplImage image){ //(re-)allocate saveImage if necessary, since cvSaveImage can handle only //8-bit 1 or 3 channel images (it also assumes BGR order in the latter //case) if ((image.depth != IPL_DEPTH_8U) || ((image.nChannels != 1) && (image.nChannels != 3))) { int w = image.width, h = image.height; int nc = (image.nChannels > 1) ? 3 : 1; if ((saveImage == null) || (saveImage.width != w) || (saveImage.height != h) || (saveImage.nChannels != nc)) { if (saveImage != null) saveImage.release(); saveImage = cvCreateImage(cvSize(w, h), IPL_DEPTH_8U, nc); } } if (saveImage != null) { cvConvertImage(image, saveImage, 0); image = saveImage; } return (cvSaveImage(filename, image) != 0); } /** * Constructor initializes members. * * Call init() and then mainLoop() to run the default application. **/ CvBase(String appname) { if (appname == null) this.appname = DEF_APPNAME; } /** Uses {@link #DEF_APPNAME}. **/ CvBase() { this(null); } /** Calls {@link #release}. **/ public void finalize() { release(); } /** Frees memory, closes windows, and releases resources. **/ public void release() { if (saveImage != null) {saveImage.release(); saveImage = null;} if (cap != null) {cvReleaseCapture(cap.pointerByReference()); cap = null;} } /** * Initialize members based on command line arguments. * * In particular, this constructs #cap and opens the main window (named * #appname). * * @return the number of arguments eaten **/ public int init(int argc, String argv[]) { // System.out.println("OpenCV "+CV_VERSION+ // " ("+CV_MAJOR_VERSION+"."+CV_MINOR_VERSION+"."+ // CV_SUBMINOR_VERSION+")"); System.out.println("JavaCV build timestamp "+ JavaCV.class.getPackage().getImplementationVersion()); int ate = 0; for (int i = 0; i < argc; i++) if (("-h".equals(argv[i])) || ("-?".equals(argv[i]))) { cmdHelp(); System.exit(0); } String input = (argc >= 1) ? argv[0] : DEF_INPUT; if (argc >= 1) ate++; int index = -2; try { index = Integer.parseInt(input); //either no args or first arg parsed as an int; open a camera int w = DEF_CAM_W, h = DEF_CAM_H; if (argc >= 3) { try { w = Integer.parseInt(argv[1]); ate++; try { h = Integer.parseInt(argv[2]); ate++; } catch (NumberFormatException nfeh) {} } catch (NumberFormatException nfew) {} } if ((cap = cvCreateCameraCapture(index)) == null) { System.err.println("error opening camera "+index); System.exit(-1); } int rw = cvSetCaptureProperty(cap, CV_CAP_PROP_FRAME_WIDTH, w); int rh = cvSetCaptureProperty(cap, CV_CAP_PROP_FRAME_HEIGHT, h); if ((rw == 0) || (rh == 0)) System.err.println("W: error setting camera to "+w+"x"+h); System.out.println( "reading frames from "+ ((index >= 0) ? ("camera "+index) : "first available camera")); } catch (NumberFormatException nfe) { //input specifier did not parse as an int, try to open it as a video file if ((cap = cvCreateFileCapture(input)) == null){ System.err.println("error opening file \""+input+"\""); System.exit(-1); } System.out.println("reading frames from file \""+input+"\""); } if (cvNamedWindow(appname, CV_WINDOW_AUTOSIZE) == 0) System.err.println("W: error opening window"); cvMoveWindow(appname, 0, 0); return initExt(argc, argv, ate); } /** * Extra initialization, for subclasses. * * @param ate the number of command line arguments already eaten by init() * * @return the total number of arguments eaten **/ public int initExt(int argc, String argv[], int ate) { return ate; } /** * Main frame processing loop. * * Default impl acquires a new frame, calls process(), displays the result * (if any), delays for the remainder of the minimum frame time (inverse of * #maxFPS), and handles user input. **/ public void mainLoop() { double frameStartMS, startMS; int minFrameMS = (int) (1000.0/maxFPS); frameN = -1; for (;;) { frameStartMS = nowMS(); if (!paused) { if (dbg) System.out.println("-- capturing frame "+frameN); startMS = nowMS(); capImage = cvQueryFrame(cap); if (capImage != null) { if (dbg) System.out.println("captured "+capImage.width+"x"+capImage.height+ " frame ("+fmt(nowMS()-startMS)+"ms)"); if (frameN == 0) System.out.println( "capture size "+capImage.width+"x"+capImage.height+ ", "+capImage.nChannels+" channels"+ ", "+((capImage.depth)&(~IPL_DEPTH_SIGN))+" bits"+ ", "+((((capImage.depth)&IPL_DEPTH_SIGN) != 0) ? "signed" : "unsigned")); frameN++; } else { System.err.println("W: error capturing frame"); } } else if (dbg) System.out.println("-- paused on frame "+frameN); if (dbg) System.out.println("processing..."); startMS = nowMS(); procImage = process(capImage); if (dbg) System.out.println( "processed into "+procImage.width+"x"+procImage.height+ " image ("+fmt(nowMS()-startMS)+"ms)"); if (cvGetWindowHandle(appname) == null) { System.out.println("main window closed, exiting"); break; } if (procImage != null) { //note: small race condition here; if user closes window now, it will be //reopened when we call cvShowImage() if (dbg) System.out.println("displaying processed image"); startMS = nowMS(); //note: if necessary, this call will deal with whatever bit depth or //number of channels the image may have (it will assume BGR order for 3 //channel images) cvShowImage(appname, procImage); if (dbg) System.out.println("displayed "+procImage.width+"x"+procImage.height +" image ("+fmt(nowMS()-startMS)+"ms)"); } if (dbg) System.out.println("frame time "+fmt(nowMS() - frameStartMS)+"ms"); int waitUntilMS = (int) (frameStartMS + minFrameMS); if (waitUntilMS < nowMS()) waitUntilMS = (int) (nowMS()+minDelayMS); if (dbg) System.out.println( "-- finished frame "+(paused ? frameN : (frameN-1)) +" ("+fmt(1000.0/(waitUntilMS-frameStartMS))+" FPS)"); boolean wasDbg = dbg; for (int w = (int) (waitUntilMS-nowMS()); w > 0; w = (int) (waitUntilMS-nowMS())) { if (dbg) System.out.println("waiting "+w+"ms (or until keypress)"); int c = cvWaitKey(w); if (c < 0) break; //cvWaitKey timed out if (!handleKey(c)) { System.out.flush(); return; } } System.out.flush(); if (wasDbg) dbg = false; } } }