diff options
Diffstat (limited to 'WPIJavaCV/src/edu/wpi/first/wpijavacv')
15 files changed, 1361 insertions, 0 deletions
diff --git a/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIBinaryImage.java b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIBinaryImage.java new file mode 100644 index 0000000..1b862f9 --- /dev/null +++ b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIBinaryImage.java @@ -0,0 +1,277 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package edu.wpi.first.wpijavacv; + +import java.util.ArrayList; +import static com.googlecode.javacv.cpp.opencv_imgproc.*; +import static com.googlecode.javacv.cpp.opencv_core.*; + +/** + * A {@link WPIBinaryImage} object is a black and white image. + * + * @author Greg Granito + */ +public class WPIBinaryImage extends WPIImage { + + /** + * Instantiates a {@link WPIBinaryImage} from the given {@link IplImage}. + * The resulting image will be directly wrapped around the given image, and so any modifications + * to the {@link WPIBinaryImage} will reflect on the given image. + * @param image the image to wrap + */ + protected WPIBinaryImage(IplImage image) { + super(image); + } + + /** + * Returns the result of "and-ing" the given image with the calling image. Every pixel in the image + * will be "and-ed" with the corresponding pixel in the other image to form a new image. The result + * of and-ing two pixels is pictured in the table below. + * + * <table border="1"><tr> + * <th></th> + * <th>White</th> + * <th>Black</th> + * </tr><tr> + * <th>White</th> + * <td>White</td> + * <td>Black</td> + * </tr><tr> + * <th>Black</th> + * <td>Black</td> + * <td>Black</td> + * </tr></table> + * + * @param image2 a second {@link WPIBinaryImage} + * @return an image that is the pixel-wise and of the two images. + */ + public WPIBinaryImage getAnd(WPIBinaryImage image2) { + validateDisposed(); + + IplImage result = IplImage.create(image.cvSize(), image.depth(), 1); + cvAnd(image, image2.image, result, null); + return new WPIBinaryImage(result); + } + + /** + * "and-s" this image with the given image. Every pixel in this image + * will be "and-ed" with the corresponding pixel in the other image. The result + * of and-ing two pixels is pictured in the table below. + * + * <table border="1"><tr> + * <th></th> + * <th>White</th> + * <th>Black</th> + * </tr><tr> + * <th>White</th> + * <td>White</td> + * <td>Black</td> + * </tr><tr> + * <th>Black</th> + * <td>Black</td> + * <td>Black</td> + * </tr></table> + * + * This will modify the image. If you do now wish to modify the image, use {@link WPIBinaryImage#getAnd(wpijavacv.WPIBinaryImage) getAnd(...)} + * instead. + * + * @param image2 a second {@link WPIBinaryImage} + */ + public void and(WPIBinaryImage image2) { + validateDisposed(); + + cvAnd(image, image2.image, image, null); + } + + /** + * Returns the result of "or-ing" the given image with the calling image. Every pixel in the image + * will be "or-ed" with the corresponding pixel in the other image to form a new image. The result + * of or-ing two pixels is pictured in the table below. + * + * <table border="1"><tr> + * <th></th> + * <th>White</th> + * <th>Black</th> + * </tr><tr> + * <th>White</th> + * <td>White</td> + * <td>White</td> + * </tr><tr> + * <th>Black</th> + * <td>White</td> + * <td>Black</td> + * </tr></table> + * + * @param image2 a second {@link WPIBinaryImage} + * @return an image that is the pixel-wise or of the two images. + */ + public WPIBinaryImage getOr(WPIBinaryImage image2) { + validateDisposed(); + + IplImage result = IplImage.create(image.cvSize(), image.depth(), 1); + cvOr(image, image2.image, result, null); + return new WPIBinaryImage(result); + } + + /** + * "or-s" this image with the given image. Every pixel in this image + * will be "or-ed" with the corresponding pixel in the other image. The result + * of or-ing two pixels is pictured in the table below. This will modify the image. + * + * <table border="1"><tr> + * <th></th> + * <th>White</th> + * <th>Black</th> + * </tr><tr> + * <th>White</th> + * <td>White</td> + * <td>White</td> + * </tr><tr> + * <th>Black</th> + * <td>White</td> + * <td>Black</td> + * </tr></table> + * + * This will modify the image. If you do now wish to modify the image, use {@link WPIBinaryImage#getOr(wpijavacv.WPIBinaryImage) getOr(...)} + * instead. + * + * @param image2 a second {@link WPIBinaryImage} + */ + public void or(WPIBinaryImage image2) { + validateDisposed(); + + cvOr(image, image2.image, image, null); + } + + /** + * Inverts this image. Everything which was white will now be black and + * everything which was black will now be white. + * This will modify the image. If you do now wish to modify the image, use {@link WPIBinaryImage#getInverse() getInverse()} instead. + * instead. + */ + public void invert() { + validateDisposed(); + + cvInv(image, image); + } + + /** + * Returns an image that is the inverse of the calling one. In other words, it returns a copy of this image except + * that the copy will replace every black pixel with white one and every white pixel with a black one. + * @return a new {@link WPIBinaryImage} that is the inverse of the image + */ + public WPIBinaryImage getInverse() { + validateDisposed(); + + IplImage result = IplImage.create(image.cvSize(), image.depth(), 1); + cvInv(image, result); + return new WPIBinaryImage(result); + } + + /** + * Dilates the image the specified number of times. Every time the image is dilated, every pixel which borders a white pixel + * will itself become white. The effect is that white pixels in the image begin to spread. + * This is useful if you wish to remove small "holes" from the image. + * + * This will modify the image. If you do now wish to modify the image, use {@link WPIBinaryImage#getDilated(int) getDilated(...)} instead. + * instead. + * + * @param iterations the number of times to perform the dilation. For example, if this is 3, then every pixel within three + * spaces of a white pixel will become white. + */ + public void dilate(int iterations) { + validateDisposed(); + + cvDilate(image, image, null, iterations); + } + + /** + * Returns an image that is the result of dilating the specified number of times. Every time an image is dilated, every pixel which borders a white pixel + * will itself become white. The effect is that white pixels in the image begin to spread. + * This is useful if you wish to remove small "holes" from the image. + * + * @param iterations the number of times to perform the dilation. For example, if this is 3, then every pixel within three + * @return returns a new {@link WPIBinaryImage} in which the surrounding pixels to each white pixel + * are changed to white + */ + public WPIBinaryImage getDilated(int iterations) { + validateDisposed(); + + IplImage result = IplImage.create(image.cvSize(), image.depth(), 1); + cvDilate(image, result, null, iterations); + return new WPIBinaryImage(result); + } + + /** + * Erodes the image the specified number of times. Every time the image is eroded, every pixel which borders a black pixel + * will itself become black. The effect is that black pixels in the image begin to spread. + * This is useful if you wish to shrink or remove white blobs from an image. + * + * This will modify the image. If you do now wish to modify the image, use {@link WPIBinaryImage#getEroded(int) getEroded(...)} instead. + * instead. + * + * @param iterations the number of times to perform the erosion. For example, if this is 3, then every pixel within three + * spaces of a black pixel will become black. + */ + public void erode(int iterations) { + validateDisposed(); + + cvErode(image, image, null, iterations); + } + + /** + * Returns an image that is the result of eroding the specified number of times. Every time the image is eroded, every pixel which borders a black pixel + * will itself become black. The effect is that black pixels in the image begin to spread. + * This is useful if you wish to shrink or remove white blobs from an image. + * + * @param iterations the number of times to perform the erosion. For example, if this is 3, then every pixel within three + * spaces of a black pixel will become black. + * @return a new {@link WPIBinaryImage} in which the surrounding pixels to each black pixel + * are changed to black + */ + public WPIBinaryImage getEroded(int iterations) { + validateDisposed(); + + IplImage result = IplImage.create(image.cvSize(), image.depth(), 1); + cvErode(image, result, null, iterations); + return new WPIBinaryImage(result); + } + + /** + * Finds all the "contours" in the image. A contour is basically an outline. + * @return an array of {@link WpiContour} that is all of the edges in the image + */ + public WPIContour[] findContours() { + validateDisposed(); + + IplImage tempImage = IplImage.create(image.cvSize(), image.depth(), 1); + + cvCopy(image, tempImage); + + final CvMemStorage storage = CvMemStorage.create(); + WPIMemoryPool pool = new WPIMemoryPool() { + + @Override + protected void disposed() { + cvClearMemStorage(storage); + storage.release(); + } + }; + + CvSeq contours = new CvSeq(); + cvFindContours(tempImage, storage, contours, 256, CV_RETR_LIST, CV_CHAIN_APPROX_TC89_KCOS); + ArrayList<WPIContour> results = new ArrayList(); + while (!isNull(contours)) { + WPIContour contour = new WPIContour(cvCloneSeq(contours, storage)); + results.add(contour); + pool.addToPool(contour); + contours = contours.h_next(); + } + + tempImage.release(); + WPIContour[] array = new WPIContour[results.size()]; + return results.toArray(array); + } +} diff --git a/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPICamera.java b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPICamera.java new file mode 100644 index 0000000..ed9752f --- /dev/null +++ b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPICamera.java @@ -0,0 +1,26 @@ +package edu.wpi.first.wpijavacv; + +/** + * A class used to gather images from the robot's camera. + * @author Joe Grinstead and Greg Granito + */ +public class WPICamera extends WPIFFmpegVideo { + + private static final int DEFAULT_ENDING_IP = 11; + + public WPICamera(String loginName, String password, int team) { + this(loginName + ":" + password + "@10." + (team / 100) + "." + (team % 100) + "." + DEFAULT_ENDING_IP); + } + + public WPICamera(int team) { + this("10." + (team / 100) + "." + (team % 100) + "." + DEFAULT_ENDING_IP); + } + + public WPICamera(String loginName, String password, String ip) { + this(loginName + ":" + password + "@" + ip); + } + + public WPICamera(String ip) { + super("http://" + ip + "/mjpg/video.mjpg"); + } +} diff --git a/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIColor.java b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIColor.java new file mode 100644 index 0000000..50f585b --- /dev/null +++ b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIColor.java @@ -0,0 +1,64 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package edu.wpi.first.wpijavacv; + +import java.awt.Color; +import static com.googlecode.javacv.cpp.opencv_core.*; +/** + * A class of colors used for drawing function + * @author Greg Granito + */ +public class WPIColor { + public static final WPIColor BLACK = new WPIColor(CvScalar.BLACK, Color.BLACK); + public static final WPIColor BLUE = new WPIColor(CvScalar.BLUE, Color.BLUE); + public static final WPIColor CYAN = new WPIColor(CvScalar.CYAN, Color.CYAN); + public static final WPIColor GRAY = new WPIColor(CvScalar.GRAY, Color.GRAY); + public static final WPIColor GREEN = new WPIColor(CvScalar.GREEN, Color.GREEN); + public static final WPIColor MAGENTA = new WPIColor(CvScalar.MAGENTA, Color.MAGENTA); + public static final WPIColor ONE = new WPIColor(CvScalar.ONE); + public static final WPIColor ONEHALF = new WPIColor(CvScalar.ONEHALF); + public static final WPIColor RED = new WPIColor(CvScalar.RED, Color.RED); + public static final WPIColor WHITE = new WPIColor(CvScalar.WHITE, Color.WHITE); + public static final WPIColor YELLOW = new WPIColor(CvScalar.YELLOW, Color.YELLOW); + public static final WPIColor ZERO = new WPIColor(CvScalar.ZERO); + + private final CvScalar scalar; + private Color color; + + WPIColor(CvScalar scalar) { + this.scalar = scalar; + } + + WPIColor(CvScalar scalar, Color color) { + this(scalar); + this.color = color; + } + + /** + * Creates a new WPIColor with the specified rgb values + * @param red red value, 0 - 255 + * @param green green value, 0 - 255 + * @param blue blue value, 0 - 255 + */ + public WPIColor(int red, int green, int blue){ + this(CV_RGB(red, green, blue)); + } + + public WPIColor(Color color) { + this(CV_RGB(color.getRed(), color.getGreen(), color.getBlue()), color); + } + + CvScalar toCvScalar(){ + return scalar; + } + + public Color toColor() { + if (color == null) { + color = new Color((int) scalar.red(), (int) scalar.green(), (int) scalar.blue()); + } + return color; + } +} diff --git a/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIColorImage.java b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIColorImage.java new file mode 100644 index 0000000..b643efa --- /dev/null +++ b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIColorImage.java @@ -0,0 +1,158 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package edu.wpi.first.wpijavacv; + +import java.awt.image.BufferedImage; +import static com.googlecode.javacv.cpp.opencv_core.*; + +/** + * A color image + * @author Greg Granito + */ +public class WPIColorImage extends WPIImage { + + private WPIGrayscaleImage red; + private WPIGrayscaleImage blue; + private WPIGrayscaleImage green; + + /** + * creates a WpiColorImage based on the BufferedImage source + * @param imageSrc the source image + */ + public WPIColorImage(BufferedImage imageSrc) { + super(imageSrc); + } + + WPIColorImage(IplImage imageSrc) { + super(imageSrc); + } + + /** + * Draws a WpiContour + * @param c the contour + * @param color the desired WPIColor + * @param thickness the thickness in pixels + */ + public void drawContour(WPIContour c, WPIColor color, int thickness) { + cvDrawContours(image, c.getCVSeq(), color.toCvScalar(), color.toCvScalar(), 100, thickness, 8); + } + + /** + * Draws all the WpiContours in an array + * @param c the contours + * @param color the desired WPIColor + * @param thickness the thickness in pixels + */ + public void drawContours(WPIContour[] c, WPIColor color, int thickness){ + for(WPIContour con:c){ + drawContour(con, color, thickness); + } + } + + /** + * Draws a line between the two specified WpiPoints + * @param p1 Point 1 + * @param p2 Point 2 + * @param color the desired WPIColor + * @param thickness the thickness in pixels + */ + public void drawLine(WPIPoint p1, WPIPoint p2, WPIColor color, int thickness){ + cvLine(image, p1.getCvPoint(), p2.getCvPoint(), color.toCvScalar(), thickness, 8, 0); + } + + /** + * Draws a WpiPolygon + * @param p the WpiPolygon + * @param color the desired WPIColor + * @param thickness the thickness in pixels + */ + public void drawPolygon(WPIPolygon p, WPIColor color, int thickness){ + cvDrawContours(image, p.getCVSeq(), color.toCvScalar(), color.toCvScalar(), 100, thickness, 8); + } + + /** + * Draws a WpiPoint + * @param p the WpiPoint + * @param color the desired WPIColor + * @param thickness the thickness in pixels + */ + public void drawPoint(WPIPoint p, WPIColor color, int thickness){ + cvDrawCircle(image,p.getCvPoint(), thickness, color.toCvScalar(), CV_FILLED, 8, 0); + } + /** + * Draws all the WpiPoints in an array + * @param p the WpiPoint array + * @param color the desired WPIColor + * @param thickness the thickness in pixels + */ + public void drawPoints(WPIPoint[] p, WPIColor color, int thickness){ + for(int i = 0; i< p.length; i++) + drawPoint(p[i], color, thickness); + } + /** + * Draws all the WpiPolygons in an array + * @param p the WpiPolygon array + * @param color the desired WPIColor + * @param thickness the thickness in pixels + */ + public void drawPolygons(WPIPolygon[] p, WPIColor color, int thickness){ + for(WPIPolygon polygon : p){ + if(polygon != null && !polygon.getCVSeq().isNull()) + drawPolygon(polygon, color, thickness); + } + } + /** + * Draws a rectangle + * @param x the top left corner x coord + * @param y the top left corner y coord + * @param width the width of the rectangle + * @param height the height of the rectangle + * @param color the desired WPIColor + * @param thickness the thickness in pixels + */ + public void drawRect(int x, int y, int width, int height, WPIColor color, int thickness){ + cvDrawRect(image, cvPoint(x, y),cvPoint(x+width, y+height), color.toCvScalar(), thickness, 8, 0); + } + + private void generateChannels() { + if (red == null) { + IplImage redChannel = IplImage.create(image.cvSize(), 8, 1); + IplImage greenChannel = IplImage.create(image.cvSize(), 8, 1); + IplImage blueChannel = IplImage.create(image.cvSize(), 8, 1); + cvSplit(image, blueChannel, greenChannel, redChannel, null); + red = new WPIGrayscaleImage(redChannel); + blue = new WPIGrayscaleImage(blueChannel); + green = new WPIGrayscaleImage(greenChannel); + } + } + + /** + * Gets the red channel from the color image + * @return a WpiGrayscaleImage that represents the red channel of the WPIColor image + */ + public WPIGrayscaleImage getRedChannel() { + generateChannels(); + return red; + } + + /** + * Gets the blue channel from the color image + * @return a WpiGrayscaleImage that represents the blue channel of the WPIColor image + */ + public WPIGrayscaleImage getBlueChannel() { + generateChannels(); + return blue; + } + + /** + * Gets the green channel from the color image + * @return a WpiGrayscaleImage that represents the green channel of the WPIColor image + */ + + public WPIGrayscaleImage getGreenChannel() { + generateChannels(); + return green; + } +} diff --git a/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIContour.java b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIContour.java new file mode 100644 index 0000000..5cd9fc4 --- /dev/null +++ b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIContour.java @@ -0,0 +1,100 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package edu.wpi.first.wpijavacv; + +import static com.googlecode.javacv.cpp.opencv_core.*; +import static com.googlecode.javacv.cpp.opencv_imgproc.*; +/** + * This is a class that represents contours, it can only be obtained from + * the findContours() method of WpiBinaryImage + * @author Greg Granito + */ +public class WPIContour extends WPIDisposable { + + CvSeq contours; + CvRect rect; + + WPIContour(CvSeq contours) { + this.contours = contours; +// memStorage = contours.storage(); + + } + + CvSeq getCVSeq(){ + return contours; + } + + /** + * + * @return the height of the bounding rectangle of the contour + */ + public int getHeight(){ + if(rect == null || rect.isNull()) + rect = cvBoundingRect(contours, 1); + + return rect.height(); + } + + /** + * + * @return the width of the bounding rectangle of the contour + */ + public int getWidth(){ + if(rect == null || rect.isNull()) + rect = cvBoundingRect(contours, 1); + + return rect.width(); + } + + /** + * + * @return the x coord of the top left corner of the bounding rectangle + */ + public int getX(){ + if(rect == null || rect.isNull()) + rect = cvBoundingRect(contours, 1); + + return rect.x(); + } + + /** + * + * @return the y coord of the top left corner of the bounding rectangle + */ + public int getY(){ + if(rect == null || rect.isNull()) + rect = cvBoundingRect(contours, 1); + + return rect.y(); + } + + /** + * + * @param percentAccuracy the percentage the perimeter of the polygon can be off + * the perimeter of the contour. The higher the value, the fewer points the polygon + * will have. A value of 4-5 is recommended. + * @return the approximated WpiPolygon + */ + public synchronized WPIPolygon approxPolygon(double percentAccuracy){ + WPIPolygon polygon = new WPIPolygon(cvApproxPoly(contours, contours.header_size(), contours.storage(), CV_POLY_APPROX_DP, percentAccuracy, 0)); + if (getPool() != null) { + getPool().addToPool(polygon); + } + return polygon; + } + + /** + * + * @return the perimeter of the contour + */ + public int getlength(){ + return (int) cvContourPerimeter(contours); + } + + public void disposed() { + contours.deallocate(); + } +} diff --git a/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIDisposable.java b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIDisposable.java new file mode 100644 index 0000000..6d4b23b --- /dev/null +++ b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIDisposable.java @@ -0,0 +1,119 @@ +package edu.wpi.first.wpijavacv; + +import com.googlecode.javacpp.Pointer; + +/** + * This class is an abstract class defining disposable elements. + * It is the superclass of most things which links directly to the javacv core. + * + * @author Joe Grinstead + */ +public abstract class WPIDisposable { + + /** The memory pool to report to when disposed, or null if there is none */ + private WPIMemoryPool pool = null; + + /** Whether or not this is disposed */ + private boolean disposed = false; + + /** + * Sets the {@link WPIMemoryPool} that this disposable is linked to. + * @param pool the pool to link to (null is allowed) + */ + protected void setPool(WPIMemoryPool pool) { + this.pool = pool; + } + + protected WPIMemoryPool getPool() { + return pool; + } + + /** + * Disposes this object. This may be called multiple times. + * + * Programmers should call this when they no longer need an object. However, even if they don't, this will be called + * when this object is collected by the garbage collector. + */ + public void dispose() { + if (!disposed) { + disposed = true; + disposed(); + if (pool != null) { + pool.removeFromPool(this); + } + } + } + + /** + * This is called when {@link WPIDisposable#dispose() dispose()} is called for the first time. + * Subclasses should clear out whatever internal resources they are using. + */ + protected abstract void disposed(); + + /** + * Returns whether or not this object is disposed. + * @return whether or not this object is disposed + * + * @see WPIDisposable#dispose() dispose() + */ + public boolean isDisposed() { + return disposed; + } + + /** + * Checks if this {@link WPIDisposable} has already been disposed. If it has, + * then it will throw a {@link DisposedException} with a default message. + */ + protected void validateDisposed() { + if (disposed) { + throw new DisposedException(this + " has been disposed"); + } + } + + /** + * Checks if this {@link WPIDisposable} has already been disposed. If it has, + * then it will throw a {@link DisposedException} with the given message. + * @param message the message to give the exception + */ + protected void validateDisposed(String message) { + if (disposed) { + throw new DisposedException(message); + } + } + + /** + * Returns whether or not the given pointer is null in either the java sense or the javacv sense. + * @param pointer the pointer + * @return whether it is null + */ + protected static boolean isNull(Pointer pointer) { + return pointer == null || pointer.isNull(); + } + + /** + * Attempts to free (in the c sense) the given pointer. Does nothing if given null. + * @param pointer the pointer to free + */ + protected static void free(Pointer pointer) { + if (pointer != null && !pointer.isNull()) { + pointer.deallocate(); + } + } + + /** + * An exception to be thrown if an element has already been disposed and the user attempts to + * perform an operation on it. + */ + public static class DisposedException extends RuntimeException { + + public DisposedException(String message) { + super(message); + } + } + + @Override + protected void finalize() throws Throwable { + dispose(); + super.finalize(); + } +} diff --git a/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIFFmpegVideo.java b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIFFmpegVideo.java new file mode 100644 index 0000000..e52bced --- /dev/null +++ b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIFFmpegVideo.java @@ -0,0 +1,151 @@ +package edu.wpi.first.wpijavacv; + +import com.googlecode.javacv.FFmpegFrameGrabber; +import static com.googlecode.javacv.cpp.opencv_core.*; +import com.googlecode.javacv.cpp.opencv_core.IplImage; + +/** + * A class used to gather images from the robot's camera. + * @author Joe Grinstead and Greg Granito + */ +public class WPIFFmpegVideo extends WPIDisposable { + + private FFmpegFrameGrabber grabber; + private IplImage image; + private boolean readImage = true; + private boolean badConnection = false; + private final Object imageLock = new Object(); + private final Object grabberLock = new Object(); + + public WPIFFmpegVideo(final String path) { + new Thread() { + + @Override + public void run() { + grabber = new FFmpegFrameGrabber(path); + grabber.setFrameRate(1.0); + try { + grabber.start(); + + while (!isDisposed()) { + try { + IplImage newest; + synchronized (grabberLock) { + if (isDisposed()) { + return; + } + newest = grabber.grab(); + } + if (isNull(newest)) { + synchronized (imageLock) { + badConnection = true; + imageLock.notify(); + } + return; + } else { + synchronized (imageLock) { + if (image == null) { + image = cvCreateImage(newest.cvSize(), newest.depth(), newest.nChannels()); + } + cvCopy(newest, image); + readImage = false; + badConnection = false; + imageLock.notify(); + } + } + } catch (Exception ex) { + synchronized (imageLock) { + badConnection = true; + imageLock.notify(); + } + ex.printStackTrace(); + return; + } + try { + Thread.sleep(20); + } catch (InterruptedException ex) { + } + } + } catch (Exception ex) { + synchronized (imageLock) { + badConnection = true; + imageLock.notify(); + } + ex.printStackTrace(); + } + } + }.start(); + } + + public WPIImage getImage() throws BadConnectionException { + validateDisposed(); + + synchronized (imageLock) { + if (badConnection) { + throw new BadConnectionException(); + } else if (image == null) { + return null; + } else if (image.nChannels() == 1) { + return new WPIGrayscaleImage(image.clone()); + } else { + assert image.nChannels() == 3; + return new WPIColorImage(image.clone()); + } + } + } + + public WPIImage getNewImage(double timeout) throws BadConnectionException { + validateDisposed(); + + synchronized (imageLock) { + readImage = true; + while (readImage && !badConnection) { + try { + badConnection = true; + imageLock.wait((long) (timeout * 1000)); + } catch (InterruptedException ex) { + } + } + readImage = true; + + if (badConnection) { + throw new BadConnectionException(); + } else if (image.nChannels() == 1) { + return new WPIGrayscaleImage(image.clone()); + } else { + assert image.nChannels() == 3; + return new WPIColorImage(image.clone()); + } + } + } + + public WPIImage getNewImage() throws BadConnectionException { + return getNewImage(0); + } + + @Override + protected void disposed() { + try { + synchronized (imageLock) { + if (!isNull(image)) { + image.release(); + } + image = null; + } + } catch (Exception ex) { + } + } + + /** + * An exception that occurs when the camera can not be reached. + * @author Greg Granito + */ + public static class BadConnectionException extends Exception { + } + + @Override + protected void finalize() throws Throwable { + grabber.stop(); + super.finalize(); + } +} diff --git a/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIGrayscaleImage.java b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIGrayscaleImage.java new file mode 100644 index 0000000..facbb09 --- /dev/null +++ b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIGrayscaleImage.java @@ -0,0 +1,53 @@ +/* + * To change this template, choose Tools | Templates + * getAnd open the template in the editor. + */ +package edu.wpi.first.wpijavacv; + +import static com.googlecode.javacv.cpp.opencv_core.*; +import static com.googlecode.javacv.cpp.opencv_imgproc.*; + +/** + * A grayscale image + * + * @author Greg Granito + */ +public class WPIGrayscaleImage extends WPIImage { + + WPIGrayscaleImage(IplImage imageSrc) { + super(imageSrc); + } + + /** + * Returns a black and white image where every pixel that is higher (in the 0-255 scale) than the given threshold is <bold>white</bold>, + * and everything below is <bold>black</bold>. + * @param threshold a value 0-255. if a pixel has a value below the theshold, it becomes black + * if the pixel value is above or equal to the threshold, the pixel becomes white + * @return a new {@link WPIBinaryImage} that represents the threshold + */ + public WPIBinaryImage getThreshold(int threshold) { + validateDisposed(); + + IplImage bin = IplImage.create(image.cvSize(), 8, 1); + cvThreshold(image, bin, threshold, 255, CV_THRESH_BINARY); + return new WPIBinaryImage(bin); + } + + /** + * Returns a black and white image where every pixel that is higher (in the 0-255 scale) than the given threshold is <bold>black</bold>, + * and everything below is <bold>white</bold>. + * + * In other words, this will return the inverted image of {@link WpiGrayscaleImage#getThreshold(int) getThreshold(...)} but is + * more efficient than calling {@link WpiGrayscaleImage#getThreshold(int) getThreshold(...)}.{@link WPIBinaryImage#getInverse() getInverse()} + * @param threshold a value 0-255. if a pixel has a value below the theshold, it becomes black + * if the pixel value is above or equal to the threshold, the pixel becomes white + * @return a new {@link WPIBinaryImage} that represents the threshold + */ + public WPIBinaryImage getThresholdInverted(int threshold) { + validateDisposed(); + + IplImage bin = IplImage.create(image.cvSize(), 8, 1); + cvThreshold(image, bin, threshold, 255, CV_THRESH_BINARY_INV); + return new WPIBinaryImage(bin); + } +} diff --git a/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIImage.java b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIImage.java new file mode 100644 index 0000000..609aaaa --- /dev/null +++ b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIImage.java @@ -0,0 +1,76 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package edu.wpi.first.wpijavacv; + +import static com.googlecode.javacv.cpp.opencv_core.*; +import java.awt.image.BufferedImage; + +/** + * This class is the superclass of all images. + * + * @author Greg Granito + */ +public class WPIImage extends WPIDisposable { + + /** The underlying {@link IplImage} */ + protected IplImage image; + + /** + * Instantiates a {@link WPIImage} from a {@link BufferedImage}. + * Useful for interacting with swing. + * This will not keep a reference to the given image, so any modification to this + * {@link WPIImage} will not change the given image. + * @param image the image to copy into a {@link WPIImage} + */ + public WPIImage(BufferedImage image) { + this(IplImage.createFrom(image)); + } + + /** + * Instantiates a {@link WPIImage} from the given {@link IplImage}. + * The resulting image will be directly wrapped around the given image, and so any modifications + * to the {@link WPIImage} will reflect on the given image. + * @param image the image to wrap + */ + public WPIImage(IplImage image) { + this.image = image; + } + + /** + * Returns the width of the image. + * @return the width in pixels of the image + */ + public int getWidth() { + validateDisposed(); + + return image.width(); + } + + /** + * Returns the height of the image. + * @return the height in pixels of the image + */ + public int getHeight() { + validateDisposed(); + + return image.height(); + } + + /** + * Copies this {@link WPIImage} into a {@link BufferedImage}. + * This method will always generate a new image. + * @return a copy of the image + */ + public BufferedImage getBufferedImage() { + validateDisposed(); + + return image.getBufferedImage(); + } + + @Override + protected void disposed() { + image.release(); + } +} diff --git a/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIJavaCVUtils.java b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIJavaCVUtils.java new file mode 100644 index 0000000..b31da02 --- /dev/null +++ b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIJavaCVUtils.java @@ -0,0 +1,41 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package edu.wpi.first.wpijavacv; + +import static com.googlecode.javacv.cpp.opencv_highgui.*; + +/** + * A class with utility functions. Use this instead of Thread.sleep() + * @author Greg Granito + */ +public class WPIJavaCVUtils { + + /** + * Waits for the specified key to be pressed + * @param key the key (case sensitive) + */ + public static void waitForKey(char key){ + while(cvWaitKey() != key); + } + + /** + * waits until any key has been pressed + * @return the key that was pressed + */ + public static char waitForAnyKey(){ + return (char)cvWaitKey(); + } + + /** + * Waits until the timeout or until a key is pressed + * @param key the case sensitive key + * @param timeoutMillis the timeout + * @return returns whether the key was pressed (false if the timeout occurs) + */ + public static boolean keyIsPressed(char key, int timeoutMillis){ + return cvWaitKey(timeoutMillis) == key; + } +} diff --git a/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPILaptopCamera.java b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPILaptopCamera.java new file mode 100644 index 0000000..1414b35 --- /dev/null +++ b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPILaptopCamera.java @@ -0,0 +1,28 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package edu.wpi.first.wpijavacv; + +import static com.googlecode.javacv.cpp.opencv_highgui.*; +/** + * A class used to gather images from cameras connected to the laptop + * @author Greg + */ +public class WPILaptopCamera extends WPIDisposable { + CvCapture cam; + + public WPILaptopCamera() { + cam = cvCreateCameraCapture(0); + } + + public WPIColorImage getCurrentFrame(){ + return new WPIColorImage(cvQueryFrame(cam)); + } + + @Override + protected void disposed() { + } + +} diff --git a/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIMemoryPool.java b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIMemoryPool.java new file mode 100644 index 0000000..1d9a3b4 --- /dev/null +++ b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIMemoryPool.java @@ -0,0 +1,34 @@ +package edu.wpi.first.wpijavacv; + +/** + * This class allows a bunch of disposable items to be put into a pool which will do something if all of them are disposed. + * Subclasses should override the dispose method to react to when everything in the pool is disposed. + * @author Joe Grinstead + */ +public abstract class WPIMemoryPool extends WPIDisposable { + + /** The number of elements remaining in the pool */ + private int remaining; + + /** + * Adds the given disposable item to the memory pool + * @param disposable the item + */ + public synchronized void addToPool(WPIDisposable disposable) { + validateDisposed(); + disposable.setPool(this); + remaining++; + } + + /** + * Removes the given disposable item from the memory pool + * @param disposable + */ + public synchronized void removeFromPool(WPIDisposable disposable) { + validateDisposed(); + disposable.setPool(null); + if (--remaining <= 0) { + dispose(); + } + } +} diff --git a/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIPoint.java b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIPoint.java new file mode 100644 index 0000000..a5a2016 --- /dev/null +++ b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIPoint.java @@ -0,0 +1,56 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package edu.wpi.first.wpijavacv; + +import com.googlecode.javacv.cpp.opencv_core.CvPoint; + +/** + * A class representing a point + * @author Greg Granito + */ +public class WPIPoint { + + CvPoint p; + /** + * Creates a new WpiPoint with the specified x and y coordinates + * @param x the x coord + * @param y the y coord + */ + public WPIPoint(int x, int y) { + p = new CvPoint(x, y); + } + + WPIPoint(CvPoint c) { + p = c; + } + + /** + * + * @return the x coord + */ + public int getX(){ + return p.x(); + } + + /** + * + * @return the y coord + */ + public int getY(){ + return p.y(); + } + + CvPoint getCvPoint(){ + return p; + } + + @Override + public String toString() { + return p.toString(); + } + + +} diff --git a/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIPolygon.java b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIPolygon.java new file mode 100644 index 0000000..a54a297 --- /dev/null +++ b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIPolygon.java @@ -0,0 +1,126 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package edu.wpi.first.wpijavacv; + +import static com.googlecode.javacv.cpp.opencv_imgproc.*; +import static com.googlecode.javacv.cpp.opencv_core.*; + +/** + * A class that represents a polygon, can be obtained from approxPolygon() + * in WpiContour + * @author Greg Granito + */ +public class WPIPolygon extends WPIDisposable { + + CvSeq polygon; + CvRect boundingRect; + + WPIPolygon(CvSeq data) { + polygon = data; + } + + CvSeq getCVSeq() { + return polygon; + } + + /** + * + * @return an array of WpiPoints of the vertices of the polygon + */ + public WPIPoint[] getPoints() { + CvPoint points = new CvPoint(getNumVertices()); + WPIPoint[] wpiPoints= new WPIPoint[getNumVertices()]; + cvCvtSeqToArray(polygon, points.position(0), CV_WHOLE_SEQ); + for (int j = 0; j < getNumVertices(); j++) { + + wpiPoints[j] = new WPIPoint(points.position(j).x(), points.position(j).y()); + + } + return wpiPoints; + } + + /** + * + * @return the width of the bounding rectangle of the polygon + */ + public int getWidth() { + if (boundingRect == null) { + boundingRect = cvBoundingRect(polygon, 0); + } + return boundingRect.width(); + } + + /** + * + * @return the height of the bounding rectangle of the polygon + */ + public int getHeight() { + if (boundingRect == null) { + boundingRect = cvBoundingRect(polygon, 0); + } + return boundingRect.height(); + } + + /** + * + * @return the x coord of the top left corner of the bounding + * rectangle of the polygon + */ + public int getX() { + if (boundingRect == null) { + boundingRect = cvBoundingRect(polygon, 0); + } + return boundingRect.x(); + } + + /** + * + * @return the y coord of the top left corner of the bounding + * rectangle of the polygon + */ + public int getY() { + if (boundingRect == null) { + boundingRect = cvBoundingRect(polygon, 0); + } + return boundingRect.y(); + } + + /** + * + * @return the number of vertices in the polygon + */ + public int getNumVertices() { + return polygon.total(); + + } + + /** + * + * @return whether or not the polygon is convex + */ + public boolean isConvex() { + return cvCheckContourConvexity(polygon) == 0 ? false : true; + } + + /** + * + * @return the area in pixels of the polygon + */ + public int getArea() { + return Math.abs((int) cvContourArea(polygon, CV_WHOLE_SEQ, -1)); + } + + /** + * + * @return the perimeter in pixels of the polygon + */ + public int getPerimeter() { + return (int) cvArcLength(polygon, CV_WHOLE_SEQ, -1); + } + + public void disposed() { + polygon.deallocate(); + } +} diff --git a/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIWindow.java b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIWindow.java new file mode 100644 index 0000000..f15db45 --- /dev/null +++ b/WPIJavaCV/src/edu/wpi/first/wpijavacv/WPIWindow.java @@ -0,0 +1,52 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package edu.wpi.first.wpijavacv; + +import static com.googlecode.javacv.cpp.opencv_highgui.*; + +/** + * A class that can be used for displaying images + * @author Greg Granito + */ +public class WPIWindow { + + private static int count = 0; + + private String name; + + /** + * Creates a new window with a default name that will be in the format + * "Window " + windowNumber + */ + public WPIWindow() { + count++; + + name = "Window " +count; + cvNamedWindow(name); + + } + + /** + * Creates a new window with the specified name must be unique + * (including the windows named using the default constructor) + * @param name the desired name + */ + public WPIWindow(String name) { + this.name = name; + } + + + /** + * Shows the specified image, must be the same resolution and depth + * as the first image the window shows + * @param image the image + */ + public void showImage(WPIImage image) { + + if(image != null) + cvShowImage(name, image.image); + else cvShowImage(name, null); + } +} |