Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Customizable WebcamMotionDetector #312

Merged
merged 2 commits into from
Feb 15, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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