Recognizer2013 now recognizes some 2013 targets. More tuning to come.
git-svn-id: https://robotics.mvla.net/svn/frc971/2013/trunk/src@4125 f308d9b7-e957-4cde-b6ac-9a88185e7312
diff --git a/971CV/src/edu/wpi/first/wpijavacv/DaisyExtensions.java b/971CV/src/edu/wpi/first/wpijavacv/DaisyExtensions.java
index f914f4d..31647fb 100644
--- a/971CV/src/edu/wpi/first/wpijavacv/DaisyExtensions.java
+++ b/971CV/src/edu/wpi/first/wpijavacv/DaisyExtensions.java
@@ -52,9 +52,14 @@
return image.image;
}
+ /**
+ * Finds a flat (non-hierarchical) list of contours in the given image, then
+ * computes the convex hull of each contour.
+ */
public WPIContour[] findConvexContours(WPIBinaryImage image) {
image.validateDisposed();
+ // TODO(jerry): Reuse tempImage from frame to frame.
IplImage tempImage = IplImage.create(image.image.cvSize(),
image.image.depth(), 1);
diff --git a/971CV/src/org/frc971/Recognizer2013.java b/971CV/src/org/frc971/Recognizer2013.java
index 9654e3a..f8c8b7d 100644
--- a/971CV/src/org/frc971/Recognizer2013.java
+++ b/971CV/src/org/frc971/Recognizer2013.java
@@ -24,18 +24,29 @@
*/
public class Recognizer2013 implements Recognizer {
- // Constants that need to be tuned
- static final double kRoughlyHorizontalSlope = Math.tan(Math.toRadians(20));
- static final double kRoughlyVerticalSlope = Math.tan(Math.toRadians(90 - 20));
- static final int kMinWidth = 20;
- static final int kMaxWidth = 400;
- static final int kHoleClosingIterations = 9;
+ // --- Constants that need to be tuned.
+ static final double kRoughlyHorizontalSlope = Math.tan(Math.toRadians(25));
+ static final double kRoughlyVerticalSlope = Math.tan(Math.toRadians(90 - 25));
+ static final double kMin1Hue = 55 - 1; // - 1 because cvThreshold() does > not >=
+ static final double kMax1Hue = 118 + 1;
+ static final double kMin1Sat = 80 - 1;
+ static final double kMin1Val = 69 - 1;
+ static final int kHoleClosingIterations = 3;
+ static final double kPolygonPercentFit = 12; // was 20
+
+ static final int kMinWidthAt320 = 35; // for high goal and middle goals
+
+ // These aspect ratios include the outside edges of the vision target tape.
+ static final double kHighGoalAspect = (21 + 8.0) / (54 + 8);
+ static final double kMiddleGoalAspect = (24 + 8.0) / (54 + 8);
+ static final double kMinAspect = kHighGoalAspect * 0.6;
+ static final double kMaxAspect = kMiddleGoalAspect * 1.4;
static final double kShooterOffsetDeg = 0;
static final double kHorizontalFOVDeg = 47.0;
static final double kVerticalFOVDeg = 480.0 / 640.0 * kHorizontalFOVDeg;
- // Colors for drawing indicators on the image.
+ // --- Colors for drawing indicators on the image.
private static final WPIColor reject1Color = WPIColor.GRAY;
private static final WPIColor reject2Color = WPIColor.YELLOW;
private static final WPIColor candidateColor = WPIColor.BLUE;
@@ -45,19 +56,21 @@
private final DebugCanvas thresholdedCanvas = new DebugCanvas("thresholded");
private final DebugCanvas morphedCanvas = new DebugCanvas("morphed");
- // JavaCV data to reuse for each frame.
+ // Data to reuse for each frame.
private final DaisyExtensions daisyExtensions = new DaisyExtensions();
private final IplConvKernel morphKernel = IplConvKernel.create(3, 3, 1, 1,
opencv_imgproc.CV_SHAPE_RECT, null);
- private CvSize size = null;
- private WPIContour[] contours;
private final ArrayList<WPIPolygon> polygons = new ArrayList<WPIPolygon>();
+
+ // Frame-size-dependent data to reuse for each frame.
+ private CvSize size = null;
private WPIColorImage rawImage;
private IplImage bin;
private IplImage hsv;
private IplImage hue;
private IplImage sat;
private IplImage val;
+ private int minWidth;
private WPIPoint linePt1, linePt2; // crosshair endpoints
public Recognizer2013() {
@@ -78,6 +91,7 @@
hue = IplImage.create(size, 8, 1);
sat = IplImage.create(size, 8, 1);
val = IplImage.create(size, 8, 1);
+ minWidth = (kMinWidthAt320 * cameraImage.getWidth() + 319) / 320;
int horizontalOffsetPixels = (int)Math.round(
kShooterOffsetDeg * size.width() / kHorizontalFOVDeg);
@@ -85,6 +99,7 @@
linePt1 = new WPIPoint(x, size.height() - 1);
linePt2 = new WPIPoint(x, 0);
} else {
+ // Copy the camera image so it's safe to draw on.
opencv_core.cvCopy(DaisyExtensions.getIplImage(cameraImage),
DaisyExtensions.getIplImage(rawImage));
}
@@ -93,16 +108,16 @@
// Threshold the pixels in HSV color space.
// TODO(jerry): Do this in one pass of a pixel-processing loop.
- opencv_imgproc.cvCvtColor(input, hsv, opencv_imgproc.CV_BGR2HSV);
+ opencv_imgproc.cvCvtColor(input, hsv, opencv_imgproc.CV_BGR2HSV_FULL);
opencv_core.cvSplit(hsv, hue, sat, val, null);
// NOTE: Since red is at the end of the cyclic color space, you can OR
// a threshold and an inverted threshold to match red pixels.
// TODO(jerry): Use tunable constants instead of literals.
- opencv_imgproc.cvThreshold(hue, bin, 60 - 15, 255, opencv_imgproc.CV_THRESH_BINARY);
- opencv_imgproc.cvThreshold(hue, hue, 60 + 15, 255, opencv_imgproc.CV_THRESH_BINARY_INV);
- opencv_imgproc.cvThreshold(sat, sat, 200, 255, opencv_imgproc.CV_THRESH_BINARY);
- opencv_imgproc.cvThreshold(val, val, 55, 255, opencv_imgproc.CV_THRESH_BINARY);
+ opencv_imgproc.cvThreshold(hue, bin, kMin1Hue, 255, opencv_imgproc.CV_THRESH_BINARY);
+ opencv_imgproc.cvThreshold(hue, hue, kMax1Hue, 255, opencv_imgproc.CV_THRESH_BINARY_INV);
+ opencv_imgproc.cvThreshold(sat, sat, kMin1Sat, 255, opencv_imgproc.CV_THRESH_BINARY);
+ opencv_imgproc.cvThreshold(val, val, kMin1Val, 255, opencv_imgproc.CV_THRESH_BINARY);
// Combine the results to obtain a binary image which is mostly the
// interesting pixels.
@@ -119,29 +134,43 @@
morphedCanvas.showImage(bin);
// Find contours.
+ //
+ // TODO(jerry): Request contours as a two-level hierarchy (blobs and
+ // holes)? The targets have known sizes and their holes have known,
+ // smaller sizes. This matters for distance measurement. OTOH it's moot
+ // if/when we use the vertical stripes for distance measurement.
WPIBinaryImage binWpi = DaisyExtensions.makeWPIBinaryImage(bin);
- contours = daisyExtensions.findConvexContours(binWpi);
+ WPIContour[] contours = daisyExtensions.findConvexContours(binWpi);
- // Simplify the contour to polygons and filter by size and aspect ratio.
- // TODO(jerry): Use tunable constants instead of literals.
+ // Simplify the contours to polygons and filter by size and aspect ratio.
+ //
+ // TODO(jerry): Also look for the two vertical stripe vision targets.
+ // They'll greatly increase the precision of measuring the distance. If
+ // both stripes are visible, they'll increase the accuracy for
+ // identifying the high goal.
polygons.clear();
for (WPIContour c : contours) {
- double ratio = ((double) c.getHeight()) / ((double) c.getWidth());
- if (ratio < 1.0 && ratio > 0.5 && c.getWidth() >= kMinWidth
- && c.getWidth() <= kMaxWidth) {
- polygons.add(c.approxPolygon(20));
+ if (c.getWidth() >= minWidth) {
+ double ratio = ((double) c.getHeight()) / c.getWidth();
+ if (ratio >= kMinAspect && ratio <= kMaxAspect) {
+ polygons.add(c.approxPolygon(kPolygonPercentFit));
+// System.out.println(" Accepted aspect ratio " + ratio);
+ } else {
+// System.out.println(" Rejected aspect ratio " + ratio);
+ }
}
}
- // Pick the highest target that matches more filter criteria.
+ // Pick the target with the highest center-point that matches yet more
+ // filter criteria.
WPIPolygon bestTarget = null;
int highestY = Integer.MAX_VALUE;
for (WPIPolygon p : polygons) {
+ // TODO(jerry): Replace boolean filters with a scoring function?
if (p.isConvex() && p.getNumVertices() == 4) { // quadrilateral
WPIPoint[] points = p.getPoints();
- // We expect the polygon to have a top line that is nearly
- // horizontal and two side lines that are nearly vertical.
+ // Filter for polygons with 2 ~horizontal and 2 ~vertical sides.
int numRoughlyHorizontal = 0;
int numRoughlyVertical = 0;
for (int i = 0; i < 4; ++i) {
@@ -159,14 +188,14 @@
}
}
- if (numRoughlyHorizontal >= 1 && numRoughlyVertical == 2) {
+ if (numRoughlyHorizontal >= 2 && numRoughlyVertical == 2) {
rawImage.drawPolygon(p, candidateColor, 2);
int pCenterX = p.getX() + p.getWidth() / 2;
int pCenterY = p.getY() + p.getHeight() / 2;
rawImage.drawPoint(new WPIPoint(pCenterX, pCenterY),
- candidateColor, 3);
+ targetColor, 2);
if (pCenterY < highestY) {
bestTarget = p;
highestY = pCenterY;
diff --git a/971CV/src/org/frc971/VisionTuner.java b/971CV/src/org/frc971/VisionTuner.java
index 2d57ea5..7b80158 100644
--- a/971CV/src/org/frc971/VisionTuner.java
+++ b/971CV/src/org/frc971/VisionTuner.java
@@ -40,8 +40,6 @@
public VisionTuner(String[] imageFilenames) {
cameraFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
- cameraFrame.setFocusable(true);
- cameraFrame.requestFocus();
loadTestImages(imageFilenames);
}