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;
|
| 6 | import java.io.File;
|
| 7 | import java.io.IOException;
|
| 8 |
|
| 9 | import javax.imageio.ImageIO;
|
jerrym | cd2c332 | 2013-02-18 08:49:01 +0000 | [diff] [blame] | 10 | import javax.swing.JPanel;
|
| 11 | import javax.swing.JSlider;
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 12 | import javax.swing.WindowConstants;
|
jerrym | cd2c332 | 2013-02-18 08:49:01 +0000 | [diff] [blame] | 13 | import javax.swing.event.ChangeEvent;
|
| 14 | import javax.swing.event.ChangeListener;
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 15 |
|
| 16 | import com.googlecode.javacv.CanvasFrame;
|
| 17 |
|
| 18 | import edu.wpi.first.wpijavacv.WPIColorImage;
|
jerrym | 6ebe645 | 2013-02-18 03:00:31 +0000 | [diff] [blame] | 19 | import edu.wpi.first.wpijavacv.WPIImage;
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 20 |
|
| 21 | /* REQUIRED JAVA LIBRARIES:
|
jerrym | 38d8af1 | 2013-02-18 06:54:13 +0000 | [diff] [blame] | 22 | * external_jars/
|
| 23 | * javacpp.jar
|
| 24 | * javacv-YOUR_OS.jar
|
| 25 | * javacv.jar
|
| 26 | * WPIJavaCV.jar
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 27 | *
|
jerrym | 38d8af1 | 2013-02-18 06:54:13 +0000 | [diff] [blame] | 28 | * REQUIRED NATIVE CODE LIBRARIES ON $PATH:
|
| 29 | * Program Files/WPIJavaCV/ [for example]
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 30 | * JavaCV_2.2.0/javacv-bin/javacv-YOUR_OS.jar
|
| 31 | * OpenCV_2.2.0/bin/*
|
jerrym | 38d8af1 | 2013-02-18 06:54:13 +0000 | [diff] [blame] | 32 | *
|
| 33 | * 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] | 34 | */
|
| 35 | /**
|
| 36 | * FRC 2013 vision-target recognizer tuner app.
|
| 37 | *
|
jerrym | f96c32c | 2013-02-18 19:30:45 +0000 | [diff] [blame^] | 38 | * <p>
|
| 39 | * See {@link #processEvents()} for the keystroke commands.
|
| 40 | *
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 41 | * @author jerry
|
| 42 | */
|
| 43 | public class VisionTuner {
|
| 44 | private String[] testImageFilenames;
|
| 45 | private WPIColorImage[] testImages;
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 46 | private int currentIndex = 0;
|
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 |
|
jerrym | cd2c332 | 2013-02-18 08:49:01 +0000 | [diff] [blame] | 49 | private final CanvasFrame cameraFrame = new CanvasFrame("Camera");
|
| 50 | private final JPanel panel = new JPanel();
|
| 51 | private final JSlider hueMinSlider = new JSlider();
|
| 52 | private final JSlider hueMaxSlider = new JSlider();
|
| 53 | private final JSlider satMinSlider = new JSlider();
|
| 54 | private final JSlider valMinSlider = new JSlider();
|
| 55 |
|
jerrym | f96c32c | 2013-02-18 19:30:45 +0000 | [diff] [blame^] | 56 | private int totalFrames;
|
| 57 | private double totalMsec;
|
| 58 | private double minMsec = Double.MAX_VALUE;
|
| 59 | private double maxMsec;
|
jerrym | a1cd68d | 2013-02-18 09:16:19 +0000 | [diff] [blame] | 60 |
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 61 | public VisionTuner(String[] imageFilenames) {
|
| 62 | cameraFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 63 |
|
| 64 | loadTestImages(imageFilenames);
|
jerrym | f96c32c | 2013-02-18 19:30:45 +0000 | [diff] [blame^] | 65 | recognizer.showIntermediateStages(true);
|
jerrym | cd2c332 | 2013-02-18 08:49:01 +0000 | [diff] [blame] | 66 |
|
| 67 | cameraFrame.getContentPane().add(panel, BorderLayout.SOUTH);
|
| 68 | panel.setLayout(new GridLayout(0, 2, 0, 0));
|
| 69 |
|
| 70 | ChangeListener sliderListener = new ChangeListener() {
|
| 71 | @Override
|
jerrym | dda6013 | 2013-02-18 09:25:03 +0000 | [diff] [blame] | 72 | public void stateChanged(ChangeEvent e) {
|
| 73 | System.out.println("New HSV range ["
|
| 74 | + hueMinSlider.getValue() + " .. "
|
| 75 | + hueMaxSlider.getValue() + "], ["
|
| 76 | + satMinSlider.getValue() + " .. 255], ["
|
| 77 | + valMinSlider.getValue() + " .. 255]");
|
| 78 | recognizer.setHSVRange(
|
| 79 | hueMinSlider.getValue(), hueMaxSlider.getValue(),
|
| 80 | satMinSlider.getValue(),
|
| 81 | valMinSlider.getValue());
|
| 82 | processCurrentImage();
|
jerrym | cd2c332 | 2013-02-18 08:49:01 +0000 | [diff] [blame] | 83 | }
|
| 84 | };
|
| 85 |
|
| 86 | hueMinSlider.setValue(recognizer.getHueMin());
|
| 87 | hueMinSlider.setToolTipText("minimum HSV hue");
|
| 88 | hueMinSlider.setMaximum(255);
|
| 89 | panel.add(hueMinSlider);
|
| 90 |
|
| 91 | hueMaxSlider.setValue(recognizer.getHueMax());
|
| 92 | hueMaxSlider.setToolTipText("maximum HSV hue");
|
| 93 | hueMaxSlider.setMaximum(255);
|
| 94 | panel.add(hueMaxSlider);
|
| 95 |
|
| 96 | satMinSlider.setValue(recognizer.getSatMin());
|
| 97 | satMinSlider.setToolTipText("minimum HSV color saturation");
|
| 98 | satMinSlider.setMaximum(255);
|
| 99 | panel.add(satMinSlider);
|
| 100 |
|
| 101 | valMinSlider.setValue(recognizer.getValMin());
|
| 102 | valMinSlider.setToolTipText("minimum HSV brightness value");
|
| 103 | valMinSlider.setMaximum(255);
|
| 104 | panel.add(valMinSlider);
|
| 105 |
|
| 106 | hueMinSlider.addChangeListener(sliderListener);
|
| 107 | hueMaxSlider.addChangeListener(sliderListener);
|
| 108 | satMinSlider.addChangeListener(sliderListener);
|
| 109 | valMinSlider.addChangeListener(sliderListener);
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 110 | }
|
| 111 |
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 112 | /**
|
| 113 | * Loads the named test image files.
|
| 114 | * Sets testImageFilenames and testImages.
|
| 115 | */
|
| 116 | private void loadTestImages(String[] imageFilenames) {
|
jerrym | dda6013 | 2013-02-18 09:25:03 +0000 | [diff] [blame] | 117 | testImageFilenames = imageFilenames;
|
| 118 | testImages = new WPIColorImage[testImageFilenames.length];
|
| 119 | currentIndex = 0;
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 120 |
|
jerrym | dda6013 | 2013-02-18 09:25:03 +0000 | [diff] [blame] | 121 | for (int i = 0; i < testImageFilenames.length; i++) {
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 122 | String imageFilename = testImageFilenames[i];
|
| 123 |
|
| 124 | System.out.println("Loading image file: " + imageFilename);
|
| 125 | WPIColorImage rawImage = null;
|
| 126 | try {
|
jerrym | 6ebe645 | 2013-02-18 03:00:31 +0000 | [diff] [blame] | 127 | rawImage = new WPIColorImage(ImageIO.read(
|
jerrym | dda6013 | 2013-02-18 09:25:03 +0000 | [diff] [blame] | 128 | new File(imageFilename)));
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 129 | } catch (IOException e) {
|
jerrym | 6ebe645 | 2013-02-18 03:00:31 +0000 | [diff] [blame] | 130 | System.err.println("Couldn't load image file: " + imageFilename
|
jerrym | dda6013 | 2013-02-18 09:25:03 +0000 | [diff] [blame] | 131 | + ": " + e.getMessage());
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 132 | System.exit(1);
|
| 133 | return;
|
| 134 | }
|
| 135 | testImages[i] = rawImage;
|
jerrym | dda6013 | 2013-02-18 09:25:03 +0000 | [diff] [blame] | 136 | }
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 137 | }
|
| 138 |
|
| 139 | private void processCurrentImage() {
|
jerrym | 6ebe645 | 2013-02-18 03:00:31 +0000 | [diff] [blame] | 140 | WPIColorImage cameraImage = testImages[currentIndex];
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 141 | cameraFrame.setTitle(testImageFilenames[currentIndex]);
|
jerrym | 6ebe645 | 2013-02-18 03:00:31 +0000 | [diff] [blame] | 142 |
|
jerrym | a1cd68d | 2013-02-18 09:16:19 +0000 | [diff] [blame] | 143 | long startTime = System.nanoTime();
|
jerrym | 6ebe645 | 2013-02-18 03:00:31 +0000 | [diff] [blame] | 144 | WPIImage processedImage = recognizer.processImage(cameraImage);
|
jerrym | a1cd68d | 2013-02-18 09:16:19 +0000 | [diff] [blame] | 145 | long endTime = System.nanoTime();
|
| 146 |
|
jerrym | 6ebe645 | 2013-02-18 03:00:31 +0000 | [diff] [blame] | 147 | cameraFrame.showImage(processedImage.getBufferedImage());
|
jerrym | a1cd68d | 2013-02-18 09:16:19 +0000 | [diff] [blame] | 148 |
|
| 149 | double milliseconds = (endTime - startTime) / 1e6;
|
| 150 | ++totalFrames;
|
| 151 | totalMsec += milliseconds;
|
jerrym | f96c32c | 2013-02-18 19:30:45 +0000 | [diff] [blame^] | 152 | minMsec = Math.min(minMsec, milliseconds);
|
| 153 | maxMsec = Math.max(maxMsec, milliseconds);
|
| 154 | System.out.format("The recognizer took %.2f ms, %.2f fps, %.2f avg%n",
|
jerrym | dda6013 | 2013-02-18 09:25:03 +0000 | [diff] [blame] | 155 | milliseconds, 1000 / milliseconds,
|
| 156 | 1000 * totalFrames / totalMsec);
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 157 | }
|
| 158 |
|
| 159 | private void previousImage() {
|
jerrym | dda6013 | 2013-02-18 09:25:03 +0000 | [diff] [blame] | 160 | if (currentIndex > 0) {
|
| 161 | --currentIndex;
|
| 162 | }
|
| 163 | processCurrentImage();
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 164 | }
|
| 165 |
|
| 166 | private void nextImage() {
|
jerrym | dda6013 | 2013-02-18 09:25:03 +0000 | [diff] [blame] | 167 | if (currentIndex + 1 < testImages.length) {
|
| 168 | ++currentIndex;
|
| 169 | }
|
| 170 | processCurrentImage();
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 171 | }
|
| 172 |
|
| 173 | private void processEvents() {
|
jerrym | dda6013 | 2013-02-18 09:25:03 +0000 | [diff] [blame] | 174 | KeyEvent e = cameraFrame.waitKey();
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 175 |
|
jerrym | dda6013 | 2013-02-18 09:25:03 +0000 | [diff] [blame] | 176 | switch (e.getKeyCode()) {
|
jerrym | f96c32c | 2013-02-18 19:30:45 +0000 | [diff] [blame^] | 177 | case KeyEvent.VK_LEFT: // left arrow key: go to previous image
|
jerrym | dda6013 | 2013-02-18 09:25:03 +0000 | [diff] [blame] | 178 | previousImage();
|
| 179 | break;
|
jerrym | f96c32c | 2013-02-18 19:30:45 +0000 | [diff] [blame^] | 180 | case KeyEvent.VK_RIGHT: // right arrow key: go to next image
|
jerrym | dda6013 | 2013-02-18 09:25:03 +0000 | [diff] [blame] | 181 | nextImage();
|
| 182 | break;
|
jerrym | f96c32c | 2013-02-18 19:30:45 +0000 | [diff] [blame^] | 183 | case KeyEvent.VK_Q: // Q: print time measurements then quit
|
| 184 | System.out.format("The recognizer took %.2f ms avg, %.2f min,"
|
| 185 | + " %.2f max, %.2f fps avg%n",
|
| 186 | totalMsec / totalFrames,
|
| 187 | minMsec, maxMsec,
|
| 188 | 1000 * totalFrames / totalMsec);
|
| 189 | System.exit(0);
|
jerrym | dda6013 | 2013-02-18 09:25:03 +0000 | [diff] [blame] | 190 | }
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 191 | }
|
| 192 |
|
| 193 | public static void main(final String[] args) {
|
| 194 | if (args.length == 0) {
|
| 195 | System.err.println("Usage: " + VisionTuner.class.getName()
|
jerrym | dda6013 | 2013-02-18 09:25:03 +0000 | [diff] [blame] | 196 | + " test image filenames...");
|
jerrym | cb7a06a | 2013-02-17 22:32:29 +0000 | [diff] [blame] | 197 | System.exit(1);
|
| 198 | }
|
| 199 |
|
| 200 | VisionTuner tuner = new VisionTuner(args);
|
| 201 | tuner.processCurrentImage();
|
| 202 |
|
| 203 | for (;;) {
|
| 204 | tuner.processEvents();
|
| 205 | }
|
| 206 | }
|
| 207 |
|
| 208 | }
|