package com.github.sarxos.webcam;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Webcam motion detector.
*
* @author Bartosz Firyn (SarXos)
*/
public class WebcamMotionDetector {
/**
* Logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(WebcamMotionDetector.class);
/**
* Thread number in pool.
*/
private static final AtomicInteger NT = new AtomicInteger(0);
/**
* Thread factory.
*/
private static final ThreadFactory THREAD_FACTORY = new DetectorThreadFactory();
/**
* Default check interval, in milliseconds, set to 500 ms.
*/
public static final int DEFAULT_INTERVAL = 500;
/**
* Create new threads for detector internals.
*
* @author Bartosz Firyn (SarXos)
*/
private static final class DetectorThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable runnable) {
Thread t = new Thread(runnable, String.format("motion-detector-%d", NT.incrementAndGet()));
t.setUncaughtExceptionHandler(WebcamExceptionHandler.getInstance());
t.setDaemon(true);
return t;
}
}
/**
* Run motion detector.
*
* @author Bartosz Firyn (SarXos)
*/
private class Runner implements Runnable {
@Override
public void run() {
running.set(true);
while (running.get() && webcam.isOpen()) {
try {
detect();
Thread.sleep(interval);
} catch (InterruptedException e) {
break;
} catch (Exception e) {
WebcamExceptionHandler.handle(e);
}
}
running.set(false);
}
}
/**
* Change motion to false after specified number of seconds.
*
* @author Bartosz Firyn (SarXos)
*/
private class Inverter implements Runnable {
@Override
public void run() {
int delay = 0;
while (running.get()) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
break;
}
delay = inertia != -1 ? inertia : 2 * interval;
if (lastMotionTimestamp + delay < System.currentTimeMillis()) {
motion = false;
}
}
}
}
/**
* Executor.
*/
private final ExecutorService executor = Executors.newFixedThreadPool(2, THREAD_FACTORY);
/**
* Motion listeners.
*/
private final List<WebcamMotionListener> listeners = new ArrayList<WebcamMotionListener>();
/**
* Is detector running?
*/
private final AtomicBoolean running = new AtomicBoolean(false);
/**
* Is motion?
*/
private volatile boolean motion = false;
/**
* Previously captured image.
*/
private BufferedImage previousOriginal = null;
/**
* Previously captured image with blur and gray filters applied.
*/
private BufferedImage previousModified = null;
/**
* Webcam to be used to detect motion.
*/
private Webcam webcam = null;
/**
* Motion check interval (1000 ms by default).
*/
private volatile int interval = DEFAULT_INTERVAL;
/**
* How long motion is valid (in milliseconds). Default value is 2 seconds.
*/
private volatile int inertia = -1;
/**
* Timestamp when motion has been observed last time.
*/
private volatile long lastMotionTimestamp = 0;
/**
* Implementation of motion detection algorithm.
*/
private final WebcamMotionDetectorAlgorithm detectorAlgorithm;
/**
* Create motion detector. Will open webcam if it is closed.
*
* @param webcam web camera instance
* @param motion detector algorithm implementation
* @param interval the check interval
*/
public WebcamMotionDetector(Webcam webcam, WebcamMotionDetectorAlgorithm detectorAlgorithm, int interval) {
this.webcam = webcam;
this.detectorAlgorithm = detectorAlgorithm;
setInterval(interval);
}
/**
* Create motion detector. Will open webcam if it is closed.
* Uses WebcamMotionDetectorDefaultAlgorithm for motion detection.
*
* @param webcam web camera instance
* @param pixelThreshold intensity threshold (0 - 255)
* @param areaThreshold percentage threshold of image covered by motion
* @param interval the check interval
*/
public WebcamMotionDetector(Webcam webcam, int pixelThreshold, double areaThreshold, int interval) {
this(webcam, new WebcamMotionDetectorDefaultAlgorithm(pixelThreshold, areaThreshold), interval);
}
/**
* Create motion detector with default parameter inertia = 0.
* Uses WebcamMotionDetectorDefaultAlgorithm for motion detection.
*
* @param webcam web camera instance
* @param pixelThreshold intensity threshold (0 - 255)
* @param areaThreshold percentage threshold of image covered by motion (0 -
* 100)
*/
public WebcamMotionDetector(Webcam webcam, int pixelThreshold, double areaThreshold) {
this(webcam, pixelThreshold, areaThreshold, DEFAULT_INTERVAL);
}
/**
* Create motion detector with default parameter inertia = 0.
* Uses WebcamMotionDetectorDefaultAlgorithm for motion detection.
*
* @param webcam web camera instance
* @param pixelThreshold intensity threshold (0 - 255)
*/
public WebcamMotionDetector(Webcam webcam, int pixelThreshold) {
this(webcam, pixelThreshold, WebcamMotionDetectorDefaultAlgorithm.DEFAULT_AREA_THREASHOLD);
}
/**
* Create motion detector with default parameters - threshold = 25, inertia
* = 0.
*
* @param webcam web camera instance
*/
public WebcamMotionDetector(Webcam webcam) {
this(webcam, WebcamMotionDetectorDefaultAlgorithm.DEFAULT_PIXEL_THREASHOLD);
}
public void start() {
if (running.compareAndSet(false, true)) {
webcam.open();
executor.submit(new Runner());
executor.submit(new Inverter());
}
}
public void stop() {
if (running.compareAndSet(true, false)) {
webcam.close();
executor.shutdownNow();
}
}
protected void detect() {
if (!webcam.isOpen()) {
motion = false;
return;
}
BufferedImage currentOriginal = webcam.getImage();
if (currentOriginal == null) {
motion = false;
return;
}
BufferedImage currentModified = detectorAlgorithm.prepareImage(currentOriginal);
boolean movementDetected = detectorAlgorithm.detect(previousModified, currentModified);
if (movementDetected) {
motion = true;
lastMotionTimestamp = System.currentTimeMillis();
notifyMotionListeners(currentOriginal);
}
previousOriginal = currentOriginal;
previousModified = currentModified;
}
/**
* Will notify all attached motion listeners.
* @param image with the motion detected
*/
private void notifyMotionListeners(BufferedImage currentOriginal) {
WebcamMotionEvent wme = new WebcamMotionEvent(this, previousOriginal, currentOriginal, detectorAlgorithm.getArea(), detectorAlgorithm.getCog(), detectorAlgorithm.getPoints());
for (WebcamMotionListener l : listeners) {
try {
l.motionDetected(wme);
} catch (Exception e) {
WebcamExceptionHandler.handle(e);
}
}
}
/**
* Add motion listener.
*
* @param l listener to add
* @return true if listeners list has been changed, false otherwise
*/
public boolean addMotionListener(WebcamMotionListener l) {
return listeners.add(l);
}
/**
* @return All motion listeners as array
*/
public WebcamMotionListener[] getMotionListeners() {
return listeners.toArray(new WebcamMotionListener[listeners.size()]);
}
/**
* Removes motion listener.
*
* @param l motion listener to remove
* @return true if listener was available on the list, false otherwise
*/
public boolean removeMotionListener(WebcamMotionListener l) {
return listeners.remove(l);
}
/**
* @return Motion check interval in milliseconds
*/
public int getInterval() {
return interval;
}
/**
* Motion check interval in milliseconds. After motion is detected, it's
* valid for time which is equal to value of 2 * interval.
*
* @param interval the new motion check interval (ms)
* @see #DEFAULT_INTERVAL
*/
public void setInterval(int interval) {
if (interval < 100) {
throw new IllegalArgumentException("Motion check interval cannot be less than 100 ms");
}
this.interval = interval;
}
/**
* Sets pixelThreshold to the underlying detector algorithm, but only if the
* algorithm is (or extends) WebcamMotionDetectorDefaultAlgorithm
*
* @see WebcamMotionDetectorDefaultAlgorithm#setPixelThreshold(int)
*
* @param threshold the pixel intensity difference threshold
*/
public void setPixelThreshold(int threshold) {
if (detectorAlgorithm instanceof WebcamMotionDetectorDefaultAlgorithm) {
((WebcamMotionDetectorDefaultAlgorithm)detectorAlgorithm).setPixelThreshold(threshold);
}
}
/**
* Sets areaThreshold to the underlying detector algorithm, but only if the
* algorithm is (or extends) WebcamMotionDetectorDefaultAlgorithm
*
* @see WebcamMotionDetectorDefaultAlgorithm#setAreaThreshold(double)
*
* @param threshold the percentage fraction of image area
*/
public void setAreaThreshold(double threshold) {
if (detectorAlgorithm instanceof WebcamMotionDetectorDefaultAlgorithm) {
((WebcamMotionDetectorDefaultAlgorithm)detectorAlgorithm).setAreaThreshold(threshold);
}
}
/**
* Set motion inertia (time when motion is valid). If no value specified
* this is set to 2 * interval. To reset to default value,
* {@link #clearInertia()} method must be used.
*
* @param inertia the motion inertia time in milliseconds
* @see #clearInertia()
*/
public void setInertia(int inertia) {
if (inertia < 0) {
throw new IllegalArgumentException("Inertia time must not be negative!");
}
this.inertia = inertia;
}
/**
* Reset inertia time to value calculated automatically on the base of
* interval. This value will be set to 2 * interval.
*/
public void clearInertia() {
this.inertia = -1;
}
/**
* Get attached webcam object.
*
* @return Attached webcam
*/
public Webcam getWebcam() {
return webcam;
}
public boolean isMotion() {
if (!running.get()) {
LOG.warn("Motion cannot be detected when detector is not running!");
}
return motion;
}
/**
* Get percentage fraction of image covered by motion. 0 means no motion on
* image and 100 means full image covered by spontaneous motion.
*
* @return Return percentage image fraction covered by motion
*/
public double getMotionArea() {
return detectorAlgorithm.getArea();
}
/**
* Get motion center of gravity. When no motion is detected this value
* points to the image center.
*
* @return Center of gravity point
*/
public Point getMotionCog() {
Point cog = detectorAlgorithm.getCog();
if (cog == null) {
// detectorAlgorithm hasn't been called so far - get image center
int w = webcam.getViewSize().width;
int h = webcam.getViewSize().height;
cog = new Point(w / 2, h / 2);
}
return cog;
}
/**
* @return the detectorAlgorithm
*/
public WebcamMotionDetectorAlgorithm getDetectorAlgorithm() {
return detectorAlgorithm;
}
public void setMaxMotionPoints(int i){
detectorAlgorithm.setMaxPoints(i);
}
public int getMaxMotionPoints(){
return detectorAlgorithm.getMaxPoints();
}
public void setPointRange(int i){
detectorAlgorithm.setPointRange(i);
}
public int getPointRange(){
return detectorAlgorithm.getPointRange();
}
}