/**
* A motion detection algoritm for use with the Java Media Framework API (JMF).
* The main idea of the algorithm is to compare the pixelcolours of two successive frames in an incoming videostream.
* To prevent noise to be mistaken for motion each frame is divided into many small squares for which only the mean colour is used for compairson.
* @version 2002-09-26
* @author Mattias Hedlund, mathed-8.
* @author Fredrik Jonsson, frejon-9.
* @author David Åberg, davabe-9 all students at Luleå University of Technology.
*/
import javax.media.*;
import javax.media.format.*;
import java.awt.*;
import java.io.IOException;
public class MotionDetectionEffect implements Effect {
/**
* The initial square side.
*/
private final static int INITIAL_SQUARE_SIZE = 5;
public final static Format[] supportedFormat = new Format[] { //
new RGBFormat(null, //
Format.NOT_SPECIFIED, //
Format.byteArray, //
Format.NOT_SPECIFIED, //
24, //
3, 2, 1, //
3, Format.NOT_SPECIFIED, //
Format.TRUE, //
Format.NOT_SPECIFIED) //
};
private Format inputFormat;
private Format outputFormat;
private Format[] inputFormats;
private Format[] outputFormats;
private int[] bwPixels;
private byte[] bwData;
/**
* Visual mode is set.
*/
private boolean visualize = true;
/**
* Server mode is set.
*/
private boolean serverActive = true;
/**
* Update requested is set.
*/
private boolean updateRequested;
private int avg_ref_intensity;
private int avg_img_intensity;
/**
* The RGBFormat of the inbuffer.
*/
private RGBFormat vfIn = null;
/**
* Four different thresholds. Set initial values here.
*/
private int[] threshs = { 20, 30, 40, 50 };
private int det_thresh = threshs[1];
/**
* The corresponding colours to the four different thresholds.
*/
private int[] colors = { 0x00FF0000, 0x00FF9900, 0x00FFFF00, 0x00FFFFFF };
/**
* The mean values of all squares in an image.
*/
private int[] newImageSquares = null;
/**
* The mean values of all squares in an image.
*/
private int[] oldImageSquares = null;
/**
* The difference of all the mean values of all squares in an image.
*/
private int[] changedSquares = null;
/**
* The number of squares fitted in the image.
*/
private int numberOfSquaresWide;
/**
* The number of squares fitted in the image.
*/
private int numberOfSquaresHigh;
/**
* The number of squares fitted in the image.
*/
private int numberOfSquares;
/**
* The square side, in pixels.
*/
private int sqSide = INITIAL_SQUARE_SIZE;
/**
* The square area, in pixels.
*/
private int sqArea = 0;
/**
* The amount of pixels left when all normal sized squares have been removed.
*/
private int sqWidthLeftover = 0;
/**
* The amount of pixels left when all normal sized squares have been removed.
*/
private int sqHeightLeftover = 0;
/**
* Optional, less processing is needed if some pixels are left out during some of the calculations.
*/
private int pixelSpace = 0;
/**
* Image property.
*/
private int imageWidth = 0;
/**
* Image property.
*/
private int imageHeight = 0;
/**
* Image property.
*/
private int imageArea = 0;
/**
* Initialize the effect plugin.
*/
public MotionDetectionEffect() {
inputFormats = new Format[] { new RGBFormat(null, Format.NOT_SPECIFIED, Format.byteArray, Format.NOT_SPECIFIED, 24, 3, 2, 1, 3, Format.NOT_SPECIFIED, Format.TRUE, Format.NOT_SPECIFIED) };
outputFormats = new Format[] { new RGBFormat(null, Format.NOT_SPECIFIED, Format.byteArray, Format.NOT_SPECIFIED, 24, 3, 2, 1, 3, Format.NOT_SPECIFIED, Format.TRUE, Format.NOT_SPECIFIED) };
}
/**
* Get the inputformats that we support.
* @return All supported Formats.
*/
public Format[] getSupportedInputFormats() {
return inputFormats;
}
/**
* Get the outputformats that we support.
* @param input the current inputformat.
* @return All supported Formats.
*/
public Format[] getSupportedOutputFormats(Format input) {
if (input == null) {
return outputFormats;
}
if (matches(input, inputFormats) != null) {
return new Format[] { outputFormats[0].intersects(input) };
} else {
return new Format[0];
}
}
/**
* Set the input format.
*
*/
public Format setInputFormat(Format input) {
inputFormat = input;
return input;
}
/**
* Set our output format.
*
*/
public Format setOutputFormat(Format output) {
if (output == null || matches(output, outputFormats) == null)
return null;
RGBFormat incoming = (RGBFormat) output;
Dimension size = incoming.getSize();
int maxDataLength = incoming.getMaxDataLength();
int lineStride = incoming.getLineStride();
float frameRate = incoming.getFrameRate();
int flipped = incoming.getFlipped();
int endian = incoming.getEndian();
if (size == null)
return null;
if (maxDataLength < size.width * size.height * 3)
maxDataLength = size.width * size.height * 3;
if (lineStride < size.width * 3)
lineStride = size.width * 3;
if (flipped != Format.FALSE)
flipped = Format.FALSE;
outputFormat = outputFormats[0].intersects(new RGBFormat(size, maxDataLength, null, frameRate, Format.NOT_SPECIFIED, Format.NOT_SPECIFIED, Format.NOT_SPECIFIED, Format.NOT_SPECIFIED, Format.NOT_SPECIFIED, lineStride, Format.NOT_SPECIFIED, Format.NOT_SPECIFIED));
return outputFormat;
}
/**
* Process the buffer. This is where motion is analysed and optionally visualized.
*
*/
public synchronized int process(Buffer inBuffer, Buffer outBuffer) {
int outputDataLength = ((VideoFormat) outputFormat).getMaxDataLength();
validateByteArraySize(outBuffer, outputDataLength);
outBuffer.setLength(outputDataLength);
outBuffer.setFormat(outputFormat);
outBuffer.setFlags(inBuffer.getFlags());
byte[] inData = (byte[]) inBuffer.getData();
byte[] outData = (byte[]) outBuffer.getData();
int[] sqAvg = null;
int[] refsqAvg = null;
vfIn = (RGBFormat) inBuffer.getFormat();
Dimension sizeIn = vfIn.getSize();
int pixStrideIn = vfIn.getPixelStride();
int lineStrideIn = vfIn.getLineStride();
imageWidth = (vfIn.getLineStride()) / 3; //Divide by 3 since each pixel has 3 colours.
imageHeight = ((vfIn.getMaxDataLength()) / 3) / imageWidth;
imageArea = imageWidth * imageHeight;
int r, g, b = 0; //Red, green and blue values.
if (oldImageSquares == null) { //For the first frame.
changeSqSize(INITIAL_SQUARE_SIZE);
updateRequested = true;
}
//Copy all data from the inbuffer to the outbuffer. The purpose is to display the video input on the screen.
System.arraycopy(inData, 0, outData, 0, outData.length);
// Simplify the image to black and white, image information shrinks to one third of the original amount. Less processing needed.
bwPixels = new int[outputDataLength / 3];
for (int ip = 0; ip < outputDataLength; ip += 3) {
int bw = 0;
r = (int) inData[ip] & 0xFF;
g = (int) inData[ip + 1] & 0xFF;
b = (int) inData[ip + 2] & 0xFF;
bw = (int) ((r + b + g) / (double) 3);
bwPixels[ip / 3] = bw; //Now containing a black and white image.
}
if (updateRequested) {
updateRequested = false;
updateSquares();
return BUFFER_PROCESSED_OK;
} else {
updateSquares();
oldNewChange();
int c = 0;
for (int i = 0; i < changedSquares.length; i++) {
if (changedSquares[i] > det_thresh) {
c++;
}
}
if (c > 10 && serverActive) {
// try{
System.out.println("Motion detected (motion at " + c + "areas");
// multicast.send("Motion detected"); - Disabled
// } catch(IOException e){}
}
// If chosen, the detected motion is presented on top of the video input, thus covering the edges of the moving object.
if (visualize) {
for (int i = 1; i <= numberOfSquares; i++) { // For all blobs
if ((changedSquares[i - 1] > threshs[0])) { // Critical threshold, if less, then no motion is said to have occured.
if (((i % numberOfSquaresWide) != 0) && (numberOfSquares - i) > numberOfSquaresWide) {//Normal square, the other cases is not presented!
int begin = ((((i % numberOfSquaresWide) - 1) * sqSide) + ((i / numberOfSquaresWide) * imageWidth * sqSide)) * 3; //Calculate start of square.
if (changedSquares[i - 1] > threshs[3]) { //Very strong motion.
b = (byte) (colors[3] & 0xFF);
g = (byte) ((colors[3] >> 8) & 0xFF);
r = (byte) ((colors[3] >> 16) & 0xFF);
} else if (changedSquares[i - 1] > threshs[2]) { //Strong motion.
b = (byte) (colors[2] & 0xFF);
g = (byte) ((colors[2] >> 8) & 0xFF);
r = (byte) ((colors[2] >> 16) & 0xFF);
} else if (changedSquares[i - 1] > threshs[1]) { //Weak motion.
b = (byte) (colors[1] & 0xFF);
g = (byte) ((colors[1] >> 8) & 0xFF);
r = (byte) ((colors[1] >> 16) & 0xFF);
} else { //The Weakest motion detected.
b = (byte) (colors[0] & 0xFF);
g = (byte) ((colors[0] >> 8) & 0xFF);
r = (byte) ((colors[0] >> 16) & 0xFF);
}
for (int k = begin; k < (begin + (sqSide * imageWidth * 3)); k = k + (imageWidth * 3)) { //Ev <=
for (int j = k; j < (k + (sqSide * 3)); j = j + 3) {
try {
outData[j] = (byte) b;
outData[j + 1] = (byte) g;
outData[j + 2] = (byte) r;
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Nullpointer: j = " + j + ". Outdata.length = " + outData.length);
System.exit(1);
}
}
}
}
}
}
}
}
return BUFFER_PROCESSED_OK;
}
// Methods for interface PlugIn
public String getName() {
return "Motion Detection Codec";
}
public void open() {
}
public void close() {
}
public void reset() {
}
// Methods for interface javax.media.Controls
public Object getControl(String controlType) {
System.out.println(controlType);
return null;
}
public Object[] getControls() {
return null;
}
// Utility methods.
public Format matches(Format in, Format outs[]) {
for (int i = 0; i < outs.length; i++) {
if (in.matches(outs[i]))
return outs[i];
}
return null;
}
// Credit : example at www.java.sun.com
byte[] validateByteArraySize(Buffer buffer, int newSize) {
Object objectArray = buffer.getData();
byte[] typedArray;
if (objectArray instanceof byte[]) { // Has correct type and is not null
typedArray = (byte[]) objectArray;
if (typedArray.length >= newSize) { // Has sufficient capacity
return typedArray;
}
byte[] tempArray = new byte[newSize]; // Reallocate array
System.arraycopy(typedArray, 0, tempArray, 0, typedArray.length);
typedArray = tempArray;
} else {
typedArray = new byte[newSize];
}
buffer.setData(typedArray);
return typedArray;
}
/**
* Sets the current pixelspace, default is zero.
* This is mainly for use where limited processing capacity are availible. Some pixels are left out in the calculations.
* @param newSpace the space between two successive pixels.
*/
private void setPixelSpace(int newSpace) {
pixelSpace = newSpace;
}
/**
* Changes the size of the square shaped area that divides the detection area into many small parts.
* @param newSide the side of the square, in pixels.
*/
private void changeSqSize(int newSide) {
sqSide = newSide;
sqArea = newSide * newSide;
int wid = (imageWidth / sqSide); //The number of squares wide.
int hei = (imageHeight / sqSide); //The number of squares high.
sqWidthLeftover = imageWidth % sqSide;
sqHeightLeftover = imageHeight % sqSide;
if (sqWidthLeftover > 0) {
wid++;
}
if (sqHeightLeftover > 0) {
hei++;
}
numberOfSquaresWide = wid;
numberOfSquaresHigh = hei;
numberOfSquares = wid * hei;
newImageSquares = new int[numberOfSquares];
oldImageSquares = new int[numberOfSquares];
changedSquares = new int[numberOfSquares];
}
/**
* Calculates the average colour in each square thus indirect eliminate noise.
* @param startX the starting position of this square, in pixels, left edge.
* @param startY the starting position of this square, in pixels, bottom edge.
* @param sqWidth the width of this square, in pixels.
* @param sqHeight the height of this square, in pixels.
* @return The average greyscale value for this square.
*/
private int averageInSquare(int startX, int startY, int sqWidth, int sqHeight) {
int average = 0;
for (int i = 0; i < sqHeight; i = i + 1 + pixelSpace) {// For all pixels
for (int j = 0; j < sqWidth; j = j + 1 + pixelSpace) {
average += bwPixels[(((startY + i) * imageWidth) + (startX + j))]; //Sum all the pixel values.
}
}
average = average / (sqWidth * sqHeight); //Divide by the number of pixels to get the average value.
return average;
}
/**
* Backup the most recent frame examined. For the new frame, calculate the average greyscale value for all squares.
*/
private void updateSquares() {
System.arraycopy(newImageSquares, 0, oldImageSquares, 0, newImageSquares.length);
int sqCount = 0; //Keep track of the current square
for (int j = 0; j < (imageHeight); j = j + sqSide) { //For all squares
for (int i = 0; i < (imageWidth); i = i + sqSide) {
if (i <= (imageWidth - sqSide) && j <= (imageHeight - sqSide)) {
newImageSquares[sqCount] = averageInSquare(i, j, sqSide, sqSide); //No edge!
} else if (i > (imageWidth - sqSide) && j <= (imageHeight - sqSide)) {
newImageSquares[sqCount] = averageInSquare(i, j, sqWidthLeftover, sqSide); //Right edge!
} else if (i <= (imageWidth - sqSide) && j > (imageHeight - sqSide)) {
newImageSquares[sqCount] = averageInSquare(i, j, sqSide, sqHeightLeftover); //Bottom edge!
} else if (i > (imageWidth - sqSide) && j > (imageHeight - sqSide)) {
newImageSquares[sqCount] = averageInSquare(i, j, sqWidthLeftover, sqHeightLeftover); //Bottom right edge!
}
sqCount++;
}
}
}
/**
* Calculate the difference per square between currently stored frames.
*/
private void oldNewChange() {
for (int i = 0; i <= (numberOfSquares - 1); i++) { //For all squares
int difference = Math.abs((newImageSquares[i]) - (oldImageSquares[i])); //Compare each square with the corresponding square in the previous frame.
changedSquares[i] = difference; //Save the difference.
}
}
public synchronized void updateModel(boolean visualize, boolean serverActive, boolean simplified, int[] threshs, int[] colors, int sqSide, int det_thresh) {
this.visualize = visualize;
this.serverActive = serverActive;
if (sqSide != this.sqSide)
changeSqSize(sqSide);
if (!simplified) {
System.out.println((colors == null) + " " + (this.colors == null));
System.arraycopy(colors, 0, this.colors, 0, colors.length);
System.arraycopy(threshs, 0, this.threshs, 0, colors.length);
this.det_thresh = det_thresh;
System.out.println("New det_threhsh: " + this.det_thresh);
}
updateRequested = true;
}
/**
*Check if the visualize variable is set.
*@returns the current value.
*/
public boolean isVisual() {
return visualize;
}
/**
*Get the current threshold values in a vector.
*@returns the current values.
*/
public int[] getThreshholds() {
return threshs;
}
/**
*Check if the server is active.
*@returns the current value.
*/
public boolean isServerActive() {
return serverActive;
}
public int[] getColors() {
return colors;
}
/**
*Get the current square side.
*@returns the current value.
*/
public int getSqSide() {
return sqSide;
}
}
|