Skip to content

Commit

Permalink
Merge pull request #312 from krok32/master
Browse files Browse the repository at this point in the history
Customizable WebcamMotionDetector
  • Loading branch information
sarxos committed Feb 15, 2015
2 parents 0b96dd2 + fd1c944 commit 646f426
Show file tree
Hide file tree
Showing 4 changed files with 307 additions and 160 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.sarxos.webcam.util.jh.JHBlurFilter;
import com.github.sarxos.webcam.util.jh.JHGrayFilter;


/**
* Webcam motion detector.
Expand All @@ -39,21 +36,11 @@ public class WebcamMotionDetector {
*/
private static final ThreadFactory THREAD_FACTORY = new DetectorThreadFactory();

/**
* Default pixel difference intensity threshold (set to 25).
*/
public static final int DEFAULT_PIXEL_THREASHOLD = 25;

/**
* Default check interval, in milliseconds, set to 500 ms.
*/
public static final int DEFAULT_INTERVAL = 500;

/**
* Default percentage image area fraction threshold (set to 0.2%).
*/
public static final double DEFAULT_AREA_THREASHOLD = 0.2;

/**
* Create new threads for detector internals.
*
Expand Down Expand Up @@ -166,65 +153,50 @@ public void run() {
*/
private volatile int interval = DEFAULT_INTERVAL;

/**
* Pixel intensity threshold (0 - 255).
*/
private volatile int pixelThreshold = DEFAULT_PIXEL_THREASHOLD;

/**
* Pixel intensity threshold (0 - 100).
*/
private volatile double areaThreshold = DEFAULT_AREA_THREASHOLD;

/**
* How long motion is valid (in milliseconds). Default value is 2 seconds.
*/
private volatile int inertia = -1;

/**
* Motion strength (0 = no motion, 100 = full image covered by motion).
*/
private double area = 0;

/**
* Center of motion gravity.
*/
private Point cog = new Point(0, 0);

/**
* Timestamp when motion has been observed last time.
*/
private volatile long lastMotionTimestamp = 0;

/**
* Blur filter instance.
* Implementation of motion detection algorithm.
*/
private final JHBlurFilter blur = new JHBlurFilter(6, 6, 1);
private final WebcamMotionDetectorAlgorithm detectorAlgorithm;

/**
* Gray filter instance.
* 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
*/
private final JHGrayFilter gray = new JHGrayFilter();

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.
* 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 = webcam;

setPixelThreshold(pixelThreshold);
setAreaThreshold(areaThreshold);
setInterval(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)
Expand All @@ -237,12 +209,13 @@ public WebcamMotionDetector(Webcam webcam, int pixelThreshold, double areaThresh

/**
* 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, DEFAULT_AREA_THREASHOLD);
this(webcam, pixelThreshold, WebcamMotionDetectorDefaultAlgorithm.DEFAULT_AREA_THREASHOLD);
}

/**
Expand All @@ -252,19 +225,12 @@ public WebcamMotionDetector(Webcam webcam, int pixelThreshold) {
* @param webcam web camera instance
*/
public WebcamMotionDetector(Webcam webcam) {
this(webcam, DEFAULT_PIXEL_THREASHOLD);
this(webcam, WebcamMotionDetectorDefaultAlgorithm.DEFAULT_PIXEL_THREASHOLD);
}

public void start() {
if (running.compareAndSet(false, true)) {

webcam.open();

int w = webcam.getViewSize().width;
int h = webcam.getViewSize().height;

cog = new Point(w / 2, h / 2);

executor.submit(new Runner());
executor.submit(new Inverter());
}
Expand All @@ -291,49 +257,16 @@ protected void detect() {
return;
}

BufferedImage currentModified = blur.filter(currentOriginal, null);
currentModified = gray.filter(currentModified, null);

int p = 0;

int cogX = 0;
int cogY = 0;

int w = currentModified.getWidth();
int h = currentModified.getHeight();

if (previousModified != null) {
for (int x = 0; x < w; x++) {
for (int y = 0; y < h; y++) {

int cpx = currentModified.getRGB(x, y);
int ppx = previousModified.getRGB(x, y);
int pid = combinePixels(cpx, ppx) & 0x000000ff;

if (pid >= pixelThreshold) {
cogX += x;
cogY += y;
p += 1;
}
}
}
}

area = p * 100d / (w * h);

if (area >= areaThreshold) {

cog = new Point(cogX / p, cogY / p);
BufferedImage currentModified = detectorAlgorithm.prepareImage(currentOriginal);

boolean movementDetected = detectorAlgorithm.detect(previousModified, currentModified);

if (movementDetected) {
motion = true;
lastMotionTimestamp = System.currentTimeMillis();

notifyMotionListeners(currentOriginal);

} else {
cog = new Point(w / 2, h / 2);
}

previousOriginal = currentOriginal;
previousModified = currentModified;
}
Expand All @@ -343,7 +276,7 @@ protected void detect() {
* @param image with the motion detected
*/
private void notifyMotionListeners(BufferedImage currentOriginal) {
WebcamMotionEvent wme = new WebcamMotionEvent(this, previousOriginal, currentOriginal, area, cog);
WebcamMotionEvent wme = new WebcamMotionEvent(this, previousOriginal, currentOriginal, detectorAlgorithm.getArea(), detectorAlgorithm.getCog());
for (WebcamMotionListener l : listeners) {
try {
l.motionDetected(wme);
Expand Down Expand Up @@ -404,39 +337,31 @@ public void setInterval(int interval) {
}

/**
* Set pixel intensity difference threshold above which pixel is classified
* as "moved". Minimum value is 0 and maximum is 255. Default value is 10.
* This value is equal for all RGB components difference.
*
* 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
* @see #DEFAULT_PIXEL_THREASHOLD
*/
public void setPixelThreshold(int threshold) {
if (threshold < 0) {
throw new IllegalArgumentException("Pixel intensity threshold cannot be negative!");
if (detectorAlgorithm instanceof WebcamMotionDetectorDefaultAlgorithm) {
((WebcamMotionDetectorDefaultAlgorithm)detectorAlgorithm).setPixelThreshold(threshold);
}
if (threshold > 255) {
throw new IllegalArgumentException("Pixel intensity threshold cannot be higher than 255!");
}
this.pixelThreshold = threshold;
}

/**
* Set percentage fraction of detected motion area threshold above which it
* is classified as "moved". Minimum value for this is 0 and maximum is 100,
* which corresponds to full image covered by spontaneous motion.
*
* 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
* @see #DEFAULT_AREA_THREASHOLD
*/
public void setAreaThreshold(double threshold) {
if (threshold < 0) {
throw new IllegalArgumentException("Area fraction threshold cannot be negative!");
if (detectorAlgorithm instanceof WebcamMotionDetectorDefaultAlgorithm) {
((WebcamMotionDetectorDefaultAlgorithm)detectorAlgorithm).setAreaThreshold(threshold);
}
if (threshold > 100) {
throw new IllegalArgumentException("Area fraction threshold cannot be higher than 100!");
}
this.areaThreshold = threshold;
}

/**
Expand Down Expand Up @@ -485,7 +410,7 @@ public boolean isMotion() {
* @return Return percentage image fraction covered by motion
*/
public double getMotionArea() {
return area;
return detectorAlgorithm.getArea();
}

/**
Expand All @@ -495,54 +420,21 @@ public double getMotionArea() {
* @return Center of gravity point
*/
public Point getMotionCog() {
return cog;
}

private static int combinePixels(int rgb1, int rgb2) {

// first ARGB

int a1 = (rgb1 >> 24) & 0xff;
int r1 = (rgb1 >> 16) & 0xff;
int g1 = (rgb1 >> 8) & 0xff;
int b1 = rgb1 & 0xff;

// second ARGB

int a2 = (rgb2 >> 24) & 0xff;
int r2 = (rgb2 >> 16) & 0xff;
int g2 = (rgb2 >> 8) & 0xff;
int b2 = rgb2 & 0xff;

r1 = clamp(Math.abs(r1 - r2));
g1 = clamp(Math.abs(g1 - g2));
b1 = clamp(Math.abs(b1 - b2));

// in case if alpha is enabled (translucent image)

if (a1 != 0xff) {
a1 = a1 * 0xff / 255;
int a3 = (255 - a1) * a2 / 255;
r1 = clamp((r1 * a1 + r2 * a3) / 255);
g1 = clamp((g1 * a1 + g2 * a3) / 255);
b1 = clamp((b1 * a1 + b2 * a3) / 255);
a1 = clamp(a1 + a3);
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 (a1 << 24) | (r1 << 16) | (g1 << 8) | b1;
return cog;
}

/**
* Clamp a value to the range 0..255
* @return the detectorAlgorithm
*/
private static int clamp(int c) {
if (c < 0) {
return 0;
}
if (c > 255) {
return 255;
}
return c;
public WebcamMotionDetectorAlgorithm getDetectorAlgorithm() {
return detectorAlgorithm;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.github.sarxos.webcam;

import java.awt.Point;
import java.awt.image.BufferedImage;


/**
* Implementation of this interface is responsible for decision whether the
* difference between two images represents movement or not. Instance may
* specified as parameter of WebcamMotionDetector constructor, otherwise
* WebcamMotionDetectorDefaultAlgorithm is used.
*/
public interface WebcamMotionDetectorAlgorithm {

/**
* WebcamMotionDetector calls this method for each image used as parameter
* of the method {@link #detect(BufferedImage, BufferedImage)}.
* Implementation may transform the original image and prepare it for
* comparison of two images.
* May return the same instance if no there is no need to transform.
*
* @param original image
* @return modified image
*/
BufferedImage prepareImage(BufferedImage original);

/**
* Detects motion by comparison of the two specified images content.
* {@link #prepareImage(BufferedImage)} method was called for both specified images.
*
* @param previousModified
* @param currentModified
* @return If the motion was detected returns true, otherwise returns false
*/
boolean detect(BufferedImage previousModified, BufferedImage currentModified);

/**
* Get motion center of gravity. When no motion is detected this value
* points to the image center.
* May return null before the first movement check.
*
* @return Center of gravity point
*/
Point getCog();

/**
* 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
*/
double getArea();
}
Loading

0 comments on commit 646f426

Please sign in to comment.