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(); } }