danielp | b913fa7 | 2013-03-03 06:23:20 +0000 | [diff] [blame] | 1 | package org.spartanrobotics;
|
| 2 |
|
| 3 | import java.awt.BorderLayout;
|
| 4 | import java.awt.GridLayout;
|
| 5 | import java.awt.event.ActionEvent;
|
| 6 | import java.awt.event.ActionListener;
|
| 7 | import java.awt.event.KeyEvent;
|
| 8 |
|
| 9 | import java.util.Arrays;
|
| 10 | import java.util.logging.Level;
|
| 11 | import java.util.logging.Logger;
|
| 12 |
|
| 13 | import java.io.FileNotFoundException;
|
| 14 | import java.io.IOException;
|
| 15 |
|
| 16 | import javax.swing.JButton;
|
| 17 | import javax.swing.JLabel;
|
| 18 | import javax.swing.JPanel;
|
| 19 | import javax.swing.JSlider;
|
| 20 | import javax.swing.WindowConstants;
|
| 21 | import javax.swing.event.ChangeEvent;
|
| 22 | import javax.swing.event.ChangeListener;
|
| 23 |
|
| 24 | import com.googlecode.javacv.CanvasFrame;
|
| 25 | import edu.wpi.first.wpijavacv.WPIColorImage;
|
| 26 | import edu.wpi.first.wpijavacv.WPIImage;
|
| 27 |
|
| 28 | /* REQUIRED JAVA LIBRARIES:
|
| 29 | * external_jars/
|
| 30 | * javacpp.jar
|
| 31 | * javacv-YOUR_OS.jar
|
| 32 | * javacv.jar
|
| 33 | * WPIJavaCV.jar
|
| 34 | *
|
| 35 | * REQUIRED NATIVE CODE LIBRARIES ON $PATH:
|
| 36 | * Program Files/WPIJavaCV/ [for example]
|
| 37 | * JavaCV_2.2.0/javacv-bin/javacv-YOUR_OS.jar
|
| 38 | * OpenCV_2.2.0/bin/*
|
| 39 | *
|
| 40 | * The native libraries and javacv-YOUR_OS.jar must match the 32 vs. 64-bit JVM.
|
| 41 | */
|
| 42 | /**
|
| 43 | * FRC 2013 vision-target recognizer tuner app.
|
| 44 | *
|
| 45 | * <p>
|
| 46 | * See {@link #processEvents()} for the keystroke commands.
|
| 47 | *
|
| 48 | * @author jerry
|
| 49 | * @author daniel
|
| 50 | */
|
| 51 | public class VisionTuner {
|
| 52 | private Recognizer recognizer = new Recognizer2013();
|
| 53 |
|
| 54 | private final static Logger LOG = Logger.getLogger(
|
| 55 | VisionTuner.class.getName());
|
| 56 |
|
| 57 | private final CanvasFrame cameraFrame = new CanvasFrame("Camera");
|
| 58 | private final JPanel panel = new JPanel();
|
| 59 | private final JSlider hueMinSlider = new JSlider();
|
| 60 | private final JSlider hueMaxSlider = new JSlider();
|
| 61 | private final JSlider satMinSlider = new JSlider();
|
| 62 | private final JSlider valMinSlider = new JSlider();
|
| 63 | private final JButton showCalibration = new JButton("Calibrate");
|
| 64 | private final JLabel frameRate = new JLabel("0");
|
| 65 |
|
| 66 | private ResultSender sender = null;
|
| 67 |
|
| 68 | private int totalFrames = -1; // don't count the first (warm-up) frame
|
| 69 | private double totalMsec;
|
| 70 | private double minMsec = Double.MAX_VALUE;
|
| 71 | private double maxMsec;
|
| 72 |
|
| 73 | private TestImageGetter getter;
|
| 74 |
|
| 75 | private WPIColorImage current;
|
| 76 |
|
| 77 | private boolean debug = false;
|
| 78 |
|
| 79 | long startTime;
|
| 80 | long endTime;
|
| 81 |
|
| 82 | public VisionTuner() {
|
| 83 | //set logger to log everything
|
| 84 | LOG.setLevel(Level.ALL);
|
| 85 | try {
|
| 86 | LogHandler handler = new LogHandler("ds_vision.log");
|
| 87 | TimeFormatter formatter = new TimeFormatter();
|
| 88 | handler.setFormatter(formatter);
|
| 89 | LOG.addHandler(handler);
|
| 90 | } catch (FileNotFoundException e) {
|
| 91 | Messages.warning("Logging initialization failed.");
|
| 92 | }
|
| 93 |
|
| 94 | //initialize result sender
|
| 95 | try {
|
| 96 | sender = new ResultSender();
|
| 97 | } catch (IOException e) {
|
| 98 | LOG.severe("Server initialization failed: " + e.getMessage() + ". Result reporting disabled.");
|
| 99 | }
|
| 100 | cameraFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
|
| 101 |
|
| 102 | cameraFrame.getContentPane().add(panel, BorderLayout.SOUTH);
|
| 103 | panel.setLayout(new GridLayout(0, 2, 0, 0));
|
| 104 |
|
| 105 | showCalibration.setToolTipText("Click here if the system is not finding targets well enough.");
|
| 106 | panel.add(showCalibration);
|
| 107 | showCalibration.addActionListener(new ActionListener() {
|
| 108 | public void actionPerformed(ActionEvent e) {
|
| 109 | showCalibrationWindow();
|
| 110 | }
|
| 111 | });
|
| 112 |
|
| 113 | panel.add(frameRate);
|
| 114 |
|
| 115 | }
|
| 116 |
|
| 117 | /** Shows a calibration window when the user clicks the Calibrate button. */
|
| 118 | private void showCalibrationWindow() {
|
| 119 | final CanvasFrame calibrationWindow = new CanvasFrame("Calibration");
|
| 120 | calibrationWindow.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
| 121 |
|
| 122 | final JPanel panel = new JPanel();
|
| 123 | calibrationWindow.getContentPane().add(panel, BorderLayout.SOUTH);
|
| 124 | panel.setLayout(new GridLayout(3, 3, 0, 0));
|
| 125 |
|
| 126 | hueMinSlider.setToolTipText("minimum HSV hue");
|
| 127 | hueMinSlider.setMaximum(255);
|
| 128 | hueMinSlider.setValue(recognizer.getHueMin());
|
| 129 | panel.add(hueMinSlider);
|
| 130 |
|
| 131 | panel.add(new JLabel("min hue max hue"));
|
| 132 |
|
| 133 | hueMaxSlider.setToolTipText("maximum HSV hue");
|
| 134 | hueMaxSlider.setMaximum(255);
|
| 135 | hueMaxSlider.setValue(recognizer.getHueMax());
|
| 136 | panel.add(hueMaxSlider);
|
| 137 |
|
| 138 | satMinSlider.setToolTipText("minimum HSV color saturation");
|
| 139 | satMinSlider.setMaximum(255);
|
| 140 | satMinSlider.setValue(recognizer.getSatMin());
|
| 141 | panel.add(satMinSlider);
|
| 142 |
|
| 143 | panel.add(new JLabel("min saturation max saturation"));
|
| 144 |
|
| 145 | valMinSlider.setToolTipText("minimum HSV brightness value");
|
| 146 | valMinSlider.setMaximum(255);
|
| 147 | valMinSlider.setValue(recognizer.getValMin());
|
| 148 | panel.add(valMinSlider);
|
| 149 |
|
| 150 | panel.add(new JLabel("")); //empty cells can cause problems
|
| 151 |
|
| 152 | final JButton done = new JButton("Close");
|
| 153 | panel.add(done);
|
| 154 | done.addActionListener(new ActionListener() {
|
| 155 | public void actionPerformed(ActionEvent e) {
|
| 156 | calibrationWindow.dispose();
|
| 157 | }
|
| 158 | });
|
| 159 |
|
| 160 | panel.add(new JLabel("")); //empty cells can cause problems
|
| 161 |
|
| 162 | ChangeListener sliderListener = new ChangeListener() {
|
| 163 | @Override
|
| 164 | public void stateChanged(ChangeEvent e) {
|
| 165 | LOG.fine("New HSV range ["
|
| 166 | + hueMinSlider.getValue() + " .. "
|
| 167 | + hueMaxSlider.getValue() + "] "
|
| 168 | + satMinSlider.getValue() + "+ "
|
| 169 | + valMinSlider.getValue() + "+");
|
| 170 | recognizer.setHSVRange(
|
| 171 | hueMinSlider.getValue(), hueMaxSlider.getValue(),
|
| 172 | satMinSlider.getValue(),
|
| 173 | valMinSlider.getValue());
|
| 174 | if (debug) {
|
| 175 | processImage(current, getter.getName());
|
| 176 | }
|
| 177 | }
|
| 178 | };
|
| 179 |
|
| 180 | hueMinSlider.addChangeListener(sliderListener);
|
| 181 | hueMaxSlider.addChangeListener(sliderListener);
|
| 182 | satMinSlider.addChangeListener(sliderListener);
|
| 183 | valMinSlider.addChangeListener(sliderListener);
|
| 184 |
|
| 185 | calibrationWindow.pack();
|
| 186 |
|
| 187 | }
|
| 188 |
|
| 189 | /**
|
| 190 | * Loads the named test image files.
|
| 191 | * Sets testImageFilenames and testImages.
|
| 192 | */
|
| 193 | private void processImage(WPIColorImage cameraImage, String title) {
|
| 194 | current = cameraImage;
|
| 195 |
|
| 196 | //set window title if it needs to be changed
|
| 197 | cameraFrame.setTitle(title);
|
| 198 |
|
| 199 | Target target = recognizer.processImage(cameraImage);
|
| 200 | WPIImage processedImage = target.editedPicture;
|
| 201 | endTime = System.nanoTime();
|
| 202 |
|
| 203 | cameraFrame.showImage(processedImage.getBufferedImage());
|
| 204 |
|
| 205 | double milliseconds = (endTime - startTime) / 1e6;
|
| 206 | if (++totalFrames > 0) {
|
| 207 | totalMsec += milliseconds;
|
| 208 | minMsec = Math.min(minMsec, milliseconds);
|
| 209 | maxMsec = Math.max(maxMsec, milliseconds);
|
| 210 | LOG.fine("The recognizer took " + milliseconds + " ms, " +
|
| 211 | (1000 * totalFrames / totalMsec) + " fps avg");
|
| 212 | }
|
| 213 |
|
| 214 | //send results to atom. (and any connected clients)
|
| 215 | if (sender != null) {
|
| 216 | sender.send(target.azimuth, target.elevation, target.range);
|
| 217 | }
|
| 218 |
|
| 219 | //show average fps
|
| 220 | double fps = (1000 / milliseconds);
|
| 221 | frameRate.setText("FPS: " + String.valueOf(fps));
|
| 222 |
|
| 223 | }
|
| 224 |
|
| 225 | private void previousImage() {
|
| 226 | WPIColorImage toProcess = getter.getPrev();
|
| 227 | if (toProcess != null)
|
| 228 | processImage(toProcess, getter.getName());
|
| 229 | }
|
| 230 |
|
| 231 | private void nextImage() {
|
| 232 | WPIColorImage toProcess = getter.getFrame();
|
| 233 | if (toProcess != null)
|
| 234 | processImage(toProcess, getter.getName());
|
| 235 | }
|
| 236 |
|
| 237 | private void processEvents() {
|
| 238 | KeyEvent e = cameraFrame.waitKey();
|
| 239 |
|
| 240 | switch (e.getKeyCode()) {
|
| 241 | case KeyEvent.VK_LEFT: // left arrow key: go to previous image
|
| 242 | previousImage();
|
| 243 | break;
|
| 244 | case KeyEvent.VK_RIGHT: // right arrow key: go to next image
|
| 245 | nextImage();
|
| 246 | break;
|
| 247 | case KeyEvent.VK_Q: // Q: print time measurements then quit
|
| 248 | LOG.fine("The recognizer took " + (totalMsec / totalFrames) + "ms avg, " + minMsec +" min,"
|
| 249 | + maxMsec + " max, " + (1000 * totalFrames / totalMsec) + " fps avg");
|
| 250 | System.exit(0);
|
| 251 | }
|
| 252 | }
|
| 253 |
|
| 254 | public static void main(final String[] args) {
|
| 255 | VisionTuner tuner = new VisionTuner();
|
| 256 | Messages.SetWindow(tuner.cameraFrame);
|
| 257 |
|
| 258 | String atomIP = null;
|
| 259 | try {
|
| 260 | atomIP = args[0];
|
| 261 | } catch (ArrayIndexOutOfBoundsException e) {
|
| 262 | System.out.println("Usage: VisionTuner [atom IP address]");
|
| 263 | System.exit(0);
|
| 264 | }
|
| 265 |
|
| 266 | if (Arrays.asList(args).contains("-debug")) {
|
| 267 | //debug mode has been requested
|
| 268 | tuner.debug = true;
|
| 269 |
|
| 270 | //show debugging windows
|
| 271 | tuner.recognizer.showIntermediateStages(true);
|
| 272 | tuner.getter = new TestImageGetter(".");
|
| 273 | WPIColorImage to_process = tuner.getter.getFrame();
|
| 274 | tuner.processImage(to_process, tuner.getter.getName());
|
| 275 | for (;;) {
|
| 276 | tuner.processEvents();
|
| 277 | }
|
| 278 | } else {
|
| 279 | try {
|
| 280 | HTTPClient client = new HTTPClient(atomIP);
|
| 281 | for (;;) {
|
| 282 | tuner.startTime = System.nanoTime();
|
| 283 | WPIColorImage to_process = client.getFrame();
|
| 284 | if (to_process != null) {
|
| 285 | tuner.processImage(to_process, client.getName());
|
| 286 | LOG.fine("Captured time: " + Double.toString(client.getTimestamp()));
|
| 287 | }
|
| 288 | }
|
| 289 | } catch (IOException e) {
|
| 290 | LOG.severe("Client initialization failed: " + e.getMessage() + ".");
|
| 291 | Messages.severe("Client initialization failed: " + e.getMessage() + ".");
|
| 292 | }
|
| 293 | }
|
| 294 | }
|
| 295 |
|
| 296 | }
|