com.shootoff.camera.Camera.java Source code

Java tutorial

Introduction

Here is the source code for com.shootoff.camera.Camera.java

Source

/*
 * ShootOFF - Software for Laser Dry Fire Training
 * Copyright (C) 2016 phrack
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.shootoff.camera;

import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeoutException;

import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfDouble;
import org.opencv.imgproc.Imgproc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.sarxos.webcam.Webcam;
import com.github.sarxos.webcam.WebcamCompositeDriver;
import com.github.sarxos.webcam.WebcamException;
import com.github.sarxos.webcam.ds.buildin.WebcamDefaultDriver;
import com.github.sarxos.webcam.ds.ipcam.IpCamAuth;
import com.github.sarxos.webcam.ds.ipcam.IpCamDevice;
import com.github.sarxos.webcam.ds.ipcam.IpCamDeviceRegistry;
import com.github.sarxos.webcam.ds.ipcam.IpCamDriver;
import com.github.sarxos.webcam.ds.ipcam.IpCamMode;
import com.xuggle.xuggler.video.ConverterFactory;

/**
 * A facade class to decouple webcam interaction implementation from webcam use
 * throughout ShootOFF. This class should be re-written if a new webcam library
 * is being used.
 * 
 * @author phrack
 */
public class Camera {
    // These are used in a hack to get this code to work on Mac.
    // On Mac several of the webcam-capture API's can only be
    // called on the main thread before a JavaFX thread is started
    // or the library will hopeless hang and take ShootOFF with it.
    // Our solution is to cache the things we need that will hang
    // the program on start-up. This has the side effect that the
    // cameras that are known when ShootOFF starts are the only
    // ones it will ever know on Mac.
    private static final boolean isMac;
    private static final Webcam defaultWebcam;
    private static final List<Camera> knownWebcams;

    private static final Logger logger = LoggerFactory.getLogger(Camera.class);

    private final Webcam webcam;
    private final boolean isIpCam;

    public static class CompositeDriver extends WebcamCompositeDriver {
        public CompositeDriver() {
            super();
            add(new WebcamDefaultDriver());
            add(new IpCamDriver());
        }
    }

    static {
        Webcam.setDriver(new CompositeDriver());
        final String os = System.getProperty("os.name");

        if (os != null && os.equals("Mac OS X")) {
            isMac = true;
            defaultWebcam = Webcam.getDefault();

            knownWebcams = new ArrayList<Camera>();

            for (final Webcam w : Webcam.getWebcams()) {
                knownWebcams.add(new Camera(w));
            }

        } else {
            isMac = false;
            defaultWebcam = null;
            knownWebcams = null;
        }
    }

    public static Camera registerIpCamera(String cameraName, URL cameraURL, Optional<String> username,
            Optional<String> password)
            throws MalformedURLException, URISyntaxException, UnknownHostException, TimeoutException {
        // These are here because webcam-capture wraps this exception in a
        // WebcamException if the
        // URL has a syntax issue. We don't want to use webcam-capture classes
        // outside of this
        // class, thus to handle this error we need to artificially cause it
        // earlier if it is
        // going to be a problem.
        cameraURL.toURI();

        try {
            IpCamDevice ipcam;
            if (username.isPresent() && password.isPresent()) {
                IpCamAuth auth = new IpCamAuth(username.get(), password.get());
                ipcam = IpCamDeviceRegistry.register(new IpCamDevice(cameraName, cameraURL, IpCamMode.PUSH, auth));
            } else {
                ipcam = IpCamDeviceRegistry.register(new IpCamDevice(cameraName, cameraURL, IpCamMode.PUSH));
            }

            // If a camera can't be reached, webcam capture seems to freeze
            // indefinitely. This is done
            // to add an artificial timeout.
            Thread t = new Thread(() -> ipcam.getResolution(), "GetIPcamResolution");
            t.start();
            final int ipcamTimeout = 6000;
            try {
                t.join(ipcamTimeout);
            } catch (InterruptedException e) {
                logger.error("Error connecting to webcam", e);
            }

            if (t.isAlive()) {
                IpCamDeviceRegistry.unregister(cameraName);
                throw new TimeoutException();
            }

            return new Camera(ipcam);
        } catch (WebcamException we) {
            Throwable cause = we.getCause();

            if (cause instanceof UnknownHostException) {
                throw (UnknownHostException) cause;
            }

            logger.error("Error cocnnecting to webcam", we);
            throw we;
        }
    }

    public static boolean unregisterIpCamera(final String cameraName) {
        return IpCamDeviceRegistry.unregister(cameraName);
    }

    // For testing
    protected Camera() {
        this.webcam = null;
        this.isIpCam = false;
    }

    private Camera(final Webcam webcam) {
        this.webcam = webcam;
        this.isIpCam = false;

        logger.debug("WebcamDevice type: {}", webcam.getDevice().getClass().getName());
    }

    private Camera(final IpCamDevice ipcam) {
        this.isIpCam = true;

        for (final Camera webcam : getWebcams()) {
            if (webcam.getName().equals(ipcam.getName())) {
                this.webcam = webcam.getWebcam();
                return;
            }
        }

        this.webcam = null;
    }

    protected Webcam getWebcam() {
        return webcam;
    }

    public static Optional<Camera> getDefault() {
        Camera defaultCam;

        if (isMac) {
            defaultCam = new Camera(defaultWebcam);
        } else {
            final Webcam cam = Webcam.getDefault();

            if (cam == null) {
                defaultCam = null;
            } else {
                defaultCam = new Camera(cam);
            }
        }

        return Optional.ofNullable(defaultCam);
    }

    public static List<Camera> getWebcams() {
        if (isMac)
            return knownWebcams;

        final List<Camera> webcams = new ArrayList<Camera>();

        for (Webcam w : Webcam.getWebcams()) {
            webcams.add(new Camera(w));
        }

        return webcams;
    }

    public BufferedImage getImage() {
        return webcam.getImage();
    }

    public boolean isIpCam() {
        return isIpCam;
    }

    public boolean open() {
        boolean open = false;

        try {
            open = webcam.open();
        } catch (WebcamException we) {
            open = false;
        }

        return open;
    }

    public Dimension getViewSize() {
        return webcam.getViewSize();
    }

    public boolean close() {
        if (isMac) {
            new Thread(() -> {
                webcam.close();
            }, "CloseMacOSXWebcam").start();
            return true;
        } else {
            return webcam.close();
        }
    }

    public String getName() {
        return webcam.getName();
    }

    public boolean isOpen() {
        return webcam.isOpen();
    }

    public boolean isLocked() {
        return webcam.getLock().isLocked();
    }

    public boolean isImageNew() {
        return webcam.isImageNew();
    }

    public double getFPS() {
        return webcam.getFPS();
    }

    public void setViewSize(final Dimension size) {
        try {
            webcam.setCustomViewSizes(new Dimension[] { size });

            webcam.setViewSize(size);
        } catch (IllegalArgumentException e) {
            logger.error(String.format("Failed to set dimensions for camera: camera.getName() = %s", getName()), e);
        }

    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((webcam == null) ? 0 : webcam.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Camera other = (Camera) obj;
        if (!this.getName().equals(other.getName()))
            return false;
        return true;
    }

    public static BufferedImage matToBufferedImage(Mat matBGR) {
        BufferedImage image = new BufferedImage(matBGR.width(), matBGR.height(), BufferedImage.TYPE_3BYTE_BGR);
        final byte[] targetPixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
        matBGR.get(0, 0, targetPixels);

        return image;
    }

    public static Mat bufferedImageToMat(BufferedImage frame) {
        BufferedImage transformedFrame = ConverterFactory.convertToType(frame, BufferedImage.TYPE_3BYTE_BGR);
        byte[] pixels = ((DataBufferByte) transformedFrame.getRaster().getDataBuffer()).getData();
        Mat mat = new Mat(frame.getHeight(), frame.getWidth(), CvType.CV_8UC3);
        mat.put(0, 0, pixels);

        return mat;
    }

    public static Mat colorTransfer(Mat source, Mat target) {
        Mat src = new Mat();
        Mat dst = new Mat();

        Imgproc.cvtColor(source, src, Imgproc.COLOR_BGR2Lab);
        Imgproc.cvtColor(target, dst, Imgproc.COLOR_BGR2Lab);

        ArrayList<Mat> src_channels = new ArrayList<Mat>();
        ArrayList<Mat> dst_channels = new ArrayList<Mat>();
        Core.split(src, src_channels);
        Core.split(dst, dst_channels);

        for (int i = 0; i < 3; i++) {
            MatOfDouble src_mean = new MatOfDouble(), src_std = new MatOfDouble();
            MatOfDouble dst_mean = new MatOfDouble(), dst_std = new MatOfDouble();
            Core.meanStdDev(src_channels.get(i), src_mean, src_std);
            Core.meanStdDev(dst_channels.get(i), dst_mean, dst_std);

            dst_channels.get(i).convertTo(dst_channels.get(i), CvType.CV_64FC1);
            Core.subtract(dst_channels.get(i), dst_mean, dst_channels.get(i));
            Core.divide(dst_std, src_std, dst_std);
            Core.multiply(dst_channels.get(i), dst_std, dst_channels.get(i));
            Core.add(dst_channels.get(i), src_mean, dst_channels.get(i));
            dst_channels.get(i).convertTo(dst_channels.get(i), CvType.CV_8UC1);
        }

        Core.merge(dst_channels, dst);

        Imgproc.cvtColor(dst, dst, Imgproc.COLOR_Lab2BGR);

        return dst;
    }
}