diff --git a/build.gradle b/build.gradle index 2659619..a12168a 100644 --- a/build.gradle +++ b/build.gradle @@ -42,6 +42,8 @@ dependencies { // Poofs library and its dependencies compile name: 'json-simple-1.1.1' compile name: 'poofs.2017' + compile group: 'log4j', name: 'log4j', version: '1.2.17' + compile 'org.json:json:20090211' testCompile group: 'junit', name: 'junit', version: '4.12' testCompile group: 'org.mockito', name: 'mockito-all', version: '1.10.19' } @@ -65,4 +67,4 @@ jar { task wrapper(type: Wrapper) { gradleVersion = '4.4' -} \ No newline at end of file +} diff --git a/src/main/java/org/ilite/frc/common/util/NamedThreadFactory.java b/src/main/java/org/ilite/frc/common/util/NamedThreadFactory.java new file mode 100644 index 0000000..1932db9 --- /dev/null +++ b/src/main/java/org/ilite/frc/common/util/NamedThreadFactory.java @@ -0,0 +1,18 @@ +package org.ilite.frc.common.util; + +import java.util.concurrent.ThreadFactory; + +public class NamedThreadFactory implements ThreadFactory { + + private String mThreadName; + + public NamedThreadFactory(String pThreadName) { + mThreadName = pThreadName; + } + + @Override + public Thread newThread(Runnable pR) { + return new Thread(pR, mThreadName); + } + +} diff --git a/src/main/java/org/ilite/frc/vision/api/messages/RobotVisionMsg.java b/src/main/java/org/ilite/frc/vision/api/messages/RobotVisionMsg.java new file mode 100644 index 0000000..4fb9e79 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/api/messages/RobotVisionMsg.java @@ -0,0 +1,33 @@ +package org.ilite.frc.vision.api.messages; + +import java.awt.image.BufferedImage; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import org.ilite.frc.vision.camera.opencv.OpenCVUtils; + +/** + * Contains data coming from the camera + * @author Daniel Christopher + * @version 2/1/15 + * + */ +public final class RobotVisionMsg { + private final BufferedImage rawImage; + private final List visibleObjects; + + public RobotVisionMsg(BufferedImage rawImage) { + this.rawImage = rawImage; + + visibleObjects = new LinkedList(); + } + + public BufferedImage getRawImage() { + return OpenCVUtils.deepCopy(rawImage); + } + + public List getVisibleObjects() { + return Collections.unmodifiableList(visibleObjects); + } +} diff --git a/src/main/java/org/ilite/frc/vision/api/messages/VisibleObject.java b/src/main/java/org/ilite/frc/vision/api/messages/VisibleObject.java new file mode 100644 index 0000000..3c06ddc --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/api/messages/VisibleObject.java @@ -0,0 +1,36 @@ +package org.ilite.frc.vision.api.messages; + +import java.awt.Point; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * + * @author Daniel Christopher + * @version 2/1/15 + * + */ +public final class VisibleObject { + private final String name; + private final LinkedList points; + private final Point centerPoint; + + public VisibleObject(String name, LinkedList points, Point centerPoint) { + this.name = name; + this.points = points; + this.centerPoint = centerPoint; + } + + public String getName() { + return name; + } + + public List getPoints() { + return Collections.unmodifiableList(points); + } + + public Point getCenterPoint() { + return new Point(centerPoint); + } +} diff --git a/src/main/java/org/ilite/frc/vision/api/system/IVisionSystem.java b/src/main/java/org/ilite/frc/vision/api/system/IVisionSystem.java new file mode 100644 index 0000000..0640b15 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/api/system/IVisionSystem.java @@ -0,0 +1,34 @@ +package org.ilite.frc.vision.api.system; + +import org.ilite.frc.vision.api.messages.RobotVisionMsg; + +/** + * Interface to connect to a single camera. This will provide users with the ability + * to register with the camera and receive updates. Implementations of the {@link VisionListener} + * will be notified when there is a new {@link RobotVisionMsg} available + * + */ +public interface IVisionSystem { + + /** + * Method to start the camera processing. This should be called before processing can begin. + * Subsequent calls to this method will do nothing + */ + public void start(); + + /** + * Method to subscribe for vision updates + * @param listener + * Listener to register + */ + public void subscribe(VisionListener listener); + + /** + * Method to unsubscribe for vision updates + * @param listener + * listener to stop registering + */ + public void unsubscribe(VisionListener listener); + + +} \ No newline at end of file diff --git a/src/main/java/org/ilite/frc/vision/api/system/ImageBlender.java b/src/main/java/org/ilite/frc/vision/api/system/ImageBlender.java new file mode 100644 index 0000000..11faa55 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/api/system/ImageBlender.java @@ -0,0 +1,140 @@ +package org.ilite.frc.vision.api.system; + +import java.awt.AlphaComposite; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.List; + +import javax.imageio.ImageIO; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import org.ilite.frc.vision.api.messages.RobotVisionMsg; +import org.ilite.frc.vision.camera.opencv.ImagePanel; +import org.ilite.frc.vision.camera.opencv.OverlaySlider; + + +/** + * A proof of concept to overlay one image on top of each other. This will register + * with the live camera, using the {@link VisionSystemAPI} to get a reference. + * When a new frame comes in, this image will blend the mOverlayImage on top of + * the frame and then render it to the display. + * + * There {@link OverlaySlider} is used to adjust the alpha value. Valid alpha + * values are from 0.0 to 1.0. Essentially the alpha values are percentages, with + * 1.0 representing full opaque and 0.0 being completely invisible. + * + * This class will put the {@link OverlaySlider} on the top of the frame and + * the image panel in the center + */ +public class ImageBlender extends JPanel implements VisionListener { + + /** + * The panel that will render the mFinalImage onto this frame. + */ + private final ImagePanel mImagePanel = new ImagePanel(); + + /** + * {@link List} containing all of the overlays + */ + private final ListmOverlays = new ArrayList(); + /** + * The main content of this frame + */ + /** + * The final image that is to be rendered. This will be created once in the + * onVisonDataReceived, once this starts to receive frames from the camera + */ + private BufferedImage mFinalImage; + private BufferedImage mBackgroundImage; + + private JLabel mPercentageLabel; + private final DecimalFormat mFormat = new DecimalFormat("000"); + + /** + * Creates and shows this frame + * @throws IOException + * Thrown if there was an issue loading the overlay Image + */ + + + + public ImageBlender() throws IOException { + super(new BorderLayout()); + mOverlays.add(VisionSystemAPI.loadImageAsResource("Overlay.png")); + + mImagePanel.getPanel().setPreferredSize(new Dimension(800, 600)); + + add(mImagePanel.getPanel(), BorderLayout.CENTER); + setVisible(true); + } + + /** + * {@inheritDoc} + */ + @Override + public void onVisionDataRecieved(RobotVisionMsg message) { + BufferedImage frameImage = message.getRawImage(); + redraw(frameImage); + } + + private void redraw(BufferedImage frameImage) { + if(mFinalImage == null) { + mFinalImage = new BufferedImage(frameImage.getWidth(), frameImage.getHeight(), BufferedImage.TYPE_INT_RGB); + setPreferredSize(new Dimension(mFinalImage.getWidth(), mFinalImage.getHeight())); + + } + int screenWidth = frameImage.getWidth(); + int screenHeight = frameImage.getHeight(); + + Graphics2D graphics = mFinalImage.createGraphics(); + graphics.fillRect(0, 0, mFinalImage.getWidth(), mFinalImage.getHeight()); + graphics.drawImage(frameImage, 0, 0,frameImage.getWidth(), frameImage.getHeight(), null); + graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,.5f)); + + for(BufferedImage anOverlay : mOverlays) { + graphics.drawImage(anOverlay, 0, 0,screenWidth,screenHeight, null); + } + graphics.dispose(); + + mImagePanel.updateImage(mFinalImage); + } + + /** + * Starts the Image Blender and registers it with the {@link VisionSystemAPI} + * @param args + * Not used + * @throws IOException + * thrown if the overlay image could not be loaded + */ + public static void main(String[] args) throws IOException { + JFrame frame = new JFrame(); + ImageBlender blender = new ImageBlender(); + + File background = new File("src/main/resources/images/Screenshot1.jpg"); + BufferedImage backgroundImage = ImageIO.read(background); + blender.setbackgroundImage(backgroundImage); + +// VisionSystemAPI.getVisionSystem(ECameraType.LOCAL_CAMERA).subscribe(blender); + frame.setContentPane(blender); + + + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setVisible(true); + frame.pack(); + + } + + private void setbackgroundImage(BufferedImage pBackgroundImage) { + mBackgroundImage = pBackgroundImage; + redraw(mBackgroundImage); + + } +} diff --git a/src/main/java/org/ilite/frc/vision/api/system/VisionListener.java b/src/main/java/org/ilite/frc/vision/api/system/VisionListener.java new file mode 100644 index 0000000..4a073a5 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/api/system/VisionListener.java @@ -0,0 +1,7 @@ +package org.ilite.frc.vision.api.system; + +import org.ilite.frc.vision.api.messages.RobotVisionMsg; + +public interface VisionListener { + public void onVisionDataRecieved(RobotVisionMsg message); +} diff --git a/src/main/java/org/ilite/frc/vision/api/system/VisionSystem.java b/src/main/java/org/ilite/frc/vision/api/system/VisionSystem.java new file mode 100644 index 0000000..f253e5c --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/api/system/VisionSystem.java @@ -0,0 +1,84 @@ +package org.ilite.frc.vision.api.system; + +import java.awt.image.BufferedImage; +import java.util.LinkedHashSet; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.ilite.frc.vision.api.messages.RobotVisionMsg; +import org.ilite.frc.vision.camera.CameraConnectionFactory; +import org.ilite.frc.vision.camera.ICameraConnection; +import org.ilite.frc.vision.camera.ICameraFrameUpdateListener; +import org.ilite.frc.vision.camera.opencv.OpenCVUtils; + +final class VisionSystem implements ICameraFrameUpdateListener, IVisionSystem { + private LinkedHashSet listeners; + private ICameraConnection connection; + /** + * Flag indicating whether this vision system has been initializied. + * This is used to ensure that multiple calls to start do nothing + */ + private final AtomicBoolean mIsInit = new AtomicBoolean(false); + private static final ExecutorService sService = Executors.newCachedThreadPool(new ThreadFactory() { + + @Override + public Thread newThread(Runnable pR) { + return new Thread(pR, "Vision System Connection Thread"); + } + }); + + protected VisionSystem(String pIP) { + + OpenCVUtils.init(); + listeners = new LinkedHashSet(); + + connection = CameraConnectionFactory.getCameraConnection(pIP); + + connection.addCameraFrameListener(this); + + } + + @Override + public void frameAvail(BufferedImage pImage) { + RobotVisionMsg message = new RobotVisionMsg(pImage); + + for(VisionListener listener : listeners) { + listener.onVisionDataRecieved(message); + } + } + + @Override + public void start() { + boolean isInit = mIsInit.getAndSet(true); + + if(!isInit) { + sService.submit(new Runnable() { + + @Override + public void run() { + connection.start(); + } + }); + } + + } + + /* (non-Javadoc) + * @see org.ilite.vision.api.system.IVisionSystem#subscribe(org.ilite.vision.api.system.VisionListener) + */ + @Override + public void subscribe(VisionListener listener) { + listeners.add(listener); + } + + /* (non-Javadoc) + * @see org.ilite.vision.api.system.IVisionSystem#unsubscribe(org.ilite.vision.api.system.VisionListener) + */ + @Override + public void unsubscribe(VisionListener listener) { + listeners.remove(listener); + } + +} diff --git a/src/main/java/org/ilite/frc/vision/api/system/VisionSystemAPI.java b/src/main/java/org/ilite/frc/vision/api/system/VisionSystemAPI.java new file mode 100644 index 0000000..89f7774 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/api/system/VisionSystemAPI.java @@ -0,0 +1,81 @@ +package org.ilite.frc.vision.api.system; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.EnumMap; +import java.util.Map; + +import javax.imageio.ImageIO; + +import org.ilite.frc.vision.camera.opencv.OpenCVUtils; +import org.ilite.frc.vision.constants.ECameraConfig; +import org.ilite.frc.vision.constants.ECameraType; + +public class VisionSystemAPI { + + /** + * Log4j logger + */ +// private static final Logger sLogger = Logger.getLogger(VisionSystemAPI.class); + + private static class INSTANCE_HOLDER { + + private static MapmCameraTypes; + + static { + MaptempMap = new EnumMap<>(ECameraType.class); + for(ECameraType aType : ECameraType.values()) { + tempMap.put(aType, new VisionSystem(aType.getCameraIP())); + } + mCameraTypes = Collections.unmodifiableMap(tempMap); + } + } + + public static BufferedImage loadImage(String path)throws IOException { + BufferedImage myImage = ImageIO.read(new File(path)); + + return myImage; + + } + + public static BufferedImage loadImageAsResource(String pResourceName) throws IOException { + return ImageIO.read(ClassLoader.getSystemResource(pResourceName)); + } + + public static ImageBlender getImageBlender(IVisionSystem system) throws IOException { + ImageBlender b = new ImageBlender(); + system.subscribe(b); + + return b; + } + + public static IVisionSystem getVisionSystem(ECameraType pCameraType) { + ECameraType aType = pCameraType; + + if(ECameraConfig.USE_LOCAL_IF_NOT_AVAILABLE.getBooleanValue() && !OpenCVUtils.isAvailable(aType.getCameraIP())) { + aType = ECameraType.LOCAL_CAMERA; + +// if(sLogger.isEnabledFor(Level.WARNING)) { +// StringBuilder warnString = new StringBuilder(); +// warnString.append("Unable to connect to camera type: "); +// warnString.append(pCameraType); +// warnString.append(", iP= ").append(pCameraType.getCameraIP()); +// sLogger.warn(warnString); +// } + } + +// if(sLogger.isDebugEnabled()) { +// StringBuilder debugString = new StringBuilder(); +// debugString.append("Loading vision system for type= ").append(aType); +// debugString.append(", IP= ").append(aType.getCameraIP()); +// sLogger.debug(debugString); +// } + + IVisionSystem iVisionSystem = INSTANCE_HOLDER.mCameraTypes.get(aType); + iVisionSystem.start(); + return iVisionSystem; + } + +} diff --git a/src/main/java/org/ilite/frc/vision/api/system/VisionSystemTester.java b/src/main/java/org/ilite/frc/vision/api/system/VisionSystemTester.java new file mode 100644 index 0000000..04a3d2b --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/api/system/VisionSystemTester.java @@ -0,0 +1,28 @@ +package org.ilite.frc.vision.api.system; + +import java.io.IOException; + +import javax.swing.JFrame; +import javax.swing.SwingUtilities; + +import org.ilite.frc.vision.constants.ECameraType; + +public class VisionSystemTester { + + public static void main(String[] args) throws IOException { + IVisionSystem aVisionSystem = VisionSystemAPI.getVisionSystem(ECameraType.ALIGNMENT_CAMERA); + final ImageBlender aImageBlender = VisionSystemAPI.getImageBlender(aVisionSystem); + + SwingUtilities.invokeLater(new Runnable() { + + @Override + public void run() { + JFrame aFrame = new JFrame(); + aFrame.setContentPane(aImageBlender); + aFrame.pack(); + aFrame.setVisible(true); + } + }); + } + +} diff --git a/src/main/java/org/ilite/frc/vision/camera/AbstractCameraConnection.java b/src/main/java/org/ilite/frc/vision/camera/AbstractCameraConnection.java new file mode 100644 index 0000000..6a21b56 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/AbstractCameraConnection.java @@ -0,0 +1,31 @@ +package org.ilite.frc.vision.camera; + +import java.awt.image.BufferedImage; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +public abstract class AbstractCameraConnection implements ICameraConnection { + + private final Set mListeners = new CopyOnWriteArraySet(); + + @Override + public void addCameraFrameListener(ICameraFrameUpdateListener pListener) { + mListeners.add(pListener); + } + + public void removeCameraFrameListener(ICameraFrameUpdateListener pListener) { + mListeners.remove(pListener); + } + + protected void notifyListeners(BufferedImage pImage) { + + for (ICameraFrameUpdateListener aListener : mListeners) { + try { + aListener.frameAvail(pImage); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + +} diff --git a/src/main/java/org/ilite/frc/vision/camera/Camera.java b/src/main/java/org/ilite/frc/vision/camera/Camera.java new file mode 100644 index 0000000..c902ad6 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/Camera.java @@ -0,0 +1,148 @@ +package org.ilite.frc.vision.camera; + +import java.awt.image.BufferedImage; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +import org.ilite.frc.vision.camera.opencv.OpenCVUtils; +import org.ilite.frc.vision.constants.ECameraConfig; +import org.opencv.core.Mat; +import org.opencv.videoio.VideoCapture; + +public class Camera extends AbstractCameraConnection { + + private String cameraIP; + + /** + * Default Constructor that sets cameraIP to null and uses local camera + */ + + public Camera(){ + cameraIP = null; + } + /** + * Constructor that takes in a camera's IP address + * @param p_CameraIP + */ + + public Camera(String p_CameraIP){ + + cameraIP = p_CameraIP; + + } + + /** + * The last buffered image + */ + private BufferedImage mLastFrame; + + /** + * The rate at which to pull frames from the camera, in milliseconds + */ + private static final long CAM_RATE_MILLIS = ECameraConfig.CAM_RATE_MILLIS.getValue(); + + /** + * Executor service to start a timer to start pulling frames of the camera + * and then notify listeners + */ + private static final ScheduledExecutorService CAMERA_EXEC = Executors + .newSingleThreadScheduledExecutor(new ThreadFactory() { + + @Override + public Thread newThread(Runnable pR) { + return new Thread(pR, "Local Camera Thread"); + } + }); + + private VideoCapture mCamera; + + private ScheduledFuture mScheduleAtFixedRate; + + @Override + public void start() { + + if (mCamera == null) { + + if(cameraIP == null){ + int DEVICE = (int) ECameraConfig.DEVICE.getValue(); + mCamera = new VideoCapture(DEVICE); + try { + Thread.sleep(ECameraConfig.INITIAL_CAMERA_DELAY.getValue()); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } + mCamera.open(DEVICE); + } + else{ + mCamera = new VideoCapture(); + cameraIP = "http://"+cameraIP+"/mjpg/video.mjpg"; + try { + Thread.sleep(ECameraConfig.INITIAL_CAMERA_DELAY.getValue()); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + mCamera.open(cameraIP); + } + + if (!mCamera.isOpened()) { + throw new IllegalStateException("Unable to start the camera"); + } + + } + + pauseResume(false); + + } + + /** + * Timer task that will grab a frame and then notify listeners + */ + private final Runnable mCameraRunnable = new Runnable() { + + + @Override + public void run() { + Mat currentFrame = new Mat(); + long start = 0; + long end = 0; + do { + start = System.currentTimeMillis(); + mCamera.read(currentFrame); + end = System.currentTimeMillis(); + mLastFrame = OpenCVUtils.toBufferedImage(currentFrame); + } while(end - start < 10); + notifyListeners(mLastFrame); + long updateRate = (cameraIP == null)?CAM_RATE_MILLIS : 5; + mScheduleAtFixedRate = CAMERA_EXEC.schedule(mCameraRunnable, updateRate, TimeUnit.MILLISECONDS); + } + }; + @Override + public void destroy() { + CAMERA_EXEC.shutdown(); + mCamera.release(); + } + public void pauseResume(boolean pShouldPause) { + + if(pShouldPause) { + if(mScheduleAtFixedRate != null) { + mScheduleAtFixedRate.cancel(true); + mScheduleAtFixedRate = null; + } + + notifyListeners(mLastFrame); + + + } else if(mScheduleAtFixedRate == null){ + + long updateRate = (cameraIP == null)?CAM_RATE_MILLIS : 5; +// mScheduleAtFixedRate = CAMERA_EXEC.scheduleAtFixedRate(mCameraRunnable, updateRate, +// updateRate, TimeUnit.MILLISECONDS); + mScheduleAtFixedRate = CAMERA_EXEC.schedule(mCameraRunnable, updateRate, TimeUnit.MILLISECONDS); + } + + }; +} diff --git a/src/main/java/org/ilite/frc/vision/camera/CameraConnectionFactory.java b/src/main/java/org/ilite/frc/vision/camera/CameraConnectionFactory.java new file mode 100644 index 0000000..416e6c1 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/CameraConnectionFactory.java @@ -0,0 +1,47 @@ +package org.ilite.frc.vision.camera; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +import org.ilite.frc.vision.camera.opencv.OpenCVUtils; +import org.ilite.frc.vision.constants.ECameraConfig; + +public class CameraConnectionFactory { + + private static MapmConnections = new ConcurrentHashMap<>(); + + public static synchronized ICameraConnection getCameraConnection(String pIP) { + String key = pIP; + + if(ECameraConfig.USE_LOCAL_IF_NOT_AVAILABLE.getBooleanValue() && !OpenCVUtils.isAvailable(pIP)) { + key = null; + } + + if(key == null) { + key = "LOCAL"; + } + + ICameraConnection returnedConnection = mConnections.get(key); + + if(returnedConnection == null) { + + if(key.equals("LOCAL")) { + returnedConnection = new Camera(); + } else { + returnedConnection = new Camera(pIP); + } + mConnections.put(key, returnedConnection); + + } + + return returnedConnection; + } + + public static void destroy() { + for(EntryanEntry : mConnections.entrySet()) { + anEntry.getValue().destroy(); + } + mConnections.clear(); + } +} diff --git a/src/main/java/org/ilite/frc/vision/camera/ICameraConnection.java b/src/main/java/org/ilite/frc/vision/camera/ICameraConnection.java new file mode 100644 index 0000000..3e77274 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/ICameraConnection.java @@ -0,0 +1,19 @@ +package org.ilite.frc.vision.camera; + +public interface ICameraConnection { + + public void addCameraFrameListener(ICameraFrameUpdateListener pListener); + + public void removeCameraFrameListener(ICameraFrameUpdateListener pListener); + + /** + * Method to start and stop the camera feed. + * @param pShouldPause + * true if the camera should pause, false if it should resume + */ + public void pauseResume(boolean pShouldPause); + + public void destroy(); + public void start(); + +} diff --git a/src/main/java/org/ilite/frc/vision/camera/ICameraFrameUpdateListener.java b/src/main/java/org/ilite/frc/vision/camera/ICameraFrameUpdateListener.java new file mode 100644 index 0000000..2a9b716 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/ICameraFrameUpdateListener.java @@ -0,0 +1,8 @@ +package org.ilite.frc.vision.camera; + +import java.awt.image.BufferedImage; + +public interface ICameraFrameUpdateListener { + + public void frameAvail(BufferedImage pImage); +} diff --git a/src/main/java/org/ilite/frc/vision/camera/axis/AxisCameraConnection.java b/src/main/java/org/ilite/frc/vision/camera/axis/AxisCameraConnection.java new file mode 100644 index 0000000..cacac28 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/axis/AxisCameraConnection.java @@ -0,0 +1,512 @@ +package org.ilite.frc.vision.camera.axis; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.imageio.ImageIO; + +import org.ilite.frc.common.util.NamedThreadFactory; +import org.ilite.frc.vision.camera.AbstractCameraConnection; +import org.ilite.frc.vision.constants.ECameraConfig; + +import javafx.scene.Camera; + + +/** + * + * History: Base code pieced together from code found on java.sun.com forum + * posting + * http://forum.java.sun.com/thread.jspa?threadID=494920&start=15&tstart=0 + * + * Modified by Carl Gould + * + * @author David E. Mireles, Ph.D. + * @author Carl Gould + * @deprecated Please use {@link Camera} with the IP of the axis camera instead + */ +public class AxisCameraConnection extends AbstractCameraConnection implements Runnable { + private String ipAddress; + private String mjpgURL; + private String username = ECameraConfig.USERNAME.getStringValue(); + private String password = ECameraConfig.PASSWORD.getStringValue(); + private String base64authorization = null; + private HttpURLConnection huc = null; + private boolean isPaused; + private boolean connected = false; + private MJPEGParser parser; + private Future cameraFuture; + private Future connectionFuture; + private int cameraDelay = (int) ECameraConfig.INITIAL_CAMERA_DELAY.getValue(); + + /** + * Log4j logger + */ +// private static final Logger sLogger = +// Logger.getLogger(AxisCameraConnection.class); + + /** + * Executor service used to keep trying to connect on a regular interval, until + * the camera connects + */ + private static final ScheduledExecutorService CONNECT_EXEC = + Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("Connection Thread")); + + + /** + * The executor that's used to run the Axis Camera's run method + */ + private static final ExecutorService AXIS_CAMERA_EXEC = Executors + .newSingleThreadExecutor(new NamedThreadFactory("Axis Camera Runnable")); + + /** + * The executor that's used to offload the start method so that it doesn't + * block the thread that calls it + */ + private static final ExecutorService START_EXCE = Executors.newSingleThreadExecutor(new NamedThreadFactory("Start EXEC")); + + /** + * The executor that's responsible for pulling a camera frame at a regular + * interval + */ + private static final ScheduledExecutorService FRAME_PULLER_EXEC = + Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("Camera Frame Puller")); + + + /** Creates a new instance of AxisCamera */ + public AxisCameraConnection(String pIp) { + ipAddress = pIp; + mjpgURL = "http://" + ipAddress + "/mjpg/video.mjpg"; + +// sLogger.debug("Created Axis Camera Connection with URL= " + mjpgURL); + + // only use authorization if all informations are available + if (username != null && password != null) { + base64authorization = encodeUsernameAndPasswordInBase64(username, + password); + } + + Runtime.getRuntime().addShutdownHook(new Thread() { + public void run() { + disconnect(); + } + }); + } + + /** + * encodes username and password in Base64-encoding + */ + private String encodeUsernameAndPasswordInBase64(String usern, String psswd) { + String s = usern + ":" + psswd; + String encs = (new Base64Encoder()).encode(s); + return "Basic " + encs; + } + + private boolean connect() { + try { +// sLogger.debug("Starting Connect"); + URL u = new URL(mjpgURL); + huc = (HttpURLConnection) u.openConnection(); + + // if authorization is required set up the connection with the + // encoded authorization-information + if (base64authorization != null) { + huc.setDoInput(true); + huc.setRequestProperty("Authorization", base64authorization); + huc.connect(); + } + /* + * This is the boundary string that my camera uses. I don't know if + * it is a standard or not, I kind of doubt it... + */ + String boundary = "--myboundary"; + String contentType = huc.getContentType(); + Pattern pattern = Pattern.compile("boundary=(.*)$"); + Matcher matcher = pattern.matcher(contentType); + + try { + matcher.find(); + boundary = matcher.group(1); + } catch (Exception e) { +// sLogger.error("Exception while trying to use matcher/boundary", e); + } + + InputStream is = huc.getInputStream(); + connected = true; +// sLogger.debug("Currently connected"); + parser = new MJPEGParser(is, boundary); + + return true; + + } catch (IOException e) { // incase no connection exists wait and try + // again, instead of printing the error +// sLogger.error("Caught an IOException while trying to connect, will retry",e); + + huc.disconnect(); + +// sLogger.debug("Retrying connection..."); + } catch (Exception e) { +// sLogger.error("Unexpected error in connect",e); + } + + return false; + } + + public void disconnect() { + try { + if (connected) { +// sLogger.debug("Disconnecting camera"); + parser.setCanceled(true); + connected = false; + } + } catch (Exception e) { +// sLogger.error("Unexpected error in disconnect", e); + } + } + + NumberFormat decFormat = DecimalFormat.getNumberInstance(); + + public void run() { + + try { +// sLogger.debug("Staring parser"); + parser.parse(); + + } catch (IOException e) { +// sLogger.error("Exception in parser",e); + + } + + } + + public BufferedImage grabImage() { + +// sLogger.debug("Grabbing Image"); + byte[] segment = parser.getSegment(); + + if (segment == null) { +// sLogger.error("Failed to get a segment, returning null"); + return null; + } + + if (segment.length > 0) { + + try { + + return ImageIO.read(new ByteArrayInputStream(segment)); + + } catch (IOException e1) { +// sLogger.error("Failed to read the image, due to exception: ",e1); + + } + } + +// sLogger.error("Segment has no lengths, return null"); + return null; + + } + + @Override + public void start() { + START_EXCE.submit(new Runnable() { + + @Override + public void run() { +// sLogger.debug("Starting"); + + connectionFuture = CONNECT_EXEC.scheduleAtFixedRate(new Runnable() { + + @Override + public void run() { + if(connect()) { + connectionFuture.cancel(false); + +// sLogger.debug("Beging Parser"); + AXIS_CAMERA_EXEC.submit(AxisCameraConnection.this); +// sLogger.debug("Executing camera thread"); + executeCameraThread(); + } + } + + }, 1, 1, TimeUnit.SECONDS); + } + }); + } + + private void executeCameraThread() { + cameraFuture = FRAME_PULLER_EXEC.scheduleAtFixedRate(new Runnable() { + + @Override + public void run() { + BufferedImage aGrabImage = grabImage(); + notifyListeners(aGrabImage); + + } + }, cameraDelay, + (int) ECameraConfig.CAM_RATE_MILLIS.getValue(), + TimeUnit.MILLISECONDS); + + } + + @Override + public void destroy() { + + } + + public void pauseResume(boolean pShouldPause) { + isPaused = pShouldPause; +// sLogger.debug("Camera pause state is now: " + pShouldPause); + + cameraFuture.cancel(isPaused); + + if(!isPaused) { + executeCameraThread(); + } + } +} + +/** + * This class parses an MJPEG stream, notifying ChangeListeners whenever a valid + * segment is encountered. + * + * @author Carl Gould + */ + +class MJPEGParser { + + private static final byte[] JPEG_START = new byte[] { (byte) 0xFF, (byte) 0xD8 }; + + private static final int INITIAL_BUFFER_SIZE = 4096; + + InputStream in; + + byte[] boundary; + + byte[] segment; + + byte[] buf; + + int cur, len; + + boolean canceled = false; + + public boolean isCanceled() { + + return canceled; + + } + + public void setCanceled(boolean canceled) { + + this.canceled = canceled; + + if (canceled) { + + try { + + // TODO make this thread-safe + + in.close(); + + } catch (IOException e) { + + + } + + } + + } + + /** + * Creates a new MJPEG parser. Call parse() to begin parsing. + * + * @param in + * An input stream to parse + * @param boundary + * The boundary marker for this MJPEG stream. + */ + + public MJPEGParser(InputStream in, String boundary) { + + this.in = in; + + this.boundary = boundary.getBytes(); + + buf = new byte[INITIAL_BUFFER_SIZE]; + + cur = 0; + + len = INITIAL_BUFFER_SIZE; + + } + + /** + * Reads from the MJPEG input stream, parsing it for JPEG segments. Every + * time a JPEG segment is found, all registered change listeners will be + * notifed. They can retrieve the latest segment via getSegment(). Note that + * this isn't thread-safe: change listeners should retrieve the segment in + * the same thread in which they are notified. + * + */ + + public void parse() throws IOException { + + int b; + + while ((b = in.read()) != -1 && !canceled) { + + append(b); + + if (checkBoundary()) { + + // We found a boundary marker. Process the segment to find the + // JPEG image in it + + processSegment(); + + // And clear out our internal buffer. + + cur = 0; + + } + + } + + } + + /** + * Processes the current byte buffer. Ignores the last len(BOUNDARY) bytes + * in the buffer. Searches through the buffer for the start of a JPEG. If a + * JPEG is found, the bytes comprising the JPEG are copied into the + * segment field. If no JPEG is found, nothing is done. + * + */ + + protected void processSegment() { + + // First, look through the new segment for the start of a JPEG + + boolean found = false; + + int i; + + for (i = 0; i < cur - JPEG_START.length; i++) { + + if (segmentsEqual(buf, i, JPEG_START, 0, JPEG_START.length)) { + + found = true; + + break; + + } + + } + + if (found) { + int segLength = cur - boundary.length - i; + + segment = new byte[segLength]; + + System.arraycopy(buf, i, segment, 0, segLength); + } + + } + + /** + * @return The last JPEG segment found in the MJPEG stream. + */ + + public byte[] getSegment() { + return segment; + } + + /** + * Compares sections of two buffers to see if they are equal. + * + * @param b1 + * The first buffer. + * @param b1Start + * The starting offset into the first buffer. + * @param b2 + * The second buffer. + * @param b2Start + * The starting offset into the second buffer. + * @param len + * The number of bytes to compare. + * @return true if the len consecutive bytes in + * b1 starting at b1Start are equal to the + * lenconsecutive bytes in b2 starting at + * b2Start, false otherwise. + */ + protected boolean segmentsEqual(byte[] b1, int b1Start, byte[] b2, int b2Start, int len) { + + if (b1Start < 0 || b2Start < 0 || b1Start + len > b1.length || b2Start + len > b2.length) { + + return false; + + } else { + for (int i = 0; i < len; i++) { + if (b1[b1Start + i] != b2[b2Start + i]) { + + return false; + + } + } + + return true; + } + + } + + /** + * @return true if if the end of the buffer matches the boundary + */ + + protected boolean checkBoundary() { + return segmentsEqual(buf, cur - boundary.length, boundary, 0, boundary.length); + } + + /** + * @return the length of the internal image buffer in bytes + */ + + public int getBufferSize() { + return len; + } + + /** + * Appends the given byte into the internal buffer. If it won't fit, the + * buffer's size is doubled. + * + * @param i + * the byte to append onto the internal byte buffer + */ + protected void append(int i) { + if (cur >= len) { + + // make buf bigger + byte[] newBuf = new byte[len * 2]; + + System.arraycopy(buf, 0, newBuf, 0, len); + + buf = newBuf; + + len = len * 2; + + } + + buf[cur++] = (byte) i; + } +} \ No newline at end of file diff --git a/src/main/java/org/ilite/frc/vision/camera/axis/Base64Encoder.java b/src/main/java/org/ilite/frc/vision/camera/axis/Base64Encoder.java new file mode 100644 index 0000000..1ca1973 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/axis/Base64Encoder.java @@ -0,0 +1,32 @@ +package org.ilite.frc.vision.camera.axis; + +public class Base64Encoder { + public String encode(String s) { + while (s.length() % 3 != 0) + s += (char) 0; + String encoded = ""; + for (int i = 0; i < s.length(); i += 3) { + int src = ((int) s.charAt(i) << 16) + ((int) s.charAt(i + 1) << 8) + + (int) s.charAt(i + 2); + encoded += encodeChar((src & 0xFC0000) >> 18); + encoded += encodeChar((src & 0x03F000) >> 12); + encoded += encodeChar((src & 0x000FC0) >> 6); + encoded += encodeChar(src & 0x00003F); + } + encoded = encoded.replaceAll("AA$", "=="); + encoded = encoded.replaceAll("A$", "="); + return encoded; + } + + private char encodeChar(int c) { + if (c == 63) + return (char) 47; + if (c == 62) + return (char) 43; + if (c >= 52) + return (char) (c - 4); + if (c >= 26) + return (char) (c + 71); + return (char) (c + 65); + } +} \ No newline at end of file diff --git a/src/main/java/org/ilite/frc/vision/camera/calibration/CameraCalibrationData.java b/src/main/java/org/ilite/frc/vision/camera/calibration/CameraCalibrationData.java new file mode 100644 index 0000000..7ce7ced --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/calibration/CameraCalibrationData.java @@ -0,0 +1,63 @@ +package org.ilite.frc.vision.camera.calibration; + +import org.opencv.core.Mat; + +/** + * Structure to hold onto all of the camera calibration data. + * This is the data that is calculated by the {@link CameraCalibration} + */ +public class CameraCalibrationData { + /** + * The matrix that holds the camera parameters, in the format: + * [ [fx 0 cx] + * [0 fy cy] + * [0 0 1] + * where fx = focal length in x direction, in pixels + * where fy = focal length in y direction, in pixels + * where cx = principal point in x, typically the center of the image + * where cy = principal point in y, typically the center of the image + */ + private final Mat mCameraMatrix; + /** + * The matrix that holds distance coefficients. This will be in this format: (k_1, k_2, p_1, p_2[, k_3[, k_4, k_5, k_6]]) of 4, 5, or 8 elements. + * where k_3 through k_6 are optional. i.e. this will hold either 4,5 or 8 elements + * + * k_1, k_2, k_3, k_4, k_5, and k_6 are radial distortion coefficients. This is used + * since each lens tends to have some slight distortion + */ + private final Mat mDistCoeffs; + /** + * Constructs the {@link CameraCalibrationData} with the camera matrix + * and the distance coefficients + * @param pCameraMatrix + * The camera matrix + * @param pDistCoeffs + * The distance coefficients + */ + public CameraCalibrationData(Mat pCameraMatrix, Mat pDistCoeffs) { + super(); + this.mCameraMatrix = pCameraMatrix; + this.mDistCoeffs = pDistCoeffs; + } + /** + * @return + * The {@link Mat} that contains the camera parameters + */ + public Mat getCameraMatrix() { + return mCameraMatrix; + } + /** + * @return + * The {@link Mat} that contains the distance coefficients + */ + public Mat getDistCoeffs() { + return mDistCoeffs; + } + + @Override + public String toString() { + return "CameraCalibrationData [cameraMatrix=" + mCameraMatrix.dump() + + ", distCoeffs=" + mDistCoeffs.dump() + "]"; + } + +} diff --git a/src/main/java/org/ilite/frc/vision/camera/calibration/Hello.java b/src/main/java/org/ilite/frc/vision/camera/calibration/Hello.java new file mode 100644 index 0000000..c8e5c08 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/calibration/Hello.java @@ -0,0 +1,38 @@ +package org.ilite.frc.vision.camera.calibration; + +import java.awt.image.BufferedImage; + +import org.ilite.frc.vision.camera.CameraConnectionFactory; +import org.ilite.frc.vision.camera.ICameraConnection; +import org.ilite.frc.vision.camera.ICameraFrameUpdateListener; +import org.ilite.frc.vision.camera.opencv.ImageWindow; +import org.ilite.frc.vision.camera.opencv.OpenCVUtils; +import org.opencv.core.Mat; +import org.opencv.imgproc.Imgproc; + +public class Hello { + + public static void main(String[] args) { + OpenCVUtils.init(); + + ICameraConnection cameraConnection = CameraConnectionFactory.getCameraConnection(null); + + + final ImageWindow aWindow = new ImageWindow(null); + cameraConnection.addCameraFrameListener(new ICameraFrameUpdateListener() { + + @Override + public void frameAvail(BufferedImage pImage) { + + Mat orig = OpenCVUtils.toMatrix(pImage); + Mat gray = new Mat(); + + Imgproc.cvtColor(orig, gray, Imgproc.COLOR_RGB2GRAY); + aWindow.updateImage(OpenCVUtils.toBufferedImage(gray)); + } + }); + cameraConnection.start(); + aWindow.show(); + } + +} diff --git a/src/main/java/org/ilite/frc/vision/camera/opencv/HSVWindow.java b/src/main/java/org/ilite/frc/vision/camera/opencv/HSVWindow.java new file mode 100644 index 0000000..db87ef3 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/opencv/HSVWindow.java @@ -0,0 +1,81 @@ +package org.ilite.frc.vision.camera.opencv; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.image.BufferedImage; +import java.io.IOException; + +import javax.imageio.ImageIO; +import javax.swing.Box; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JLabel; + +import org.opencv.core.Mat; +import org.opencv.imgproc.Imgproc; + +public class HSVWindow { + private JFrame frame; + private JLabel imageLabel; + + public HSVWindow() { + frame = new JFrame(); + + final Box box = Box.createVerticalBox(); + + imageLabel = new JLabel(); + + JButton button = new JButton("Open Image"); + + //button.setPreferredSize(new Dimension(imageLabel.getPreferredSize().width, imageLabel.getPreferredSize().height)); + + button.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + JFileChooser chooser = new JFileChooser(); + + int result = chooser.showOpenDialog(frame); + + if(result == JFileChooser.APPROVE_OPTION) { + + try { + BufferedImage img = ImageIO.read(chooser.getSelectedFile()); + + Mat rgba = OpenCVUtils.toMatrix(img); + Mat hsv = new Mat(); + + Imgproc.cvtColor(rgba, hsv, Imgproc.COLOR_RGB2HSV_FULL); + + img = OpenCVUtils.toBufferedImage(hsv); + + imageLabel = new JLabel(new ImageIcon(img)); + + box.add(imageLabel); + frame.pack(); + } catch (IOException e1) { + e1.printStackTrace(); + } + + } + } + + }); + + box.add(imageLabel); + box.add(button); + + frame.add(box); + + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.pack(); + frame.setLocationRelativeTo(null); + frame.setVisible(true); + } + + public static void main(String[] args) { + new HSVWindow(); + } +} diff --git a/src/main/java/org/ilite/frc/vision/camera/opencv/IRenderable.java b/src/main/java/org/ilite/frc/vision/camera/opencv/IRenderable.java new file mode 100644 index 0000000..b2f2734 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/opencv/IRenderable.java @@ -0,0 +1,13 @@ +package org.ilite.frc.vision.camera.opencv; + +import java.awt.Graphics; +import java.awt.image.BufferedImage; + +/** + * Interface for a component that can draw. This is used mainly for overlays in + * the {@link ImageWindow} + */ +public interface IRenderable { + + public void paint(Graphics pGraphics, BufferedImage pImage); +} diff --git a/src/main/java/org/ilite/frc/vision/camera/opencv/ISelectionChangedListener.java b/src/main/java/org/ilite/frc/vision/camera/opencv/ISelectionChangedListener.java new file mode 100644 index 0000000..564541a --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/opencv/ISelectionChangedListener.java @@ -0,0 +1,9 @@ +package org.ilite.frc.vision.camera.opencv; + +import java.awt.Rectangle; + +public interface ISelectionChangedListener { + + public void selectionBoundsChanged(Rectangle pRect); + +} diff --git a/src/main/java/org/ilite/frc/vision/camera/opencv/ImagePanel.java b/src/main/java/org/ilite/frc/vision/camera/opencv/ImagePanel.java new file mode 100644 index 0000000..b9b7087 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/opencv/ImagePanel.java @@ -0,0 +1,51 @@ +package org.ilite.frc.vision.camera.opencv; + +import java.awt.image.BufferedImage; + +import javax.swing.JPanel; + +import org.ilite.frc.vision.api.messages.RobotVisionMsg; +import org.ilite.frc.vision.api.system.IVisionSystem; +import org.ilite.frc.vision.api.system.VisionListener; + +public class ImagePanel implements VisionListener { + private BufferedImage currentFrame; + private JPanel panel; + private IVisionSystem system; + + + public ImagePanel() { + this(new NullVisionSystem()); + } + + public ImagePanel(IVisionSystem pSystem) { + system = pSystem; + + system.subscribe(this); + + panel = new JPanel() { + protected void paintComponent(java.awt.Graphics g) { + super.paintComponent(g); + + if (currentFrame != null) { + g.drawImage(currentFrame, 0, 0, getWidth(), getHeight(), null); + } + } + }; + } + + @Override + public void onVisionDataRecieved(RobotVisionMsg message) { + currentFrame = message.getRawImage(); + panel.repaint(); + } + + public void updateImage(BufferedImage pImage) { + currentFrame = pImage; + panel.repaint(); + } + + public JPanel getPanel() { + return panel; + } +} diff --git a/src/main/java/org/ilite/frc/vision/camera/opencv/ImageWindow.java b/src/main/java/org/ilite/frc/vision/camera/opencv/ImageWindow.java new file mode 100644 index 0000000..634b404 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/opencv/ImageWindow.java @@ -0,0 +1,285 @@ +package org.ilite.frc.vision.camera.opencv; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +import javax.imageio.ImageIO; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; + +import org.ilite.frc.vision.camera.CameraConnectionFactory; +import org.ilite.frc.vision.camera.ICameraConnection; +import org.ilite.frc.vision.camera.opencv.renderables.ObjectDetectorRenderable; +import org.ilite.frc.vision.constants.Paths; +import org.opencv.core.Mat; + +public class ImageWindow { + + private Set mRenderables = new CopyOnWriteArraySet(); + private JButton pauseButton; + private JButton generateOverlay; + private ObjectDetectorRenderable listener; + private JButton saveImageButton; + + private JPanel mImagePanel = new JPanel() { + + protected void paintComponent(java.awt.Graphics g) { + super.paintComponent(g); + + if (mCurrentFrame != null) { + g.drawImage(mCurrentFrame, 0, 0, mCurrentFrame.getWidth(), mCurrentFrame.getHeight(), null); + } + + for (IRenderable renderables2 : mRenderables) { + renderables2.paint(g, mCurrentFrame); + } + } + }; + + public boolean isPaused() { + return pauseButton.getText().equals("resume"); + } + + public void addRenderable(IRenderable pRenderable) { + mRenderables.add(pRenderable); + + mImagePanel.repaint(); + } + + public ImageWindow(BufferedImage pImage) { + this(pImage,""); + } + + public ImageWindow(BufferedImage pImage, String pTitle) { + this(pImage, pTitle, false,false); + } + + public ImageWindow(BufferedImage pImage, boolean pShowPause) { + this(pImage, "", pShowPause,false); + } + + public ImageWindow(BufferedImage pImage, String pWindowTitle, boolean pShowPause, boolean pShowGenOverlay) { + + mFrame = new JFrame(pWindowTitle); + mCurrentFrame = pImage; + + if (mCurrentFrame != null) { + mImagePanel.setPreferredSize(new Dimension( + pImage.getWidth(), pImage.getHeight())); + } + generateOverlay = new JButton("generate overlay"); + generateOverlay.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + listener.onGenerateOverlayClicked(); + } + + }); + generateOverlay.setEnabled(false); + generateOverlay.setVisible(pShowGenOverlay); + + saveImageButton = new JButton("save current frame"); + saveImageButton.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + File dir = new File(Paths.IMAGES_FOLDER_PATH.getValue()); + JFileChooser aChooser = new JFileChooser(dir); + int showOpenDialog = aChooser.showOpenDialog(saveImageButton); + if (showOpenDialog == aChooser.APPROVE_OPTION ){ + File file = aChooser.getSelectedFile(); + try { + ImageIO.write(mCurrentFrame, "png", file); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + + + } + + }); + + pauseButton = new JButton("pause"); + pauseButton.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent arg0) { + + boolean isPaused = false; + + listener.onPauseClicked(); + + if (pauseButton.getText().equals("pause")) { + pauseButton.setText("resume"); + isPaused = true; + + } else if (pauseButton.getText().equals("resume")) { + isPaused = false; + pauseButton.setText("pause"); + } + + if(mCameraConnection != null) { + mCameraConnection.pauseResume(isPaused); + } + + generateOverlay.setEnabled(isPaused); + } + }); + + mButtonPanel = new JPanel(); + mButtonPanel.setLayout(new FlowLayout()); + mButtonPanel.add(pauseButton); + mButtonPanel.add(saveImageButton); + mButtonPanel.add(generateOverlay); + + JPanel wrapper = new JPanel(new BorderLayout()); + wrapper.add(mButtonPanel, BorderLayout.NORTH); + wrapper.add(mImagePanel, BorderLayout.CENTER); + + mFrame.setContentPane(wrapper); + mFrame.pack(); + mFrame.setResizable(false); + mFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + + mFrame.addWindowListener(new WindowListener() { + + @Override + public void windowActivated(WindowEvent arg0) { + } + + @Override + public void windowClosed(WindowEvent arg0) { + CameraConnectionFactory.destroy(); + } + + @Override + public void windowClosing(WindowEvent arg0) { + } + + @Override + public void windowDeactivated(WindowEvent arg0) { + } + + @Override + public void windowDeiconified(WindowEvent arg0) { + } + + @Override + public void windowIconified(WindowEvent arg0) { + } + + @Override + public void windowOpened(WindowEvent arg0) { + } + + }); + + mMouseRenderable = new MouseRenderable(mImagePanel); + addRenderable(mMouseRenderable); + mImagePanel.addMouseListener(mMouseRenderable); + mImagePanel.addMouseMotionListener(mMouseRenderable); + } + + private BufferedImage mCurrentFrame = null; + private JFrame mFrame; + + private MouseRenderable mMouseRenderable; + private JPanel mButtonPanel; + private ICameraConnection mCameraConnection; + + public void updateImage(BufferedImage pImage) { + + mCurrentFrame = pImage; + if (mCurrentFrame != null) { + mImagePanel.setPreferredSize(new Dimension(mCurrentFrame + .getWidth(), mCurrentFrame.getHeight())); + } + + mImagePanel.repaint(); + mFrame.pack(); + + } + public void updateImage(Mat mImage) { + + BufferedImage bImage = OpenCVUtils.toBufferedImage(mImage); + updateImage(bImage); + + } + + public void show() { + mFrame.setVisible(true); + } + + public void addMouseListener(MouseListener pListener) { + mImagePanel.addMouseListener(pListener); + } + + public void removeMouseListener(MouseListener pListener) { + mImagePanel.removeMouseListener(pListener); + } + + public void addMouseMotionListener(MouseMotionListener pListener) { + mImagePanel.addMouseMotionListener(pListener); + } + + public void removeMouseMotionListener(MouseMotionListener pListener) { + mImagePanel.removeMouseMotionListener(pListener); + } + + public MouseRenderable getMouseRenderable() { + return mMouseRenderable; + } + + public void setListener(ObjectDetectorRenderable listener) { + this.listener = listener; + } + + public ObjectDetectorRenderable getListener() { + return listener; + } + + public void repaint() { + mImagePanel.repaint(); + } + + public void addComponentToButtonPanel(final JComponent pComponent) { + if(SwingUtilities.isEventDispatchThread()) { + mButtonPanel.add(pComponent); + mButtonPanel.revalidate(); + mFrame.revalidate(); + + } else { + SwingUtilities.invokeLater(new Runnable() { + + @Override + public void run() { + addComponentToButtonPanel(pComponent); + } + }); + } + } + + public void setCameraConnection(ICameraConnection pCamera) { + mCameraConnection =pCamera; + + } + +} diff --git a/src/main/java/org/ilite/frc/vision/camera/opencv/MouseRenderable.java b/src/main/java/org/ilite/frc/vision/camera/opencv/MouseRenderable.java new file mode 100644 index 0000000..7e0c978 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/opencv/MouseRenderable.java @@ -0,0 +1,137 @@ +package org.ilite.frc.vision.camera.opencv; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.awt.image.BufferedImage; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +import javax.swing.JPanel; + +public class MouseRenderable implements IRenderable, MouseListener, + MouseMotionListener { + private final JPanel mView; + private Point mMousePoint; + private Point mDragStart = null; + private Point mDragEnd = null; + + private Set mSelectionListeners = new CopyOnWriteArraySet(); + + public MouseRenderable(JPanel pPanel) { + mView = pPanel; + } + + @Override + public void mouseDragged(MouseEvent pE) { + if (mDragStart != null) { + mDragEnd = pE.getPoint(); + } + mView.repaint(); + + } + + @Override + public void mouseMoved(MouseEvent pE) { + mMousePoint = pE.getPoint(); + + mView.repaint(); + + } + + @Override + public void mouseClicked(MouseEvent pE) { + // TODO Auto-generated method stub + + } + + @Override + public void mousePressed(MouseEvent pE) { + mDragStart = pE.getPoint(); + mDragEnd = mDragStart; + mView.repaint(); + + } + + @Override + public void mouseReleased(MouseEvent pE) { + Rectangle selectionRect = getSelectionRectangle(); + mDragStart = null; + mDragEnd = null; + mView.repaint(); + notifyListener(selectionRect); + } + + @Override + public void mouseEntered(MouseEvent pE) { + + } + + @Override + public void mouseExited(MouseEvent pE) { + + } + + @Override + public void paint(Graphics pGraphics, BufferedImage pImage) { + + if (mMousePoint != null) { + Color oldColor = pGraphics.getColor(); + pGraphics.setColor(Color.MAGENTA); + pGraphics.drawLine(0, mMousePoint.y, mView.getWidth(), + mMousePoint.y); + pGraphics.drawLine(mMousePoint.x, 0, mMousePoint.x, + mView.getHeight()); + pGraphics.setColor(oldColor); + } + + if (mDragStart != null && mDragEnd != null) { + pGraphics.setColor(Color.RED); + + Rectangle selectRect = getSelectionRectangle(); + + if(selectRect != null) { + pGraphics.drawRect(selectRect.x, selectRect.y, selectRect.width, selectRect.height); + } + } + + } + + private Rectangle getSelectionRectangle() { + int startX = Math.min(mDragStart.x, mDragEnd.x); + int startY = Math.min(mDragStart.y, mDragEnd.y); + + int endX = Math.max(mDragStart.x, mDragEnd.x); + int endY = Math.max(mDragStart.y, mDragEnd.y); + + int width = Math.abs(startX - endX); + int height = Math.abs(startY - endY); + + if(width > 0 && height > 0) { + return new Rectangle(startX, startY, width, height); + } + else { + return null; + } + + } + + public void addSelectionListener(ISelectionChangedListener pListener) { + mSelectionListeners.add(pListener); + } + + public void removeSelectionListener(ISelectionChangedListener pListener) { + mSelectionListeners.remove(pListener); + } + + private void notifyListener(Rectangle pNewRect) { + for (ISelectionChangedListener aListener : mSelectionListeners) { + aListener.selectionBoundsChanged(pNewRect); + } + } + +} diff --git a/src/main/java/org/ilite/frc/vision/camera/opencv/NullVisionSystem.java b/src/main/java/org/ilite/frc/vision/camera/opencv/NullVisionSystem.java new file mode 100644 index 0000000..70dac1f --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/opencv/NullVisionSystem.java @@ -0,0 +1,30 @@ +package org.ilite.frc.vision.camera.opencv; + +import org.ilite.frc.vision.api.system.IVisionSystem; +import org.ilite.frc.vision.api.system.VisionListener; + +/** + * A null implementation of the {@link IVisionSystem}, used when there is no + * vision system. This is done to prevent {@link NullPointerException}. This implementation + * will do nothing + * + */ +class NullVisionSystem implements IVisionSystem { + + @Override + public void start() { + System.err.println("Attempting to start null camera!"); + + } + + @Override + public void subscribe(VisionListener pListener) { + + } + + @Override + public void unsubscribe(VisionListener pListener) { + + } + +} diff --git a/src/main/java/org/ilite/frc/vision/camera/opencv/OpenCVUtils.java b/src/main/java/org/ilite/frc/vision/camera/opencv/OpenCVUtils.java new file mode 100644 index 0000000..cec7165 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/opencv/OpenCVUtils.java @@ -0,0 +1,211 @@ +package org.ilite.frc.vision.camera.opencv; + +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferByte; +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; + +import org.opencv.core.Core; +import org.opencv.core.CvType; +import org.opencv.core.Mat; +import org.opencv.core.Scalar; +import org.opencv.core.Size; +import org.opencv.imgproc.Imgproc; + +/** + * Class containing utility methods to go back and forth between OPENCV and Java + * + * @author Christopher + * + */ +public class OpenCVUtils { + +// private static final Logger sLogger = Logger.getLogger(OpenCVUtils.class); + + /** + * Load the opencv library + */ + static { + +// sLogger.debug("Loading OPENCV library: " + Core.NATIVE_LIBRARY_NAME); + System.loadLibrary(Core.NATIVE_LIBRARY_NAME); + + } + + public static void init() { +// sLogger.debug("Starting opencv..."); + } + + /** + * Method that is used to create a deep copy of a BufferedImage + * @param image Image to make a copy of + * @return The copied image + */ + public static BufferedImage deepCopy(BufferedImage image) { + return new BufferedImage(image.getColorModel(), + image.copyData(null), + image.getColorModel().isAlphaPremultiplied(), + null); + } + + /** + * Helper method to convert an OPENCV {@link Mat} to an {@link Image} If the + * passed in image is a gray scale, the returned image will be gray. If the + * passed in image is multi-channel, the return image is RGB + * + * @param pMatrix + * The matrix to convert + * @return The Image + */ + public static BufferedImage toBufferedImage(Mat pMatrix) { + + int type = BufferedImage.TYPE_BYTE_GRAY; + + if (pMatrix.channels() > 1) { + Mat m2 = new Mat(); + Imgproc.cvtColor(pMatrix, m2, Imgproc.COLOR_BGR2RGB); + type = BufferedImage.TYPE_3BYTE_BGR; + pMatrix = m2; + } + + byte[] b = new byte[pMatrix.channels() * pMatrix.cols() * pMatrix.rows()]; + + pMatrix.get(0, 0, b); // get all the pixels + + BufferedImage image = new BufferedImage(pMatrix.cols(), pMatrix.rows(), type); + + image.getRaster().setDataElements(0, 0, pMatrix.cols(), pMatrix.rows(), b); + + return image; + } + + /** + * Method to convert an image + * + * @param pImage + * The image to convert + * @return A {@link Mat} implemetation of the image. The type will be 8-bit, + * unsigned, 3 channels (RGB) + */ + public static Mat toMatrix(BufferedImage pImage) { + BufferedImage origImage = pImage; + + + int cvType = CvType.CV_8UC3; + + switch(pImage.getType()) { + case BufferedImage.TYPE_4BYTE_ABGR: + origImage = new BufferedImage(pImage.getWidth(), pImage.getHeight(), BufferedImage.TYPE_3BYTE_BGR); + Graphics2D graphics = origImage.createGraphics(); + graphics.drawImage(pImage, 0, 0, null); + graphics.dispose(); + break; + case BufferedImage.TYPE_3BYTE_BGR: + break; + default: + cvType = CvType.CV_8UC1; + + } + byte[] pixels = ((DataBufferByte) origImage.getRaster().getDataBuffer()) + .getData(); + Mat tmp = new Mat(origImage.getHeight(), origImage.getWidth(), cvType); + tmp.put(0, 0, pixels); + return tmp; + } + + public static ImageWindow showImage(BufferedImage pImage) { + return new ImageWindow(pImage); + } + + /** + * Method to convert a scalar's HSV color value to the RGBA Colro Value + * + * @param hsvColor + * @return + */ + public static Scalar converScalarHsv2Rgba(Scalar hsvColor) { + Mat pointMatRgba = new Mat(); + Mat pointMatHsv = new Mat(1, 1, CvType.CV_8UC3, hsvColor); + Imgproc.cvtColor(pointMatHsv, pointMatRgba, Imgproc.COLOR_HSV2RGB_FULL, + 4); + + return new Scalar(pointMatRgba.get(0, 0)); + } + + + + /** + * Method to see if a passed in IP (for the camera) is pingable + * @param pTargetIP + * The IP address of the target + * @return + * true if the IP is pingable + */ + public static boolean isAvailable(String pTargetIP) { + + boolean result = true; + + try { + InetAddress target = InetAddress.getByName(pTargetIP); + result = target.isReachable(5000); //timeout 5sec + } catch (UnknownHostException ex) { +// sLogger.error("Unable to reach IP: " + pTargetIP, ex); + result = false; + } catch (IOException ex) { +// sLogger.error("IOException while trying to connect to: " + pTargetIP); + result = false; + } + + + return result; + } + + /** + * Method to take an image and translate it into the x and y position. + * @param pDeltaX + * The amount, in x to move the image, in pixels + * @param pDelaY + * The amount, in y, to move the iamge, in pixels + * @param pOrigImg + * The image to return + * @return + * The image have the translation has been applied + */ + public static Mat translateMat(int pDeltaX, int pDelaY, Mat pOrigImg) { + + Mat returnMat = new Mat(); + + //TODO: This is the transformation matrix. This should be: + //[ 1 0 tx] + //[ 0 1 ty] + //[ 0 0 1 ] + //M should be size 3x3 + //data needs to be set. + //Hint: Mat.Zeros() will initialize a matrix with all 0s, for a given size + Mat M = null; + //This should be the same size as the origImg. + //Hint: Mat has a method to get size + Size dsize = null; + Imgproc.warpAffine(pOrigImg, returnMat, M, dsize); + + return returnMat; + + } + + /** + * Flips the image 180 degrees + * @param pOrigImage + * The original image + * @return + * The same image, but rotated 180 degrees + */ + public static Mat flipImage180(Mat pOrigImage) { + Mat flippedMat = new Mat(); + + Core.flip(pOrigImage, flippedMat, 0); + return flippedMat; + } +} diff --git a/src/main/java/org/ilite/frc/vision/camera/opencv/OverlaySlider.java b/src/main/java/org/ilite/frc/vision/camera/opencv/OverlaySlider.java new file mode 100644 index 0000000..092a1ca --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/opencv/OverlaySlider.java @@ -0,0 +1,57 @@ +package org.ilite.frc.vision.camera.opencv; + +import java.util.LinkedHashSet; +import java.util.Set; + +import javax.swing.JSlider; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +public class OverlaySlider { + private Set listeners; + private final JSlider slider; + + public static interface OverlaySliderListener { + public void onSliderChanged(float value); + } + + public OverlaySlider() { + listeners = new LinkedHashSet(); + + slider = new JSlider(); + slider.setMaximum(100); + slider.setMinimum(0); + slider.setValue(50); + + slider.addChangeListener(new ChangeListener() { + + @Override + public void stateChanged(ChangeEvent e) { + for(OverlaySliderListener l : listeners) { + l.onSliderChanged(slider.getValue() / 100f); + } + } + + }); + } + + public JSlider getSlider() { + return slider; + } + + public void subscribe(OverlaySliderListener l) { + listeners.add(l); + } + + public void unsubscribe(OverlaySliderListener l) { + listeners.remove(l); + } + + public static void main(String[] args) { + new OverlaySlider(); + } + + public float getValue() { + return slider.getValue()/100f; + } +} diff --git a/src/main/java/org/ilite/frc/vision/camera/opencv/Renderable.java b/src/main/java/org/ilite/frc/vision/camera/opencv/Renderable.java new file mode 100644 index 0000000..4db01f3 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/opencv/Renderable.java @@ -0,0 +1,13 @@ +package org.ilite.frc.vision.camera.opencv; + +import java.awt.Graphics; +import java.awt.image.BufferedImage; + +public class Renderable implements IRenderable { + + @Override + public void paint(Graphics pG, BufferedImage pImage) { + + } + +} diff --git a/src/main/java/org/ilite/frc/vision/camera/opencv/SaveDialog.java b/src/main/java/org/ilite/frc/vision/camera/opencv/SaveDialog.java new file mode 100644 index 0000000..2416d11 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/opencv/SaveDialog.java @@ -0,0 +1,122 @@ +package org.ilite.frc.vision.camera.opencv; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.text.DecimalFormat; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.BoxLayout; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JColorChooser; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.ilite.frc.vision.camera.tools.colorblob.BlobModel; +import org.ilite.frc.vision.constants.Paths; +import org.ilite.frc.vision.data.JSONManager; +import org.json.JSONException; + +public class SaveDialog extends JFrame { + private BufferedImage image; + private BlobModel model; + private JTextField nameTextField; + private static final DecimalFormat sDecimalFormat = new DecimalFormat("000.00"); + + public SaveDialog(BufferedImage image, final BlobModel pModel) { + this.image = image; + this.model = pModel; + + JButton saveButton = new JButton("Save"); + + saveButton.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + + + model.setName(nameTextField.getText()); + + Map objects = new HashMap(); + + objects.put("NAME", model.getName()); + objects.put("AVERAGE_HUE", model.getAverageHue()); + objects.put("AVERAGE_SATURATION", model.getAverageSaturation()); + objects.put("AVERAGE_VALUE", model.getAverageValue()); + + try { + JSONManager.write(objects, new File(Paths.BLOB_CONFIG_PATH.getValue()), "Blob Data"); + } catch (JSONException | IOException e1) { + e1.printStackTrace(); + } + + } + }); + + JButton dropButton = new JButton("Drop"); + + dropButton.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + dispose(); + } + + }); + + JPanel buttonPanel = new JPanel(); + + buttonPanel.add(saveButton); + buttonPanel.add(dropButton); +// buttonPanel.setPreferredSize(new Dimension(image.getWidth(), saveButton.getPreferredSize().height + 5)); + + JLabel imageLabel = new JLabel(new ImageIcon(image)); + + nameTextField = new JTextField("Name"); +// nameTextField.setPreferredSize(new Dimension(image.getWidth(), nameTextField.getPreferredSize().height)); + + JPanel box = new JPanel(); + box.setLayout(new BoxLayout(box, BoxLayout.Y_AXIS)); + box.add(imageLabel); + box.add(buildOutputLabel("Average Hue: ",model.getAverageHue())); + box.add(buildOutputLabel("Average Saturation: ", model.getAverageSaturation())); + box.add(buildOutputLabel("Average Value: ", model.getAverageSaturation())); + + final JButton overlayColor = new JButton("Overlay Color"); + overlayColor.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent pE) { + Color aShowDialog = JColorChooser.showDialog(overlayColor, "Overlay Color", model.getOverlayColor()); + model.setOverlayColor(aShowDialog); + overlayColor.setForeground(aShowDialog); + } + }); + overlayColor.setForeground(model.getOverlayColor()); + buttonPanel.add(overlayColor); + + + JPanel contentPanel = new JPanel(new BorderLayout()); + contentPanel.add(nameTextField, BorderLayout.NORTH); + contentPanel.add(box, BorderLayout.CENTER); + contentPanel.add(buttonPanel, BorderLayout.SOUTH); + + setContentPane(contentPanel); + + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + pack(); + setVisible(true); + } + + private static JLabel buildOutputLabel(String pPreText, double pVal) { + return new JLabel(pPreText + sDecimalFormat.format(pVal)); + } +} diff --git a/src/main/java/org/ilite/frc/vision/camera/opencv/renderables/EHSVValues.java b/src/main/java/org/ilite/frc/vision/camera/opencv/renderables/EHSVValues.java new file mode 100644 index 0000000..fd72be2 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/opencv/renderables/EHSVValues.java @@ -0,0 +1,42 @@ +package org.ilite.frc.vision.camera.opencv.renderables; + +import org.opencv.core.Scalar; + +public enum EHSVValues { + HUE("Hue Value",25,0,100), + SAT("Sat Value",50,0,100), + VALUE("Value Value: ",50,0,100), + MIN_CONTOUR("Min Contour %: ", 10,0,100); + + private String mLabelText; + private double mDefault; + private double mMin; + private double mMax; + + private EHSVValues(String pLabelText, double pDefault, double pMin, double pMax) { + mLabelText = pLabelText; + mDefault = pDefault; + mMin = pMin; + mMax = pMax; + } + + public String getLabelText() { + return mLabelText; + } + + public static double getValue(Scalar pScaler, EHSVValues pValue) { + return pScaler.val[pValue.ordinal()]; + } + + public double getDefault() { + return mDefault; + } + + public double getMax() { + return mMax; + } + public double getMin() { + return mMin; + } + +} diff --git a/src/main/java/org/ilite/frc/vision/camera/opencv/renderables/ObjectDetectorRenderable.java b/src/main/java/org/ilite/frc/vision/camera/opencv/renderables/ObjectDetectorRenderable.java new file mode 100644 index 0000000..de47dd0 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/opencv/renderables/ObjectDetectorRenderable.java @@ -0,0 +1,9 @@ +package org.ilite.frc.vision.camera.opencv.renderables; + +public interface ObjectDetectorRenderable { + + void onGenerateOverlayClicked(); + + void onPauseClicked(); + +} diff --git a/src/main/java/org/ilite/frc/vision/camera/tools/colorblob/BlobModel.java b/src/main/java/org/ilite/frc/vision/camera/tools/colorblob/BlobModel.java new file mode 100644 index 0000000..888c825 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/tools/colorblob/BlobModel.java @@ -0,0 +1,221 @@ +package org.ilite.frc.vision.camera.tools.colorblob; + +import java.awt.Color; + +import org.opencv.core.CvType; +import org.opencv.core.Mat; +import org.opencv.core.Scalar; +import org.opencv.imgproc.Imgproc; + + +public class BlobModel { + /** + * Log4j logger + */ +// private static final Logger sLog = Logger.getLogger(BlobModel.class); + /** + * The scale factor of the contours, used to scale up the + */ + public static final Scalar CONTOUR_SCALAR = new Scalar(4, 4); + /** + * Indexes for array representations of the colors + */ + private static final int HUE_IDX = 0; + private static final int SATURATION_IDX = 1; + private static final int VALUE_IDX = 2; + + /** + * The name of the object + */ + private String mNameOfObject; + /** + * The average color, as HSV. The first element is hue, the second sat and + * the last is value + */ + private double[] mHSVAverageColor; + /** + * {@link Scalar} representation of the lower color and the upper color + */ + private Scalar mLowerBound, mUpperBound; + /** + * Minimum contour area in percent for contours filtering + */ + private double mMinContourArea; + /** + * Color radius for range checking in HSV color space + */ + private Scalar mColorRadius; + /** + * The average color of the blob + */ + private Scalar mBlobColorHsv; + /** + * The masks + */ + private Mat mPyrDownMat, mHsvMat, mMask, mDilatedMask, mHierarchy, mSpectrum; + private Color mOverlayColor = Color.YELLOW; + + + public double[] getHsv() { + return mHSVAverageColor; + } + + public Scalar getLowerBound() { + return mLowerBound; + } + + public Scalar getUpperBound() { + return mUpperBound; + } + + public double getMinContourArea() { + return mMinContourArea; + } + + public Scalar getColorRadius() { + return mColorRadius; + } + + public Scalar getBlobColorHsv() { + return mBlobColorHsv; + } + + public Mat getPyrDownMat() { + return mPyrDownMat; + } + + public Mat getHsvMat() { + return mHsvMat; + } + + public Mat getMask() { + return mMask; + } + + public Mat getDilatedMask() { + return mDilatedMask; + } + + public Mat getHierarchy() { + return mHierarchy; + } + + public Mat getSpectrum() { + return mSpectrum; + } + + public BlobModel() { + mHSVAverageColor = new double[3]; + mLowerBound = new Scalar(0); + mUpperBound = new Scalar(0); + mMinContourArea = 0.1; + mColorRadius = new Scalar(25, 50, 50, 0); + mSpectrum = new Mat(); + mBlobColorHsv = null; + mPyrDownMat = new Mat(); + mHsvMat = new Mat(); + mDilatedMask = new Mat(); + mMask = new Mat(); + mHierarchy = new Mat(); + } + + public void setName(String name) { + this.mNameOfObject = name; + } + + public void setAverageHue(double hue) { + mHSVAverageColor[HUE_IDX] = hue; + } + + public void setAverageValue(double value) { + mHSVAverageColor[VALUE_IDX] = value; + } + + public void setAverageSaturation(double saturation) { + mHSVAverageColor[SATURATION_IDX] = saturation; + } + + public double getAverageHue() { + return mHSVAverageColor[HUE_IDX]; + } + + public double getAverageValue() { + return mHSVAverageColor[VALUE_IDX]; + } + + public double getAverageSaturation() { + return mHSVAverageColor[SATURATION_IDX]; + } + + public String getName() { + return mNameOfObject; + } + + @Override + public String toString() { + return "Name: " + mNameOfObject + "\nHSV: [" + mHSVAverageColor[0] + ", " + mHSVAverageColor[1] + ", " + mHSVAverageColor[2] + "]\n"; + } + + public void setHsvColor(Scalar hsvColor) { + +// if(sLog.isDebugEnabled()) { +// StringBuilder debugString = new StringBuilder(); +// debugString.append("Setting the HSV color= {"); +// debugString.append(hsvColor.val[HUE_IDX]); +// debugString.append(", ").append(hsvColor.val[SATURATION_IDX]); +// debugString.append(", ").append(hsvColor.val[VALUE_IDX]); +// sLog.debug(debugString);; +// } + setAverageHue(hsvColor.val[0]); + setAverageSaturation(hsvColor.val[1]); + setAverageValue(hsvColor.val[2]); + + mBlobColorHsv = hsvColor; + double minH = (hsvColor.val[0] >= mColorRadius.val[0]) ? hsvColor.val[0] + - mColorRadius.val[0] + : 0; + double maxH = (hsvColor.val[0] + mColorRadius.val[0] <= 255) ? hsvColor.val[0] + + mColorRadius.val[0] + : 255; + + + + mLowerBound.val[0] = minH; + mUpperBound.val[0] = maxH; + + mLowerBound.val[1] = hsvColor.val[1] - mColorRadius.val[1]; + mUpperBound.val[1] = hsvColor.val[1] + mColorRadius.val[1]; + + mLowerBound.val[2] = hsvColor.val[2] - mColorRadius.val[2]; + mUpperBound.val[2] = hsvColor.val[2] + mColorRadius.val[2]; + + mLowerBound.val[3] = 0; + mUpperBound.val[3] = 255; + +// if(sLog.isDebugEnabled()) { +// StringBuilder debugString = new StringBuilder(); +// debugString.append("hue color range= {").append(mLowerBound.val[HUE_IDX]).append(", ").append(mUpperBound.val[HUE_IDX]).append("}"); +// debugString.append("sat color range= {").append(mLowerBound.val[SATURATION_IDX]).append(", ").append(mUpperBound.val[SATURATION_IDX]).append("}"); +// debugString.append("value color range= {").append(mLowerBound.val[VALUE_IDX]).append(", ").append(mUpperBound.val[VALUE_IDX]).append("}"); +// sLog.debug(debugString); +// } + + Mat spectrumHsv = new Mat(1, (int) (maxH - minH), CvType.CV_8UC3); + + for (int j = 0; j < maxH - minH; j++) { + byte[] tmp = { (byte) (minH + j), (byte) 255, (byte) 255 }; + spectrumHsv.put(0, j, tmp); + } + + Imgproc.cvtColor(spectrumHsv, mSpectrum, Imgproc.COLOR_HSV2RGB_FULL, 4); + } + + public Color getOverlayColor() { + return mOverlayColor; + } + + public void setOverlayColor(Color pOverlayColor) { + mOverlayColor = pOverlayColor; + } + +} diff --git a/src/main/java/org/ilite/frc/vision/camera/tools/colorblob/histogram/HistoGramImageWindow.java b/src/main/java/org/ilite/frc/vision/camera/tools/colorblob/histogram/HistoGramImageWindow.java new file mode 100644 index 0000000..50849f3 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/tools/colorblob/histogram/HistoGramImageWindow.java @@ -0,0 +1,79 @@ +package org.ilite.frc.vision.camera.tools.colorblob.histogram; + +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +import org.ilite.frc.vision.camera.opencv.ImageWindow; +import org.ilite.frc.vision.camera.opencv.OpenCVUtils; +import org.opencv.core.Core; +import org.opencv.core.CvType; +import org.opencv.core.Mat; +import org.opencv.core.MatOfFloat; +import org.opencv.core.MatOfInt; +import org.opencv.core.Scalar; +import org.opencv.imgproc.Imgproc; + +public class HistoGramImageWindow extends ImageWindow { + + public HistoGramImageWindow(BufferedImage pImage) { + super(null, "Histogram"); + } + + @Override + public void updateImage(BufferedImage pImage) { + // 1. Convert the image to a Matrix + Mat image = OpenCVUtils.toMatrix(pImage); + // 2. Create a list of Mat, this will be the individual color channels, + // RGB + List rgb = new ArrayList(); + // 3. Split the image into it's three channels, use the list created in + // step 2 + Core.split(image, rgb); + // 4. Create a MatofInt with initial size of 256 this will be used to + // define + // the range, i.e. 0-255 + + MatOfInt histSize = new MatOfInt(255); + Mat histImage = Mat.zeros(100, (int) histSize.get(0, 0)[0], + CvType.CV_8UC3); + histImage = calcHistogramSingleChannel(Arrays.asList(rgb.get(0)), + histImage, histSize, new Scalar(255, 0, 0)); + histImage = calcHistogramSingleChannel(Arrays.asList(rgb.get(1)), + histImage, histSize, new Scalar(0, 255, 0)); + histImage = calcHistogramSingleChannel(Arrays.asList(rgb.get(2)), + histImage, histSize, new Scalar(0, 0, 255)); + + super.updateImage(OpenCVUtils.toBufferedImage(histImage)); + } + + private Mat calcHistogramSingleChannel(List rgb, Mat pHistImage, + MatOfInt pHistSize, Scalar pHistoColor) { + // Calculate histogram + java.util.List matList = new LinkedList(); + matList.add(rgb.get(0)); + Mat histogram = new Mat(); + MatOfFloat ranges = new MatOfFloat(0, 256); + Imgproc.calcHist(matList, new MatOfInt(0), new Mat(), histogram, + pHistSize, ranges); + + // Create space for histogram image + // Normalize histogram + Core.normalize(histogram, histogram, 1, pHistImage.rows(), + Core.NORM_MINMAX, -1, new Mat()); + + // Draw lines for histogram points + for (int i = 0; i < (int) pHistSize.get(0, 0)[0]; i++) { + Imgproc.line( + pHistImage, + new org.opencv.core.Point(i, pHistImage.rows()), + new org.opencv.core.Point(i, pHistImage.rows() + - Math.round(histogram.get(i, 0)[0])), pHistoColor, + 1, 8, 0); + } + return pHistImage; + } + +} diff --git a/src/main/java/org/ilite/frc/vision/camera/tools/overlayGenerator/CannyEdgeFun.java b/src/main/java/org/ilite/frc/vision/camera/tools/overlayGenerator/CannyEdgeFun.java new file mode 100644 index 0000000..fefb2ff --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/tools/overlayGenerator/CannyEdgeFun.java @@ -0,0 +1,90 @@ +package org.ilite.frc.vision.camera.tools.overlayGenerator; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +import javax.imageio.ImageIO; +import javax.swing.JFrame; +import javax.swing.JSlider; +import javax.swing.SwingUtilities; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.ilite.frc.vision.camera.opencv.ImageWindow; +import org.ilite.frc.vision.camera.opencv.OpenCVUtils; +import org.opencv.core.Mat; +import org.opencv.core.Size; +import org.opencv.imgproc.Imgproc; + +public class CannyEdgeFun { + + private final JSlider mSlider = new JSlider(1, 100); + + private final BufferedImage mImage; + + private ImageWindow mCanny; + + public CannyEdgeFun(BufferedImage pImage) { + mImage = pImage; + + ImageWindow aWindow = new ImageWindow(mImage,"OrigImage"); + aWindow.show(); + + mCanny = new ImageWindow(mImage, "Canny"); + mCanny.show(); + mSlider.addChangeListener(new ChangeListener() { + + @Override + public void stateChanged(ChangeEvent pE) { + applyCanny(); + + } + }); + + } + + protected void applyCanny() { + int sliderValue = mSlider.getValue(); + Mat src = OpenCVUtils.toMatrix(mImage); + Mat srcGray = new Mat(); + Mat detectedEdges = new Mat(); + Mat dst = src.clone(); + + Imgproc.cvtColor(src, srcGray, Imgproc.COLOR_RGB2GRAY); + Imgproc.GaussianBlur(srcGray, detectedEdges,new Size(3,3),1); + Imgproc.Canny(detectedEdges, detectedEdges, sliderValue,sliderValue*3); + + dst = Mat.zeros(dst.size(), dst.type()); + src.copyTo(dst, detectedEdges); + + mCanny.updateImage(OpenCVUtils.toBufferedImage(dst)); + + + } + + public static void createAndShowGUI() throws IOException { + JFrame aFrame = new JFrame(); + File imageFile = new File("src/main/resources/images/Screenshot1.jpg"); + aFrame.setContentPane(new CannyEdgeFun(ImageIO.read(imageFile)).mSlider); + aFrame.pack(); + aFrame.setVisible(true); + } + + public static void main(String[] args) { + SwingUtilities.invokeLater(new Runnable() { + + @Override + public void run() { + try { + createAndShowGUI(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + }); + + } + +} diff --git a/src/main/java/org/ilite/frc/vision/camera/tools/overlayGenerator/EThreshAlgorithm.java b/src/main/java/org/ilite/frc/vision/camera/tools/overlayGenerator/EThreshAlgorithm.java new file mode 100644 index 0000000..156dc46 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/tools/overlayGenerator/EThreshAlgorithm.java @@ -0,0 +1,10 @@ +package org.ilite.frc.vision.camera.tools.overlayGenerator; + +public enum EThreshAlgorithm { + + BINARY, + BINARY_INVERTED, + THRESHOLD_TRUNCATED, + THRESHOLD_TO_ZERO, + THRESHOLD_TO_ZERO_INVERTED; +} diff --git a/src/main/java/org/ilite/frc/vision/camera/tools/overlayGenerator/EdgeDetectWindow.java b/src/main/java/org/ilite/frc/vision/camera/tools/overlayGenerator/EdgeDetectWindow.java new file mode 100644 index 0000000..3b9a625 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/tools/overlayGenerator/EdgeDetectWindow.java @@ -0,0 +1,22 @@ +package org.ilite.frc.vision.camera.tools.overlayGenerator; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +import javax.imageio.ImageIO; + +public class EdgeDetectWindow { + + public static void main(String[] args) throws IOException { + File imageFile = new File("src/main/resources/images/Screenshot1.jpg"); + + BufferedImage aRead; + try { + aRead = ImageIO.read(imageFile); + } catch (IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/org/ilite/frc/vision/camera/tools/overlayGenerator/JsonExample.java b/src/main/java/org/ilite/frc/vision/camera/tools/overlayGenerator/JsonExample.java new file mode 100644 index 0000000..319b6dd --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/tools/overlayGenerator/JsonExample.java @@ -0,0 +1,34 @@ +package org.ilite.frc.vision.camera.tools.overlayGenerator; + +public class JsonExample { + +// public static void main(String[] args) throws JSONException, IOException { +// +// JSONObject anObject = new JSONObject(); +// +// JSONObject tote = new JSONObject(); +// tote.put("Name", "Tote"); +// tote.put("AVERAGE_HUE", 4); +// tote.put("AVERAGE_SAT", 4); +// tote.put("AVERAGE_VALUE", 4); +// Listtotes = new ArrayList(); +// totes.add(tote); +// +// JSONObject trashCan = new JSONObject(); +// trashCan.put("Name", "TrashCan"); +// trashCan.put("Name", "Tote"); +// trashCan.put("AVERAGE_HUE", 45); +// trashCan.put("AVERAGE_SAT", 45); +// trashCan.put("AVERAGE_VALUE", 45); +// +// totes.add(trashCan); +// JSONArray objects = new JSONArray(totes); +// +// anObject.append("Objects", objects); +// +// FileWriter fw = new FileWriter(new File("src/main/resources/example.json")); +// fw.write(anObject.toString()); +// fw.close(); +// } + +} diff --git a/src/main/java/org/ilite/frc/vision/camera/tools/overlayGenerator/OverlayGeneratorCode.java b/src/main/java/org/ilite/frc/vision/camera/tools/overlayGenerator/OverlayGeneratorCode.java new file mode 100644 index 0000000..dc09909 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/tools/overlayGenerator/OverlayGeneratorCode.java @@ -0,0 +1,86 @@ +package org.ilite.frc.vision.camera.tools.overlayGenerator; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +import javax.imageio.ImageIO; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; +import javax.swing.filechooser.FileFilter; + +public class OverlayGeneratorCode { + private final JPanel mView = new JPanel(new BorderLayout()); + + public OverlayGeneratorCode() { + JMenuBar aMenuBar = new JMenuBar(); + + JMenu fileMenu = new JMenu("FILE"); + + JMenuItem load = new JMenuItem("Load Image"); + load.addActionListener(new ActionListener() { + + private BufferedImage mCurrentImage; + + @Override + public void actionPerformed(ActionEvent pE) { + JFileChooser aChooser = new JFileChooser(); + aChooser.setFileFilter(new FileFilter() { + + @Override + public String getDescription() { + return "Images"; + } + + @Override + public boolean accept(File pF) { + return pF.isDirectory() || pF.getName().endsWith("png"); + } + }); + int aShowOpenDialog = aChooser.showOpenDialog(mView); + if(aShowOpenDialog ==JFileChooser.APPROVE_OPTION) { + try { + mCurrentImage = ImageIO.read(aChooser.getSelectedFile()); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + } + }); + + aMenuBar.add(fileMenu); + + + mView.add(aMenuBar, BorderLayout.NORTH); + } + + public static final void createAndShowGUI() { + JFrame aFrame = new JFrame(); + OverlayGeneratorCode aCode = new OverlayGeneratorCode(); + aFrame.setContentPane(aCode.mView); + aFrame.pack(); + aFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + aFrame.setVisible(true); + + } + + public static void main(String[] args) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + createAndShowGUI(); + } + }); + } + +} diff --git a/src/main/java/org/ilite/frc/vision/camera/tools/overlayGenerator/SobelEdgeFun.java b/src/main/java/org/ilite/frc/vision/camera/tools/overlayGenerator/SobelEdgeFun.java new file mode 100644 index 0000000..ed09ef7 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/camera/tools/overlayGenerator/SobelEdgeFun.java @@ -0,0 +1,54 @@ +package org.ilite.frc.vision.camera.tools.overlayGenerator; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +import javax.imageio.ImageIO; + +import org.ilite.frc.vision.camera.opencv.ImageWindow; +import org.ilite.frc.vision.camera.opencv.OpenCVUtils; +import org.opencv.core.Core; +import org.opencv.core.CvType; +import org.opencv.core.Mat; +import org.opencv.core.Size; +import org.opencv.imgproc.Imgproc; + +public class SobelEdgeFun { + + public static void main(String[] args) throws IOException { + File imageFile = new File("src/main/resources/images/Screenshot1.jpg"); + + BufferedImage aRead = ImageIO.read(imageFile); + ImageWindow origImage =new ImageWindow(aRead, "ORIGINAL"); + origImage.show(); + Mat aMatrix = OpenCVUtils.toMatrix(aRead); + + Imgproc.GaussianBlur(aMatrix, aMatrix, new Size(3,3), 0,0); + + Imgproc.cvtColor(aMatrix, aMatrix, Imgproc.COLOR_RGB2GRAY); + + + Mat gradx = new Mat(); + Mat grady = new Mat(); + Mat gradabsx = new Mat(); + Mat gradabsy = new Mat(); + + Imgproc.Sobel(aMatrix, gradx, CvType.CV_16U,1,0,3,1,0); + Core.convertScaleAbs(gradx, gradabsx); + + Imgproc.Sobel(aMatrix, grady, CvType.CV_16U,0,1,3,1,0); + Core.convertScaleAbs(grady, gradabsy); + + Mat grad = new Mat(); + Core.addWeighted(gradabsx, .5, gradabsy, .5, 0, grad); + + ImageWindow finalWindow = new ImageWindow(OpenCVUtils.toBufferedImage(grad),"SOBEL"); + + + + finalWindow.show(); + + } + +} diff --git a/src/main/java/org/ilite/frc/vision/constants/BlobData.java b/src/main/java/org/ilite/frc/vision/constants/BlobData.java new file mode 100644 index 0000000..4d69516 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/constants/BlobData.java @@ -0,0 +1,62 @@ +package org.ilite.frc.vision.constants; + +import java.util.LinkedHashSet; + +import org.ilite.frc.vision.camera.tools.colorblob.BlobModel; + +public class BlobData { + private static LinkedHashSet models; + + public static void readBlobData() /*throws JSONException, IOException*/ { + +// File file = new File(Paths.BLOB_CONFIG_PATH.getValue()); +// +// byte[] buffer = new byte[(int) file.length()]; +// +// FileInputStream stream = new FileInputStream(file); +// DataInputStream dstream = new DataInputStream(stream); +// +// dstream.readFully(buffer); +// +// dstream.close(); +// stream.close(); +// +// BufferedReader br = new BufferedReader(new FileReader(file)); +// JSONArray array = new JSONArray(); +// +// if(br.readLine() != null) { +// String string = new String(buffer); +// JSONObject rootObject = new JSONObject(string); +// Object object = rootObject.get("Blob Data"); +// +// if(object instanceof JSONArray) { +// array = (JSONArray)object; +// } +// } +// +// br.close(); +// +// models = new LinkedHashSet(); + +// for(int i = 0; i < array.length(); i++) { +// JSONObject o = array.getJSONObject(i); + +// BlobModel m = new BlobModel(); +// m.setName(o.getString("NAME")); +// m.setAverageHue(o.getDouble("AVERAGE_HUE")); +// m.setAverageSaturation(o.getDouble("AVERAGE_SATURATION")); +// m.setAverageValue(o.getDouble("AVERAGE_VALUE")); + +// models.add(m); +// } + } + + public static LinkedHashSet getBlobData() throws Exception { + + if(models == null) { + throw new Exception("No blob models loaded from file. Call readBlobData()."); + } + + return models; + } +} diff --git a/src/main/java/org/ilite/frc/vision/constants/ECameraConfig.java b/src/main/java/org/ilite/frc/vision/constants/ECameraConfig.java new file mode 100644 index 0000000..19a0a93 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/constants/ECameraConfig.java @@ -0,0 +1,45 @@ +package org.ilite.frc.vision.constants; + +import org.ilite.frc.vision.data.Configurations; + +public enum ECameraConfig { + CAM_RATE_MILLIS(Configurations.getIntValue("CAM_RATE_MILLIS")), + CAMERA_PERIOD(Configurations.getIntValue("CAMERA_PERIOD")), + INITIAL_CAMERA_DELAY(Configurations.getIntValue("INITIAL_CAMERA_DELAY")), + DEVICE(Configurations.getLongValue("DEVICE")), + USERNAME(Configurations.getStringValue("USERNAME")), + PASSWORD(Configurations.getStringValue("PASSWORD")), + USE_LOCAL_IF_NOT_AVAILABLE(Configurations.getBooleanValue("USE_LOCAL_IF_NOT_AVAILABLE")); + + private long val; + private String val2; + private boolean val3; + + private ECameraConfig(boolean b) { + this.val3 = b; + } + + private ECameraConfig(long v) { + val = v; + } + + private ECameraConfig(String val) { + val2 = val; + } + + public boolean getBooleanValue() { + return val3; + } + + public String getStringValue() { + if(val2 == null) { + return Long.toString(val); + } + + return val2; + } + + public long getValue() { + return val; + } +} diff --git a/src/main/java/org/ilite/frc/vision/constants/ECameraType.java b/src/main/java/org/ilite/frc/vision/constants/ECameraType.java new file mode 100644 index 0000000..dd7d6d1 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/constants/ECameraType.java @@ -0,0 +1,19 @@ +package org.ilite.frc.vision.constants; + +import org.ilite.frc.vision.data.Configurations; + +public enum ECameraType { + ALIGNMENT_CAMERA("ALIGNED_CAMERA_IP"), + FIELD_CAMERA("FIELD_CAMERA_IP"), + LOCAL_CAMERA("LOCAL_CAMERA"); + + private ECameraType(String pPropertyName) { + mCameraIP = (String) Configurations.getValue(pPropertyName); + } + + public String getCameraIP() { + return mCameraIP; + } + private final String mCameraIP; + +} diff --git a/src/main/java/org/ilite/frc/vision/constants/Paths.java b/src/main/java/org/ilite/frc/vision/constants/Paths.java new file mode 100644 index 0000000..8a010fd --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/constants/Paths.java @@ -0,0 +1,22 @@ +package org.ilite.frc.vision.constants; + +import org.ilite.frc.vision.data.Configurations; + +public enum Paths { + IMAGES_FOLDER_PATH("IMAGES_FOLDER_PATH"), + IMAGES_NUMBER4_PATH("IMAGES_NUMBER4_PATH"), + SCREEN_SHOT1_PATH("SCREEN_SHOT1_PATH"), + BLOB_CONFIG_PATH("BLOB_CONFIG_PATH"), + OVERLAY_HOOK_PATH("OVERLAY_HOOK_PATH"), + OVERLAY_TOTE_PATH("OVERLAY_TOTE_PATH"); + + private String value; + + private Paths(String val) { + value = Configurations.getStringValue(val); + } + + public String getValue() { + return value; + } +} diff --git a/src/main/java/org/ilite/frc/vision/data/Configurations.java b/src/main/java/org/ilite/frc/vision/data/Configurations.java new file mode 100644 index 0000000..1f2eb2e --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/data/Configurations.java @@ -0,0 +1,97 @@ +package org.ilite.frc.vision.data; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + + +public class Configurations { + private static final Map mKeyMap; +// private static final Logger sLogger = Logger.getLogger(Configurations.class); + static { + + MaptempMap = new HashMap<>(); + +// try { +// Map map = JSONManager.read(new File("properties.json")); +// +// for(EntryanEntry : map.entrySet()) { +// tempMap.put(anEntry.getKey(), anEntry.getValue()); +// } +// +// } catch (IOException | JSONException e) { +// e.printStackTrace(); +// } + + mKeyMap = Collections.unmodifiableMap(tempMap); + } + + public static float getFloatValue(String key) { + return ((Double) mKeyMap.get(key)).floatValue(); + } + + public static long getLongValue(String key) { + return ((Double) mKeyMap.get(key)).longValue(); + } + + public static int getIntValue(String key) { + return ((Integer) mKeyMap.get(key)).intValue(); + } + + public static boolean getBooleanValue(String key) { + return ((Boolean) mKeyMap.get(key)).booleanValue(); + } + + public static String getStringValue(String key) { + return (String) mKeyMap.get(key); + } + + public static Object getValue(String pKey) { + return mKeyMap.get(pKey); + } + + public static double []getDoubleArray(String pKey) { +// double [] returnVal = null; +// Object object = mKeyMap.get(pKey); +// System.out.println(object.getClass() + ""); +// JSONArray val = (JSONArray)object; +// returnVal = new double[val.length()]; +// try +// { +// for(int i = 0; i < returnVal.length; i++) +// { +// returnVal[i] = val.getDouble(i); +// } +// +// +// } catch (JSONException e) +// { +// e.printStackTrace(); +// } +// +// if(sLogger.isDebugEnabled()) { +// for(int i = 0; i < returnVal.length; i++) +// { +// sLogger.debug(returnVal[i]); +// } +// } +// +// +// //TODO: Get the value of the map and cast accordingly +// return returnVal; + return null; + } + + + public static void main(String[] args) + { +// for(EntryanObject : mKeyMap.entrySet()) { +// System.out.println(anObject.getKey() + " " + anObject.getValue()); +// } +// +// double[] doubleArray = getDoubleArray("HIGH_COLOR"); + getDoubleArray("HIGH_COLOR"); + + + } +} diff --git a/src/main/java/org/ilite/frc/vision/data/JSONManager.java b/src/main/java/org/ilite/frc/vision/data/JSONManager.java new file mode 100644 index 0000000..a60f002 --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/data/JSONManager.java @@ -0,0 +1,105 @@ +package org.ilite.frc.vision.data; + +import java.io.BufferedReader; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +public class JSONManager { + + + public static LinkedHashMap read(File file) throws IOException, JSONException { + JSONObject root; + + FileInputStream stream = new FileInputStream(file); + + byte[] buffer = new byte[(int) file.length()]; + + DataInputStream dstream = new DataInputStream(stream); + + dstream.readFully(buffer); + + root = new JSONObject(new String(buffer)); + + dstream.close(); + stream.close(); + + LinkedHashMap map = new LinkedHashMap(); + + Iterator it = root.keys(); + + while(it.hasNext()) { + String s = (String) it.next(); + + map.put(s, root.get(s)); + } + + return map; + } + + public static void write(Map data, File file, String header) throws JSONException, IOException { + JSONArray array = null; + + byte[] buffer = new byte[(int) file.length()]; + + FileInputStream stream = new FileInputStream(file); + DataInputStream dstream = new DataInputStream(stream); + + dstream.readFully(buffer); + + BufferedReader br = new BufferedReader(new FileReader(file)); + + if(br.readLine() != null) { + String string = new String(buffer); + JSONObject rootObject = new JSONObject(string); + Object object = rootObject.get(header); + + if(object instanceof JSONArray) { + array = (JSONArray)object; + } + else { + array = new JSONArray(); + } + } + else { + array = new JSONArray(); + } + + br.close(); + + List objects = new ArrayList(); + + JSONObject o = new JSONObject(); + + for(int i = 0; i < array.length(); i++) { + objects.add(array.getJSONObject(i)); + } + + for(Entry e : data.entrySet()) { + o.put(e.getKey(), e.getValue()); + } + + objects.add(o); + + array = new JSONArray(objects); + + FileWriter w = new FileWriter(file); + + w.write("{\"" + header + "\":" + array.toString() + "}"); + + w.close(); + } +} diff --git a/src/main/java/org/ilite/frc/vision/hello/HelloOpenCV.java b/src/main/java/org/ilite/frc/vision/hello/HelloOpenCV.java new file mode 100644 index 0000000..0f33e0f --- /dev/null +++ b/src/main/java/org/ilite/frc/vision/hello/HelloOpenCV.java @@ -0,0 +1,108 @@ +package org.ilite.frc.vision.hello; + +import java.awt.image.BufferedImage; + +import org.ilite.frc.vision.camera.CameraConnectionFactory; +import org.ilite.frc.vision.camera.ICameraConnection; +import org.ilite.frc.vision.camera.ICameraFrameUpdateListener; +import org.ilite.frc.vision.camera.opencv.ImageWindow; +import org.ilite.frc.vision.camera.opencv.OpenCVUtils; +import org.ilite.frc.vision.constants.ECameraType; +import org.opencv.core.Mat; +import org.opencv.core.Size; +import org.opencv.imgproc.Imgproc; + +public class HelloOpenCV { + + public static void main(String[] args) { + //The opencv library must be added to the path. The options are to add + //a VM parameter to the run configuration: -Djava.library.path=libraries/opencv/build/java/x64 + + //This must be called before using opencv + OpenCVUtils.init(); + + //Get a reference to the camera. This takes an IP address to the camera. If + //null, the this will just connect to the local camera, i.e. the webcamera + //of the computer + ICameraConnection cameraConnection = + CameraConnectionFactory.getCameraConnection(ECameraType.ALIGNMENT_CAMERA.getCameraIP()); + + //A window to display the camera field + final ImageWindow aWindow = new ImageWindow(null, "Camera Image"); + + //A window to display the grayscaled output image after processing. + final ImageWindow grayImageWindow = new ImageWindow(null, "Grayscale Image"); + + //A window to display the blurred output image after processing. + final ImageWindow blurredImageWindow = new ImageWindow(null, "Blurred Image"); + + //A window to display an output image that highlights edges after processing. + final ImageWindow edgesImageWindow = new ImageWindow(null, "Edges Image"); + + //Register a listener for frame updates. This will be notified every time + //there's a new image available for the camera + final ImageWindow cWindow = new ImageWindow(null); + cameraConnection.addCameraFrameListener(new ICameraFrameUpdateListener() { + + @Override + public void frameAvail(BufferedImage pImage) { + aWindow.updateImage(pImage); + + //TODO: Perform pre-processing. Such as blurring. Note that all + //opencv methods use images stored in Mat, so you will need to + //convert: + Mat imageAsMat = OpenCVUtils.toMatrix(pImage); + + //It's often a good idea to create copies of the outputs. This way. + //we can hold onto the original image + Mat grayMat = new Mat(); + Mat blurredMat = new Mat(); + Mat edgesMat = new Mat(); + Mat cMat = new Mat(); + //Most algorithms will require the image to be gray scale: + Imgproc.cvtColor(imageAsMat, grayMat, Imgproc.COLOR_BGR2GRAY); + + //Other algorithms will require the image to be blurred: + Imgproc.GaussianBlur(imageAsMat, blurredMat, new Size(), 20.0); + + //Finally, certain algorithms need to identify edges: + Imgproc.GaussianBlur(imageAsMat, edgesMat, new Size(), 5.0); + Imgproc.Canny(edgesMat, edgesMat, 5.0, 30.0); + + //Perform some pre-processing. This will use convolution with + //a 3x3 kernal matrix. To learn more about convolution, see: + //http://setosa.io/ev/image-kernels/ + Imgproc.GaussianBlur(grayMat, grayMat, new Size(3,3),0); + + //The image window uses a Buffered image, so convert: + BufferedImage grayImage = OpenCVUtils.toBufferedImage(grayMat); + grayImageWindow.updateImage(grayImage); + + BufferedImage blurredImage = OpenCVUtils.toBufferedImage(blurredMat); + blurredImageWindow.updateImage(blurredImage); + + BufferedImage edgesImage = OpenCVUtils.toBufferedImage(edgesMat); + edgesImageWindow.updateImage(edgesImage); + + Imgproc.cvtColor(imageAsMat, cMat, Imgproc.COLOR_BGR2Luv); + BufferedImage cImage = OpenCVUtils.toBufferedImage(cMat); + cWindow.updateImage(cImage); + } + }); + + //Show the windows + aWindow.show(); + grayImageWindow.show(); + blurredImageWindow.show(); + edgesImageWindow.show(); + cWindow.show(); + //Start the camera: + cameraConnection.start(); + + } + + + + + +} diff --git a/src/main/resources/Overlay.png b/src/main/resources/Overlay.png new file mode 100644 index 0000000..3caf755 Binary files /dev/null and b/src/main/resources/Overlay.png differ diff --git a/src/main/resources/example.json b/src/main/resources/example.json new file mode 100644 index 0000000..6964421 --- /dev/null +++ b/src/main/resources/example.json @@ -0,0 +1 @@ +{"Objects":[[{"AVERAGE_SAT":4,"AVERAGE_HUE":4,"AVERAGE_VALUE":4,"Name":"Tote"},{"AVERAGE_SAT":45,"AVERAGE_HUE":45,"AVERAGE_VALUE":45,"Name":"Tote"}]]} \ No newline at end of file diff --git a/src/main/resources/images/NumberFour.png b/src/main/resources/images/NumberFour.png new file mode 100644 index 0000000..3d60a89 Binary files /dev/null and b/src/main/resources/images/NumberFour.png differ diff --git a/src/main/resources/images/Overlay.png b/src/main/resources/images/Overlay.png new file mode 100644 index 0000000..5fe8849 Binary files /dev/null and b/src/main/resources/images/Overlay.png differ diff --git a/src/main/resources/images/OverlayTote.png b/src/main/resources/images/OverlayTote.png new file mode 100644 index 0000000..39344e3 Binary files /dev/null and b/src/main/resources/images/OverlayTote.png differ diff --git a/src/main/resources/images/Screenshot1.jpg b/src/main/resources/images/Screenshot1.jpg new file mode 100644 index 0000000..40fb428 Binary files /dev/null and b/src/main/resources/images/Screenshot1.jpg differ diff --git a/src/main/resources/images/Thumbs.db b/src/main/resources/images/Thumbs.db new file mode 100644 index 0000000..9779104 Binary files /dev/null and b/src/main/resources/images/Thumbs.db differ diff --git a/src/main/resources/images/goal.jpg b/src/main/resources/images/goal.jpg new file mode 100644 index 0000000..81a1f1f Binary files /dev/null and b/src/main/resources/images/goal.jpg differ diff --git a/src/main/resources/images/goal2.bmp b/src/main/resources/images/goal2.bmp new file mode 100644 index 0000000..26eef51 Binary files /dev/null and b/src/main/resources/images/goal2.bmp differ diff --git a/src/main/resources/images/goal2.jpg b/src/main/resources/images/goal2.jpg new file mode 100644 index 0000000..7b2461f Binary files /dev/null and b/src/main/resources/images/goal2.jpg differ diff --git a/src/main/resources/images/image10.png b/src/main/resources/images/image10.png new file mode 100644 index 0000000..efed3c8 Binary files /dev/null and b/src/main/resources/images/image10.png differ diff --git a/src/main/resources/images/image11.png b/src/main/resources/images/image11.png new file mode 100644 index 0000000..06ef50b Binary files /dev/null and b/src/main/resources/images/image11.png differ diff --git a/src/main/resources/images/image12.png b/src/main/resources/images/image12.png new file mode 100644 index 0000000..bf2d9fe Binary files /dev/null and b/src/main/resources/images/image12.png differ diff --git a/src/main/resources/images/image13.png b/src/main/resources/images/image13.png new file mode 100644 index 0000000..2536a89 Binary files /dev/null and b/src/main/resources/images/image13.png differ diff --git a/src/main/resources/images/image14.png b/src/main/resources/images/image14.png new file mode 100644 index 0000000..f6c3ac8 Binary files /dev/null and b/src/main/resources/images/image14.png differ diff --git a/src/main/resources/images/image15.png b/src/main/resources/images/image15.png new file mode 100644 index 0000000..0252bc3 Binary files /dev/null and b/src/main/resources/images/image15.png differ diff --git a/src/main/resources/images/image9.png b/src/main/resources/images/image9.png new file mode 100644 index 0000000..f8208c1 Binary files /dev/null and b/src/main/resources/images/image9.png differ diff --git a/src/main/resources/images/overlayScreenShot.png b/src/main/resources/images/overlayScreenShot.png new file mode 100644 index 0000000..bf6cde8 Binary files /dev/null and b/src/main/resources/images/overlayScreenShot.png differ diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties new file mode 100644 index 0000000..09a08c6 --- /dev/null +++ b/src/main/resources/log4j.properties @@ -0,0 +1,5 @@ +log4j.rootLogger=WARN, STDOUT +log4j.logger.deng=INFO +log4j.appender.STDOUT=org.apache.log4j.ConsoleAppender +log4j.appender.STDOUT.layout=org.apache.log4j.PatternLayout +log4j.appender.STDOUT.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n \ No newline at end of file