diff --git a/README.md b/README.md index c64b85f..ddcacf8 100644 --- a/README.md +++ b/README.md @@ -17,20 +17,6 @@ Download the jar file from the releases page (https://github.com/kylecorry31/Rob Then add both files to your Eclipse buildpath. -### Example -```Java -CameraSource camera = new CameraSource(new UsbCamera("cam", 0)); -camera.setExposure(0); -camera.setBrightness(0); -camera.setResolution(640, 480); - -TargetDetector targetDetector = new TargetDetector(new ExampleSpecs(), (targets) -> { - // do something -}); -camera.setDetector(targetDetector); - -camera.start(); -``` ### Displaying the most probable target example ```Java @@ -40,20 +26,22 @@ camera.setExposure(0); camera.setBrightness(0); camera.setResolution(640, 480); -TargetGroupDetector pegDetector = new TargetGroupDetector(new PegSingleRetroreflectiveSpecs(), new PegGroupSpecs(), (targets) -> { - // Draw the most probable target on the output stream - if (!targets.isEmpty()) { - Mat image = camera.getPicture(); - Rect boundary = new Rect((int) Math.round(targets.get(0).getPosition().x), - (int) Math.round(targets.get(0).getPosition().y), - (int) Math.round(targets.get(0).getWidth()), - (int) Math.round(targets.get(0).getHeight())); - Imgproc.rectangle(image, boundary.tl(), boundary.br(), new Scalar(0, 255, 0)); - outputStream.putFrame(image); - } -}); - -camera.setDetector(pegDetector); - camera.start(); + +final int MIN_PIXEL_AREA = 200; + +PegDetector pegDetector = new PegDetector(new IndividualPegDetector(new BrightnessFilter(200, 255), MIN_PIXEL_AREA)); + +Mat image = camera.getPicture(); + +List targets = pegDetector.detect(image); + +for(Target target: targets){ + Rect boundary = new Rect((int) Math.round(target.getPosition().x), + (int) Math.round(target.getPosition().y), + (int) Math.round(target.getWidth()), + (int) Math.round(target.getHeight())); + Imgproc.rectangle(image, boundary.tl(), boundary.br(), new Scalar(0, 255, 0)); + outputStream.putFrame(image); +} ``` diff --git a/build.gradle b/build.gradle index 210a172..2421406 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { } group 'com.kylecorry.frc.vision' -version '0.6' +version '0.7' sourceCompatibility = 1.8 @@ -14,11 +14,11 @@ sourceCompatibility = 1.8 repositories { mavenCentral() maven { - name = "GradleRio" - url = "http://dev.imjac.in/maven" + name = 'GradleRio' + url = 'http://dev.imjac.in/maven' } - maven { url "https://plugins.gradle.org/m2/" } + maven { url 'https://plugins.gradle.org/m2/' } } dependencies { @@ -28,10 +28,10 @@ dependencies { } wpi { - wpilibVersion = "+" // The WPILib version to use. For this version of GradleRIO, must be a 2017 version - ntcoreVersion = "+" // The NetworkTables Core version to use. - opencvVersion = "+" // The OpenCV version to use - cscoreVersion = "+" // The CSCore version to use + wpilibVersion = '+' // The WPILib version to use. For this version of GradleRIO, must be a 2017 version + ntcoreVersion = '+' // The NetworkTables Core version to use. + opencvVersion = '+' // The OpenCV version to use + cscoreVersion = '+' // The CSCore version to use } jar { diff --git a/libs/Geometry-0.5.jar b/libs/Geometry-0.5.jar deleted file mode 100644 index 40263dd..0000000 Binary files a/libs/Geometry-0.5.jar and /dev/null differ diff --git a/libs/Geometry-0.6.1.jar b/libs/Geometry-0.6.1.jar new file mode 100644 index 0000000..df1aeb6 Binary files /dev/null and b/libs/Geometry-0.6.1.jar differ diff --git a/libs/libopencv_java340.so b/libs/libopencv_java340.so new file mode 100755 index 0000000..3d01101 Binary files /dev/null and b/libs/libopencv_java340.so differ diff --git a/libs/opencv_java340_32.dll b/libs/opencv_java340_32.dll new file mode 100644 index 0000000..31c1daa Binary files /dev/null and b/libs/opencv_java340_32.dll differ diff --git a/libs/opencv_java340_64.dll b/libs/opencv_java340_64.dll new file mode 100644 index 0000000..b3c34d8 Binary files /dev/null and b/libs/opencv_java340_64.dll differ diff --git a/settings.gradle b/settings.gradle index b78f81b..5607c12 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,2 @@ -rootProject.name = 'Robot Vision API' +rootProject.name = 'RobotVisionAPI' diff --git a/src/main/java/com/kylecorry/frc/vision/CameraSource.java b/src/main/java/com/kylecorry/frc/vision/CameraSource.java deleted file mode 100644 index 82628f5..0000000 --- a/src/main/java/com/kylecorry/frc/vision/CameraSource.java +++ /dev/null @@ -1,223 +0,0 @@ -package com.kylecorry.frc.vision; - -import org.opencv.core.Mat; - -import edu.wpi.cscore.CvSink; -import edu.wpi.cscore.HttpCamera; -import edu.wpi.cscore.UsbCamera; -import edu.wpi.cscore.VideoCamera; -import edu.wpi.first.wpilibj.CameraServer; -import edu.wpi.first.wpilibj.DriverStation; - -public class CameraSource implements CameraSourceInterface { - - @Deprecated - public enum Type { - USB, HTTP - } - - private VideoCamera camera; - private CvSink sink = new CvSink("CameraSource CvSink"); - private Mat frame = new Mat(); - private Thread detectionThread; - private Detector detector; - - /** - * Create a CameraSource from a {@link VideoCamera} with a {@link Detector} to process the images asynchronously. - * - * @param camera The camera. - * @param detector The detector to process the images. - */ - public CameraSource(VideoCamera camera, Detector detector) { - this.camera = camera; - setDetector(detector); - sink.setSource(camera); - } - - /** - * Create a CameraSource from a {@link VideoCamera}. - * - * @param camera The camera. - */ - public CameraSource(VideoCamera camera) { - this(camera, null); - } - - private Thread createDetectionThread() { - Thread t = new Thread(() -> { - while (!Thread.interrupted()) { - if (detector != null) - detector.receiveFrame(getPicture()); - } - }); - t.setDaemon(true); - return t; - } - - public void start() { - CameraServer.getInstance().startAutomaticCapture(camera); - detectionThread = createDetectionThread(); - detectionThread.start(); - } - - public Mat getPicture() { - long frameTime = sink.grabFrame(frame); - if (frameTime == 0) { - String error = sink.getError(); - DriverStation.reportError(error, true); - return null; - } else { - return frame; - } - } - - /** - * Set the detector of the camera. - * - * @param detector The detector. - */ - public void setDetector(Detector detector) { - this.detector = detector; - } - - public void stop() { - if (detectionThread != null) - detectionThread.interrupt(); - CameraServer.getInstance().removeCamera(camera.getName()); - } - - public void setBrightness(int brightness) { - camera.setBrightness(brightness); - } - - public void setExposure(int exposure) { - camera.setExposureManual(exposure); - } - - public void setResolution(int width, int height) { - camera.setResolution(width, height); - } - - public void setFPS(int fps) { - camera.setFPS(fps); - } - - public void setExposureAuto() { - camera.setExposureAuto(); - } - - /** - * @deprecated This class was a bit confusing to use, instead use the constructors in {@link CameraSource} instead. - */ - @Deprecated - public static class Builder { - - private Detector detector; - - private Type cameraType = Type.USB; - private int port = 0; - private String url = ""; - private int fps = 15; - private int width = -1, height = -1; - - /** - * Create a CameraSource.Builder without a detector. - */ - public Builder() { - detector = null; - } - - /** - * Create a CameraSource.Builder with a detector for target detection. - * - * @param detector The detector to process images with. - */ - public Builder(Detector detector) { - this.detector = detector; - } - - /** - * Set the type of camera that the CameraSource will get its images - * from. - * - * @param cameraType The type of camera. - * @return This Builder for chaining. - */ - public Builder setType(Type cameraType) { - this.cameraType = cameraType; - return this; - } - - /** - * Set the port of the camera if the camera is a USB camera. This - * defaults to 0. - * - * @param port The USB port number of the camera. - * @return This Builder for chaining. - */ - public Builder setPort(int port) { - this.port = port; - return this; - } - - /** - * Set the url of the camera if the camera is an HTTP camera. - * - * @param url The url of the camera. - * @return This Builder for chaining. - */ - public Builder setURL(String url) { - this.url = url; - return this; - } - - /** - * Set the frames per second of the camera. - * - * @param fps The FPS of the camera. - * @return This Builder for chaining. - */ - public Builder setFps(int fps) { - this.fps = fps; - return this; - } - - /** - * Set the resolution of the camera. - * - * @param width The width of the camera image in pixels. - * @param height The height of the camera image in pixels. - * @return This Builder for chaining. - */ - public Builder setResolution(int width, int height) { - this.width = width; - this.height = height; - return this; - } - - /** - * Build the CameraSource with the set attributes. - * - * @return The CameraSource. - */ - public CameraSource build() { - VideoCamera camera; - - switch (cameraType) { - case HTTP: - camera = new HttpCamera("cam", url); - break; - case USB: - default: - camera = new UsbCamera("cam", port); - } - - camera.setFPS(fps); - if (width != -1 && height != -1) - camera.setResolution(width, height); - return new CameraSource(camera, detector); - } - - } - -} diff --git a/src/main/java/com/kylecorry/frc/vision/Detector.java b/src/main/java/com/kylecorry/frc/vision/Detector.java deleted file mode 100644 index d2ab3ce..0000000 --- a/src/main/java/com/kylecorry/frc/vision/Detector.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.kylecorry.frc.vision; - -import java.util.List; - -import org.opencv.core.Mat; - -public abstract class Detector { - - private Processor processor; - - /** - * Detect objects in an image. - * - * @param frame - * The image to detect objects in. - * @return The list of objects in the image. - */ - public abstract List detect(Mat frame); - - /** - * Accept a frame and process the output of detect using the processor if - * supplied. - * - * @param frame - * The image to process. - */ - public void receiveFrame(Mat frame) { - if (processor != null) { - processor.receiveDetections(detect(frame)); - } - } - - /** - * Set the processor of the detector for processing the output of the - * detect. - * - * @param processor - * The processor to process the objects found in the image. - */ - public void setProcessor(Processor processor) { - this.processor = processor; - } - - public static interface Processor { - /** - * Receive the objects detected in the image to process. - * - * @param detections - * The detections in the image. - */ - void receiveDetections(List detections); - } - -} diff --git a/src/main/java/com/kylecorry/frc/vision/MultiTargetDetector.java b/src/main/java/com/kylecorry/frc/vision/MultiTargetDetector.java deleted file mode 100644 index 4d00831..0000000 --- a/src/main/java/com/kylecorry/frc/vision/MultiTargetDetector.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.kylecorry.frc.vision; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.opencv.core.Mat; - -// TODO: Add better way to identify which detector found what target -public class MultiTargetDetector extends Detector { - - private List> detectors; - - /** - * Create a MultiTargetDetector to detect multiple types of targets in an image. - * - * @param detectors The detector for each target to identify. - */ - public MultiTargetDetector(List> detectors) { - this.detectors = detectors; - } - - /** - * Create a MultiTargetDetector to detect multiple types of targets in an image. - * - * @param detectors The detector for each target to identify. - */ - public MultiTargetDetector(Detector... detectors) { - this(Arrays.asList(detectors)); - } - - /** - * Detect the targets in the image. - * - * @param frame The image to detect targets in. - * @return The list of possible targets ordered by confidence from greatest to least and by which detector found the target. - */ - @Override - public List detect(Mat frame) { - List targets = new ArrayList<>(); - for (Detector detector : detectors) { - targets.addAll(detector.detect(frame)); - } - return targets; - } - - /** - * @deprecated This class was a bit confusing to use, instead use the constructors in {@link MultiTargetDetector} instead. - */ - @Deprecated - public static class Builder { - - private List> detectors; - - public Builder() { - detectors = new ArrayList<>(); - } - - public Builder add(Detector detector) { - detectors.add(detector); - return this; - } - - public MultiTargetDetector build() { - return new MultiTargetDetector(detectors); - } - } - -} diff --git a/src/main/java/com/kylecorry/frc/vision/Target.java b/src/main/java/com/kylecorry/frc/vision/Target.java deleted file mode 100644 index 2a61191..0000000 --- a/src/main/java/com/kylecorry/frc/vision/Target.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.kylecorry.frc.vision; - -import com.kylecorry.geometry.Point; - -public class Target { - private double confidence; - private double width, height; - private Point position; - - /** - * Create a target. - * - * @param confidence The confidence that this is the real target from 0 to 1 inclusive. - * @param width The width of the target in pixels. - * @param height The height of the target in pixels. - * @param position The top left position in pixels. - */ - Target(double confidence, double width, double height, Point position) { - this.confidence = confidence; - this.width = width; - this.height = height; - this.position = position; - } - - /** - * Get how confident that this object is the specified target. - * - * @return The confidence from 0 to 1 inclusive. - */ - public double getIsTargetProbability() { - return confidence; - } - - /** - * Get the width in pixels of the target's bounding box. - * - * @return The width of the target in pixels. - */ - public double getWidth() { - return width; - } - - /** - * Get the height in pixels of the target's bounding box. - * - * @return The height of the target in pixels. - */ - public double getHeight() { - return height; - } - - /** - * Get the position of the top left point of the target's bounding box. - * - * @return The target's top left point in pixels. - */ - public Point getPosition() { - return position; - } - - /** - * Get the position of the center of the target's bounding box. - * - * @return The target's center point in pixels. - */ - public Point getCenterPosition() { - return new Point(getPosition().x + getWidth() / 2.0, getPosition().y + getHeight() / 2.0, 0); - } - - /** - * Compute the distance to the target. - * - * @param imageWidth The width of the image. - * @param imageHeight The height of the image. - * @param heightRelativeToCamera The height of the target relative to the camera (distance from camera to target along Y axis). - * @param horizontalViewAngle The horizontal view angle in degrees. - * @return The distance to the target in the same units as the targetActualWidth. - */ - public double computeDistance(int imageWidth, int imageHeight, double heightRelativeToCamera, double horizontalViewAngle) { - return CameraSpecs.calculateFocalLengthPixels(imageWidth, horizontalViewAngle) * heightRelativeToCamera / (getCenterPosition().y - imageHeight / 2.0 + 0.5); - } - - /** - * Compute the angle to the target from the center of the camera. This returns angle to the target from the coordinate frame placed on the camera. - * So 0 is directly to the right of the camera, 180 is directly to the left, and 90 is directly ahead. - * To convert it to allow for the left of center to be negative, and right of center to be positive subtract this angle from 90. - * - * @param imageWidth The width of the image in pixels. - * @param horizontalViewAngle The horizontal view angle in degrees. - * @return The angle to the target from the coordinate frame centered on the camera. - */ - public double computeAngle(int imageWidth, double horizontalViewAngle) { - return 90 - Math.toDegrees(Math.atan((getCenterPosition().x - imageWidth / 2.0 + 0.5) / CameraSpecs.calculateFocalLengthPixels(imageWidth, horizontalViewAngle))); - } - -} diff --git a/src/main/java/com/kylecorry/frc/vision/TargetDetector.java b/src/main/java/com/kylecorry/frc/vision/TargetDetector.java deleted file mode 100644 index e887647..0000000 --- a/src/main/java/com/kylecorry/frc/vision/TargetDetector.java +++ /dev/null @@ -1,335 +0,0 @@ -package com.kylecorry.frc.vision; - -import java.util.ArrayList; -import java.util.List; - -import org.opencv.core.Core; -import org.opencv.core.CvType; -import org.opencv.core.Mat; -import org.opencv.core.MatOfInt; -import org.opencv.core.MatOfPoint; -import org.opencv.core.MatOfPoint2f; -import org.opencv.core.Rect; -import org.opencv.core.Scalar; -import org.opencv.imgproc.Imgproc; - -import com.kylecorry.geometry.Point; - -public class TargetDetector extends Detector { - - TargetSpecs targetSpecs; - - /** - * Create a TargetDetector to find a single target. - * - * @param specs The specifications of the target. - * @param processor The processor to handle the targets when detected. - */ - public TargetDetector(TargetSpecs specs, Processor processor) { - this.targetSpecs = specs; - setProcessor(processor); - } - - /** - * Create a TargetDetector to find a single target. - * - * @param specs The specifications of the target. - */ - public TargetDetector(TargetSpecs specs) { - this(specs, null); - } - - /** - * Detect the target in the image. - * - * @param frame The image to detect targets in. - * @return The list of possible targets ordered by confidence from greatest to least. - */ - @Override - public List detect(Mat frame) { - double[] hue = {targetSpecs.getHue().start, targetSpecs.getHue().end}; - double[] sat = {targetSpecs.getSaturation().start, targetSpecs.getSaturation().end}; - double[] val = {targetSpecs.getValue().start, targetSpecs.getValue().end}; - Pipeline p = new Pipeline(); - p.source0 = frame; - p.process(hue, sat, val, targetSpecs.getMinPixelArea()); - List contours = p.filterContoursOutput(); - List detections = new ArrayList<>(); - for (MatOfPoint contour : contours) { - Rect boundary = Imgproc.boundingRect(contour); - double aspectRatio = (boundary.width / (double) boundary.height); - double aspectScore = Scorer.score(aspectRatio, targetSpecs.getWidth() / targetSpecs.getHeight()); - - double areaRatio = Imgproc.contourArea(contour) / (double) boundary.area(); - double areaScore = Scorer.score(areaRatio, - targetSpecs.getArea() / (targetSpecs.getHeight() * targetSpecs.getWidth())); - - double confidence = Math.round((aspectScore + areaScore) / 2) / 100.0; - - Target target = new Target(confidence, boundary.width, boundary.height, - new Point(boundary.x, boundary.y, 0)); - detections.add(target); - } - detections.sort((a, b) -> { - if (b.getIsTargetProbability() > a.getIsTargetProbability()) { - return 1; - } else if (a.getIsTargetProbability() > b.getIsTargetProbability()) { - return -1; - } - return 0; - }); - return detections; - } - - /** - * @deprecated This class was a bit confusing to use, instead use the constructors in {@link TargetDetector} instead. - */ - @Deprecated - public static class Builder { - private TargetSpecs specs; - private Processor processor; - - public Builder setTargetSpecs(TargetSpecs specs) { - this.specs = specs; - return this; - } - - public Builder setProcessor(Processor processor) { - this.processor = processor; - return this; - } - - public TargetDetector build() { - return new TargetDetector(specs, processor); - } - - } - - class Pipeline { - - // Outputs - private Mat hsvThresholdOutput = new Mat(); - private Mat cvErodeOutput = new Mat(); - private ArrayList findContoursOutput = new ArrayList(); - private ArrayList filterContoursOutput = new ArrayList(); - - // Sources - private Mat source0; - - /** - * This constructor sets up the pipeline - */ - public Pipeline() { - } - - /** - * This is the primary method that runs the entire pipeline and updates - * the outputs. - */ - public void process(double[] hsvThresholdHue, double[] hsvThresholdSaturation, double[] hsvThresholdValue, - double filterContoursMinArea) { - // Step HSV_Threshold0: - Mat hsvThresholdInput = source0; - hsvThreshold(hsvThresholdInput, hsvThresholdHue, hsvThresholdSaturation, hsvThresholdValue, - hsvThresholdOutput); - - // Step CV_erode0: - Mat cvErodeSrc = hsvThresholdOutput; - Mat cvErodeKernel = new Mat(); - org.opencv.core.Point cvErodeAnchor = new org.opencv.core.Point(-1, -1); - double cvErodeIterations = 1.0; - int cvErodeBordertype = Core.BORDER_CONSTANT; - Scalar cvErodeBordervalue = new Scalar(-1); - cvErode(cvErodeSrc, cvErodeKernel, cvErodeAnchor, cvErodeIterations, cvErodeBordertype, cvErodeBordervalue, - cvErodeOutput); - - // Step Find_Contours0: - Mat findContoursInput = cvErodeOutput; - boolean findContoursExternalOnly = false; - findContours(findContoursInput, findContoursExternalOnly, findContoursOutput); - - // Step Filter_Contours0: - ArrayList filterContoursContours = findContoursOutput; - double filterContoursMinPerimeter = 0; - double filterContoursMinWidth = 0; - double filterContoursMaxWidth = 1000; - double filterContoursMinHeight = 0; - double filterContoursMaxHeight = 1000; - double[] filterContoursSolidity = {0, 100}; - double filterContoursMaxVertices = 1000000; - double filterContoursMinVertices = 0; - double filterContoursMinRatio = 0; - double filterContoursMaxRatio = 1000; - filterContours(filterContoursContours, filterContoursMinArea, filterContoursMinPerimeter, - filterContoursMinWidth, filterContoursMaxWidth, filterContoursMinHeight, filterContoursMaxHeight, - filterContoursSolidity, filterContoursMaxVertices, filterContoursMinVertices, - filterContoursMinRatio, filterContoursMaxRatio, filterContoursOutput); - - } - - /** - * This method is a generated setter for source0. - * - * @param source the Mat to set - */ - public void setsource0(Mat source0) { - this.source0 = source0; - } - - /** - * This method is a generated getter for the output of a HSV_Threshold. - * - * @return Mat output from HSV_Threshold. - */ - public Mat hsvThresholdOutput() { - return hsvThresholdOutput; - } - - /** - * This method is a generated getter for the output of a CV_erode. - * - * @return Mat output from CV_erode. - */ - public Mat cvErodeOutput() { - return cvErodeOutput; - } - - /** - * This method is a generated getter for the output of a Find_Contours. - * - * @return ArrayList output from Find_Contours. - */ - public ArrayList findContoursOutput() { - return findContoursOutput; - } - - /** - * This method is a generated getter for the output of a - * Filter_Contours. - * - * @return ArrayList output from Filter_Contours. - */ - public ArrayList filterContoursOutput() { - return filterContoursOutput; - } - - /** - * Segment an image based on hue, saturation, and value ranges. - * - * @param input The image on which to perform the HSL threshold. - * @param hue The min and max hue - * @param sat The min and max saturation - * @param val The min and max value - * @param output The image in which to store the output. - */ - private void hsvThreshold(Mat input, double[] hue, double[] sat, double[] val, Mat out) { - Imgproc.cvtColor(input, out, Imgproc.COLOR_BGR2HSV); - Core.inRange(out, new Scalar(hue[0], sat[0], val[0]), new Scalar(hue[1], sat[1], val[1]), out); - } - - /** - * Expands area of lower value in an image. - * - * @param src the Image to erode. - * @param kernel the kernel for erosion. - * @param anchor the center of the kernel. - * @param iterations the number of times to perform the erosion. - * @param borderType pixel extrapolation method. - * @param borderValue value to be used for a constant border. - * @param dst Output Image. - */ - private void cvErode(Mat src, Mat kernel, org.opencv.core.Point anchor, double iterations, int borderType, - Scalar borderValue, Mat dst) { - if (kernel == null) { - kernel = new Mat(); - } - if (anchor == null) { - anchor = new org.opencv.core.Point(-1, -1); - } - if (borderValue == null) { - borderValue = new Scalar(-1); - } - Imgproc.erode(src, dst, kernel, anchor, (int) iterations, borderType, borderValue); - } - - /** - * Sets the values of pixels in a binary image to their distance to the - * nearest black pixel. - * - * @param input The image on which to perform the Distance Transform. - * @param type The Transform. - * @param maskSize the size of the mask. - * @param output The image in which to store the output. - */ - private void findContours(Mat input, boolean externalOnly, List contours) { - Mat hierarchy = new Mat(); - contours.clear(); - int mode; - if (externalOnly) { - mode = Imgproc.RETR_EXTERNAL; - } else { - mode = Imgproc.RETR_LIST; - } - int method = Imgproc.CHAIN_APPROX_SIMPLE; - Imgproc.findContours(input, contours, hierarchy, mode, method); - } - - /** - * Filters out contours that do not meet certain criteria. - * - * @param inputContours is the input list of contours - * @param output is the the output list of contours - * @param minArea is the minimum area of a contour that will be kept - * @param minPerimeter is the minimum perimeter of a contour that will be kept - * @param minWidth minimum width of a contour - * @param maxWidth maximum width - * @param minHeight minimum height - * @param maxHeight maximimum height - * @param Solidity the minimum and maximum solidity of a contour - * @param minVertexCount minimum vertex Count of the contours - * @param maxVertexCount maximum vertex Count - * @param minRatio minimum ratio of width to height - * @param maxRatio maximum ratio of width to height - */ - private void filterContours(List inputContours, double minArea, double minPerimeter, - double minWidth, double maxWidth, double minHeight, double maxHeight, double[] solidity, - double maxVertexCount, double minVertexCount, double minRatio, double maxRatio, - List output) { - final MatOfInt hull = new MatOfInt(); - output.clear(); - // operation - for (int i = 0; i < inputContours.size(); i++) { - final MatOfPoint contour = inputContours.get(i); - final Rect bb = Imgproc.boundingRect(contour); - if (bb.width < minWidth || bb.width > maxWidth) - continue; - if (bb.height < minHeight || bb.height > maxHeight) - continue; - final double area = Imgproc.contourArea(contour); - if (area < minArea) - continue; - if (Imgproc.arcLength(new MatOfPoint2f(contour.toArray()), true) < minPerimeter) - continue; - Imgproc.convexHull(contour, hull); - MatOfPoint mopHull = new MatOfPoint(); - mopHull.create((int) hull.size().height, 1, CvType.CV_32SC2); - for (int j = 0; j < hull.size().height; j++) { - int index = (int) hull.get(j, 0)[0]; - double[] point = new double[]{contour.get(index, 0)[0], contour.get(index, 0)[1]}; - mopHull.put(j, 0, point); - } - final double solid = 100 * area / Imgproc.contourArea(mopHull); - if (solid < solidity[0] || solid > solidity[1]) - continue; - if (contour.rows() < minVertexCount || contour.rows() > maxVertexCount) - continue; - final double ratio = bb.width / (double) bb.height; - if (ratio < minRatio || ratio > maxRatio) - continue; - output.add(contour); - } - } - - } - -} diff --git a/src/main/java/com/kylecorry/frc/vision/TargetSpecs.java b/src/main/java/com/kylecorry/frc/vision/TargetSpecs.java deleted file mode 100644 index 177e72f..0000000 --- a/src/main/java/com/kylecorry/frc/vision/TargetSpecs.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.kylecorry.frc.vision; - -import org.opencv.core.Range; - -public interface TargetSpecs { - - /** - * Gets the hue of the target from 0 to 180 degrees inclusive. - * - * @return The target's hue. - */ - Range getHue(); - - /** - * Gets the saturation of the target from 0 to 255 inclusive. - * - * @return The target's saturation. - */ - Range getSaturation(); - - /** - * Gets the value of the target from 0 to 255 inclusive. - * - * @return The target's value. - */ - Range getValue(); - - /** - * Gets the width of the target. - * - * @return The target's width. - */ - double getWidth(); - - /** - * Gets the height of the target. - * - * @return The target's height. - */ - double getHeight(); - - /** - * Gets the area of the target (not the bounding box, unless the target - * fills the entire bounding box). - * - * @return The target's area. - */ - double getArea(); - - /** - * Gets the minimum area of the target in pixels for it to be analyzed. - * - * @return The minimum required area in pixels. - */ - int getMinPixelArea(); -} diff --git a/src/main/java/com/kylecorry/frc/vision/Version.java b/src/main/java/com/kylecorry/frc/vision/Version.java new file mode 100644 index 0000000..83a4979 --- /dev/null +++ b/src/main/java/com/kylecorry/frc/vision/Version.java @@ -0,0 +1,15 @@ +package com.kylecorry.frc.vision; + +public class Version { + public int getMajorVersion(){ + return 0; + } + + public int getMinorVersion(){ + return 7; + } + + public int getBuildVersion(){ + return 0; + } +} diff --git a/src/main/java/com/kylecorry/frc/vision/camera/CameraSource.java b/src/main/java/com/kylecorry/frc/vision/camera/CameraSource.java new file mode 100644 index 0000000..b7fa07b --- /dev/null +++ b/src/main/java/com/kylecorry/frc/vision/camera/CameraSource.java @@ -0,0 +1,90 @@ +package com.kylecorry.frc.vision.camera; + +import com.kylecorry.frc.vision.targetDetection.Detector; +import org.opencv.core.Mat; + +import edu.wpi.cscore.CvSink; +import edu.wpi.cscore.VideoCamera; +import edu.wpi.first.wpilibj.CameraServer; +import edu.wpi.first.wpilibj.DriverStation; + +public class CameraSource implements CameraSourceInterface { + + private VideoCamera camera; + private CvSink sink = new CvSink("CameraSource CvSink"); + private Mat frame = new Mat(); + private Thread detectionThread; + private Detector detector; + + /** + * Create a CameraSource from a {@link VideoCamera} with a {@link Detector} to process the images asynchronously. + * + * @param camera The camera. + * @param detector The detector to process the images. + */ + public CameraSource(VideoCamera camera, Detector detector) { + this.camera = camera; + setDetector(detector); + sink.setSource(camera); + } + + /** + * Create a CameraSource from a {@link VideoCamera}. + * + * @param camera The camera. + */ + public CameraSource(VideoCamera camera) { + this(camera, null); + } + + public void start() { + CameraServer.getInstance().startAutomaticCapture(camera); + } + + public Mat getPicture() { + long frameTime = sink.grabFrame(frame); + if (frameTime == 0) { + String error = sink.getError(); + DriverStation.reportError(error, true); + return null; + } else { + return frame; + } + } + + /** + * Set the detector of the camera. + * + * @param detector The detector. + */ + public void setDetector(Detector detector) { + this.detector = detector; + } + + public void stop() { + if (detectionThread != null) + detectionThread.interrupt(); + CameraServer.getInstance().removeCamera(camera.getName()); + } + + public void setBrightness(int brightness) { + camera.setBrightness(brightness); + } + + public void setExposure(int exposure) { + camera.setExposureManual(exposure); + } + + public void setResolution(int width, int height) { + camera.setResolution(width, height); + } + + public void setFPS(int fps) { + camera.setFPS(fps); + } + + public void setExposureAuto() { + camera.setExposureAuto(); + } + +} diff --git a/src/main/java/com/kylecorry/frc/vision/CameraSourceInterface.java b/src/main/java/com/kylecorry/frc/vision/camera/CameraSourceInterface.java similarity index 92% rename from src/main/java/com/kylecorry/frc/vision/CameraSourceInterface.java rename to src/main/java/com/kylecorry/frc/vision/camera/CameraSourceInterface.java index 87954d9..090b21b 100644 --- a/src/main/java/com/kylecorry/frc/vision/CameraSourceInterface.java +++ b/src/main/java/com/kylecorry/frc/vision/camera/CameraSourceInterface.java @@ -1,4 +1,4 @@ -package com.kylecorry.frc.vision; +package com.kylecorry.frc.vision.camera; import org.opencv.core.Mat; @@ -22,7 +22,7 @@ public interface CameraSourceInterface { void start(); /** - * Stop the camera from streaming and detecting targets. + * Stop the camera from streaming and detecting targetDetection. */ void stop(); diff --git a/src/main/java/com/kylecorry/frc/vision/CameraSpecs.java b/src/main/java/com/kylecorry/frc/vision/camera/CameraSpecs.java similarity index 86% rename from src/main/java/com/kylecorry/frc/vision/CameraSpecs.java rename to src/main/java/com/kylecorry/frc/vision/camera/CameraSpecs.java index d77111f..5559a88 100644 --- a/src/main/java/com/kylecorry/frc/vision/CameraSpecs.java +++ b/src/main/java/com/kylecorry/frc/vision/camera/CameraSpecs.java @@ -1,11 +1,11 @@ -package com.kylecorry.frc.vision; +package com.kylecorry.frc.vision.camera; public class CameraSpecs { public static class MicrosoftLifeCam { /** * The horizontal view angle of the Microsoft LifeCam in degrees. */ - public static final double HORIZONTAL_VIEW_ANGLE = 25.233 * 2; + public static final double HORIZONTAL_VIEW_ANGLE = 50.466; } /** diff --git a/src/main/java/com/kylecorry/frc/vision/InvalidCameraException.java b/src/main/java/com/kylecorry/frc/vision/camera/InvalidCameraException.java similarity index 85% rename from src/main/java/com/kylecorry/frc/vision/InvalidCameraException.java rename to src/main/java/com/kylecorry/frc/vision/camera/InvalidCameraException.java index 261783d..36fd85e 100644 --- a/src/main/java/com/kylecorry/frc/vision/InvalidCameraException.java +++ b/src/main/java/com/kylecorry/frc/vision/camera/InvalidCameraException.java @@ -1,4 +1,4 @@ -package com.kylecorry.frc.vision; +package com.kylecorry.frc.vision.camera; /** * Created by Kylec on 2/19/2017. diff --git a/src/main/java/com/kylecorry/frc/vision/MultiCameraSource.java b/src/main/java/com/kylecorry/frc/vision/camera/MultiCameraSource.java similarity index 97% rename from src/main/java/com/kylecorry/frc/vision/MultiCameraSource.java rename to src/main/java/com/kylecorry/frc/vision/camera/MultiCameraSource.java index bd26391..6773f4b 100644 --- a/src/main/java/com/kylecorry/frc/vision/MultiCameraSource.java +++ b/src/main/java/com/kylecorry/frc/vision/camera/MultiCameraSource.java @@ -1,9 +1,9 @@ -package com.kylecorry.frc.vision; +package com.kylecorry.frc.vision.camera; +import com.kylecorry.frc.vision.Experimental; import edu.wpi.cscore.CvSink; import edu.wpi.cscore.MjpegServer; import edu.wpi.cscore.VideoCamera; -import edu.wpi.first.wpilibj.CameraServer; import edu.wpi.first.wpilibj.DriverStation; import org.opencv.core.Mat; diff --git a/src/main/java/com/kylecorry/frc/vision/contourFilters/ContourFilter.java b/src/main/java/com/kylecorry/frc/vision/contourFilters/ContourFilter.java new file mode 100644 index 0000000..36f1d4b --- /dev/null +++ b/src/main/java/com/kylecorry/frc/vision/contourFilters/ContourFilter.java @@ -0,0 +1,9 @@ +package com.kylecorry.frc.vision.contourFilters; + +import org.opencv.core.MatOfPoint; + +import java.util.List; + +public interface ContourFilter { + List filterContours(List contours); +} diff --git a/src/main/java/com/kylecorry/frc/vision/contourFilters/ConvexHullContourFilter.java b/src/main/java/com/kylecorry/frc/vision/contourFilters/ConvexHullContourFilter.java new file mode 100644 index 0000000..ed73b22 --- /dev/null +++ b/src/main/java/com/kylecorry/frc/vision/contourFilters/ConvexHullContourFilter.java @@ -0,0 +1,77 @@ +package com.kylecorry.frc.vision.contourFilters; + +import org.opencv.core.*; +import org.opencv.imgproc.Imgproc; + +import java.util.ArrayList; +import java.util.List; + +public class ConvexHullContourFilter implements ContourFilter { + + private double minArea; + private double minPerimeter; + private Range width; + private Range height; + private Range solidity; // 0 - 100 + private Range vertexCount; + private Range widthToHeightRatio; + + /** + * A conved hull contour filter. + * + * @param minArea The minimum area of the contour in pixels. + * @param minPerimeter The minimum perimeter of the contour in pixels. + * @param width The range of the width in pixels. + * @param height The range of the height in pixels. + * @param solidity The solidity of the contour from 0 to 100 inclusive. + * @param vertexCount The range of the number of vertices. + * @param widthToHeightRatio The ratio of the width to the height of the contour's bounding rectangle. + */ + public ConvexHullContourFilter(double minArea, double minPerimeter, Range width, Range height, Range solidity, Range vertexCount, Range widthToHeightRatio) { + this.minArea = minArea; + this.minPerimeter = minPerimeter; + this.width = width; + this.height = height; + this.solidity = solidity; + this.vertexCount = vertexCount; + this.widthToHeightRatio = widthToHeightRatio; + } + + @Override + public List filterContours(List contours) { + List output = new ArrayList<>(); + // Generated and tested by GRIP, variables slightly renamed + final MatOfInt hull = new MatOfInt(); + for (int i = 0; i < contours.size(); i++) { + final MatOfPoint contour = contours.get(i); + final Rect bb = Imgproc.boundingRect(contour); + if (bb.width < width.start || bb.width > width.end) + continue; + if (bb.height < height.start || bb.height > height.end) + continue; + final double area = Imgproc.contourArea(contour); + if (area < minArea) + continue; + if (Imgproc.arcLength(new MatOfPoint2f(contour.toArray()), true) < minPerimeter) + continue; + Imgproc.convexHull(contour, hull); + MatOfPoint mopHull = new MatOfPoint(); + mopHull.create((int) hull.size().height, 1, CvType.CV_32SC2); + for (int j = 0; j < hull.size().height; j++) { + int index = (int) hull.get(j, 0)[0]; + double[] point = new double[]{contour.get(index, 0)[0], contour.get(index, 0)[1]}; + mopHull.put(j, 0, point); + } + final double solid = 100 * area / Imgproc.contourArea(mopHull); + if (solid < solidity.start || solid > solidity.end) + continue; + if (contour.rows() < vertexCount.start || contour.rows() > vertexCount.end) + continue; + final double ratio = bb.width / (double) bb.height; + if (ratio < widthToHeightRatio.start || ratio > widthToHeightRatio.end) + continue; + output.add(contour); + } + return output; + } +} diff --git a/src/main/java/com/kylecorry/frc/vision/contourFinders/ContourFinder.java b/src/main/java/com/kylecorry/frc/vision/contourFinders/ContourFinder.java new file mode 100644 index 0000000..d2e01fe --- /dev/null +++ b/src/main/java/com/kylecorry/frc/vision/contourFinders/ContourFinder.java @@ -0,0 +1,10 @@ +package com.kylecorry.frc.vision.contourFinders; + +import org.opencv.core.Mat; +import org.opencv.core.MatOfPoint; + +import java.util.List; + +public interface ContourFinder { + List findContours(Mat image); +} diff --git a/src/main/java/com/kylecorry/frc/vision/contourFinders/StandardContourFinder.java b/src/main/java/com/kylecorry/frc/vision/contourFinders/StandardContourFinder.java new file mode 100644 index 0000000..6afed55 --- /dev/null +++ b/src/main/java/com/kylecorry/frc/vision/contourFinders/StandardContourFinder.java @@ -0,0 +1,20 @@ +package com.kylecorry.frc.vision.contourFinders; + +import org.opencv.core.*; +import org.opencv.imgproc.Imgproc; + +import java.util.ArrayList; +import java.util.List; + +public class StandardContourFinder implements ContourFinder { + + @Override + public List findContours(Mat image) { + List contours = new ArrayList<>(); + Mat hierarchy = new Mat(); + int mode = Imgproc.RETR_EXTERNAL; + int method = Imgproc.CHAIN_APPROX_SIMPLE; + Imgproc.findContours(image, contours, hierarchy, mode, method); + return contours; + } +} diff --git a/src/main/java/com/kylecorry/frc/vision/filters/BrightnessFilter.java b/src/main/java/com/kylecorry/frc/vision/filters/BrightnessFilter.java new file mode 100644 index 0000000..0226ade --- /dev/null +++ b/src/main/java/com/kylecorry/frc/vision/filters/BrightnessFilter.java @@ -0,0 +1,31 @@ +package com.kylecorry.frc.vision.filters; + +import org.opencv.core.Core; +import org.opencv.core.Mat; +import org.opencv.core.Range; +import org.opencv.core.Scalar; +import org.opencv.imgproc.Imgproc; + +public class BrightnessFilter implements TargetFilter { + + private Range brightness; + + public BrightnessFilter(Range brightness) { + this.brightness = brightness; + } + + public BrightnessFilter(int minBrightness, int maxBrightness){ + this.brightness = new Range(minBrightness, maxBrightness); + } + + @Override + public Mat filter(Mat image) { + Mat out = new Mat(); + Imgproc.cvtColor(image, out, Imgproc.COLOR_BGR2GRAY); + Scalar lower = new Scalar(brightness.start); + Scalar upper = new Scalar(brightness.end); + Core.inRange(out, lower, upper, out); + return out; + } + +} diff --git a/src/main/java/com/kylecorry/frc/vision/filters/HSVFilter.java b/src/main/java/com/kylecorry/frc/vision/filters/HSVFilter.java new file mode 100644 index 0000000..64d02a9 --- /dev/null +++ b/src/main/java/com/kylecorry/frc/vision/filters/HSVFilter.java @@ -0,0 +1,31 @@ +package com.kylecorry.frc.vision.filters; + +import org.opencv.core.Core; +import org.opencv.core.Mat; +import org.opencv.core.Range; +import org.opencv.core.Scalar; +import org.opencv.imgproc.Imgproc; + +public class HSVFilter implements TargetFilter { + + private Range hue; + private Range saturation; + private Range value; + + public HSVFilter(Range hue, Range saturation, Range value) { + this.hue = hue; + this.saturation = saturation; + this.value = value; + } + + @Override + public Mat filter(Mat image) { + Mat hsvImg = new Mat(); + Imgproc.cvtColor(image, hsvImg, Imgproc.COLOR_BGR2HSV); + Scalar lower = new Scalar(hue.start % 360 / 2, saturation.start, value.start); + Scalar upper = new Scalar(hue.end % 360 / 2, saturation.end, value.end); + Core.inRange(hsvImg, lower, upper, hsvImg); + return hsvImg; + } + +} diff --git a/src/main/java/com/kylecorry/frc/vision/filters/RGBFilter.java b/src/main/java/com/kylecorry/frc/vision/filters/RGBFilter.java new file mode 100644 index 0000000..da4cbc6 --- /dev/null +++ b/src/main/java/com/kylecorry/frc/vision/filters/RGBFilter.java @@ -0,0 +1,29 @@ +package com.kylecorry.frc.vision.filters; + +import org.opencv.core.Core; +import org.opencv.core.Mat; +import org.opencv.core.Range; +import org.opencv.core.Scalar; + +public class RGBFilter implements TargetFilter { + + private Range red; + private Range green; + private Range blue; + + public RGBFilter(Range red, Range green, Range blue) { + this.red = red; + this.green = green; + this.blue = blue; + } + + @Override + public Mat filter(Mat image) { + Mat out = new Mat(); + Scalar lower = new Scalar(blue.start, green.start, red.start); + Scalar upper = new Scalar(blue.end, green.end, red.end); + Core.inRange(image, lower, upper, out); + return out; + } + +} diff --git a/src/main/java/com/kylecorry/frc/vision/filters/TargetFilter.java b/src/main/java/com/kylecorry/frc/vision/filters/TargetFilter.java new file mode 100644 index 0000000..a452528 --- /dev/null +++ b/src/main/java/com/kylecorry/frc/vision/filters/TargetFilter.java @@ -0,0 +1,7 @@ +package com.kylecorry.frc.vision.filters; + +import org.opencv.core.Mat; + +public interface TargetFilter { + Mat filter(Mat image); +} diff --git a/src/main/java/com/kylecorry/frc/vision/frc/targets2016/CastleDetector.java b/src/main/java/com/kylecorry/frc/vision/frc/targets2016/CastleDetector.java new file mode 100644 index 0000000..ae207c1 --- /dev/null +++ b/src/main/java/com/kylecorry/frc/vision/frc/targets2016/CastleDetector.java @@ -0,0 +1,10 @@ +package com.kylecorry.frc.vision.frc.targets2016; + +import com.kylecorry.frc.vision.filters.TargetFilter; +import com.kylecorry.frc.vision.targetDetection.TargetDetector; + +public class CastleDetector extends TargetDetector { + public CastleDetector(TargetFilter filter, double minPixelArea) { + super(new CastleTarget(), filter, minPixelArea); + } +} diff --git a/src/main/java/com/kylecorry/frc/vision/frc/targets2016/CastleTarget.java b/src/main/java/com/kylecorry/frc/vision/frc/targets2016/CastleTarget.java new file mode 100644 index 0000000..a742ce0 --- /dev/null +++ b/src/main/java/com/kylecorry/frc/vision/frc/targets2016/CastleTarget.java @@ -0,0 +1,20 @@ +package com.kylecorry.frc.vision.frc.targets2016; + +import com.kylecorry.frc.vision.targetDetection.TargetSpecs; + +public class CastleTarget implements TargetSpecs { + @Override + public double getWidth() { + return 0.508; + } + + @Override + public double getHeight() { + return 0.3048; + } + + @Override + public double getArea() { + return 0.0516128; + } +} diff --git a/src/main/java/com/kylecorry/frc/vision/frc/targets2017/BoilerBottomTarget.java b/src/main/java/com/kylecorry/frc/vision/frc/targets2017/BoilerBottomTarget.java new file mode 100644 index 0000000..095b124 --- /dev/null +++ b/src/main/java/com/kylecorry/frc/vision/frc/targets2017/BoilerBottomTarget.java @@ -0,0 +1,20 @@ +package com.kylecorry.frc.vision.frc.targets2017; + +import com.kylecorry.frc.vision.targetDetection.TargetSpecs; + +public class BoilerBottomTarget implements TargetSpecs { + @Override + public double getWidth() { + return 0.381; + } + + @Override + public double getHeight() { + return 0.0508; + } + + @Override + public double getArea() { + return getWidth() * getHeight(); + } +} diff --git a/src/main/java/com/kylecorry/frc/vision/frc/targets2017/BoilerBottomTargetDetector.java b/src/main/java/com/kylecorry/frc/vision/frc/targets2017/BoilerBottomTargetDetector.java new file mode 100644 index 0000000..891c1e7 --- /dev/null +++ b/src/main/java/com/kylecorry/frc/vision/frc/targets2017/BoilerBottomTargetDetector.java @@ -0,0 +1,10 @@ +package com.kylecorry.frc.vision.frc.targets2017; + +import com.kylecorry.frc.vision.filters.TargetFilter; +import com.kylecorry.frc.vision.targetDetection.TargetDetector; + +public class BoilerBottomTargetDetector extends TargetDetector { + public BoilerBottomTargetDetector(TargetFilter filter, double minPixelArea) { + super(new BoilerBottomTarget(), filter, minPixelArea); + } +} diff --git a/src/main/java/com/kylecorry/frc/vision/frc/targets2017/BoilerTarget.java b/src/main/java/com/kylecorry/frc/vision/frc/targets2017/BoilerTarget.java new file mode 100644 index 0000000..e978def --- /dev/null +++ b/src/main/java/com/kylecorry/frc/vision/frc/targets2017/BoilerTarget.java @@ -0,0 +1,39 @@ +package com.kylecorry.frc.vision.frc.targets2017; + +import com.kylecorry.frc.vision.targetDetection.TargetGroupSpecs; +import com.kylecorry.frc.vision.targetDetection.TargetSpecs; + +public class BoilerTarget implements TargetGroupSpecs { + + private TargetSpecs top, bottom; + + public BoilerTarget(){ + top = new BoilerTopTarget(); + bottom = new BoilerBottomTarget(); + } + + @Override + public double getTargetWidthRatio() { + return 1; + } + + @Override + public double getTargetHeightRatio() { + return top.getHeight() / bottom.getHeight(); + } + + @Override + public Alignment getAlignment() { + return Alignment.LEFT; + } + + @Override + public double getGroupWidth() { + return top.getWidth(); + } + + @Override + public double getGroupHeight() { + return 0.254; + } +} diff --git a/src/main/java/com/kylecorry/frc/vision/frc/targets2017/BoilerTargetDetector.java b/src/main/java/com/kylecorry/frc/vision/frc/targets2017/BoilerTargetDetector.java new file mode 100644 index 0000000..53f400a --- /dev/null +++ b/src/main/java/com/kylecorry/frc/vision/frc/targets2017/BoilerTargetDetector.java @@ -0,0 +1,13 @@ +package com.kylecorry.frc.vision.frc.targets2017; + +import com.kylecorry.frc.vision.targetDetection.TargetGroupDetector; + +public class BoilerTargetDetector extends TargetGroupDetector { + public BoilerTargetDetector(BoilerTopTargetDetector targetDetector) { + super(targetDetector, new BoilerTarget()); + } + + public BoilerTargetDetector(BoilerBottomTargetDetector targetDetector) { + super(targetDetector, new BoilerTarget()); + } +} diff --git a/src/main/java/com/kylecorry/frc/vision/frc/targets2017/BoilerTopTarget.java b/src/main/java/com/kylecorry/frc/vision/frc/targets2017/BoilerTopTarget.java new file mode 100644 index 0000000..2d6e75d --- /dev/null +++ b/src/main/java/com/kylecorry/frc/vision/frc/targets2017/BoilerTopTarget.java @@ -0,0 +1,20 @@ +package com.kylecorry.frc.vision.frc.targets2017; + +import com.kylecorry.frc.vision.targetDetection.TargetSpecs; + +public class BoilerTopTarget implements TargetSpecs { + @Override + public double getWidth() { + return 0.381; + } + + @Override + public double getHeight() { + return 0.1016; + } + + @Override + public double getArea() { + return getWidth() * getHeight(); + } +} diff --git a/src/main/java/com/kylecorry/frc/vision/frc/targets2017/BoilerTopTargetDetector.java b/src/main/java/com/kylecorry/frc/vision/frc/targets2017/BoilerTopTargetDetector.java new file mode 100644 index 0000000..86f78e2 --- /dev/null +++ b/src/main/java/com/kylecorry/frc/vision/frc/targets2017/BoilerTopTargetDetector.java @@ -0,0 +1,10 @@ +package com.kylecorry.frc.vision.frc.targets2017; + +import com.kylecorry.frc.vision.filters.TargetFilter; +import com.kylecorry.frc.vision.targetDetection.TargetDetector; + +public class BoilerTopTargetDetector extends TargetDetector { + public BoilerTopTargetDetector(TargetFilter filter, double minPixelArea) { + super(new BoilerTopTarget(), filter, minPixelArea); + } +} diff --git a/src/main/java/com/kylecorry/frc/vision/frc/targets2017/IndividualPegDetector.java b/src/main/java/com/kylecorry/frc/vision/frc/targets2017/IndividualPegDetector.java new file mode 100644 index 0000000..1acf001 --- /dev/null +++ b/src/main/java/com/kylecorry/frc/vision/frc/targets2017/IndividualPegDetector.java @@ -0,0 +1,10 @@ +package com.kylecorry.frc.vision.frc.targets2017; + +import com.kylecorry.frc.vision.filters.TargetFilter; +import com.kylecorry.frc.vision.targetDetection.TargetDetector; + +public class IndividualPegDetector extends TargetDetector { + public IndividualPegDetector(TargetFilter filter, double minPixelArea) { + super(new IndividualPegTarget(), filter, minPixelArea); + } +} diff --git a/src/main/java/com/kylecorry/frc/vision/frc/targets2017/IndividualPegTarget.java b/src/main/java/com/kylecorry/frc/vision/frc/targets2017/IndividualPegTarget.java new file mode 100644 index 0000000..4835518 --- /dev/null +++ b/src/main/java/com/kylecorry/frc/vision/frc/targets2017/IndividualPegTarget.java @@ -0,0 +1,20 @@ +package com.kylecorry.frc.vision.frc.targets2017; + +import com.kylecorry.frc.vision.targetDetection.TargetSpecs; + +public class IndividualPegTarget implements TargetSpecs{ + @Override + public double getWidth() { + return 0.0508; + } + + @Override + public double getHeight() { + return 0.127; + } + + @Override + public double getArea() { + return getWidth() * getHeight(); + } +} diff --git a/src/main/java/com/kylecorry/frc/vision/frc/targets2017/PegDetector.java b/src/main/java/com/kylecorry/frc/vision/frc/targets2017/PegDetector.java new file mode 100644 index 0000000..c144e8e --- /dev/null +++ b/src/main/java/com/kylecorry/frc/vision/frc/targets2017/PegDetector.java @@ -0,0 +1,9 @@ +package com.kylecorry.frc.vision.frc.targets2017; + +import com.kylecorry.frc.vision.targetDetection.TargetGroupDetector; + +public class PegDetector extends TargetGroupDetector { + public PegDetector(IndividualPegDetector targetDetector) { + super(targetDetector, new PegTarget()); + } +} diff --git a/src/test/java/ExampleGroupSpecs.java b/src/main/java/com/kylecorry/frc/vision/frc/targets2017/PegTarget.java similarity index 51% rename from src/test/java/ExampleGroupSpecs.java rename to src/main/java/com/kylecorry/frc/vision/frc/targets2017/PegTarget.java index 37dac89..641576d 100644 --- a/src/test/java/ExampleGroupSpecs.java +++ b/src/main/java/com/kylecorry/frc/vision/frc/targets2017/PegTarget.java @@ -1,31 +1,30 @@ -import com.kylecorry.frc.vision.TargetGroupSpecs; +package com.kylecorry.frc.vision.frc.targets2017; -/** - * Created by Kyle on 3/6/2017. - */ -public class ExampleGroupSpecs implements TargetGroupSpecs { +import com.kylecorry.frc.vision.targetDetection.TargetGroupSpecs; + +public class PegTarget implements TargetGroupSpecs { @Override public double getTargetWidthRatio() { - return 0; + return 0.2; } @Override public double getTargetHeightRatio() { - return 0; + return 1; } @Override public Alignment getAlignment() { - return null; + return Alignment.TOP; } @Override public double getGroupWidth() { - return 0; + return 0.26035; } @Override public double getGroupHeight() { - return 0; + return 0.127; } } diff --git a/src/main/java/com/kylecorry/frc/vision/targetDetection/Detector.java b/src/main/java/com/kylecorry/frc/vision/targetDetection/Detector.java new file mode 100644 index 0000000..b1cfee0 --- /dev/null +++ b/src/main/java/com/kylecorry/frc/vision/targetDetection/Detector.java @@ -0,0 +1,18 @@ +package com.kylecorry.frc.vision.targetDetection; + +import java.util.List; + +import org.opencv.core.Mat; + +public abstract class Detector { + + /** + * Detect objects in an image. + * + * @param frame + * The image to detect objects in. + * @return The list of objects in the image. + */ + public abstract List detect(Mat frame); + +} diff --git a/src/main/java/com/kylecorry/frc/vision/targetDetection/MultiTargetDetector.java b/src/main/java/com/kylecorry/frc/vision/targetDetection/MultiTargetDetector.java new file mode 100644 index 0000000..a29a350 --- /dev/null +++ b/src/main/java/com/kylecorry/frc/vision/targetDetection/MultiTargetDetector.java @@ -0,0 +1,37 @@ +package com.kylecorry.frc.vision.targetDetection; + +import java.util.ArrayList; +import java.util.List; + +import org.opencv.core.Mat; + +// TODO: Add better way to identify which detector found what target +public class MultiTargetDetector extends Detector { + + private List> detectors; + + /** + * Create a MultiTargetDetector to detect multiple types of targetDetection in an image. + * + * @param detectors The detector for each target to identify. + */ + public MultiTargetDetector(List> detectors) { + this.detectors = detectors; + } + + /** + * Detect the targetDetection in the image. + * + * @param frame The image to detect targetDetection in. + * @return The list of possible targetDetection ordered by confidence from greatest to least and by which detector found the target. + */ + @Override + public List detect(Mat frame) { + List targets = new ArrayList<>(); + for (Detector detector : detectors) { + targets.addAll(detector.detect(frame)); + } + return targets; + } + +} diff --git a/src/main/java/com/kylecorry/frc/vision/Scorer.java b/src/main/java/com/kylecorry/frc/vision/targetDetection/Scorer.java similarity index 93% rename from src/main/java/com/kylecorry/frc/vision/Scorer.java rename to src/main/java/com/kylecorry/frc/vision/targetDetection/Scorer.java index 454e11d..c2c1d39 100644 --- a/src/main/java/com/kylecorry/frc/vision/Scorer.java +++ b/src/main/java/com/kylecorry/frc/vision/targetDetection/Scorer.java @@ -1,4 +1,4 @@ -package com.kylecorry.frc.vision; +package com.kylecorry.frc.vision.targetDetection; public class Scorer { /** diff --git a/src/main/java/com/kylecorry/frc/vision/targetDetection/Target.java b/src/main/java/com/kylecorry/frc/vision/targetDetection/Target.java new file mode 100644 index 0000000..a0c6981 --- /dev/null +++ b/src/main/java/com/kylecorry/frc/vision/targetDetection/Target.java @@ -0,0 +1,124 @@ +package com.kylecorry.frc.vision.targetDetection; + +import com.kylecorry.frc.vision.camera.CameraSpecs; +import com.kylecorry.geometry.Point; +import org.opencv.core.Size; + +public class Target { + private double confidence; + private double width, height; + private Point position; + private Point centerOfMass; + Size imageSize; + + /** + * Create a target. + * + * @param confidence The confidence that this is the real target from 0 to 1 inclusive. + * @param width The width of the target in pixels. + * @param height The height of the target in pixels. + * @param position The top left position in pixels. + * @param centerOfMass The center of mass in pixels. + * @param imageSize The size of the image that the target was located in. + */ + Target(double confidence, double width, double height, Point position, Point centerOfMass, Size imageSize) { + this.confidence = confidence; + this.width = width; + this.height = height; + this.position = position; + this.imageSize = imageSize; + this.centerOfMass = centerOfMass; + } + + /** + * Get the center of mass of the target in pixels. + * + * @return The center of mass in pixels. + */ + public Point getCenterOfMass() { + return centerOfMass; + } + + /** + * Get how confident that this object is the specified target. + * + * @return The confidence from 0 to 1 inclusive. + */ + public double getIsTargetProbability() { + return confidence; + } + + /** + * Get the width in pixels of the target's bounding box. + * + * @return The width of the target in pixels. + */ + public double getWidth() { + return width; + } + + /** + * Get the height in pixels of the target's bounding box. + * + * @return The height of the target in pixels. + */ + public double getHeight() { + return height; + } + + /** + * Get the position of the top left point of the target's bounding box. + * + * @return The target's top left point in pixels. + */ + public Point getPosition() { + return position; + } + + /** + * Get the position of the center of the target's bounding box. + * + * @return The target's center point in pixels. + */ + public Point getCenterPosition() { + return new Point(getPosition().x + getWidth() / 2.0, getPosition().y + getHeight() / 2.0, 0); + } + + /** + * Compute the distance to the target. + * + * @param heightRelativeToCamera The height of the target relative to the camera (distance from camera to target along Y axis). + * @param horizontalViewAngle The horizontal view angle in degrees. + * @return The distance to the target in the same units as the heightRelativeToCamera. + */ + public double computeDistance(double heightRelativeToCamera, double horizontalViewAngle) { + return -CameraSpecs.calculateFocalLengthPixels((int) imageSize.width, horizontalViewAngle) * heightRelativeToCamera / (getCenterPosition().y - imageSize.height / 2.0 + 0.5); + } + + /** + * Compute the yaw angle to the target from the center of the camera. This returns angle to the target from the coordinate frame placed on the camera. + * So 0 is directly to the right of the camera, 180 is directly to the left, and 90 is directly ahead. + * To convert it to allow for the left of center to be negative, and right of center to be positive subtract this angle from 90. + * + * @param horizontalViewAngle The horizontal view angle in degrees. + * @return The angle to the target from the coordinate frame centered on the camera. + */ + public double computeAngle(double horizontalViewAngle) { + return 90 - Math.toDegrees(Math.atan((getCenterPosition().x - imageSize.width / 2.0 + 0.5) / CameraSpecs.calculateFocalLengthPixels((int) imageSize.width, horizontalViewAngle))); + } + + /** + * Compute the coordinates of the target in 3D space. + * + * @param heightRelativeToCamera The height of the target relative to the camera (distance from camera to target along Y axis). + * @param horizontalViewAngle The horizontal view angle in degrees. + * @return The coordinates of the target in the same units as the heightRelativeToCamera. + */ + public Point computeCoordinates(double heightRelativeToCamera, double horizontalViewAngle) { + double distance = computeDistance(heightRelativeToCamera, horizontalViewAngle); + double x = distance * (getCenterPosition().x - imageSize.width / 2.0 + 0.5) / CameraSpecs.calculateFocalLengthPixels((int) imageSize.width, horizontalViewAngle); + double y = -distance * (getCenterPosition().y - imageSize.height / 2.0 + 0.5) / CameraSpecs.calculateFocalLengthPixels((int) imageSize.width, horizontalViewAngle); + return new Point(x, y, distance); + } + +} diff --git a/src/main/java/com/kylecorry/frc/vision/targetDetection/TargetDetector.java b/src/main/java/com/kylecorry/frc/vision/targetDetection/TargetDetector.java new file mode 100644 index 0000000..ceb41ff --- /dev/null +++ b/src/main/java/com/kylecorry/frc/vision/targetDetection/TargetDetector.java @@ -0,0 +1,85 @@ +package com.kylecorry.frc.vision.targetDetection; + +import com.kylecorry.frc.vision.contourFilters.ContourFilter; +import com.kylecorry.frc.vision.contourFilters.ConvexHullContourFilter; +import com.kylecorry.frc.vision.contourFinders.ContourFinder; +import com.kylecorry.frc.vision.contourFinders.StandardContourFinder; +import com.kylecorry.frc.vision.filters.TargetFilter; +import com.kylecorry.geometry.Point; +import org.opencv.core.*; +import org.opencv.imgproc.Imgproc; +import org.opencv.imgproc.Moments; + +import java.util.ArrayList; +import java.util.List; + +public class TargetDetector extends Detector { + + protected TargetSpecs targetSpecs; + private TargetFilter filter; + private double minPixelArea; + + + /** + * Create a TargetDetector to find a single target. + * + * @param specs The specifications of the target. + */ + public TargetDetector(TargetSpecs specs, TargetFilter filter, double minPixelArea) { + this.targetSpecs = specs; + this.filter = filter; + this.minPixelArea = minPixelArea; + } + + /** + * Detect the target in the image. + * + * @param frame The image to detect targetDetection in. + * @return The list of possible targetDetection ordered by confidence from greatest to least. + */ + @Override + public List detect(Mat frame) { + Mat filtered = filter.filter(frame); + + ContourFinder contourFinder = new StandardContourFinder(); + + List contours = contourFinder.findContours(filtered); + + filtered.release(); + + ContourFilter contourFilter = new ConvexHullContourFilter(minPixelArea, 0, new Range(0, 1000), new Range(0, 1000), new Range(0, 100), new Range(0, 1000000), new Range(0, 1000)); + contours = contourFilter.filterContours(contours); + + + List detections = new ArrayList<>(); + for (MatOfPoint contour : contours) { + Rect boundary = Imgproc.boundingRect(contour); + double aspectRatio = (boundary.width / (double) boundary.height); + double aspectScore = Scorer.score(aspectRatio, targetSpecs.getWidth() / targetSpecs.getHeight()); + + double areaRatio = Imgproc.contourArea(contour) / (double) boundary.area(); + double areaScore = Scorer.score(areaRatio, + targetSpecs.getArea() / (targetSpecs.getHeight() * targetSpecs.getWidth())); + + double confidence = Math.round((aspectScore + areaScore) / 2) / 100.0; + + Moments moments = Imgproc.moments(contour); + + Point centerOfMass = new Point(moments.m10 / moments.m00, moments.m01 / moments.m00, 0); + + Target target = new Target(confidence, boundary.width - 1, boundary.height - 1, + new Point(boundary.x, boundary.y, 0), centerOfMass, frame.size()); + detections.add(target); + } + detections.sort((a, b) -> { + if (b.getIsTargetProbability() > a.getIsTargetProbability()) { + return 1; + } else if (a.getIsTargetProbability() > b.getIsTargetProbability()) { + return -1; + } + return 0; + }); + return detections; + } + +} diff --git a/src/main/java/com/kylecorry/frc/vision/TargetGroup.java b/src/main/java/com/kylecorry/frc/vision/targetDetection/TargetGroup.java similarity index 56% rename from src/main/java/com/kylecorry/frc/vision/TargetGroup.java rename to src/main/java/com/kylecorry/frc/vision/targetDetection/TargetGroup.java index c255d78..ec58532 100644 --- a/src/main/java/com/kylecorry/frc/vision/TargetGroup.java +++ b/src/main/java/com/kylecorry/frc/vision/targetDetection/TargetGroup.java @@ -1,10 +1,13 @@ -package com.kylecorry.frc.vision; +package com.kylecorry.frc.vision.targetDetection; +import com.kylecorry.frc.vision.camera.CameraSpecs; import com.kylecorry.geometry.Point; +import org.opencv.core.Size; public class TargetGroup { private Target first, second; double confidence; + private Size imageSize; /** * Create a TargetGroup consisting of the first and second target. @@ -15,6 +18,7 @@ public class TargetGroup { public TargetGroup(Target first, Target second) { this.first = first; this.second = second; + this.imageSize = first.imageSize; } /** @@ -90,27 +94,49 @@ public Point getCenterPosition() { } /** - * Compute the distance to the target group. + * Get the center of mass of the target group in pixels. + * + * @return The center of mass in pixels. + */ + public Point getCenterOfMass() { + return new Point((getFirstTarget().getCenterOfMass().x + getSecondTarget().getCenterOfMass().x) / 2.0, + (getFirstTarget().getCenterOfMass().y + getSecondTarget().getCenterOfMass().y) / 2.0, 0); + } + + /** + * Compute the distance to the target. * - * @param imageWidth The width of the image. * @param heightRelativeToCamera The height of the target relative to the camera (distance from camera to target along Y axis). - * @param horizontalViewAngle The horizontal view angle in degrees. - * @return The distance to the target in the same units as the targetActualWidth. + * @param horizontalViewAngle The horizontal view angle in degrees. + * @return The distance to the target in the same units as the heightRelativeToCamera. */ - public double computeDistance(int imageWidth, double heightRelativeToCamera, double horizontalViewAngle) { - return CameraSpecs.calculateFocalLengthPixels(imageWidth, horizontalViewAngle) * heightRelativeToCamera / (getCenterPosition().y - imageWidth / 2.0 + 0.5); + public double computeDistance(double heightRelativeToCamera, double horizontalViewAngle) { + return CameraSpecs.calculateFocalLengthPixels((int) imageSize.width, horizontalViewAngle) * heightRelativeToCamera / (getCenterPosition().y - imageSize.height / 2.0 + 0.5); } /** - * Compute the angle to the target group from the center of the camera. This returns angle to the target from the coordinate frame placed on the camera. + * Compute the yaw angle to the target from the center of the camera. This returns angle to the target from the coordinate frame placed on the camera. * So 0 is directly to the right of the camera, 180 is directly to the left, and 90 is directly ahead. * To convert it to allow for the left of center to be negative, and right of center to be positive subtract this angle from 90. * - * @param imageWidth The width of the image in pixels. - * @param horizontalViewAngle The horizontal view angle in degrees. + * @param horizontalViewAngle The horizontal view angle in degrees. * @return The angle to the target from the coordinate frame centered on the camera. */ - public double computeAngle(int imageWidth, double horizontalViewAngle) { - return 90 - Math.toDegrees(Math.atan((getCenterPosition().x - imageWidth / 2.0 + 0.5) / CameraSpecs.calculateFocalLengthPixels(imageWidth, horizontalViewAngle))); + public double computeAngle(double horizontalViewAngle) { + return 90 - Math.toDegrees(Math.atan((getCenterPosition().x - imageSize.width / 2.0 + 0.5) / CameraSpecs.calculateFocalLengthPixels((int) imageSize.width, horizontalViewAngle))); + } + + /** + * Compute the coordinates of the target group in 3D space. + * + * @param heightRelativeToCamera The height of the target relative to the camera (distance from camera to target along Y axis). + * @param horizontalViewAngle The horizontal view angle in degrees. + * @return The coordinates of the target group in the same units as the heightRelativeToCamera. + */ + public Point computeCoordinates(double heightRelativeToCamera, double horizontalViewAngle) { + double distance = computeDistance(heightRelativeToCamera, horizontalViewAngle); + double x = distance * (getCenterPosition().x - imageSize.width / 2.0 + 0.5) / CameraSpecs.calculateFocalLengthPixels((int) imageSize.width, horizontalViewAngle); + double y = -distance * (getCenterPosition().y - imageSize.height / 2.0 + 0.5) / CameraSpecs.calculateFocalLengthPixels((int) imageSize.width, horizontalViewAngle); + return new Point(x, y, distance); } } diff --git a/src/main/java/com/kylecorry/frc/vision/TargetGroupDetector.java b/src/main/java/com/kylecorry/frc/vision/targetDetection/TargetGroupDetector.java similarity index 52% rename from src/main/java/com/kylecorry/frc/vision/TargetGroupDetector.java rename to src/main/java/com/kylecorry/frc/vision/targetDetection/TargetGroupDetector.java index d549caa..869dac2 100644 --- a/src/main/java/com/kylecorry/frc/vision/TargetGroupDetector.java +++ b/src/main/java/com/kylecorry/frc/vision/targetDetection/TargetGroupDetector.java @@ -1,4 +1,4 @@ -package com.kylecorry.frc.vision; +package com.kylecorry.frc.vision.targetDetection; import java.util.ArrayList; import java.util.List; @@ -10,31 +10,9 @@ public class TargetGroupDetector extends Detector { private TargetDetector targetDetector; private TargetGroupSpecs specs; - TargetGroupDetector(TargetDetector targetDetector, TargetGroupSpecs specs, Processor processor) { + public TargetGroupDetector(TargetDetector targetDetector, TargetGroupSpecs specs) { this.targetDetector = targetDetector; this.specs = specs; - setProcessor(processor); - } - - /** - * Create a TargetGroupDetector to detect a target that is composed of smaller vision targets. - * - * @param targetSpecs The specifications of a single vision target in the target group. - * @param targetGroupSpecs The specifications of a group of vision targets. - * @param processor The processor to handle detected target groups. - */ - public TargetGroupDetector(TargetSpecs targetSpecs, TargetGroupSpecs targetGroupSpecs, Processor processor) { - this(new TargetDetector(targetSpecs), targetGroupSpecs, processor); - } - - /** - * Create a TargetGroupDetector to detect a target that is composed of smaller vision targets. - * - * @param targetSpecs The specifications of a single vision target in the target group. - * @param targetGroupSpecs The specifications of a group of vision targets. - */ - public TargetGroupDetector(TargetSpecs targetSpecs, TargetGroupSpecs targetGroupSpecs) { - this(targetSpecs, targetGroupSpecs, null); } /** @@ -83,34 +61,4 @@ public List detect(Mat frame) { return groups; } - /** - * @deprecated This class was a bit confusing to use, instead use the constructors in {@link TargetGroupDetector} instead. - */ - @Deprecated - public static class Builder { - private TargetDetector detector; - private Processor processor; - private TargetGroupSpecs specs; - - public Builder setTargetSpecs(TargetSpecs specs) { - this.detector = new TargetDetector.Builder().setTargetSpecs(specs).build(); - return this; - } - - public Builder setTargetGroupSpecs(TargetGroupSpecs specs) { - this.specs = specs; - return this; - } - - public Builder setProcessor(Processor processor) { - this.processor = processor; - return this; - } - - public TargetGroupDetector build() { - return new TargetGroupDetector(detector, specs, processor); - } - - } - } diff --git a/src/main/java/com/kylecorry/frc/vision/TargetGroupScorer.java b/src/main/java/com/kylecorry/frc/vision/targetDetection/TargetGroupScorer.java similarity index 88% rename from src/main/java/com/kylecorry/frc/vision/TargetGroupScorer.java rename to src/main/java/com/kylecorry/frc/vision/targetDetection/TargetGroupScorer.java index c507222..b8a5f8c 100644 --- a/src/main/java/com/kylecorry/frc/vision/TargetGroupScorer.java +++ b/src/main/java/com/kylecorry/frc/vision/targetDetection/TargetGroupScorer.java @@ -1,9 +1,9 @@ -package com.kylecorry.frc.vision; +package com.kylecorry.frc.vision.targetDetection; public class TargetGroupScorer { /** - * Calculate a score from 0 to 100 for the ratio of the widths of the targets. + * Calculate a score from 0 to 100 for the ratio of the widths of the targetDetection. * * @param target The target group. * @param idealRatio The ideal ratio of the first individual target width over the second individual target width. @@ -14,7 +14,7 @@ public static double widthRatioScore(TargetGroup target, double idealRatio) { } /** - * Calculate a score from 0 to 100 for the ratio of the heights of the targets. + * Calculate a score from 0 to 100 for the ratio of the heights of the targetDetection. * * @param target The target group. * @param idealRatio The ideal ratio of the first individual target height over the second individual target height. @@ -25,7 +25,7 @@ public static double heightRatioScore(TargetGroup target, double idealRatio) { } /** - * Calculates a score from 0 to 100 for the targets being aligned to the top of the bounding box drawn around the target group. + * Calculates a score from 0 to 100 for the targetDetection being aligned to the top of the bounding box drawn around the target group. * * @param target The target group. * @return a score from 0 to 100. @@ -36,7 +36,7 @@ public static double topAlignmentScore(TargetGroup target) { } /** - * Calculates a score from 0 to 100 for the targets being aligned to the left of the bounding box drawn around the target group. + * Calculates a score from 0 to 100 for the targetDetection being aligned to the left of the bounding box drawn around the target group. * * @param target The target group. * @return a score from 0 to 100. diff --git a/src/main/java/com/kylecorry/frc/vision/TargetGroupSpecs.java b/src/main/java/com/kylecorry/frc/vision/targetDetection/TargetGroupSpecs.java similarity index 82% rename from src/main/java/com/kylecorry/frc/vision/TargetGroupSpecs.java rename to src/main/java/com/kylecorry/frc/vision/targetDetection/TargetGroupSpecs.java index 3d43992..0b38d48 100644 --- a/src/main/java/com/kylecorry/frc/vision/TargetGroupSpecs.java +++ b/src/main/java/com/kylecorry/frc/vision/targetDetection/TargetGroupSpecs.java @@ -1,4 +1,4 @@ -package com.kylecorry.frc.vision; +package com.kylecorry.frc.vision.targetDetection; public interface TargetGroupSpecs { diff --git a/src/main/java/com/kylecorry/frc/vision/targetDetection/TargetSpecs.java b/src/main/java/com/kylecorry/frc/vision/targetDetection/TargetSpecs.java new file mode 100644 index 0000000..a9835c4 --- /dev/null +++ b/src/main/java/com/kylecorry/frc/vision/targetDetection/TargetSpecs.java @@ -0,0 +1,26 @@ +package com.kylecorry.frc.vision.targetDetection; + +public interface TargetSpecs { + + /** + * Gets the width of the target. + * + * @return The target's width. + */ + double getWidth(); + + /** + * Gets the height of the target. + * + * @return The target's height. + */ + double getHeight(); + + /** + * Gets the area of the target (not the bounding box, unless the target + * fills the entire bounding box). + * + * @return The target's area. + */ + double getArea(); +} diff --git a/src/test/java/ExampleSpecs.java b/src/test/java/ExampleSpecs.java deleted file mode 100644 index baaa81d..0000000 --- a/src/test/java/ExampleSpecs.java +++ /dev/null @@ -1,42 +0,0 @@ -import com.kylecorry.frc.vision.TargetSpecs; -import org.opencv.core.Range; - -/** - * Created by Kylec on 2/12/2017. - */ -public class ExampleSpecs implements TargetSpecs { - @Override - public Range getHue() { - return null; - } - - @Override - public Range getSaturation() { - return null; - } - - @Override - public Range getValue() { - return null; - } - - @Override - public double getWidth() { - return 0; - } - - @Override - public double getHeight() { - return 0; - } - - @Override - public double getArea() { - return 0; - } - - @Override - public int getMinPixelArea() { - return 0; - } -} diff --git a/src/test/java/Examples.java b/src/test/java/Examples.java deleted file mode 100644 index 8b911b8..0000000 --- a/src/test/java/Examples.java +++ /dev/null @@ -1,44 +0,0 @@ -import com.kylecorry.frc.vision.*; -import edu.wpi.cscore.UsbCamera; -import edu.wpi.first.wpilibj.CameraServer; -import org.junit.Test; - -import java.util.List; - -/** - * Created by Kylec on 2/12/2017. - */ -public class Examples { - - public void testCameraSource(){ - UsbCamera usbCamera = CameraServer.getInstance().startAutomaticCapture(0); - usbCamera.setExposureManual(0); - usbCamera.setBrightness(0); - usbCamera.setWhiteBalanceManual(10000); - usbCamera.setResolution(160, 120); - - CameraSource cameraSource = new CameraSource(usbCamera); - - TargetGroupDetector detector = new TargetGroupDetector(new ExampleSpecs(), new ExampleGroupSpecs()); - - List targets = detector.detect(cameraSource.getPicture()); - - if(!targets.isEmpty()){ - TargetGroup bestTarget = targets.get(0); - - double angle = bestTarget.computeAngle(160, CameraSpecs.MicrosoftLifeCam.HORIZONTAL_VIEW_ANGLE); - double distance = bestTarget.computeDistance(160, new ExampleGroupSpecs().getGroupWidth(), CameraSpecs.MicrosoftLifeCam.HORIZONTAL_VIEW_ANGLE); - - double centerX = bestTarget.getCenterPosition().x; - - double confidence = bestTarget.getIsTargetGroupProbability(); // 0 - 1 - - if(confidence > 0.75){ - // do something - } - } - - } - - -} diff --git a/src/test/java/com/kylecorry/frc/vision/CameraSpecsTest.java b/src/test/java/com/kylecorry/frc/vision/camera/CameraSpecsTest.java similarity index 74% rename from src/test/java/com/kylecorry/frc/vision/CameraSpecsTest.java rename to src/test/java/com/kylecorry/frc/vision/camera/CameraSpecsTest.java index 736f594..a2a76fb 100644 --- a/src/test/java/com/kylecorry/frc/vision/CameraSpecsTest.java +++ b/src/test/java/com/kylecorry/frc/vision/camera/CameraSpecsTest.java @@ -1,5 +1,6 @@ -package com.kylecorry.frc.vision; +package com.kylecorry.frc.vision.camera; +import com.kylecorry.frc.vision.camera.CameraSpecs; import org.junit.Test; import static org.junit.Assert.*; diff --git a/src/test/java/com/kylecorry/frc/vision/contourFilters/ConvexHullContourFilterTest.java b/src/test/java/com/kylecorry/frc/vision/contourFilters/ConvexHullContourFilterTest.java new file mode 100644 index 0000000..b55d390 --- /dev/null +++ b/src/test/java/com/kylecorry/frc/vision/contourFilters/ConvexHullContourFilterTest.java @@ -0,0 +1,46 @@ +package com.kylecorry.frc.vision.contourFilters; + +import com.kylecorry.frc.vision.contourFinders.ContourFinder; +import com.kylecorry.frc.vision.contourFinders.StandardContourFinder; +import org.junit.Test; +import org.opencv.core.*; +import org.opencv.imgproc.Imgproc; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.*; + +public class ConvexHullContourFilterTest { + + private ContourFilter contourFilter; + private ContourFinder contourFinder; + + @Test + public void testFilterContours(){ + contourFinder = new StandardContourFinder(); + + contourFilter = new ConvexHullContourFilter(10, 0, new Range(0, 1000), new Range(0, 1000), new Range(0, 100), new Range(0, 1000000), new Range(0, 1000)); + + Mat image = Mat.zeros(100, 100, CvType.CV_8UC1); + Imgproc.rectangle(image, new Point(1, 1), new Point(2, 2), Scalar.all(255)); + Imgproc.rectangle(image, new Point(10, 10), new Point(20, 20), Scalar.all(255)); + + List contours = contourFinder.findContours(image); + + contours = contourFilter.filterContours(contours); + + List expected = Arrays.asList(new Point(10, 10), new Point(10, 20), new Point(20, 20), new Point(20, 10)); + + assertEquals(1, contours.size()); + + List contour = contours.get(0).toList(); + + assertEquals(expected.size(), contour.size()); + + for (Point point : expected) { + assertTrue(contour.contains(point)); + } + } + +} \ No newline at end of file diff --git a/src/test/java/com/kylecorry/frc/vision/contourFinders/StandardContourFinderTest.java b/src/test/java/com/kylecorry/frc/vision/contourFinders/StandardContourFinderTest.java new file mode 100644 index 0000000..ac8dc94 --- /dev/null +++ b/src/test/java/com/kylecorry/frc/vision/contourFinders/StandardContourFinderTest.java @@ -0,0 +1,47 @@ +package com.kylecorry.frc.vision.contourFinders; + +import com.kylecorry.frc.vision.contourFinders.StandardContourFinder; +import com.kylecorry.frc.vision.testUtils.OpenCVManager; +import com.kylecorry.frc.vision.testUtils.SystemProperties; +import org.junit.Before; +import org.junit.Test; +import org.opencv.core.*; +import org.opencv.imgproc.Imgproc; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.*; + +public class StandardContourFinderTest { + + private StandardContourFinder contourFinder; + + @Before + public void setup(){ + OpenCVManager.getInstance().load(new SystemProperties()); + } + + @Test + public void testFindExternalContours(){ + contourFinder = new StandardContourFinder(); + Mat image = Mat.zeros(100, 100, CvType.CV_8UC1); + Imgproc.rectangle(image, new Point(10, 10), new Point(20, 20), Scalar.all(255)); + + List contours = contourFinder.findContours(image); + + List expected = Arrays.asList(new Point(10, 10), new Point(10, 20), new Point(20, 20), new Point(20, 10)); + + assertEquals(1, contours.size()); + + List contour = contours.get(0).toList(); + + assertEquals(expected.size(), contour.size()); + + for (Point point : expected) { + assertTrue(contour.contains(point)); + } + + } + +} \ No newline at end of file diff --git a/src/test/java/com/kylecorry/frc/vision/filters/BrightnessFilterTest.java b/src/test/java/com/kylecorry/frc/vision/filters/BrightnessFilterTest.java new file mode 100644 index 0000000..2887b56 --- /dev/null +++ b/src/test/java/com/kylecorry/frc/vision/filters/BrightnessFilterTest.java @@ -0,0 +1,69 @@ +package com.kylecorry.frc.vision.filters; + +import com.kylecorry.frc.vision.testUtils.OpenCVImageUtils; +import com.kylecorry.frc.vision.testUtils.OpenCVManager; +import com.kylecorry.frc.vision.testUtils.SystemProperties; +import org.junit.Before; +import org.junit.Test; +import org.opencv.core.CvType; +import org.opencv.core.Mat; +import org.opencv.core.Range; +import org.opencv.core.Scalar; + +import static org.junit.Assert.assertTrue; + +public class BrightnessFilterTest { + + private BrightnessFilter filter; + + @Before + public void setup(){ + OpenCVManager.getInstance().load(new SystemProperties()); + } + + @Test + public void testFilter() { + Mat image = Mat.zeros(4, 4, CvType.CV_8UC3); + image.row(0).setTo(new Scalar(255, 255, 255)); + image.row(1).setTo(new Scalar(127, 127, 127)); + image.row(2).setTo(new Scalar(0, 90, 0)); + + + filter = new BrightnessFilter(new Range(0, 255)); + + Mat all = filter.filter(image); + + Mat allRows = Mat.zeros(4, 4, CvType.CV_8UC1); + allRows.setTo(Scalar.all(255)); + + assertTrue(OpenCVImageUtils.matEquals(allRows, all)); + + filter = new BrightnessFilter(new Range(0, 0)); + + Mat zeros = filter.filter(image); + + Mat zeroRow = Mat.zeros(4, 4, CvType.CV_8UC1); + zeroRow.row(3).setTo(Scalar.all(255)); + + assertTrue(OpenCVImageUtils.matEquals(zeroRow, zeros)); + + filter = new BrightnessFilter(new Range(127, 127)); + + Mat mids = filter.filter(image); + + Mat midRow = Mat.zeros(4, 4, CvType.CV_8UC1); + midRow.row(1).setTo(Scalar.all(255)); + + assertTrue(OpenCVImageUtils.matEquals(mids, midRow)); + + all.release(); + zeros.release(); + mids.release(); + allRows.release(); + zeroRow.release(); + midRow.release(); + image.release(); + + + } +} diff --git a/src/test/java/com/kylecorry/frc/vision/filters/HSVFilterTest.java b/src/test/java/com/kylecorry/frc/vision/filters/HSVFilterTest.java new file mode 100644 index 0000000..dfc7e0b --- /dev/null +++ b/src/test/java/com/kylecorry/frc/vision/filters/HSVFilterTest.java @@ -0,0 +1,66 @@ +package com.kylecorry.frc.vision.filters; + +import com.kylecorry.frc.vision.testUtils.OpenCVImageUtils; +import com.kylecorry.frc.vision.testUtils.OpenCVManager; +import com.kylecorry.frc.vision.testUtils.SystemProperties; +import org.junit.Before; +import org.junit.Test; +import org.opencv.core.*; + +import static org.junit.Assert.*; + +public class HSVFilterTest { + + private HSVFilter filter; + + @Before + public void setup(){ + OpenCVManager.getInstance().load(new SystemProperties()); + } + + @Test + public void testFilter() { + Mat image = Mat.zeros(4, 4, CvType.CV_8UC3); + image.row(0).setTo(new Scalar(255, 0, 0)); + image.row(1).setTo(new Scalar(0, 0, 255)); + image.row(2).setTo(new Scalar(0, 255, 0)); + + + filter = new HSVFilter(new Range(0, 20), new Range(0, 255), new Range(1, 255)); + + Mat red = filter.filter(image); + + Mat redRow = Mat.zeros(4, 4, CvType.CV_8UC1); + redRow.row(1).setTo(Scalar.all(255)); + + assertTrue(OpenCVImageUtils.matEquals(redRow, red)); + + filter = new HSVFilter(new Range(230, 250), new Range(0, 255), new Range(1, 255)); + + Mat blue = filter.filter(image); + + Mat blueRow = Mat.zeros(4, 4, CvType.CV_8UC1); + blueRow.row(0).setTo(Scalar.all(255)); + + assertTrue(OpenCVImageUtils.matEquals(blueRow, blue)); + + filter = new HSVFilter(new Range(110, 130), new Range(0, 255), new Range(1, 255)); + + Mat green = filter.filter(image); + + Mat greenRow = Mat.zeros(4, 4, CvType.CV_8UC1); + greenRow.row(2).setTo(Scalar.all(255)); + + assertTrue(OpenCVImageUtils.matEquals(greenRow, green)); + + red.release(); + blue.release(); + green.release(); + redRow.release(); + blueRow.release(); + greenRow.release(); + image.release(); + + + } +} \ No newline at end of file diff --git a/src/test/java/com/kylecorry/frc/vision/filters/RGBFilterTest.java b/src/test/java/com/kylecorry/frc/vision/filters/RGBFilterTest.java new file mode 100644 index 0000000..8ecbc83 --- /dev/null +++ b/src/test/java/com/kylecorry/frc/vision/filters/RGBFilterTest.java @@ -0,0 +1,69 @@ +package com.kylecorry.frc.vision.filters; + +import com.kylecorry.frc.vision.testUtils.OpenCVImageUtils; +import com.kylecorry.frc.vision.testUtils.OpenCVManager; +import com.kylecorry.frc.vision.testUtils.SystemProperties; +import org.junit.Before; +import org.junit.Test; +import org.opencv.core.CvType; +import org.opencv.core.Mat; +import org.opencv.core.Range; +import org.opencv.core.Scalar; + +import static org.junit.Assert.assertTrue; + +public class RGBFilterTest { + + private RGBFilter filter; + + @Before + public void setup(){ + OpenCVManager.getInstance().load(new SystemProperties()); + } + + @Test + public void testFilter() { + Mat image = Mat.zeros(4, 4, CvType.CV_8UC3); + image.row(0).setTo(new Scalar(255, 0, 0)); + image.row(1).setTo(new Scalar(0, 0, 255)); + image.row(2).setTo(new Scalar(0, 255, 0)); + + + filter = new RGBFilter(new Range(1, 255), new Range(0, 0), new Range(0, 0)); + + Mat red = filter.filter(image); + + Mat redRow = Mat.zeros(4, 4, CvType.CV_8UC1); + redRow.row(1).setTo(Scalar.all(255)); + + assertTrue(OpenCVImageUtils.matEquals(redRow, red)); + + filter = new RGBFilter(new Range(0, 0), new Range(0, 0), new Range(1, 255)); + + Mat blue = filter.filter(image); + + Mat blueRow = Mat.zeros(4, 4, CvType.CV_8UC1); + blueRow.row(0).setTo(Scalar.all(255)); + + assertTrue(OpenCVImageUtils.matEquals(blueRow, blue)); + + filter = new RGBFilter(new Range(0, 0), new Range(1, 255), new Range(0, 0)); + + Mat green = filter.filter(image); + + Mat greenRow = Mat.zeros(4, 4, CvType.CV_8UC1); + greenRow.row(2).setTo(Scalar.all(255)); + + assertTrue(OpenCVImageUtils.matEquals(greenRow, green)); + + red.release(); + blue.release(); + green.release(); + redRow.release(); + blueRow.release(); + greenRow.release(); + image.release(); + + + } +} diff --git a/src/test/java/com/kylecorry/frc/vision/targetDetection/SquareTargetSpecs.java b/src/test/java/com/kylecorry/frc/vision/targetDetection/SquareTargetSpecs.java new file mode 100644 index 0000000..fead2cd --- /dev/null +++ b/src/test/java/com/kylecorry/frc/vision/targetDetection/SquareTargetSpecs.java @@ -0,0 +1,19 @@ +package com.kylecorry.frc.vision.targetDetection; + +public class SquareTargetSpecs implements TargetSpecs { + + @Override + public double getWidth() { + return 20; + } + + @Override + public double getHeight() { + return 20; + } + + @Override + public double getArea() { + return getWidth() * getHeight(); + } +} diff --git a/src/test/java/com/kylecorry/frc/vision/targetDetection/TargetDetectorTest.java b/src/test/java/com/kylecorry/frc/vision/targetDetection/TargetDetectorTest.java new file mode 100644 index 0000000..df976f3 --- /dev/null +++ b/src/test/java/com/kylecorry/frc/vision/targetDetection/TargetDetectorTest.java @@ -0,0 +1,48 @@ +package com.kylecorry.frc.vision.targetDetection; + +import com.kylecorry.frc.vision.filters.BrightnessFilter; +import org.junit.Test; +import org.opencv.core.CvType; +import org.opencv.core.Mat; +import org.opencv.core.Point; +import org.opencv.core.Scalar; +import org.opencv.imgproc.Imgproc; + +import java.util.List; + +import static org.junit.Assert.*; + +public class TargetDetectorTest { + + private TargetDetector targetDetector; + + @Test + public void testTargetDetector(){ + targetDetector = new TargetDetector(new SquareTargetSpecs(), new BrightnessFilter(200, 255), 10); + + Mat image = Mat.zeros(100, 100, CvType.CV_8UC3); + Imgproc.rectangle(image, new Point(1, 1), new Point(2, 2), Scalar.all(255)); + Imgproc.rectangle(image, new Point(10, 10), new Point(30, 30), Scalar.all(255)); + + List targets = targetDetector.detect(image); + + assertEquals(1, targets.size()); + + Target target = targets.get(0); + + assertEquals(new com.kylecorry.geometry.Point(20, 20, 0), target.getCenterOfMass()); + + assertEquals(20, target.getHeight(), 0.0001); + + assertEquals(20, target.getWidth(), 0.0001); + + assertEquals(new com.kylecorry.geometry.Point(20, 20, 0), target.getCenterPosition()); + + assertEquals(1.0, target.getIsTargetProbability(), 0.1); + + assertEquals(new com.kylecorry.geometry.Point(10, 10, 0), target.getPosition()); + + assertEquals(image.size(), target.imageSize); + } + +} \ No newline at end of file diff --git a/src/test/java/com/kylecorry/frc/vision/TargetTest.java b/src/test/java/com/kylecorry/frc/vision/targetDetection/TargetTest.java similarity index 60% rename from src/test/java/com/kylecorry/frc/vision/TargetTest.java rename to src/test/java/com/kylecorry/frc/vision/targetDetection/TargetTest.java index c14473d..4ba9e4e 100644 --- a/src/test/java/com/kylecorry/frc/vision/TargetTest.java +++ b/src/test/java/com/kylecorry/frc/vision/targetDetection/TargetTest.java @@ -1,7 +1,8 @@ -package com.kylecorry.frc.vision; +package com.kylecorry.frc.vision.targetDetection; import com.kylecorry.geometry.Point; import org.junit.Test; +import org.opencv.core.Size; import static org.junit.Assert.*; @@ -12,12 +13,12 @@ public class TargetTest { @Test public void testAngle() { - Target target = new Target(1, 10, 10, new Point(40, 245, 0)); - double angle = target.computeAngle(640, 60); + Target target = new Target(1, 10, 10, new Point(40, 245, 0), new Point(40, 245, 0), new Size(640, 480)); + double angle = target.computeAngle(60); assertEquals(116.3, angle, 0.1); - Target target2 = new Target(1, 10, 10, new Point(315, 195, 0)); - angle = target2.computeAngle(640, 60); + Target target2 = new Target(1, 10, 10, new Point(315, 195, 0), new Point(315, 195, 0), new Size(640, 480)); + angle = target2.computeAngle(60); assertEquals(90, angle, 0.1); } diff --git a/src/test/java/com/kylecorry/frc/vision/testUtils/ISystemProperties.java b/src/test/java/com/kylecorry/frc/vision/testUtils/ISystemProperties.java new file mode 100644 index 0000000..e13d4ce --- /dev/null +++ b/src/test/java/com/kylecorry/frc/vision/testUtils/ISystemProperties.java @@ -0,0 +1,9 @@ +package com.kylecorry.frc.vision.testUtils; + +public interface ISystemProperties { + + String getArchitecture(); + + String getOperatingSystem(); + +} diff --git a/src/test/java/com/kylecorry/frc/vision/testUtils/OSInfo.java b/src/test/java/com/kylecorry/frc/vision/testUtils/OSInfo.java new file mode 100644 index 0000000..4d8d7b1 --- /dev/null +++ b/src/test/java/com/kylecorry/frc/vision/testUtils/OSInfo.java @@ -0,0 +1,58 @@ +package com.kylecorry.frc.vision.testUtils; + +public class OSInfo { + + private ISystemProperties systemProperties; + + + public OSInfo(ISystemProperties systemProperties){ + this.systemProperties = systemProperties; + } + + public enum OS { + LINUX, + WINDOWS, + MAC, + OTHER + } + + public enum Architecture { + x64, + x86, + OTHER + } + + public Architecture getArchitecture(){ + String arch = getArchitectureString(); + if(arch.contains("64")){ + return Architecture.x64; + } else if(arch.contains("32") || arch.contains("86")){ + return Architecture.x86; + } else { + return Architecture.OTHER; + } + } + + private String getArchitectureString(){ + return systemProperties.getArchitecture(); + } + + + public OS getOperatingSystem(){ + String osString = getOperatingSystemString(); + if(osString.contains("linux")){ + return OS.LINUX; + } else if (osString.contains("mac") || osString.contains("darwin")){ + return OS.MAC; + } else if (osString.contains("win")){ + return OS.WINDOWS; + } else { + return OS.OTHER; + } + } + + private String getOperatingSystemString(){ + return systemProperties.getOperatingSystem(); + } + +} diff --git a/src/test/java/com/kylecorry/frc/vision/testUtils/OpenCVImageUtils.java b/src/test/java/com/kylecorry/frc/vision/testUtils/OpenCVImageUtils.java new file mode 100644 index 0000000..2dd2d4f --- /dev/null +++ b/src/test/java/com/kylecorry/frc/vision/testUtils/OpenCVImageUtils.java @@ -0,0 +1,14 @@ +package com.kylecorry.frc.vision.testUtils; + +import org.opencv.core.Core; +import org.opencv.core.Mat; + +public class OpenCVImageUtils { + + public static boolean matEquals(Mat img1, Mat img2){ + Mat out = new Mat(); + Core.compare(img1, img2, out, Core.CMP_NE); + return Core.countNonZero(out) == 0; + } + +} diff --git a/src/test/java/com/kylecorry/frc/vision/testUtils/OpenCVManager.java b/src/test/java/com/kylecorry/frc/vision/testUtils/OpenCVManager.java new file mode 100644 index 0000000..a99d423 --- /dev/null +++ b/src/test/java/com/kylecorry/frc/vision/testUtils/OpenCVManager.java @@ -0,0 +1,93 @@ +package com.kylecorry.frc.vision.testUtils; + +import java.io.File; + +/** + * Created by Kylec on 5/14/2017. + */ +public class OpenCVManager { + + private final static String LIB_NAME_LINUX = "libopencv_java340.so"; + private final static String LIB_NAME_WIN_64 = "opencv_java340_64.dll"; + private final static String LIB_NAME_WIN_32 = "opencv_java340_32.dll"; + + private boolean isLoaded = false; + + public class OpenCVLoadException extends RuntimeException { + public OpenCVLoadException() { + super("Could not locate OpenCV libraries."); + } + } + + public class UnsupportedOperatingSystemException extends RuntimeException { + public UnsupportedOperatingSystemException() { + super("Your operating system is not supported."); + } + } + + public class UnsupportedArchitectureException extends RuntimeException { + public UnsupportedArchitectureException() { + super("Your hardware architecture is not supported."); + } + } + + private OpenCVManager() { + } + + private static class OpenCVManagerHolder { + public static final OpenCVManager instance = new OpenCVManager(); + } + + public static OpenCVManager getInstance() { + return OpenCVManagerHolder.instance; + } + + public boolean isLoaded() { + return isLoaded; + } + + public void load(ISystemProperties systemProperties) { + OSInfo osInfo = new OSInfo(systemProperties); + OSInfo.OS os = osInfo.getOperatingSystem(); + OSInfo.Architecture arch = osInfo.getArchitecture(); + String jarLocation = new File(OpenCVManager.class.getProtectionDomain().getCodeSource().getLocation().getPath()).getParent(); + String libName; + if (os == OSInfo.OS.LINUX) { + libName = LIB_NAME_LINUX; + } else if (os == OSInfo.OS.WINDOWS) { + if (arch == OSInfo.Architecture.x64) { + libName = LIB_NAME_WIN_64; + } else if (arch == OSInfo.Architecture.x86) { + libName = LIB_NAME_WIN_32; + } else { + throw new UnsupportedArchitectureException(); + } + } else { + throw new UnsupportedOperatingSystemException(); + } + boolean loaded = tryLoadingLibraries("../lib/" + libName, "libs/" + libName, jarLocation + "/" + libName); + isLoaded = loaded; + if (!loaded) { + throw new OpenCVLoadException(); + } + } + + private boolean tryLoadingLibraries(String... libraryPaths) { + for (String path : libraryPaths) { + if (tryLoadingLibrary(path)) { + return true; + } + } + return false; + } + + private boolean tryLoadingLibrary(String libraryPath) { + try { + System.load(new File(libraryPath).getAbsolutePath()); + return true; + } catch (UnsatisfiedLinkError e) { + return false; + } + } + +} diff --git a/src/test/java/com/kylecorry/frc/vision/testUtils/SystemProperties.java b/src/test/java/com/kylecorry/frc/vision/testUtils/SystemProperties.java new file mode 100644 index 0000000..559741b --- /dev/null +++ b/src/test/java/com/kylecorry/frc/vision/testUtils/SystemProperties.java @@ -0,0 +1,19 @@ +package com.kylecorry.frc.vision.testUtils; + +public class SystemProperties implements ISystemProperties { + + private final static String OS_NAME_PROP = "os.name"; + private final static String OS_NAME_GENERIC_PROP = "generic"; + private final static String ARCH_PROP = "sun.arch.data.model"; + + + @Override + public String getArchitecture() { + return System.getProperty(ARCH_PROP); + } + + @Override + public String getOperatingSystem() { + return System.getProperty(OS_NAME_PROP, OS_NAME_GENERIC_PROP).toLowerCase(); + } +}