/** * OpenCV for Processing * Computer vision with OpenCV. * https://github.com/atduskgreg/opencv-processing * * Copyright (c) 2013 Greg Borenstein http://gregborenstein.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, MA 02111-1307 USA * * @author Greg Borenstein http://gregborenstein.com * @modified 12/08/2014 * @version 0.5.2 (13) */ package gab.opencv; import gab.opencv.Contour; import gab.opencv.ContourComparator; import gab.opencv.Histogram; import gab.opencv.Line; import gab.opencv.Flow; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.awt.image.DataBufferInt; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import java.io.File; import java.lang.reflect.Field; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import org.opencv.core.Core; import org.opencv.core.CvType; import org.opencv.core.Mat; import org.opencv.core.MatOfRect; import org.opencv.core.MatOfPoint; import org.opencv.core.MatOfPoint2f; import org.opencv.core.MatOfInt; import org.opencv.core.MatOfFloat; import org.opencv.core.Rect; import org.opencv.core.Scalar; import org.opencv.core.Size; import org.opencv.core.Point; import org.opencv.calib3d.Calib3d; import org.opencv.core.CvException; import org.opencv.core.Core.MinMaxLocResult; import org.opencv.video.BackgroundSubtractorMOG; import org.opencv.objdetect.CascadeClassifier; import org.opencv.imgproc.Imgproc; import processing.core.*; /** * This is a template class and can be used to start a new processing library or tool. * Make sure you rename this class as well as the name of the example package 'template' * to your own library or tool naming convention. * * @example Hello * * (the tag @example followed by the name of an example included in folder 'examples' will * automatically include the example in the javadoc.) * */ public class OpenCV { PApplet parent; public int width; public int height; private int roiWidth; private int roiHeight; public Mat matBGRA; public Mat matR, matG, matB, matA; public Mat matHSV; public Mat matH, matS, matV; public Mat matGray; public Mat matROI; public Mat nonROImat; // so that releaseROI() can return to color/gray as appropriate private boolean useColor; private boolean useROI; public int colorSpace; private PImage outputImage; private PImage inputImage; private boolean nativeLoaded; private boolean isArm = false; public CascadeClassifier classifier; BackgroundSubtractorMOG backgroundSubtractor; public Flow flow; public final static String VERSION = "0.5.2"; public final static String CASCADE_FRONTALFACE = "haarcascade_frontalface_alt.xml"; public final static String CASCADE_PEDESTRIANS = "hogcascade_pedestrians.xml"; public final static String CASCADE_EYE = "haarcascade_eye.xml"; public final static String CASCADE_CLOCK = "haarcascade_clock.xml"; public final static String CASCADE_NOSE = "haarcascade_mcs_nose.xml"; public final static String CASCADE_MOUTH = "haarcascade_mcs_mouth.xml"; public final static String CASCADE_UPPERBODY = "haarcascade_upperbody.xml"; public final static String CASCADE_LOWERBODY = "haarcascade_lowerbody.xml"; public final static String CASCADE_FULLBODY = "haarcascade_fullbody.xml"; public final static String CASCADE_PEDESTRIAN = "hogcascade_pedestrians.xml"; public final static String CASCADE_RIGHT_EAR = "haarcascade_mcs_rightear.xml"; public final static String CASCADE_PROFILEFACE = "haarcascade_profileface.xml"; // used for both Scharr edge detection orientation // and flip(). Values are set for flip, arbitrary from POV of Scharr public final static int HORIZONTAL = 1; public final static int VERTICAL = 0; public final static int BOTH = -1; /** * Initialize OpenCV with the path to an image. * The image will be loaded and prepared for processing. * * @param theParent - A PApplet representing the user sketch, i.e "this" * @param pathToImg - A String with a path to the image to be loaded */ public OpenCV(PApplet theParent, String pathToImg){ initNative(); useColor = false; loadFromString(theParent, pathToImg); } /** * Initialize OpenCV with the path to an image. * The image will be loaded and prepared for processing. * * @param theParent - A PApplet representing the user sketch, i.e "this" * @param pathToImg - A String with a path to the image to be loaded * @param useColor - (Optional) Set to true if you want to use the color version of the image for processing. */ public OpenCV(PApplet theParent, String pathToImg, boolean useColor){ initNative(); this.useColor = useColor; if(useColor){ useColor(); // have to set the color space. } loadFromString(theParent, pathToImg); } private void loadFromString(PApplet theParent, String pathToImg){ parent = theParent; PImage imageToLoad = parent.loadImage(pathToImg); init(imageToLoad.width, imageToLoad.height); loadImage(imageToLoad); } /** * Initialize OpenCV with an image. * The image's pixels will be copied and prepared for processing. * * @param theParent * A PApplet representing the user sketch, i.e "this" * @param img * A PImage to be loaded */ public OpenCV(PApplet theParent, PImage img){ initNative(); useColor = false; loadFromPImage(theParent, img); } /** * Initialize OpenCV with an image. * The image's pixels will be copiedd and prepared for processing. * * @param theParent * A PApplet representing the user sketch, i.e "this" * @param img * A PImage to be loaded * @param useColor * (Optional) Set to true if you want to use the color version of the image for processing. */ public OpenCV(PApplet theParent, PImage img, boolean useColor){ initNative(); this.useColor = useColor; if(useColor){ useColor(); } loadFromPImage(theParent, img); } private void loadFromPImage(PApplet theParent, PImage img){ parent = theParent; init(img.width, img.height); loadImage(img); } /** * * Apply subsequent image processing to * the color version of the loaded image. * * Note: Many OpenCV functions require a grayscale * image. Those functions will raise an exception * if attempted on a color image. * */ public void useColor(){ useColor(PApplet.RGB); } /** * * Get the colorSpace of the current color image. Will be either RGB or HSB. * * @return * * The color space of the color mats. Either PApplet.RGB or PApplet.HSB */ public int getColorSpace(){ return colorSpace; } /** * * Set the main working image to be the color version of the imported image. * Subsequent image-processing functions will be applied to the color version * of the image. Image is assumed to be HSB or RGB based on the argument * * * @param colorSpace * The color space of the image to be processed. Either RGB or HSB. */ public void useColor(int colorSpace){ useColor = true; if(colorSpace != PApplet.RGB && colorSpace != PApplet.HSB){ PApplet.println("ERROR: color space must be either RGB or HSB"); } else { this.colorSpace = colorSpace; } if(this.colorSpace == PApplet.HSB){ populateHSV(); } } private void populateHSV(){ matHSV = imitate(matBGRA); Imgproc.cvtColor(matBGRA, matHSV, Imgproc.COLOR_BGR2HSV); ArrayList channels = new ArrayList(); Core.split(matHSV, channels); matH = channels.get(0); matS = channels.get(1); matV = channels.get(2); } private void populateBGRA(){ ArrayList channels = new ArrayList(); Core.split(matBGRA, channels); matB = channels.get(0); matG = channels.get(1); matR = channels.get(2); matA = channels.get(3); } /** * * Set OpenCV to do image processing on the grayscale version * of the loaded image. * */ public void useGray(){ useColor = false; } /** * * Checks whether OpenCV is currently using the color version of the image * or the grayscale version. * * @return * True if OpenCV is currently using the color version of the image. */ public boolean getUseColor(){ return useColor; } private Mat getCurrentMat(){ if(useROI){ return matROI; } else{ if(useColor){ return matBGRA; } else{ return matGray; } } } /** * Initialize OpenCV with a width and height. * You will need to load an image in before processing. * See copy(PImage img). * * @param theParent * A PApplet representing the user sketch, i.e "this" * @param width * int * @param height * int */ public OpenCV(PApplet theParent, int width, int height) { initNative(); parent = theParent; init(width, height); } private void init(int w, int h){ width = w; height = h; welcome(); setupWorkingImages(); setupFlow(); matR = new Mat(height, width, CvType.CV_8UC1); matG = new Mat(height, width, CvType.CV_8UC1); matB = new Mat(height, width, CvType.CV_8UC1); matA = new Mat(height, width, CvType.CV_8UC1); matGray = new Mat(height, width, CvType.CV_8UC1); matBGRA = new Mat(height, width, CvType.CV_8UC4); } private void setupFlow(){ flow = new Flow(parent); } private void setupWorkingImages(){ outputImage = parent.createImage(width,height, PConstants.ARGB); } private String getLibPath() { URL url = this.getClass().getResource("OpenCV.class"); if (url != null) { // Convert URL to string, taking care of spaces represented by the "%20" // string. String path = url.toString().replace("%20", " "); int n0 = path.indexOf('/'); int n1 = -1; n1 = path.indexOf("opencv_processing.jar"); if (PApplet.platform == PConstants.WINDOWS) { //platform Windows // In Windows, path string starts with "jar file/C:/..." // so the substring up to the first / is removed. n0++; } if ((-1 < n0) && (-1 < n1)) { return path.substring(n0, n1); } else { return ""; } } return ""; } private void initNative(){ if(!nativeLoaded){ int bitsJVM = PApplet.parseInt(System.getProperty("sun.arch.data.model")); String osArch = System.getProperty("os.arch"); String nativeLibPath = getLibPath(); String path = null; // determine the path to the platform-specific opencv libs if (PApplet.platform == PConstants.WINDOWS) { //platform Windows path = nativeLibPath + "windows" + bitsJVM; } if (PApplet.platform == PConstants.MACOSX) { //platform Mac path = nativeLibPath + "macosx" + bitsJVM; } if (PApplet.platform == PConstants.LINUX) { //platform Linux // attempt to detect arm architecture - is it fair to assume linux for ARM devices? isArm = osArch.contains("arm"); path = isArm ? nativeLibPath + "arm7" : nativeLibPath + "linux" + bitsJVM; } // ensure the determined path exists try { File libDir = new File(path); if (libDir.exists()) { nativeLibPath = path; } } catch (NullPointerException e) { // platform couldn't be determined System.err.println("Cannot load local version of opencv_java245 : Linux 32/64, arm7, Windows 32 bits or Mac Os 64 bits are only avaible"); e.printStackTrace(); } // this check might be redundant now... if((PApplet.platform == PConstants.MACOSX && bitsJVM == 64) || (PApplet.platform == PConstants.WINDOWS) || (PApplet.platform == PConstants.LINUX)){ try { addLibraryPath(nativeLibPath); } catch (Exception e) { e.printStackTrace(); } System.loadLibrary("opencv_java245"); } else{ System.err.println("Cannot load local version of opencv_java245 : Linux 32/64, Windows 32 bits or Mac Os 64 bits are only avaible"); } nativeLoaded = true; } } private void addLibraryPath(String path) throws Exception { String originalPath = System.getProperty("java.library.path"); // If this is an arm device running linux, Processing seems to include the linux32 dirs in the path, // which conflict with the arm-specific libs. To fix this, we remove the linux32 segments from the path. // // Alternatively, we could do one of the following: // A) prepend to the path instead of append, forcing our libs to be used // B) rename the libopencv_java245 in the arm7 dir and add logic to load it instead above in System.loadLibrary(...) if (isArm) { if (originalPath.indexOf("linux32") != -1) { originalPath = originalPath.replaceAll(":[^:]*?linux32", ""); } } System.setProperty("java.library.path", originalPath +System.getProperty("path.separator")+ path); //set sys_paths to null final Field sysPathsField = ClassLoader.class.getDeclaredField("sys_paths"); sysPathsField.setAccessible(true); sysPathsField.set(null, null); } /** * Load a cascade file for face or object detection. * Expects one of: * *
	 * OpenCV.CASCADE_FRONTALFACE
	 * OpenCV.CASCADE_PEDESTRIANS
	 * OpenCV.CASCADE_EYE			
	 * OpenCV.CASCADE_CLOCK		
	 * OpenCV.CASCADE_NOSE 		
	 * OpenCV.CASCADE_MOUTH		
	 * OpenCV.CASCADE_UPPERBODY 	
	 * OpenCV.CASCADE_LOWERBODY 	
	 * OpenCV.CASCADE_FULLBODY 	
	 * OpenCV.CASCADE_PEDESTRIANS
	 * OpenCV.CASCADE_RIGHT_EAR 	
	 * OpenCV.CASCADE_PROFILEFACE
	 * 
* * To pass your own cascade file, provide an absolute path and a second * argument of true, thusly: * *
	 * opencv.loadCascade("/path/to/my/custom/cascade.xml", true)
	 * 
* * (NB: ant build scripts copy the data folder outside of the * jar so that this will work.) * * @param cascadeFileName * The name of the cascade file to be loaded form within OpenCV for Processing. * Must be one of the constants provided by this library */ public void loadCascade(String cascadeFileName){ // localize path to cascade file to point at the library's data folder String relativePath = "cascade-files/" + cascadeFileName; String cascadePath = getLibPath(); cascadePath += relativePath; PApplet.println("Load cascade from: " + cascadePath); classifier = new CascadeClassifier(cascadePath); if(classifier.empty()){ PApplet.println("Cascade failed to load"); // raise exception here? } else { PApplet.println("Cascade loaded: " + cascadeFileName); } } /** * Load a cascade file for face or object detection. * If absolute is true, cascadeFilePath must be an * absolute path to a cascade xml file. If it is false * then cascadeFilePath must be one of the options provided * by OpenCV for Processing as in the single-argument * version of this function. * * @param cascadeFilePath * A string. Either an absolute path to a cascade XML file or * one of the constants provided by this library. * @param absolute * Whether or not the cascadeFilePath is an absolute path to an XML file. */ public void loadCascade(String cascadeFilePath, boolean absolute){ if(absolute){ classifier = new CascadeClassifier(cascadeFilePath); if(classifier.empty()){ PApplet.println("Cascade failed to load"); // raise exception here? } else { PApplet.println("Cascade loaded from absolute path: " + cascadeFilePath); } } else { loadCascade(cascadeFilePath); } } /** * Convert an array of OpenCV Rect objects into * an array of java.awt.Rectangle rectangles. * Especially useful when working with * classifier.detectMultiScale(). * * @param Rect[] rects * * @return * A Rectangle[] of java.awt.Rectangle */ public static Rectangle[] toProcessing(Rect[] rects){ Rectangle[] results = new Rectangle[rects.length]; for(int i = 0; i < rects.length; i++){ results[i] = new Rectangle(rects[i].x, rects[i].y, rects[i].width, rects[i].height); } return results; } /** * Detect objects using the cascade classifier. loadCascade() must already * have been called to setup the classifier. See the OpenCV documentation * for details on the arguments: http://docs.opencv.org/java/org/opencv/objdetect/CascadeClassifier.html#detectMultiScale(org.opencv.core.Mat, org.opencv.core.MatOfRect, double, int, int, org.opencv.core.Size, org.opencv.core.Size) * * A simpler version of detect() that doesn't need these arguments is also available. * * @param scaleFactor * @param minNeighbors * @param flags * @param minSize * @param maxSize * @return * An array of java.awt.Rectangle objects with the location, width, and height of each detected object. */ public Rectangle[] detect(double scaleFactor , int minNeighbors , int flags, int minSize , int maxSize){ Size minS = new Size(minSize, minSize); Size maxS = new Size(maxSize, maxSize); MatOfRect detections = new MatOfRect(); classifier.detectMultiScale(getCurrentMat(), detections, scaleFactor, minNeighbors, flags, minS, maxS ); return OpenCV.toProcessing(detections.toArray()); } /** * Detect objects using the cascade classifier. loadCascade() must already * have been called to setup the classifier. * * @return * An array of java.awt.Rectnangle objects with the location, width, and height of each detected object. */ public Rectangle[] detect(){ MatOfRect detections = new MatOfRect(); classifier.detectMultiScale(getCurrentMat(), detections); return OpenCV.toProcessing(detections.toArray()); } /** * Setup background subtraction. After calling this function, * updateBackground() must be called with each new frame * you want to add to the running background subtraction calculation. * * For details on the arguments, see: * http://docs.opencv.org/java/org/opencv/video/BackgroundSubtractorMOG.html#BackgroundSubtractorMOG(int, int, double) * * @param history * @param nMixtures * @param backgroundRatio */ public void startBackgroundSubtraction(int history, int nMixtures, double backgroundRatio){ backgroundSubtractor = new BackgroundSubtractorMOG(history, nMixtures, backgroundRatio); } /** * Update the running background for background subtraction based on * the current image loaded into OpenCV. startBackgroundSubtraction() * must have been called before this to setup the background subtractor. * */ public void updateBackground(){ Mat foreground = imitate(getCurrentMat()); backgroundSubtractor.apply(getCurrentMat(), foreground, 0.05); setGray(foreground); } /** * Calculate the optical flow of the current image relative * to a running series of images (typically frames from video). * Optical flow is useful for detecting what parts of the image * are moving and in what direction. * */ public void calculateOpticalFlow(){ flow.calculateOpticalFlow(getCurrentMat()); } /* * Get the total optical flow within a region of the image. * Be sure to call calculateOpticalFlow() first. * */ public PVector getTotalFlowInRegion(int x, int y, int w, int h) { return flow.getTotalFlowInRegion(x, y, w, h); } /* * Get the average optical flow within a region of the image. * Be sure to call calculateOpticalFlow() first. * */ public PVector getAverageFlowInRegion(int x, int y, int w, int h) { return flow.getAverageFlowInRegion(x,y,w,h); } /* * Get the total optical flow for the entire image. * Be sure to call calculateOpticalFlow() first. */ public PVector getTotalFlow() { return flow.getTotalFlow(); } /* * Get the average optical flow for the entire image. * Be sure to call calculateOpticalFlow() first. */ public PVector getAverageFlow() { return flow.getAverageFlow(); } /* * Get the optical flow at a single point in the image. * Be sure to call calcuateOpticalFlow() first. */ public PVector getFlowAt(int x, int y){ return flow.getFlowAt(x,y); } /* * Draw the optical flow. * Be sure to call calcuateOpticalFlow() first. */ public void drawOpticalFlow(){ flow.draw(); } /** * Flip the current image. * * @param direction * One of: OpenCV.HORIZONTAL, OpenCV.VERTICAL, or OpenCV.BOTH */ public void flip(int direction){ Core.flip(getCurrentMat(), getCurrentMat(), direction); } /** * * Adjust the contrast of the image. Works on color or black and white images. * * @param amt * Amount of contrast to apply. 0-1.0 reduces contrast. Above 1.0 increases contrast. * **/ public void contrast(float amt){ Scalar modifier; if(useColor){ modifier = new Scalar(amt,amt,amt,1); } else{ modifier = new Scalar(amt); } Core.multiply(getCurrentMat(), modifier, getCurrentMat()); } /** * Get the x-y location of the maximum value in the current image. * * @return * A PVector with the location of the maximum value. */ public PVector max(){ MinMaxLocResult r = Core.minMaxLoc(getCurrentMat()); return OpenCV.pointToPVector(r.maxLoc); } /** * Get the x-y location of the minimum value in the current image. * * @return * A PVector with the location of the minimum value. */ public PVector min(){ MinMaxLocResult r = Core.minMaxLoc(getCurrentMat()); return OpenCV.pointToPVector(r.minLoc); } /** * Helper function to convert an OpenCV Point into a Processing PVector * * @param p * A Point * @return * A PVector */ public static PVector pointToPVector(Point p){ return new PVector((float)p.x, (float)p.y); } /** * Adjust the brightness of the image. Works on color or black and white images. * * @param amt * The amount to brighten the image. Ranges -255 to 255. * **/ public void brightness(int amt){ Scalar modifier; if(useColor){ modifier = new Scalar(amt,amt,amt, 1); } else{ modifier = new Scalar(amt); } Core.add(getCurrentMat(), modifier, getCurrentMat()); } /** * Helper to create a new OpenCV Mat whose channels and * bit-depth mask an existing Mat. * * @param m * The Mat to match * @return * A new Mat */ public static Mat imitate(Mat m){ return new Mat(m.height(), m.width(), m.type()); } /** * Calculate the difference between the current image * loaded into OpenCV and a second image. The result is stored * in the loaded image in OpenCV. Works on both color and grayscale * images. * * @param img * A PImage to diff against. */ public void diff(PImage img){ Mat imgMat = imitate(getColor()); toCv(img, imgMat); Mat dst = imitate(getCurrentMat()); if(useColor){ ARGBtoBGRA(imgMat, imgMat); Core.absdiff(getCurrentMat(), imgMat, dst); } else { Core.absdiff(getCurrentMat(), OpenCV.gray(imgMat), dst); } dst.assignTo(getCurrentMat()); } /** * A helper function that diffs two Mats using absdiff. * Places the result back into mat1 * * @param mat1 * The destination Mat * @param mat2 * The Mat to diff against */ public static void diff(Mat mat1, Mat mat2){ Mat dst = imitate(mat1); Core.absdiff(mat1, mat2, dst); dst.assignTo(mat1); } /** * Apply a global threshold to an image. Produces a binary image * with white pixels where the original image was above the threshold * and black where it was below. * * @param threshold * An int from 0-255. */ public void threshold(int threshold){ Imgproc.threshold(getCurrentMat(), getCurrentMat(), threshold, 255, Imgproc.THRESH_BINARY); } /** * Apply an adaptive threshold to an image. Produces a binary image * with white pixels where the original image was above the threshold * and black where it was below. * * See: * http://docs.opencv.org/java/org/opencv/imgproc/Imgproc.html#adaptiveThreshold(org.opencv.core.Mat, org.opencv.core.Mat, double, int, int, int, double) * * @param blockSize * The size of the pixel neighborhood to use. * @param c * A constant subtracted from the mean of each neighborhood. */ public void adaptiveThreshold(int blockSize, int c){ try{ Imgproc.adaptiveThreshold(getCurrentMat(), getCurrentMat(), 255, Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C, Imgproc.THRESH_BINARY, blockSize, c); } catch(CvException e){ PApplet.println("ERROR: adaptiveThreshold function only works on gray images."); } } /** * Normalize the histogram of the image. This will spread the image's color * spectrum over the full 0-255 range. Only works on grayscale images. * * * See: http://docs.opencv.org/java/org/opencv/imgproc/Imgproc.html#equalizeHist(org.opencv.core.Mat, org.opencv.core.Mat) * */ public void equalizeHistogram(){ try{ Imgproc.equalizeHist(getCurrentMat(), getCurrentMat()); } catch(CvException e){ PApplet.println("ERROR: equalizeHistogram only works on a gray image."); } } /** * Invert the image. * See: http://docs.opencv.org/java/org/opencv/core/Core.html#bitwise_not(org.opencv.core.Mat, org.opencv.core.Mat) * */ public void invert(){ Core.bitwise_not(getCurrentMat(),getCurrentMat()); } /** * Dilate the image. Dilation is a morphological operation (i.e. it affects the shape) often used to * close holes in contours. It expands white areas of the image. * * See: * http://docs.opencv.org/java/org/opencv/imgproc/Imgproc.html#dilate(org.opencv.core.Mat, org.opencv.core.Mat, org.opencv.core.Mat) * */ public void dilate(){ Imgproc.dilate(getCurrentMat(), getCurrentMat(), new Mat()); } /** * Erode the image. Erosion is a morphological operation (i.e. it affects the shape) often used to * close holes in contours. It contracts white areas of the image. * * See: * http://docs.opencv.org/java/org/opencv/imgproc/Imgproc.html#erode(org.opencv.core.Mat, org.opencv.core.Mat, org.opencv.core.Mat) * */ public void erode(){ Imgproc.erode(getCurrentMat(), getCurrentMat(), new Mat()); } /** * Blur an image symetrically by a given number of pixels. * * @param blurSize * int - the amount to blur by in x- and y-directions. */ public void blur(int blurSize){ Imgproc.blur(getCurrentMat(), getCurrentMat(), new Size(blurSize, blurSize)); } /** * Blur an image assymetrically by a different number of pixels in x- and y-directions. * * @param blurW * amount to blur in the x-direction * @param blurH * amount to blur in the y-direction */ public void blur(int blurW, int blurH){ Imgproc.blur(getCurrentMat(), getCurrentMat(), new Size(blurW, blurH)); } /** * Find edges in the image using Canny edge detection. * * @param lowThreshold * @param highThreshold */ public void findCannyEdges(int lowThreshold, int highThreshold){ Imgproc.Canny(getCurrentMat(), getCurrentMat(), lowThreshold, highThreshold); } public void findSobelEdges(int dx, int dy){ Mat sobeled = new Mat(getCurrentMat().height(), getCurrentMat().width(), CvType.CV_32F); Imgproc.Sobel(getCurrentMat(), sobeled, CvType.CV_32F, dx, dy); sobeled.convertTo(getCurrentMat(), getCurrentMat().type()); } public void findScharrEdges(int direction){ if(direction == HORIZONTAL){ Imgproc.Scharr(getCurrentMat(), getCurrentMat(), -1, 1, 0 ); } if(direction == VERTICAL){ Imgproc.Scharr(getCurrentMat(), getCurrentMat(), -1, 0, 1 ); } if(direction == BOTH){ Mat hMat = imitate(getCurrentMat()); Mat vMat = imitate(getCurrentMat()); Imgproc.Scharr(getCurrentMat(), hMat, -1, 1, 0 ); Imgproc.Scharr(getCurrentMat(), vMat, -1, 0, 1 ); Core.add(vMat,hMat, getCurrentMat()); } } public ArrayList findContours(){ return findContours(true, false); } public ArrayList findContours(boolean findHoles, boolean sort){ ArrayList result = new ArrayList(); ArrayList contourMat = new ArrayList(); try{ int contourFindingMode = (findHoles ? Imgproc.RETR_LIST : Imgproc.RETR_EXTERNAL); Imgproc.findContours(getCurrentMat(), contourMat, new Mat(), contourFindingMode, Imgproc.CHAIN_APPROX_NONE); } catch(CvException e){ PApplet.println("ERROR: findContours only works with a gray image."); } for (MatOfPoint c : contourMat) { result.add(new Contour(parent, c)); } if(sort){ Collections.sort(result, new ContourComparator()); } return result; } public ArrayList findLines(int threshold, double minLineLength, double maxLineGap){ ArrayList result = new ArrayList(); Mat lineMat = new Mat(); Imgproc.HoughLinesP(getCurrentMat(), lineMat, 1, PConstants.PI/180.0, threshold, minLineLength, maxLineGap); for (int i = 0; i < lineMat.width(); i++) { double[] coords = lineMat.get(0, i); result.add(new Line(coords[0], coords[1], coords[2], coords[3])); } return result; } public ArrayList findChessboardCorners(int patternWidth, int patternHeight){ MatOfPoint2f corners = new MatOfPoint2f(); Calib3d.findChessboardCorners(getCurrentMat(), new Size(patternWidth,patternHeight), corners); return matToPVectors(corners); } /** * * @param mat * The mat from which to calculate the histogram. Get this from getGray(), getR(), getG(), getB(), etc.. * By default this will normalize the histogram (scale the values to 0.0-1.0). Pass false as the third argument to keep values unormalized. * @param numBins * The number of bins into which divide the histogram should be divided. * @param normalize (optional) * Whether or not to normalize the histogram (scale the values to 0.0-1.0). Defaults to true. * @return * A Histogram object that you can call draw() on. */ public Histogram findHistogram(Mat mat, int numBins){ return findHistogram(mat, numBins, true); } public Histogram findHistogram(Mat mat, int numBins, boolean normalize){ MatOfInt channels = new MatOfInt(0); MatOfInt histSize = new MatOfInt(numBins); float[] r = {0f, 256f}; MatOfFloat ranges = new MatOfFloat(r); Mat hist = new Mat(); ArrayList images = new ArrayList(); images.add(mat); Imgproc.calcHist( images, channels, new Mat(), hist, histSize, ranges); if(normalize){ Core.normalize(hist, hist); } return new Histogram(parent, hist); } /** * * Filter the image for values between a lower and upper bound. * Converts the current image into a binary image with white where pixel * values were within bounds and black elsewhere. * * @param lowerBound * @param upperBound */ public void inRange(int lowerBound, int upperBound){ Core.inRange(getCurrentMat(), new Scalar(lowerBound), new Scalar(upperBound), getCurrentMat()); } /** * * @param src * A Mat of type 8UC4 with channels arranged as BGRA. * @return * A Mat of type 8UC1 in grayscale. */ public static Mat gray(Mat src){ Mat result = new Mat(src.height(), src.width(), CvType.CV_8UC1); Imgproc.cvtColor(src, result, Imgproc.COLOR_BGRA2GRAY); return result; } public void gray(){ matGray = gray(matBGRA); useGray(); //??? } /** * Set a Region of Interest within the image. Subsequent image processing * functions will apply to this ROI rather than the full image. * Full image will display be included in output. * * @return * False if requested ROI exceed the bounds of the working image. * True if ROI was successfully set. */ public boolean setROI(int x, int y, int w, int h){ if(x < 0 || x + w > width || y < 0 || y + h > height){ return false; } else{ roiWidth = w; roiHeight = h; if(useColor){ nonROImat = matBGRA; matROI = new Mat(matBGRA, new Rect(x, y, w, h)); } else { nonROImat = matGray; matROI = new Mat(matGray, new Rect(x, y, w, h)); } useROI = true; return true; } } public void releaseROI(){ useROI = false; } /** * Load an image from a path. * * @param imgPath * String with the path to the image */ public void loadImage(String imgPath){ loadImage(parent.loadImage(imgPath)); } // NOTE: We're not handling the signed/unsigned // conversion. Is that any issue? public void loadImage(PImage img){ // FIXME: is there a better way to hold onto // this? inputImage = img; toCv(img, matBGRA); ARGBtoBGRA(matBGRA,matBGRA); populateBGRA(); if(useColor){ useColor(this.colorSpace); } else { gray(); } } public static void ARGBtoBGRA(Mat rgba, Mat bgra){ ArrayList channels = new ArrayList(); Core.split(rgba, channels); ArrayList reordered = new ArrayList(); // Starts as ARGB. // Make into BGRA. reordered.add(channels.get(3)); reordered.add(channels.get(2)); reordered.add(channels.get(1)); reordered.add(channels.get(0)); Core.merge(reordered, bgra); } public int getSize(){ return width * height; } /** * * Convert a 4 channel OpenCV Mat object into * pixels to be shoved into a 4 channel ARGB PImage's * pixel array. * * @param m * An RGBA Mat we want converted * @return * An int[] formatted to be the pixels of a PImage */ public int[] matToARGBPixels(Mat m){ int pImageChannels = 4; int numPixels = m.width()*m.height(); int[] intPixels = new int[numPixels]; byte[] matPixels = new byte[numPixels*pImageChannels]; m.get(0,0, matPixels); ByteBuffer.wrap(matPixels).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get(intPixels); return intPixels; } /** * Convert an OpenCV Mat object into a PImage * to be used in other Processing code. * Copies the Mat's pixel data into the PImage's pixel array. * Iterates over each pixel in the Mat, i.e. expensive. * * (Mainly used internally by OpenCV. Inspired by toCv() * from KyleMcDonald's ofxCv.) * * @param m * A Mat you want converted * @param img * The PImage you want the Mat converted into. */ public void toPImage(Mat m, PImage img){ img.loadPixels(); if(m.channels() == 3){ Mat m2 = new Mat(); Imgproc.cvtColor(m, m2, Imgproc.COLOR_RGB2RGBA); img.pixels = matToARGBPixels(m2); } else if(m.channels() == 1){ Mat m2 = new Mat(); Imgproc.cvtColor(m, m2, Imgproc.COLOR_GRAY2RGBA); img.pixels = matToARGBPixels(m2); } else if(m.channels() == 4){ img.pixels = matToARGBPixels(m); } img.updatePixels(); } /** * Convert a Processing PImage to an OpenCV Mat. * (Inspired by Kyle McDonald's ofxCv's toOf()) * * @param img * The PImage to convert. * @param m * The Mat to receive the image data. */ public static void toCv(PImage img, Mat m){ BufferedImage image = (BufferedImage)img.getNative(); int[] matPixels = ((DataBufferInt)image.getRaster().getDataBuffer()).getData(); ByteBuffer bb = ByteBuffer.allocate(matPixels.length * 4); IntBuffer ib = bb.asIntBuffer(); ib.put(matPixels); byte[] bvals = bb.array(); m.put(0,0, bvals); } public static ArrayList matToPVectors(MatOfPoint mat){ ArrayList result = new ArrayList(); Point[] points = mat.toArray(); for(int i = 0; i < points.length; i++){ result.add(new PVector((float)points[i].x, (float)points[i].y)); } return result; } public static ArrayList matToPVectors(MatOfPoint2f mat){ ArrayList result = new ArrayList(); Point[] points = mat.toArray(); for(int i = 0; i < points.length; i++){ result.add(new PVector((float)points[i].x, (float)points[i].y)); } return result; } public String matToS(Mat mat){ return CvType.typeToString(mat.type()); } public PImage getInput(){ return inputImage; } public PImage getOutput(){ if(useColor){ toPImage(matBGRA, outputImage); } else { toPImage(matGray, outputImage); } return outputImage; } public PImage getSnapshot(){ PImage result; if(useColor){ if(colorSpace == PApplet.HSB){ result = getSnapshot(matHSV); } else { result = getSnapshot(matBGRA); } } else { result = getSnapshot(matGray); } return result; } public PImage getSnapshot(Mat m){ PImage result = parent.createImage(m.width(), m.height(), PApplet.ARGB); toPImage(m, result); return result; } public Mat getR(){ return matR; } public Mat getG(){ return matG; } public Mat getB(){ return matB; } public Mat getA(){ return matA; } public Mat getH(){ return matH; } public Mat getS(){ return matS; } public Mat getV(){ return matV; } public Mat getGray(){ return matGray; } public void setGray(Mat m){ matGray = m; useColor = false; } public void setColor(Mat m){ matBGRA = m; useColor = true; } public Mat getColor(){ return matBGRA; } public Mat getROI(){ return matROI; } private void welcome() { System.out.println("OpenCV for Processing 0.5.2 by Greg Borenstein http://gregborenstein.com"); System.out.println("Using Java OpenCV " + Core.VERSION); } /** * return the version of the library. * * @return String */ public static String version() { return VERSION; } }