jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 1 | package org.frc971;
|
| 2 |
|
jerrym | cd2c332 | 2013-02-18 08:49:01 +0000 | [diff] [blame] | 3 | import java.awt.BorderLayout;
|
| 4 | import java.awt.GridLayout;
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 5 | import java.awt.event.KeyEvent;
|
danielp | 54e997e | 2013-02-21 01:54:23 +0000 | [diff] [blame^] | 6 |
|
danielp | 4a35a7a | 2013-02-20 20:45:39 +0000 | [diff] [blame] | 7 | import java.util.Arrays;
|
danielp | 54e997e | 2013-02-21 01:54:23 +0000 | [diff] [blame^] | 8 | import java.util.logging.Level;
|
| 9 | import java.util.logging.Logger;
|
| 10 |
|
| 11 | import java.io.FileNotFoundException;
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 12 |
|
jerrym | cd2c332 | 2013-02-18 08:49:01 +0000 | [diff] [blame] | 13 | import javax.swing.JPanel;
|
| 14 | import javax.swing.JSlider;
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 15 | import javax.swing.WindowConstants;
|
jerrym | cd2c332 | 2013-02-18 08:49:01 +0000 | [diff] [blame] | 16 | import javax.swing.event.ChangeEvent;
|
| 17 | import javax.swing.event.ChangeListener;
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 18 |
|
| 19 | import com.googlecode.javacv.CanvasFrame;
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 20 | import edu.wpi.first.wpijavacv.WPIColorImage;
|
jerrym | 6ebe645 | 2013-02-18 03:00:31 +0000 | [diff] [blame] | 21 | import edu.wpi.first.wpijavacv.WPIImage;
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 22 |
|
| 23 | /* REQUIRED JAVA LIBRARIES:
|
jerrym | 38d8af1 | 2013-02-18 06:54:13 +0000 | [diff] [blame] | 24 | * external_jars/
|
| 25 | * javacpp.jar
|
| 26 | * javacv-YOUR_OS.jar
|
| 27 | * javacv.jar
|
| 28 | * WPIJavaCV.jar
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 29 | *
|
jerrym | 38d8af1 | 2013-02-18 06:54:13 +0000 | [diff] [blame] | 30 | * REQUIRED NATIVE CODE LIBRARIES ON $PATH:
|
| 31 | * Program Files/WPIJavaCV/ [for example]
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 32 | * JavaCV_2.2.0/javacv-bin/javacv-YOUR_OS.jar
|
| 33 | * OpenCV_2.2.0/bin/*
|
jerrym | 38d8af1 | 2013-02-18 06:54:13 +0000 | [diff] [blame] | 34 | *
|
| 35 | * The native libraries and javacv-YOUR_OS.jar must match the 32 vs. 64-bit JVM.
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 36 | */
|
| 37 | /**
|
| 38 | * FRC 2013 vision-target recognizer tuner app.
|
| 39 | *
|
jerrym | f96c32c | 2013-02-18 19:30:45 +0000 | [diff] [blame] | 40 | * <p>
|
| 41 | * See {@link #processEvents()} for the keystroke commands.
|
| 42 | *
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 43 | * @author jerry
|
danielp | 4a35a7a | 2013-02-20 20:45:39 +0000 | [diff] [blame] | 44 | * @author daniel
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 45 | */
|
| 46 | public class VisionTuner {
|
jerrym | 6ebe645 | 2013-02-18 03:00:31 +0000 | [diff] [blame] | 47 | private Recognizer recognizer = new Recognizer2013();
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 48 |
|
danielp | 54e997e | 2013-02-21 01:54:23 +0000 | [diff] [blame^] | 49 | private final static Logger LOG = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
|
| 50 |
|
jerrym | cd2c332 | 2013-02-18 08:49:01 +0000 | [diff] [blame] | 51 | private final CanvasFrame cameraFrame = new CanvasFrame("Camera");
|
| 52 | private final JPanel panel = new JPanel();
|
| 53 | private final JSlider hueMinSlider = new JSlider();
|
| 54 | private final JSlider hueMaxSlider = new JSlider();
|
| 55 | private final JSlider satMinSlider = new JSlider();
|
| 56 | private final JSlider valMinSlider = new JSlider();
|
| 57 |
|
jerrym | f0c8455 | 2013-02-19 00:51:20 +0000 | [diff] [blame] | 58 | private int totalFrames = -1; // don't count the first (warmup) frame
|
jerrym | f96c32c | 2013-02-18 19:30:45 +0000 | [diff] [blame] | 59 | private double totalMsec;
|
| 60 | private double minMsec = Double.MAX_VALUE;
|
| 61 | private double maxMsec;
|
danielp | 4a35a7a | 2013-02-20 20:45:39 +0000 | [diff] [blame] | 62 |
|
| 63 | private TestImageGetter getter;
|
jerrym | a1cd68d | 2013-02-18 09:16:19 +0000 | [diff] [blame] | 64 |
|
danielp | 4a35a7a | 2013-02-20 20:45:39 +0000 | [diff] [blame] | 65 | public VisionTuner() {
|
danielp | 54e997e | 2013-02-21 01:54:23 +0000 | [diff] [blame^] | 66 | //set logger to log everything
|
| 67 | LOG.setLevel(Level.ALL);
|
| 68 | try {
|
| 69 | LogHandler handler = new LogHandler("../src/org/frc971/ds_vision.log");
|
| 70 | TimeFormatter formatter = new TimeFormatter();
|
| 71 | handler.setFormatter(formatter);
|
| 72 | LOG.addHandler(handler);
|
| 73 | }
|
| 74 | catch (FileNotFoundException e) {
|
| 75 | System.err.println("Warning: Logging initialization failed.");
|
| 76 | }
|
| 77 |
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 78 | cameraFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 79 |
|
jerrym | f96c32c | 2013-02-18 19:30:45 +0000 | [diff] [blame] | 80 | recognizer.showIntermediateStages(true);
|
jerrym | cd2c332 | 2013-02-18 08:49:01 +0000 | [diff] [blame] | 81 |
|
| 82 | cameraFrame.getContentPane().add(panel, BorderLayout.SOUTH);
|
| 83 | panel.setLayout(new GridLayout(0, 2, 0, 0));
|
| 84 |
|
| 85 | ChangeListener sliderListener = new ChangeListener() {
|
| 86 | @Override
|
jerrym | dda6013 | 2013-02-18 09:25:03 +0000 | [diff] [blame] | 87 | public void stateChanged(ChangeEvent e) {
|
danielp | 54e997e | 2013-02-21 01:54:23 +0000 | [diff] [blame^] | 88 | LOG.fine("New HSV range ["
|
jerrym | dda6013 | 2013-02-18 09:25:03 +0000 | [diff] [blame] | 89 | + hueMinSlider.getValue() + " .. "
|
jerrym | cd2469c | 2013-02-18 20:15:28 +0000 | [diff] [blame] | 90 | + hueMaxSlider.getValue() + "] "
|
| 91 | + satMinSlider.getValue() + "+ "
|
| 92 | + valMinSlider.getValue() + "+");
|
jerrym | dda6013 | 2013-02-18 09:25:03 +0000 | [diff] [blame] | 93 | recognizer.setHSVRange(
|
| 94 | hueMinSlider.getValue(), hueMaxSlider.getValue(),
|
| 95 | satMinSlider.getValue(),
|
| 96 | valMinSlider.getValue());
|
danielp | 4a35a7a | 2013-02-20 20:45:39 +0000 | [diff] [blame] | 97 | processImage(getter.GetCurrent());
|
jerrym | cd2c332 | 2013-02-18 08:49:01 +0000 | [diff] [blame] | 98 | }
|
| 99 | };
|
| 100 |
|
jerrym | cd2c332 | 2013-02-18 08:49:01 +0000 | [diff] [blame] | 101 | hueMinSlider.setToolTipText("minimum HSV hue");
|
| 102 | hueMinSlider.setMaximum(255);
|
jerrym | cd2469c | 2013-02-18 20:15:28 +0000 | [diff] [blame] | 103 | hueMinSlider.setValue(recognizer.getHueMin());
|
jerrym | cd2c332 | 2013-02-18 08:49:01 +0000 | [diff] [blame] | 104 | panel.add(hueMinSlider);
|
| 105 |
|
jerrym | cd2c332 | 2013-02-18 08:49:01 +0000 | [diff] [blame] | 106 | hueMaxSlider.setToolTipText("maximum HSV hue");
|
| 107 | hueMaxSlider.setMaximum(255);
|
jerrym | cd2469c | 2013-02-18 20:15:28 +0000 | [diff] [blame] | 108 | hueMaxSlider.setValue(recognizer.getHueMax());
|
jerrym | cd2c332 | 2013-02-18 08:49:01 +0000 | [diff] [blame] | 109 | panel.add(hueMaxSlider);
|
| 110 |
|
jerrym | cd2c332 | 2013-02-18 08:49:01 +0000 | [diff] [blame] | 111 | satMinSlider.setToolTipText("minimum HSV color saturation");
|
| 112 | satMinSlider.setMaximum(255);
|
jerrym | cd2469c | 2013-02-18 20:15:28 +0000 | [diff] [blame] | 113 | satMinSlider.setValue(recognizer.getSatMin());
|
jerrym | cd2c332 | 2013-02-18 08:49:01 +0000 | [diff] [blame] | 114 | panel.add(satMinSlider);
|
| 115 |
|
jerrym | cd2c332 | 2013-02-18 08:49:01 +0000 | [diff] [blame] | 116 | valMinSlider.setToolTipText("minimum HSV brightness value");
|
| 117 | valMinSlider.setMaximum(255);
|
jerrym | cd2469c | 2013-02-18 20:15:28 +0000 | [diff] [blame] | 118 | valMinSlider.setValue(recognizer.getValMin());
|
jerrym | cd2c332 | 2013-02-18 08:49:01 +0000 | [diff] [blame] | 119 | panel.add(valMinSlider);
|
| 120 |
|
danielp | 54e997e | 2013-02-21 01:54:23 +0000 | [diff] [blame^] | 121 | LOG.fine("Initial HSV range ["
|
jerrym | cd2469c | 2013-02-18 20:15:28 +0000 | [diff] [blame] | 122 | + hueMinSlider.getValue() + " .. "
|
| 123 | + hueMaxSlider.getValue() + "] "
|
| 124 | + satMinSlider.getValue() + "+ "
|
| 125 | + valMinSlider.getValue() + "+");
|
| 126 |
|
jerrym | cd2c332 | 2013-02-18 08:49:01 +0000 | [diff] [blame] | 127 | hueMinSlider.addChangeListener(sliderListener);
|
| 128 | hueMaxSlider.addChangeListener(sliderListener);
|
| 129 | satMinSlider.addChangeListener(sliderListener);
|
| 130 | valMinSlider.addChangeListener(sliderListener);
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 131 | }
|
| 132 |
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 133 | /**
|
| 134 | * Loads the named test image files.
|
| 135 | * Sets testImageFilenames and testImages.
|
| 136 | */
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 137 |
|
danielp | 4a35a7a | 2013-02-20 20:45:39 +0000 | [diff] [blame] | 138 | private void processImage(WPIColorImage cameraImage) {
|
danielp | 54e997e | 2013-02-21 01:54:23 +0000 | [diff] [blame^] | 139 | cameraFrame.setTitle("Input:");
|
jerrym | 6ebe645 | 2013-02-18 03:00:31 +0000 | [diff] [blame] | 140 |
|
jerrym | a1cd68d | 2013-02-18 09:16:19 +0000 | [diff] [blame] | 141 | long startTime = System.nanoTime();
|
jerrym | 6ebe645 | 2013-02-18 03:00:31 +0000 | [diff] [blame] | 142 | WPIImage processedImage = recognizer.processImage(cameraImage);
|
jerrym | a1cd68d | 2013-02-18 09:16:19 +0000 | [diff] [blame] | 143 | long endTime = System.nanoTime();
|
| 144 |
|
jerrym | 6ebe645 | 2013-02-18 03:00:31 +0000 | [diff] [blame] | 145 | cameraFrame.showImage(processedImage.getBufferedImage());
|
jerrym | a1cd68d | 2013-02-18 09:16:19 +0000 | [diff] [blame] | 146 |
|
| 147 | double milliseconds = (endTime - startTime) / 1e6;
|
jerrym | f0c8455 | 2013-02-19 00:51:20 +0000 | [diff] [blame] | 148 | if (++totalFrames > 0) {
|
| 149 | totalMsec += milliseconds;
|
| 150 | minMsec = Math.min(minMsec, milliseconds);
|
| 151 | maxMsec = Math.max(maxMsec, milliseconds);
|
danielp | 54e997e | 2013-02-21 01:54:23 +0000 | [diff] [blame^] | 152 | LOG.fine("The recognizer took " + milliseconds + " ms, " +
|
| 153 | (1000 * totalFrames / totalMsec) + " fps, %.2f avg");
|
jerrym | f0c8455 | 2013-02-19 00:51:20 +0000 | [diff] [blame] | 154 | }
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 155 | }
|
| 156 |
|
| 157 | private void previousImage() {
|
danielp | 4a35a7a | 2013-02-20 20:45:39 +0000 | [diff] [blame] | 158 | WPIColorImage to_process = getter.GetPrev();
|
| 159 | if (to_process != null)
|
| 160 | processImage(to_process);
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 161 | }
|
| 162 |
|
| 163 | private void nextImage() {
|
danielp | 4a35a7a | 2013-02-20 20:45:39 +0000 | [diff] [blame] | 164 | WPIColorImage to_process = getter.GetNext();
|
| 165 | if (to_process != null)
|
| 166 | processImage(to_process);
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 167 | }
|
| 168 |
|
| 169 | private void processEvents() {
|
jerrym | dda6013 | 2013-02-18 09:25:03 +0000 | [diff] [blame] | 170 | KeyEvent e = cameraFrame.waitKey();
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 171 |
|
jerrym | dda6013 | 2013-02-18 09:25:03 +0000 | [diff] [blame] | 172 | switch (e.getKeyCode()) {
|
jerrym | f96c32c | 2013-02-18 19:30:45 +0000 | [diff] [blame] | 173 | case KeyEvent.VK_LEFT: // left arrow key: go to previous image
|
jerrym | dda6013 | 2013-02-18 09:25:03 +0000 | [diff] [blame] | 174 | previousImage();
|
| 175 | break;
|
jerrym | f96c32c | 2013-02-18 19:30:45 +0000 | [diff] [blame] | 176 | case KeyEvent.VK_RIGHT: // right arrow key: go to next image
|
jerrym | dda6013 | 2013-02-18 09:25:03 +0000 | [diff] [blame] | 177 | nextImage();
|
| 178 | break;
|
jerrym | f96c32c | 2013-02-18 19:30:45 +0000 | [diff] [blame] | 179 | case KeyEvent.VK_Q: // Q: print time measurements then quit
|
danielp | 54e997e | 2013-02-21 01:54:23 +0000 | [diff] [blame^] | 180 | LOG.fine("The recognizer took " + (totalMsec / totalFrames) + "ms avg, " + minMsec +" min,"
|
| 181 | + maxMsec + " max, " + (1000 * totalFrames / totalMsec) + " fps avg");
|
jerrym | f96c32c | 2013-02-18 19:30:45 +0000 | [diff] [blame] | 182 | System.exit(0);
|
jerrym | dda6013 | 2013-02-18 09:25:03 +0000 | [diff] [blame] | 183 | }
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 184 | }
|
| 185 |
|
| 186 | public static void main(final String[] args) {
|
danielp | 4a35a7a | 2013-02-20 20:45:39 +0000 | [diff] [blame] | 187 | VisionTuner tuner = new VisionTuner();
|
| 188 | if (Arrays.asList(args).contains("-debug")) {
|
| 189 | //debug mode has been requested
|
| 190 | tuner.getter = new TestImageGetter(".");
|
| 191 | WPIColorImage to_process = tuner.getter.GetNext();
|
danielp | 54e997e | 2013-02-21 01:54:23 +0000 | [diff] [blame^] | 192 | if (to_process != null) {
|
danielp | 4a35a7a | 2013-02-20 20:45:39 +0000 | [diff] [blame] | 193 | tuner.processImage(to_process);
|
danielp | 54e997e | 2013-02-21 01:54:23 +0000 | [diff] [blame^] | 194 | for (;;) {
|
| 195 | tuner.processEvents();
|
| 196 | }
|
| 197 | }
|
danielp | 4a35a7a | 2013-02-20 20:45:39 +0000 | [diff] [blame] | 198 | else
|
danielp | 54e997e | 2013-02-21 01:54:23 +0000 | [diff] [blame^] | 199 | LOG.severe("Cannot find test images.");
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 200 | }
|
danielp | 4a35a7a | 2013-02-20 20:45:39 +0000 | [diff] [blame] | 201 | else {
|
| 202 | HTTPClient client = new HTTPClient();
|
| 203 | for (;;) {
|
| 204 | ImageWithTimestamp to_process = client.GetFrame();
|
| 205 | if (to_process.image != null) {
|
| 206 | tuner.processImage(to_process.image);
|
danielp | 54e997e | 2013-02-21 01:54:23 +0000 | [diff] [blame^] | 207 | LOG.fine("Captured time: " + Double.toString(to_process.timestamp));
|
danielp | 4a35a7a | 2013-02-20 20:45:39 +0000 | [diff] [blame] | 208 | }
|
| 209 | }
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 210 | }
|
| 211 | }
|
| 212 |
|
| 213 | }
|