Add sliders for adjusting the HSV thresholds, making tuning practical.
Here's a good cut at tuning those parameters and some of the other parameters.
The recognizer really works!
It can be further tuned and refined.
git-svn-id: https://robotics.mvla.net/svn/frc971/2013/trunk/src@4129 f308d9b7-e957-4cde-b6ac-9a88185e7312
diff --git a/971CV/src/org/frc971/Recognizer.java b/971CV/src/org/frc971/Recognizer.java
index 556cff0..c8818b7 100644
--- a/971CV/src/org/frc971/Recognizer.java
+++ b/971CV/src/org/frc971/Recognizer.java
@@ -9,6 +9,18 @@
* @author jerry
*/
public interface Recognizer {
+
+ /**
+ * Sets the HSV filter to allow H in [minHue .. maxHue], S >= minSat,
+ * V >= minVal.
+ */
+ public void setHSVRange(int minHue, int maxHue, int minSat, int minVal);
+
+ public int getHueMin();
+ public int getHueMax();
+ public int getSatMin();
+ public int getValMin();
+
/**
* Processes a camera image, returning an image to display for targeting
* and debugging, e.g. with cross-hairs and marked targets.
diff --git a/971CV/src/org/frc971/Recognizer2013.java b/971CV/src/org/frc971/Recognizer2013.java
index f8c8b7d..e560f1e 100644
--- a/971CV/src/org/frc971/Recognizer2013.java
+++ b/971CV/src/org/frc971/Recognizer2013.java
@@ -25,14 +25,14 @@
public class Recognizer2013 implements Recognizer {
// --- 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 double kRoughlyHorizontalSlope = Math.tan(Math.toRadians(30));
+ static final double kRoughlyVerticalSlope = Math.tan(Math.toRadians(90 - 30));
+ private int min1Hue;
+ private int max1Hue;
+ private int min1Sat;
+ private int min1Val;
+ static final int kHoleClosingIterations = 2;
+ static final double kPolygonPercentFit = 12;
static final int kMinWidthAt320 = 35; // for high goal and middle goals
@@ -74,9 +74,26 @@
private WPIPoint linePt1, linePt2; // crosshair endpoints
public Recognizer2013() {
+ setHSVRange(70, 106, 137, 27);
}
@Override
+ public void setHSVRange(int minHue, int maxHue, int minSat, int minVal) {
+ min1Hue = minHue - 1; // - 1 because cvThreshold() does > instead of >=
+ max1Hue = maxHue + 1;
+ min1Sat = minSat - 1;
+ min1Val = minVal - 1;
+ }
+ @Override
+ public int getHueMin() { return min1Hue + 1; }
+ @Override
+ public int getHueMax() { return max1Hue - 1; }
+ @Override
+ public int getSatMin() { return min1Sat - 1; }
+ @Override
+ public int getValMin() { return min1Val - 1; }
+
+ @Override
public WPIImage processImage(WPIColorImage cameraImage) {
// (Re)allocate the intermediate images if the input is a different
// size than the previous image.
@@ -114,10 +131,10 @@
// 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, 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);
+ opencv_imgproc.cvThreshold(hue, bin, min1Hue, 255, opencv_imgproc.CV_THRESH_BINARY);
+ opencv_imgproc.cvThreshold(hue, hue, max1Hue, 255, opencv_imgproc.CV_THRESH_BINARY_INV);
+ opencv_imgproc.cvThreshold(sat, sat, min1Sat, 255, opencv_imgproc.CV_THRESH_BINARY);
+ opencv_imgproc.cvThreshold(val, val, min1Val, 255, opencv_imgproc.CV_THRESH_BINARY);
// Combine the results to obtain a binary image which is mostly the
// interesting pixels.
diff --git a/971CV/src/org/frc971/VisionTuner.java b/971CV/src/org/frc971/VisionTuner.java
index 159cafd..757b46c 100644
--- a/971CV/src/org/frc971/VisionTuner.java
+++ b/971CV/src/org/frc971/VisionTuner.java
@@ -1,11 +1,17 @@
package org.frc971;
+import java.awt.BorderLayout;
+import java.awt.GridLayout;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
+import javax.swing.JPanel;
+import javax.swing.JSlider;
import javax.swing.WindowConstants;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
import com.googlecode.javacv.CanvasFrame;
@@ -34,14 +40,64 @@
public class VisionTuner {
private String[] testImageFilenames;
private WPIColorImage[] testImages;
- private final CanvasFrame cameraFrame = new CanvasFrame("Camera");
private int currentIndex = 0;
private Recognizer recognizer = new Recognizer2013();
+ private final CanvasFrame cameraFrame = new CanvasFrame("Camera");
+ private final JPanel panel = new JPanel();
+ private final JSlider hueMinSlider = new JSlider();
+ private final JSlider hueMaxSlider = new JSlider();
+ private final JSlider satMinSlider = new JSlider();
+ private final JSlider valMinSlider = new JSlider();
+
public VisionTuner(String[] imageFilenames) {
cameraFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
loadTestImages(imageFilenames);
+
+ cameraFrame.getContentPane().add(panel, BorderLayout.SOUTH);
+ panel.setLayout(new GridLayout(0, 2, 0, 0));
+
+ ChangeListener sliderListener = new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ System.out.println("New HSV range ["
+ + hueMinSlider.getValue() + " .. "
+ + hueMaxSlider.getValue() + "], ["
+ + satMinSlider.getValue() + " .. 255], ["
+ + valMinSlider.getValue() + " .. 255]");
+ recognizer.setHSVRange(
+ hueMinSlider.getValue(), hueMaxSlider.getValue(),
+ satMinSlider.getValue(),
+ valMinSlider.getValue());
+ processCurrentImage();
+ }
+ };
+
+ hueMinSlider.setValue(recognizer.getHueMin());
+ hueMinSlider.setToolTipText("minimum HSV hue");
+ hueMinSlider.setMaximum(255);
+ panel.add(hueMinSlider);
+
+ hueMaxSlider.setValue(recognizer.getHueMax());
+ hueMaxSlider.setToolTipText("maximum HSV hue");
+ hueMaxSlider.setMaximum(255);
+ panel.add(hueMaxSlider);
+
+ satMinSlider.setValue(recognizer.getSatMin());
+ satMinSlider.setToolTipText("minimum HSV color saturation");
+ satMinSlider.setMaximum(255);
+ panel.add(satMinSlider);
+
+ valMinSlider.setValue(recognizer.getValMin());
+ valMinSlider.setToolTipText("minimum HSV brightness value");
+ valMinSlider.setMaximum(255);
+ panel.add(valMinSlider);
+
+ hueMinSlider.addChangeListener(sliderListener);
+ hueMaxSlider.addChangeListener(sliderListener);
+ satMinSlider.addChangeListener(sliderListener);
+ valMinSlider.addChangeListener(sliderListener);
}
/**
@@ -51,6 +107,7 @@
private void loadTestImages(String[] imageFilenames) {
testImageFilenames = imageFilenames;
testImages = new WPIColorImage[testImageFilenames.length];
+ currentIndex = 0;
for (int i = 0; i < testImageFilenames.length; i++) {
String imageFilename = testImageFilenames[i];