blob: 95c3b8d14631a70bcaea92b0c4ce895248a58748 [file] [log] [blame]
danielpb913fa72013-03-03 06:23:20 +00001package org.spartanrobotics;
2
3import java.awt.BorderLayout;
4import java.awt.GridLayout;
5import java.awt.event.ActionEvent;
6import java.awt.event.ActionListener;
7import java.awt.event.KeyEvent;
8
9import java.util.Arrays;
10import java.util.logging.Level;
11import java.util.logging.Logger;
12
13import java.io.FileNotFoundException;
14import java.io.IOException;
15
16import javax.swing.JButton;
17import javax.swing.JLabel;
18import javax.swing.JPanel;
19import javax.swing.JSlider;
20import javax.swing.WindowConstants;
21import javax.swing.event.ChangeEvent;
22import javax.swing.event.ChangeListener;
23
24import com.googlecode.javacv.CanvasFrame;
25import edu.wpi.first.wpijavacv.WPIColorImage;
26import 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 */
51public 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}