blob: 357b58fa3425299fc24cee747dd81e9f75cf4ace [file] [log] [blame]
jerrymcb7a06a2013-02-17 22:32:29 +00001package org.frc971;
2
jerrymcd2c3322013-02-18 08:49:01 +00003import java.awt.BorderLayout;
4import java.awt.GridLayout;
danielp3c598e52013-02-24 06:12:54 +00005import java.awt.event.ActionEvent;
6import java.awt.event.ActionListener;
jerrymcb7a06a2013-02-17 22:32:29 +00007import java.awt.event.KeyEvent;
danielp54e997e2013-02-21 01:54:23 +00008
danielp4a35a7a2013-02-20 20:45:39 +00009import java.util.Arrays;
danielp54e997e2013-02-21 01:54:23 +000010import java.util.logging.Level;
11import java.util.logging.Logger;
12
13import java.io.FileNotFoundException;
danielp64c4e052013-02-23 07:21:41 +000014import java.io.IOException;
jerrymcb7a06a2013-02-17 22:32:29 +000015
danielp3c598e52013-02-24 06:12:54 +000016import javax.swing.JButton;
17import javax.swing.JLabel;
jerrymcd2c3322013-02-18 08:49:01 +000018import javax.swing.JPanel;
19import javax.swing.JSlider;
jerrymcb7a06a2013-02-17 22:32:29 +000020import javax.swing.WindowConstants;
jerrymcd2c3322013-02-18 08:49:01 +000021import javax.swing.event.ChangeEvent;
22import javax.swing.event.ChangeListener;
jerrymcb7a06a2013-02-17 22:32:29 +000023
24import com.googlecode.javacv.CanvasFrame;
jerrymcb7a06a2013-02-17 22:32:29 +000025import edu.wpi.first.wpijavacv.WPIColorImage;
jerrym6ebe6452013-02-18 03:00:31 +000026import edu.wpi.first.wpijavacv.WPIImage;
jerrymcb7a06a2013-02-17 22:32:29 +000027
28/* REQUIRED JAVA LIBRARIES:
jerrym38d8af12013-02-18 06:54:13 +000029 * external_jars/
30 * javacpp.jar
31 * javacv-YOUR_OS.jar
32 * javacv.jar
33 * WPIJavaCV.jar
jerrymcb7a06a2013-02-17 22:32:29 +000034 *
jerrym38d8af12013-02-18 06:54:13 +000035 * REQUIRED NATIVE CODE LIBRARIES ON $PATH:
36 * Program Files/WPIJavaCV/ [for example]
jerrymcb7a06a2013-02-17 22:32:29 +000037 * JavaCV_2.2.0/javacv-bin/javacv-YOUR_OS.jar
38 * OpenCV_2.2.0/bin/*
jerrym38d8af12013-02-18 06:54:13 +000039 *
40 * The native libraries and javacv-YOUR_OS.jar must match the 32 vs. 64-bit JVM.
jerrymcb7a06a2013-02-17 22:32:29 +000041 */
42/**
43 * FRC 2013 vision-target recognizer tuner app.
44 *
jerrymf96c32c2013-02-18 19:30:45 +000045 * <p>
46 * See {@link #processEvents()} for the keystroke commands.
47 *
jerrymcb7a06a2013-02-17 22:32:29 +000048 * @author jerry
danielp4a35a7a2013-02-20 20:45:39 +000049 * @author daniel
jerrymcb7a06a2013-02-17 22:32:29 +000050 */
51public class VisionTuner {
jerrym6ebe6452013-02-18 03:00:31 +000052 private Recognizer recognizer = new Recognizer2013();
jerrymcb7a06a2013-02-17 22:32:29 +000053
danielp54e997e2013-02-21 01:54:23 +000054 private final static Logger LOG = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
55
jerrymcd2c3322013-02-18 08:49:01 +000056 private final CanvasFrame cameraFrame = new CanvasFrame("Camera");
57 private final JPanel panel = new JPanel();
58 private final JSlider hueMinSlider = new JSlider();
59 private final JSlider hueMaxSlider = new JSlider();
60 private final JSlider satMinSlider = new JSlider();
61 private final JSlider valMinSlider = new JSlider();
danielp3c598e52013-02-24 06:12:54 +000062 private final JButton showCalibration = new JButton("Calibrate");
danielp64c4e052013-02-23 07:21:41 +000063
danielp3c598e52013-02-24 06:12:54 +000064 private ResultSender sender = null;
jerrymcd2c3322013-02-18 08:49:01 +000065
danielp3c598e52013-02-24 06:12:54 +000066 private int totalFrames = -1; // don't count the first (warm-up) frame
jerrymf96c32c2013-02-18 19:30:45 +000067 private double totalMsec;
68 private double minMsec = Double.MAX_VALUE;
69 private double maxMsec;
danielp4a35a7a2013-02-20 20:45:39 +000070
71 private TestImageGetter getter;
danielpb3d24ee2013-02-22 19:47:11 +000072
73 private WPIColorImage current;
danielp3c598e52013-02-24 06:12:54 +000074
75 private String currentWindowTitle;
76
77 private boolean debug = false;
jerryma1cd68d2013-02-18 09:16:19 +000078
danielp4a35a7a2013-02-20 20:45:39 +000079 public VisionTuner() {
danielp54e997e2013-02-21 01:54:23 +000080 //set logger to log everything
81 LOG.setLevel(Level.ALL);
82 try {
danielp3c598e52013-02-24 06:12:54 +000083 LogHandler handler = new LogHandler("ds_vision.log");
danielp54e997e2013-02-21 01:54:23 +000084 TimeFormatter formatter = new TimeFormatter();
85 handler.setFormatter(formatter);
86 LOG.addHandler(handler);
87 }
88 catch (FileNotFoundException e) {
danielp3c598e52013-02-24 06:12:54 +000089 Messages.warning("Logging initialization failed.");
danielp54e997e2013-02-21 01:54:23 +000090 }
91
danielp64c4e052013-02-23 07:21:41 +000092 //initialize result sender
93 try {
94 sender = new ResultSender();
95 }
96 catch (IOException e) {
danielp3c598e52013-02-24 06:12:54 +000097 LOG.severe("Server initialization failed: " + e.getMessage() + ". Result reporting disabled.");
danielp64c4e052013-02-23 07:21:41 +000098 }
jerrymcb7a06a2013-02-17 22:32:29 +000099 cameraFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
jerrymcb7a06a2013-02-17 22:32:29 +0000100
jerrymcd2c3322013-02-18 08:49:01 +0000101 cameraFrame.getContentPane().add(panel, BorderLayout.SOUTH);
danielp3c598e52013-02-24 06:12:54 +0000102 panel.setLayout(new GridLayout(0, 1, 0, 0));
103
104 showCalibration.setToolTipText("Click here if the system is not finding targets well enough.");
105 panel.add(showCalibration);
106 showCalibration.addActionListener(new ActionListener() {
107 public void actionPerformed(ActionEvent e) {
108 showCalibrationWindow();
109 }
110 });
jerrymcd2c3322013-02-18 08:49:01 +0000111
danielp3c598e52013-02-24 06:12:54 +0000112 LOG.fine("Initial HSV range ["
113 + hueMinSlider.getValue() + " .. "
114 + hueMaxSlider.getValue() + "] "
115 + satMinSlider.getValue() + "+ "
116 + valMinSlider.getValue() + "+");
117 }
118
119 /** Shows a calibration window when the user clicks the Calibrate button. */
120 private void showCalibrationWindow() {
121 final CanvasFrame calibrationWindow = new CanvasFrame("Calibration");
122 calibrationWindow.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
123
124 final JPanel panel = new JPanel();
125 calibrationWindow.getContentPane().add(panel, BorderLayout.SOUTH);
126 panel.setLayout(new GridLayout(3, 3, 0, 0));
127
128 hueMinSlider.setToolTipText("minimum HSV hue");
129 hueMinSlider.setMaximum(255);
130 hueMinSlider.setValue(recognizer.getHueMin());
131 panel.add(hueMinSlider);
132
133 panel.add(new JLabel("min hue max hue"));
134
135 hueMaxSlider.setToolTipText("maximum HSV hue");
136 hueMaxSlider.setMaximum(255);
137 hueMaxSlider.setValue(recognizer.getHueMax());
138 panel.add(hueMaxSlider);
139
140 satMinSlider.setToolTipText("minimum HSV color saturation");
141 satMinSlider.setMaximum(255);
142 satMinSlider.setValue(recognizer.getSatMin());
143 panel.add(satMinSlider);
144
145 panel.add(new JLabel("min saturation max saturation"));
146
147 valMinSlider.setToolTipText("minimum HSV brightness value");
148 valMinSlider.setMaximum(255);
149 valMinSlider.setValue(recognizer.getValMin());
150 panel.add(valMinSlider);
151
152 panel.add(new JLabel("")); //empty cells can cause problems
153
154 final JButton done = new JButton("Done");
155 panel.add(done);
156 done.addActionListener(new ActionListener() {
157 public void actionPerformed(ActionEvent e) {
158 calibrationWindow.dispose();
159 }
160 });
161
162 panel.add(new JLabel("")); //empty cells can cause problems
163
jerrymcd2c3322013-02-18 08:49:01 +0000164 ChangeListener sliderListener = new ChangeListener() {
165 @Override
jerrymdda60132013-02-18 09:25:03 +0000166 public void stateChanged(ChangeEvent e) {
danielp54e997e2013-02-21 01:54:23 +0000167 LOG.fine("New HSV range ["
jerrymdda60132013-02-18 09:25:03 +0000168 + hueMinSlider.getValue() + " .. "
jerrymcd2469c2013-02-18 20:15:28 +0000169 + hueMaxSlider.getValue() + "] "
170 + satMinSlider.getValue() + "+ "
171 + valMinSlider.getValue() + "+");
jerrymdda60132013-02-18 09:25:03 +0000172 recognizer.setHSVRange(
173 hueMinSlider.getValue(), hueMaxSlider.getValue(),
174 satMinSlider.getValue(),
175 valMinSlider.getValue());
danielp3c598e52013-02-24 06:12:54 +0000176 if (debug) {
177 processImage(current, null);
178 }
jerrymcd2c3322013-02-18 08:49:01 +0000179 }
180 };
danielp3c598e52013-02-24 06:12:54 +0000181
jerrymcd2c3322013-02-18 08:49:01 +0000182 hueMinSlider.addChangeListener(sliderListener);
183 hueMaxSlider.addChangeListener(sliderListener);
184 satMinSlider.addChangeListener(sliderListener);
185 valMinSlider.addChangeListener(sliderListener);
danielp3c598e52013-02-24 06:12:54 +0000186
187 calibrationWindow.pack();
188
jerrymcb7a06a2013-02-17 22:32:29 +0000189 }
190
jerrymcb7a06a2013-02-17 22:32:29 +0000191 /**
192 * Loads the named test image files.
193 * Sets testImageFilenames and testImages.
194 */
danielp3c598e52013-02-24 06:12:54 +0000195 private void processImage(WPIColorImage cameraImage, String title) {
danielpb3d24ee2013-02-22 19:47:11 +0000196 current = cameraImage;
danielp3c598e52013-02-24 06:12:54 +0000197
198 //set window title if it needs to be changed
199 if (title != null && !title.equals(currentWindowTitle)) {
200 cameraFrame.setTitle(title);
201 currentWindowTitle = title;
202 }
jerrym6ebe6452013-02-18 03:00:31 +0000203
jerryma1cd68d2013-02-18 09:16:19 +0000204 long startTime = System.nanoTime();
danielp3c598e52013-02-24 06:12:54 +0000205 Target target = recognizer.processImage(cameraImage);
206 WPIImage processedImage = target.editedPicture;
jerryma1cd68d2013-02-18 09:16:19 +0000207 long endTime = System.nanoTime();
208
jerrym6ebe6452013-02-18 03:00:31 +0000209 cameraFrame.showImage(processedImage.getBufferedImage());
jerryma1cd68d2013-02-18 09:16:19 +0000210
211 double milliseconds = (endTime - startTime) / 1e6;
jerrymf0c84552013-02-19 00:51:20 +0000212 if (++totalFrames > 0) {
213 totalMsec += milliseconds;
214 minMsec = Math.min(minMsec, milliseconds);
215 maxMsec = Math.max(maxMsec, milliseconds);
danielp54e997e2013-02-21 01:54:23 +0000216 LOG.fine("The recognizer took " + milliseconds + " ms, " +
217 (1000 * totalFrames / totalMsec) + " fps, %.2f avg");
jerrymf0c84552013-02-19 00:51:20 +0000218 }
danielp64c4e052013-02-23 07:21:41 +0000219
220 //send results to atom. (and any connected clients)
danielp3c598e52013-02-24 06:12:54 +0000221 if (sender != null) {
222 sender.send(target.azimuth, target.elevation, target.range);
223 }
danielp64c4e052013-02-23 07:21:41 +0000224
jerrymcb7a06a2013-02-17 22:32:29 +0000225 }
226
227 private void previousImage() {
danielp4a35a7a2013-02-20 20:45:39 +0000228 WPIColorImage to_process = getter.GetPrev();
229 if (to_process != null)
danielp3c598e52013-02-24 06:12:54 +0000230 processImage(to_process, getter.GetName());
jerrymcb7a06a2013-02-17 22:32:29 +0000231 }
232
233 private void nextImage() {
danielp4a35a7a2013-02-20 20:45:39 +0000234 WPIColorImage to_process = getter.GetNext();
235 if (to_process != null)
danielp3c598e52013-02-24 06:12:54 +0000236 processImage(to_process, getter.GetName());
jerrymcb7a06a2013-02-17 22:32:29 +0000237 }
238
239 private void processEvents() {
jerrymdda60132013-02-18 09:25:03 +0000240 KeyEvent e = cameraFrame.waitKey();
jerrymcb7a06a2013-02-17 22:32:29 +0000241
jerrymdda60132013-02-18 09:25:03 +0000242 switch (e.getKeyCode()) {
jerrymf96c32c2013-02-18 19:30:45 +0000243 case KeyEvent.VK_LEFT: // left arrow key: go to previous image
jerrymdda60132013-02-18 09:25:03 +0000244 previousImage();
245 break;
jerrymf96c32c2013-02-18 19:30:45 +0000246 case KeyEvent.VK_RIGHT: // right arrow key: go to next image
jerrymdda60132013-02-18 09:25:03 +0000247 nextImage();
248 break;
jerrymf96c32c2013-02-18 19:30:45 +0000249 case KeyEvent.VK_Q: // Q: print time measurements then quit
danielp54e997e2013-02-21 01:54:23 +0000250 LOG.fine("The recognizer took " + (totalMsec / totalFrames) + "ms avg, " + minMsec +" min,"
251 + maxMsec + " max, " + (1000 * totalFrames / totalMsec) + " fps avg");
jerrymf96c32c2013-02-18 19:30:45 +0000252 System.exit(0);
jerrymdda60132013-02-18 09:25:03 +0000253 }
jerrymcb7a06a2013-02-17 22:32:29 +0000254 }
255
256 public static void main(final String[] args) {
danielp4a35a7a2013-02-20 20:45:39 +0000257 VisionTuner tuner = new VisionTuner();
danielp3c598e52013-02-24 06:12:54 +0000258 Messages.SetWindow(tuner.cameraFrame);
259
260 String atomIP = null;
261 try {
262 atomIP = args[0];
263 }
264 catch (ArrayIndexOutOfBoundsException e) {
265 System.out.println("Usage: VisionTuner [atom ip]");
266 System.exit(0);
267 }
268
danielp4a35a7a2013-02-20 20:45:39 +0000269 if (Arrays.asList(args).contains("-debug")) {
270 //debug mode has been requested
danielp3c598e52013-02-24 06:12:54 +0000271 tuner.debug = true;
272
273 //show debugging windows
274 tuner.recognizer.showIntermediateStages(true);
275
danielp4a35a7a2013-02-20 20:45:39 +0000276 tuner.getter = new TestImageGetter(".");
277 WPIColorImage to_process = tuner.getter.GetNext();
danielp54e997e2013-02-21 01:54:23 +0000278 if (to_process != null) {
danielp3c598e52013-02-24 06:12:54 +0000279 tuner.processImage(to_process, tuner.getter.GetName());
danielp54e997e2013-02-21 01:54:23 +0000280 for (;;) {
281 tuner.processEvents();
282 }
283 }
danielp3c598e52013-02-24 06:12:54 +0000284 else {
285 LOG.severe("Could not load test images.");
286 Messages.severe("Could not load test images.");
287 }
jerrymcb7a06a2013-02-17 22:32:29 +0000288 }
danielp4a35a7a2013-02-20 20:45:39 +0000289 else {
danielp64c4e052013-02-23 07:21:41 +0000290 try {
danielp3c598e52013-02-24 06:12:54 +0000291 HTTPClient client = new HTTPClient(atomIP);
danielp64c4e052013-02-23 07:21:41 +0000292 for (;;) {
293 ImageWithTimestamp to_process = client.GetFrame();
294 if (to_process.image != null) {
danielp3c598e52013-02-24 06:12:54 +0000295 tuner.processImage(to_process.image, client.GetName());
danielp64c4e052013-02-23 07:21:41 +0000296 LOG.fine("Captured time: " + Double.toString(to_process.timestamp));
297 }
298 }
299 }
300 catch (IOException e) {
danielp3c598e52013-02-24 06:12:54 +0000301 LOG.severe("Client initialization failed: " + e.getMessage() + ".");
302 Messages.severe("Client initialization failed: " + e.getMessage() + ".");
danielp4a35a7a2013-02-20 20:45:39 +0000303 }
jerrymcb7a06a2013-02-17 22:32:29 +0000304 }
305 }
306
307}