-Changed application to display image source at the top of the window.
-Removed the extra windows to show intermediate stages when not in debug mode.
Interestingly, this improved performance significantly.
-Modified slider listener so that it (hopefully) doesn't cause any more segfaults.
-Hid all the calibration controls away in a separate calibration window.
They are accessed by a button on the main display.
I also added labels to each of the sliders.
-Application now takes the IP address of the atom as a command-line argument.
-Code now actually uses result sender, which I had forgot to do last time.
-I made a small modification to Brian's code which reduced the application's
average consumption of RAM from two gigabytes to eight hundred megabytes.
git-svn-id: https://robotics.mvla.net/svn/frc971/2013/trunk/src@4151 f308d9b7-e957-4cde-b6ac-9a88185e7312
diff --git a/971CV/src/org/frc971/AccepterThread.java b/971CV/src/org/frc971/AccepterThread.java
index e6cd5dd..1cd71e8 100644
--- a/971CV/src/org/frc971/AccepterThread.java
+++ b/971CV/src/org/frc971/AccepterThread.java
@@ -67,7 +67,8 @@
connected.add(clientSock);
}
catch (IOException e) {
- LOG.warning("Socket accept failed.");
+ LOG.warning("Cannot serve image processing results to client:" + e.getMessage());
+ Messages.warning("Cannot serve image processing results to client:" + e.getMessage());
}
}
}
diff --git a/971CV/src/org/frc971/DebugServerRun.java b/971CV/src/org/frc971/DebugServerRun.java
index 0f60665..cccf241 100644
--- a/971CV/src/org/frc971/DebugServerRun.java
+++ b/971CV/src/org/frc971/DebugServerRun.java
@@ -12,6 +12,7 @@
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
+import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -93,6 +94,7 @@
}
}
}
+
/** Constructor to start the server and bind it to a port. */
public DebugServerRun(final int port) throws IOException {
sock = ServerSocketChannel.open();
@@ -106,9 +108,20 @@
LOG.info("Writing headers...");
SocketCommon.sendAll(client, "donotcross\r\n");
}
+
/** Runs the server, and concurrently starts the vision processor with -vision flag. */
public static void main(final String args[]) throws IOException {
//main function for server
+
+ String atomIP = null;
+ try {
+ atomIP = args[0];
+ }
+ catch (ArrayIndexOutOfBoundsException e) {
+ System.out.println("Usage: VisionTuner [atom ip]");
+ System.exit(0);
+ }
+
//set logger to log everything
LOG.setLevel(Level.ALL);
try {
@@ -121,9 +134,9 @@
System.err.println("Warning: Logging initialization failed.");
}
- if (args[0].equals("-vision")) {
+ if (Arrays.asList(args).contains("-vision")) {
LOG.info("Starting vision processor.");
- new TestClient();
+ new TestClient(atomIP);
}
DebugServerRun server = new DebugServerRun(9714);
diff --git a/971CV/src/org/frc971/HTTPClient.java b/971CV/src/org/frc971/HTTPClient.java
index cc694ef..96308a1 100644
--- a/971CV/src/org/frc971/HTTPClient.java
+++ b/971CV/src/org/frc971/HTTPClient.java
@@ -24,9 +24,9 @@
/** whether or not to print debug messages to stdout. */
private final static boolean LOCAL_DEBUG = false;
- private SocketChannel sock;
+ private String atomIP;
- private final String ATOM_IP = "192.168.0.137";
+ private SocketChannel sock;
private ChannelImageGetter cgetter;
@@ -41,11 +41,12 @@
/** the constructor, initializes connection, and sets up aos getter.
* @throws IOException */
- public HTTPClient() throws IOException {
+ public HTTPClient(String atomIP) throws IOException {
//Initialize socket connection to robot
+ this.atomIP = atomIP;
sock = SocketChannel.open();
- WriteDebug("Connecting to server at " + ATOM_IP);
- sock.connect(new InetSocketAddress(ATOM_IP, 9714));
+ WriteDebug("Connecting to server at " + atomIP);
+ sock.connect(new InetSocketAddress(atomIP, 9714));
sock.configureBlocking(false);
//Write headers
//HTTPStreamer does not actually use the headers, so we can just write terminating chars.
@@ -63,10 +64,14 @@
public ImageWithTimestamp GetFrame() {
ImageWithTimestamp final_image = new ImageWithTimestamp();
//Use Brian's code to extract an image and timestamp from raw server data.
- ByteBuffer binary_image = cgetter.getJPEG();
+ ByteBuffer binaryImage = cgetter.getJPEG();
+ if (binaryImage == null) {
+ Messages.severe("Could not parse data from robot. See the log for details.");
+ return null;
+ }
//Decode ByteBuffer into an IplImage
- byte[] b = new byte[binary_image.remaining()];
- binary_image.get(b);
+ byte[] b = new byte[binaryImage.remaining()];
+ binaryImage.get(b);
try {
InputStream iis = new ByteArrayInputStream(b);
BufferedImage bImageFromConvert = ImageIO.read(iis);
@@ -80,4 +85,9 @@
return null;
}
}
+
+ /** Gets the name to display at the top of the image window. */
+ public String GetName() {
+ return atomIP;
+ }
}
diff --git a/971CV/src/org/frc971/ImageWithTimestamp.java b/971CV/src/org/frc971/ImageWithTimestamp.java
index 8b43818..51b156f 100644
--- a/971CV/src/org/frc971/ImageWithTimestamp.java
+++ b/971CV/src/org/frc971/ImageWithTimestamp.java
@@ -4,6 +4,6 @@
/** Small helper class for associating images and timestamps. */
public class ImageWithTimestamp {
- WPIColorImage image;
+ WPIColorImage image = null;
double timestamp;
}
diff --git a/971CV/src/org/frc971/Recognizer.java b/971CV/src/org/frc971/Recognizer.java
index 6ab6455..9292357 100644
--- a/971CV/src/org/frc971/Recognizer.java
+++ b/971CV/src/org/frc971/Recognizer.java
@@ -1,7 +1,6 @@
package org.frc971;
import edu.wpi.first.wpijavacv.WPIColorImage;
-import edu.wpi.first.wpijavacv.WPIImage;
/**
* Vision target recognizer.
@@ -30,5 +29,5 @@
*<p>
* SIDE EFFECTS: May modify cameraImage.
*/
- WPIImage processImage(WPIColorImage cameraImage);
+ Target processImage(WPIColorImage cameraImage);
}
diff --git a/971CV/src/org/frc971/Recognizer2013.java b/971CV/src/org/frc971/Recognizer2013.java
index 7fbf28e..812c78b 100644
--- a/971CV/src/org/frc971/Recognizer2013.java
+++ b/971CV/src/org/frc971/Recognizer2013.java
@@ -14,7 +14,6 @@
import edu.wpi.first.wpijavacv.WPIColor;
import edu.wpi.first.wpijavacv.WPIColorImage;
import edu.wpi.first.wpijavacv.WPIContour;
-import edu.wpi.first.wpijavacv.WPIImage;
import edu.wpi.first.wpijavacv.WPIPoint;
import edu.wpi.first.wpijavacv.WPIPolygon;
@@ -111,7 +110,7 @@
}
@Override
- public WPIImage processImage(WPIColorImage cameraImage) {
+ public Target processImage(WPIColorImage cameraImage) {
// (Re)allocate the intermediate images if the input is a different
// size than the previous image.
if (size == null || size.width() != cameraImage.getWidth()
@@ -239,26 +238,32 @@
}
}
+ Target found = null;
if (bestTarget != null) {
rawImage.drawPolygon(bestTarget, targetColor, 2);
- measureTarget(bestTarget);
+ found = measureTarget(bestTarget);
} else {
LOG.fine("No target found");
}
// Draw a crosshair
rawImage.drawLine(linePt1, linePt2, targetColor, 1);
+
+ if (found == null) {
+ found = new Target();
+ }
+ found.editedPicture = rawImage;
daisyExtensions.releaseMemory();
//System.gc();
-
- return rawImage;
+
+ return found;
}
/**
* Uses the camera, field, and robot dimensions to compute targeting info.
*/
- private void measureTarget(WPIPolygon target) {
+ private Target measureTarget(WPIPolygon target) {
double w = target.getWidth();
double h = target.getHeight();
double x = target.getX() + w / 2; // target center in view coords
@@ -274,10 +279,18 @@
double elevationCam = Math.atan2(yc * 2 * kTanVFOV2, vh);
double rangeIn = kTargetWidthIn * vw / (w * 2 * kTanHFOV2);
+ //Put results in target
+ Target data = new Target();
+ data.azimuth = (Math.toDegrees(azimuthCam) - kShooterOffsetDeg);
+ data.elevation = (Math.toDegrees(elevationCam));
+ data.range = (rangeIn / 12);
+
LOG.fine("Best target at (" + x + ", " + y + ") " + w +" x " + h
+ ", shot azimuth=" + (Math.toDegrees(azimuthCam) - kShooterOffsetDeg) +
" elevation=" + (Math.toDegrees(elevationCam) + kCameraPitchDeg) +
" range=" + (rangeIn / 12));
+
+ return data;
}
}
diff --git a/971CV/src/org/frc971/ResultSender.java b/971CV/src/org/frc971/ResultSender.java
index 897371d..441c105 100644
--- a/971CV/src/org/frc971/ResultSender.java
+++ b/971CV/src/org/frc971/ResultSender.java
@@ -16,7 +16,7 @@
/** Serves processing results back to the atom. */
public class ResultSender {
- private static final int PORT = 9715;
+ private static final int PORT = 9716;
private ServerSocketChannel sock;
@@ -39,15 +39,17 @@
*/
public void send(double azimuth, double elevation, double range) {
//Formulate a message as a String similar to an HTTP header.
- StringBuilder message = new StringBuilder();
- message.append("\r\n--boundarydonotcross\r\n");
- message.append("Azimuth: ");
- message.append(azimuth);
- message.append("\r\nElevation: ");
- message.append(elevation);
- message.append("\r\nRange: ");
- message.append(range);
-
- acceptor.sendtoAll(message.toString());
+ if (azimuth != -1 && elevation != -1 && range != -1) {
+ StringBuilder message = new StringBuilder();
+ message.append("\r\n--boundarydonotcross\r\n");
+ message.append("Azimuth: ");
+ message.append(azimuth);
+ message.append("\r\nElevation: ");
+ message.append(elevation);
+ message.append("\r\nRange: ");
+ message.append(range);
+
+ acceptor.sendtoAll(message.toString());
+ }
}
}
diff --git a/971CV/src/org/frc971/SocketCommon.java b/971CV/src/org/frc971/SocketCommon.java
index e49a82b..daf3a6c 100644
--- a/971CV/src/org/frc971/SocketCommon.java
+++ b/971CV/src/org/frc971/SocketCommon.java
@@ -53,7 +53,8 @@
}
}
catch (IOException e) {
- LOG.severe("Socket read failed.");
+ LOG.severe("Socket read failed. Check your network configuration.");
+ Messages.severe("Socket read failed. Check your network configuration.");
return null;
}
return message;
@@ -69,7 +70,8 @@
sock.write(message);
}
catch (IOException e) {
- LOG.warning("Socket write failed.");
+ LOG.warning("Socket write failed. Check your network configuration.");
+ Messages.severe("Socket write failed. Check your network configuration.");
return -1;
}
}
diff --git a/971CV/src/org/frc971/TestClient.java b/971CV/src/org/frc971/TestClient.java
index 926a817..227c929 100644
--- a/971CV/src/org/frc971/TestClient.java
+++ b/971CV/src/org/frc971/TestClient.java
@@ -11,15 +11,18 @@
/** Small thread for running vision code concurrently with debug server. */
public class TestClient extends Thread {
+ private String atomIP;
+
/** Constructor to set up new thread. */
- public TestClient() {
+ public TestClient(String atomIP) {
super("Test Client");
+ this.atomIP = atomIP;
start();
}
/** Simple thread, runs the vision code. */
public void run() {
- String[] args = {};
+ String[] args = {atomIP};
VisionTuner.main(args);
}
}
diff --git a/971CV/src/org/frc971/TestImageGetter.java b/971CV/src/org/frc971/TestImageGetter.java
index a61ed60..2577fa2 100644
--- a/971CV/src/org/frc971/TestImageGetter.java
+++ b/971CV/src/org/frc971/TestImageGetter.java
@@ -59,6 +59,11 @@
return path1 + "/" + path2;
}
+ /** Gets the name to display at the top of the image window. */
+ public String GetName() {
+ return images[image_index];
+ }
+
/** Constructor
*
* @param path_to_images is the path to the directory where our images are.
diff --git a/971CV/src/org/frc971/VisionTuner.java b/971CV/src/org/frc971/VisionTuner.java
index 4aba589..357b58f 100644
--- a/971CV/src/org/frc971/VisionTuner.java
+++ b/971CV/src/org/frc971/VisionTuner.java
@@ -2,6 +2,8 @@
import java.awt.BorderLayout;
import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.Arrays;
@@ -11,6 +13,8 @@
import java.io.FileNotFoundException;
import java.io.IOException;
+import javax.swing.JButton;
+import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.WindowConstants;
@@ -55,10 +59,11 @@
private final JSlider hueMaxSlider = new JSlider();
private final JSlider satMinSlider = new JSlider();
private final JSlider valMinSlider = new JSlider();
+ private final JButton showCalibration = new JButton("Calibrate");
- private ResultSender sender;
+ private ResultSender sender = null;
- private int totalFrames = -1; // don't count the first (warmup) frame
+ private int totalFrames = -1; // don't count the first (warm-up) frame
private double totalMsec;
private double minMsec = Double.MAX_VALUE;
private double maxMsec;
@@ -66,18 +71,22 @@
private TestImageGetter getter;
private WPIColorImage current;
+
+ private String currentWindowTitle;
+
+ private boolean debug = false;
public VisionTuner() {
//set logger to log everything
LOG.setLevel(Level.ALL);
try {
- LogHandler handler = new LogHandler("../src/org/frc971/ds_vision.log");
+ LogHandler handler = new LogHandler("ds_vision.log");
TimeFormatter formatter = new TimeFormatter();
handler.setFormatter(formatter);
LOG.addHandler(handler);
}
catch (FileNotFoundException e) {
- System.err.println("Warning: Logging initialization failed.");
+ Messages.warning("Logging initialization failed.");
}
//initialize result sender
@@ -85,15 +94,73 @@
sender = new ResultSender();
}
catch (IOException e) {
- LOG.severe("Server initialization failed: " + e.getMessage() + " Result reporting disabled.");
+ LOG.severe("Server initialization failed: " + e.getMessage() + ". Result reporting disabled.");
}
cameraFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
- recognizer.showIntermediateStages(true);
-
cameraFrame.getContentPane().add(panel, BorderLayout.SOUTH);
- panel.setLayout(new GridLayout(0, 2, 0, 0));
+ panel.setLayout(new GridLayout(0, 1, 0, 0));
+
+ showCalibration.setToolTipText("Click here if the system is not finding targets well enough.");
+ panel.add(showCalibration);
+ showCalibration.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ showCalibrationWindow();
+ }
+ });
+ LOG.fine("Initial HSV range ["
+ + hueMinSlider.getValue() + " .. "
+ + hueMaxSlider.getValue() + "] "
+ + satMinSlider.getValue() + "+ "
+ + valMinSlider.getValue() + "+");
+ }
+
+ /** Shows a calibration window when the user clicks the Calibrate button. */
+ private void showCalibrationWindow() {
+ final CanvasFrame calibrationWindow = new CanvasFrame("Calibration");
+ calibrationWindow.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+
+ final JPanel panel = new JPanel();
+ calibrationWindow.getContentPane().add(panel, BorderLayout.SOUTH);
+ panel.setLayout(new GridLayout(3, 3, 0, 0));
+
+ hueMinSlider.setToolTipText("minimum HSV hue");
+ hueMinSlider.setMaximum(255);
+ hueMinSlider.setValue(recognizer.getHueMin());
+ panel.add(hueMinSlider);
+
+ panel.add(new JLabel("min hue max hue"));
+
+ hueMaxSlider.setToolTipText("maximum HSV hue");
+ hueMaxSlider.setMaximum(255);
+ hueMaxSlider.setValue(recognizer.getHueMax());
+ panel.add(hueMaxSlider);
+
+ satMinSlider.setToolTipText("minimum HSV color saturation");
+ satMinSlider.setMaximum(255);
+ satMinSlider.setValue(recognizer.getSatMin());
+ panel.add(satMinSlider);
+
+ panel.add(new JLabel("min saturation max saturation"));
+
+ valMinSlider.setToolTipText("minimum HSV brightness value");
+ valMinSlider.setMaximum(255);
+ valMinSlider.setValue(recognizer.getValMin());
+ panel.add(valMinSlider);
+
+ panel.add(new JLabel("")); //empty cells can cause problems
+
+ final JButton done = new JButton("Done");
+ panel.add(done);
+ done.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ calibrationWindow.dispose();
+ }
+ });
+
+ panel.add(new JLabel("")); //empty cells can cause problems
+
ChangeListener sliderListener = new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
@@ -106,53 +173,37 @@
hueMinSlider.getValue(), hueMaxSlider.getValue(),
satMinSlider.getValue(),
valMinSlider.getValue());
- processImage(current);
+ if (debug) {
+ processImage(current, null);
+ }
}
};
-
- hueMinSlider.setToolTipText("minimum HSV hue");
- hueMinSlider.setMaximum(255);
- hueMinSlider.setValue(recognizer.getHueMin());
- panel.add(hueMinSlider);
-
- hueMaxSlider.setToolTipText("maximum HSV hue");
- hueMaxSlider.setMaximum(255);
- hueMaxSlider.setValue(recognizer.getHueMax());
- panel.add(hueMaxSlider);
-
- satMinSlider.setToolTipText("minimum HSV color saturation");
- satMinSlider.setMaximum(255);
- satMinSlider.setValue(recognizer.getSatMin());
- panel.add(satMinSlider);
-
- valMinSlider.setToolTipText("minimum HSV brightness value");
- valMinSlider.setMaximum(255);
- valMinSlider.setValue(recognizer.getValMin());
- panel.add(valMinSlider);
-
- LOG.fine("Initial HSV range ["
- + hueMinSlider.getValue() + " .. "
- + hueMaxSlider.getValue() + "] "
- + satMinSlider.getValue() + "+ "
- + valMinSlider.getValue() + "+");
-
+
hueMinSlider.addChangeListener(sliderListener);
hueMaxSlider.addChangeListener(sliderListener);
satMinSlider.addChangeListener(sliderListener);
valMinSlider.addChangeListener(sliderListener);
+
+ calibrationWindow.pack();
+
}
/**
* Loads the named test image files.
* Sets testImageFilenames and testImages.
*/
-
- private void processImage(WPIColorImage cameraImage) {
+ private void processImage(WPIColorImage cameraImage, String title) {
current = cameraImage;
- cameraFrame.setTitle("Input:");
+
+ //set window title if it needs to be changed
+ if (title != null && !title.equals(currentWindowTitle)) {
+ cameraFrame.setTitle(title);
+ currentWindowTitle = title;
+ }
long startTime = System.nanoTime();
- WPIImage processedImage = recognizer.processImage(cameraImage);
+ Target target = recognizer.processImage(cameraImage);
+ WPIImage processedImage = target.editedPicture;
long endTime = System.nanoTime();
cameraFrame.showImage(processedImage.getBufferedImage());
@@ -167,19 +218,22 @@
}
//send results to atom. (and any connected clients)
+ if (sender != null) {
+ sender.send(target.azimuth, target.elevation, target.range);
+ }
}
private void previousImage() {
WPIColorImage to_process = getter.GetPrev();
if (to_process != null)
- processImage(to_process);
+ processImage(to_process, getter.GetName());
}
private void nextImage() {
WPIColorImage to_process = getter.GetNext();
if (to_process != null)
- processImage(to_process);
+ processImage(to_process, getter.GetName());
}
private void processEvents() {
@@ -201,32 +255,51 @@
public static void main(final String[] args) {
VisionTuner tuner = new VisionTuner();
+ Messages.SetWindow(tuner.cameraFrame);
+
+ String atomIP = null;
+ try {
+ atomIP = args[0];
+ }
+ catch (ArrayIndexOutOfBoundsException e) {
+ System.out.println("Usage: VisionTuner [atom ip]");
+ System.exit(0);
+ }
+
if (Arrays.asList(args).contains("-debug")) {
//debug mode has been requested
+ tuner.debug = true;
+
+ //show debugging windows
+ tuner.recognizer.showIntermediateStages(true);
+
tuner.getter = new TestImageGetter(".");
WPIColorImage to_process = tuner.getter.GetNext();
if (to_process != null) {
- tuner.processImage(to_process);
+ tuner.processImage(to_process, tuner.getter.GetName());
for (;;) {
tuner.processEvents();
}
}
- else
- LOG.severe("Cannot find test images.");
+ else {
+ LOG.severe("Could not load test images.");
+ Messages.severe("Could not load test images.");
+ }
}
else {
try {
- HTTPClient client = new HTTPClient();
+ HTTPClient client = new HTTPClient(atomIP);
for (;;) {
ImageWithTimestamp to_process = client.GetFrame();
if (to_process.image != null) {
- tuner.processImage(to_process.image);
+ tuner.processImage(to_process.image, client.GetName());
LOG.fine("Captured time: " + Double.toString(to_process.timestamp));
}
}
}
catch (IOException e) {
- LOG.severe("Client initialization failed.");
+ LOG.severe("Client initialization failed: " + e.getMessage() + ".");
+ Messages.severe("Client initialization failed: " + e.getMessage() + ".");
}
}
}
diff --git a/971CV/src/org/frc971/private_aos_camera_jar.jar b/971CV/src/org/frc971/private_aos_camera_jar.jar
index c202fe8..3836b5b 100644
--- a/971CV/src/org/frc971/private_aos_camera_jar.jar
+++ b/971CV/src/org/frc971/private_aos_camera_jar.jar
Binary files differ