diff --git a/src/com/jwetherell/motion_detection/MotionDetectionActivity.java b/src/com/jwetherell/motion_detection/MotionDetectionActivity.java index 7f244ed..2681ed9 100644 --- a/src/com/jwetherell/motion_detection/MotionDetectionActivity.java +++ b/src/com/jwetherell/motion_detection/MotionDetectionActivity.java @@ -82,8 +82,8 @@ public void onPreviewFrame(byte[] data, Camera cam) { Camera.Size size = cam.getParameters().getPreviewSize(); if (size == null) return; - DetectionThread thread = new DetectionThread(data,size.width,size.height); - thread.start(); + DetectionThread thread = new DetectionThread(data,size.width,size.height); + thread.start(); } }; @@ -150,7 +150,7 @@ public DetectionThread(byte[] data, int width, int height) { @Override public void run() { if (!processing.compareAndSet(false, true)) return; - + Looper.prepare(); Log.d(TAG, "BEGIN PROCESSING..."); try { @@ -159,7 +159,10 @@ public void run() { if (SAVE_PREVIOUS) pre = MotionDetection.getPrevious(); //Current frame (with changes) - int[] rgb = ImageProcessing.decodeYUV420SP(data, width, height); + long bRGB = System.currentTimeMillis(); + int[] rgb = ImageProcessing.decodeYUV420SPtoRGB(data, width, height); + long aRGB = System.currentTimeMillis(); + Log.d(TAG, "Convert to RGB "+(aRGB-bRGB)); //Current frame (without changes) int[] org = null; diff --git a/src/com/jwetherell/motion_detection/detection/Comparer.java b/src/com/jwetherell/motion_detection/detection/Comparer.java index 47484b9..ad7d16a 100644 --- a/src/com/jwetherell/motion_detection/detection/Comparer.java +++ b/src/com/jwetherell/motion_detection/detection/Comparer.java @@ -34,36 +34,37 @@ public Comparison compare(State s1, State s2) { if (s1==null || s2==null) return null; if (s1.getWidth()!=s2.getWidth() || s1.getHeight()!=s2.getHeight()) return null; - int cx = comparex; - if (cx > s1.getWidth()) cx = s1.getWidth(); - int cy = comparey; - if (cy > s1.getHeight()) cy = s1.getHeight(); + // number of boxes + int xBoxes = comparex; + if (xBoxes > s1.getWidth()) xBoxes = s1.getWidth(); + int yBoxes = comparey; + if (yBoxes > s1.getHeight()) yBoxes = s1.getHeight(); - // how many points per section - int bx = (int)(Math.floor(s1.getWidth() / cx)); - if (bx <= 0) bx = 1; - int by = (int)(Math.floor(s1.getHeight() / cy)); - if (by <= 0) by = 1; - int[][] variance = new int[cy][cx]; + // how many points per box + int xPixelsPerBox = (int)(Math.floor(s1.getWidth() / xBoxes)); + if (xPixelsPerBox <= 0) xPixelsPerBox = 1; + int yPixelsPerBox = (int)(Math.floor(s1.getHeight() / yBoxes)); + if (yPixelsPerBox <= 0) yPixelsPerBox = 1; + int[][] variance = new int[yBoxes][xBoxes]; // set to a different by default, if a change is found then flag non-match boolean different = false; // loop through whole image and compare individual blocks of images int ty = 0; - for (int y = 0; y < cy; y++) { + for (int y = 0; y < yBoxes; y++) { StringBuilder output = new StringBuilder(); if (debugMode > 0) output.append("|"); - ty = y*by; - for (int x = 0; x < cx; x++) { - int tx = x*bx; - int b1 = aggregateMapArea(s1.getMap(), tx, ty, bx, by); - int b2 = aggregateMapArea(s2.getMap(), tx, ty, bx, by); + ty = y*yPixelsPerBox; + for (int x = 0; x < xBoxes; x++) { + int tx = x*xPixelsPerBox; + int b1 = aggregateMapArea(s1.getMap(), tx, ty, xPixelsPerBox, yPixelsPerBox); + int b2 = aggregateMapArea(s2.getMap(), tx, ty, xPixelsPerBox, yPixelsPerBox); int diff = Math.abs(b1 - b2); variance[y][x] = diff; // the difference in a certain region has passed the threshold value if (diff > leniency) different = true; - if (debugMode == 1) output.append((diff > leniency ? "X" : " ")); - if (debugMode == 2) output.append(diff + (x < cx - 1 ? "," : "")); + if (debugMode == 1) output.append((different ? "X" : " ")); + if (debugMode == 2) output.append(diff + (x < xBoxes - 1 ? "," : "")); } if (debugMode > 0) { output.append("|"); @@ -77,11 +78,14 @@ private static int aggregateMapArea(int[][] map, int ox, int oy, int w, int h) { if (map==null) return Integer.MIN_VALUE; int t = 0; - for (int i = 0; i < h; i++) { - int ty = oy+i; - for (int j = 0; j < w; j++) t += map[ty][ox+j]; + for (int y = 0; y < h; y++) { + int ty = oy+y; + for (int x = 0; x < w; x++) { + int tx = ox+x; + t += map[ty][tx]; + } } - return (int)(t/(w*h)); + return (t/(w*h)); } public int getComparex() { diff --git a/src/com/jwetherell/motion_detection/detection/Comparison.java b/src/com/jwetherell/motion_detection/detection/Comparison.java index ee7460d..b50c11b 100644 --- a/src/com/jwetherell/motion_detection/detection/Comparison.java +++ b/src/com/jwetherell/motion_detection/detection/Comparison.java @@ -1,5 +1,7 @@ package com.jwetherell.motion_detection.detection; +import android.graphics.Color; + /** * This class is adapted from the web site below. It is used to indicate the variance in two state objects. * http://mindmeat.blogspot.com/2008/11/java-image-comparison.html @@ -25,25 +27,35 @@ public Comparison(State s1, State s2, int[][] variance, boolean different) { this.height = variance.length; this.width = variance[0].length; } - - /* - public BufferedImage getChangeIndicator(BufferedImage cx, Comparer comparer) { - // setup change display image - Graphics2D gc = cx.createGraphics(); - gc.setColor(Color.RED); - float bx = (cx.getWidth() / width); - float by = (cx.getHeight() / height); + public void getChangeIndicator(int[] data, int width, int height, Comparer comparer) { + int bx = (width / this.width); + int by = (height / this.height); + + int tx = 0; + int ty = 0; + for (int y = 0; y < by; y++) { + ty = (int)(y * by); + for (int x = 0; x < bx; x++) { + tx = (int)(x * bx); + if (variance[y][x] > comparer.getLeniency()) { + paintArea(data,tx,ty,bx,by); + } + } + } + } + + private static void paintArea(int[] data, int ox, int oy, int w, int h) { + if (data==null) return; - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - if (variance[y][x] > comparer.leniency) - gc.drawRect((int)(x * bx), (int)(y * by), (int)bx, (int)by); + int ty = 0; + for (int y = 1; y <= h; y++) { + ty = oy*y; + for (int x = 0, xy=ty; x < w; x++, xy++) { + data[xy] = Color.RED; } } - return cx; } - */ public int[][] getVariance() { return variance; diff --git a/src/com/jwetherell/motion_detection/detection/MotionDetection.java b/src/com/jwetherell/motion_detection/detection/MotionDetection.java index 5a63a70..53b4908 100644 --- a/src/com/jwetherell/motion_detection/detection/MotionDetection.java +++ b/src/com/jwetherell/motion_detection/detection/MotionDetection.java @@ -15,9 +15,15 @@ public abstract class MotionDetection { private static final boolean USE_RGB = true; private static final boolean USE_HSL = false; private static final boolean USE_STATE = false; - private static final int mThreshold = 10000; //Number of different pixels - private static final int mPixelThreshold = 50; //Difference in pixel + + //RGB Specific settings + private static final int mRgbThreshold = 10000; //Number of different pixels + private static final int mRgbPixelThreshold = 50; //Difference in pixel + //HSL Specific settings + private static final int mHslThreshold = 1000; //Number of different pixels + private static final int mHslPixelThreshold = 1; //Difference in brightness + private static int[] mPrevious = null; private static int mPreviousWidth = 0; private static int mPreviousHeight = 0; @@ -33,26 +39,27 @@ protected static boolean isDifferentComparingState(int[] first, int width, int h if (mPreviousWidth != width || mPreviousHeight != height) return true; if (mPreviousState==null) mPreviousState = new State(mPrevious, mPreviousWidth, mPreviousHeight); - State state = new State(first, width, height); - Comparer ic = new Comparer((width/10), (height/10), 0); + + Comparer ic = new Comparer((width/10), (height/10), 25); ic.setDebugMode(1); - Comparison c = ic.compare(mPreviousState, state); + Comparison c = ic.compare(state,mPreviousState); boolean different = c.isDifferent(); String output = "isDifferent="+different; if (different) { Log.e(TAG, output); + c.getChangeIndicator(first, width, height, ic); } else { Log.d(TAG, output); } - mPreviousState = state; + mPreviousState = state.clone(); return different; } - - protected static boolean isDifferentComparingRGB(int[] first, int width, int height) { + + protected static boolean isDifferentComparingHSL(int[] first, int width, int height) { if (first==null || mPrevious==null) return false; if (first.length != mPrevious.length) return true; if (mPreviousWidth != width || mPreviousHeight != height) return true; @@ -71,7 +78,10 @@ protected static boolean isDifferentComparingRGB(int[] first, int width, int hei if (otherPix < 0) otherPix = 0; if (otherPix > 255) otherPix = 255; - if (Math.abs(pix - otherPix) >= mPixelThreshold) { + int b1 = ImageProcessing.getBrightnessAtPoint(pix); + int b2 = ImageProcessing.getBrightnessAtPoint(otherPix); + + if (Math.abs(b1 - b2) >= mHslPixelThreshold) { totDifferentPixels++; //Paint different pixel red first[ij] = Color.RED; @@ -79,7 +89,7 @@ protected static boolean isDifferentComparingRGB(int[] first, int width, int hei } } if (totDifferentPixels <= 0) totDifferentPixels = 1; - boolean different = totDifferentPixels > mThreshold; + boolean different = totDifferentPixels > mHslThreshold; int percent = 100/(size/totDifferentPixels); String output = "Number of different pixels: " + totDifferentPixels + "> " + percent + "%"; @@ -92,7 +102,7 @@ protected static boolean isDifferentComparingRGB(int[] first, int width, int hei return different; } - protected static boolean isDifferentComparingHSL(int[] first, int width, int height) { + protected static boolean isDifferentComparingRGB(int[] first, int width, int height) { if (first==null || mPrevious==null) return false; if (first.length != mPrevious.length) return true; if (mPreviousWidth != width || mPreviousHeight != height) return true; @@ -111,10 +121,7 @@ protected static boolean isDifferentComparingHSL(int[] first, int width, int hei if (otherPix < 0) otherPix = 0; if (otherPix > 255) otherPix = 255; - float b1 = ImageProcessing.getBrightnessAtPoint(pix); - float b2 = ImageProcessing.getBrightnessAtPoint(otherPix); - - if (Math.abs(b1 - b2) >= mPixelThreshold) { + if (Math.abs(pix - otherPix) >= mRgbPixelThreshold) { totDifferentPixels++; //Paint different pixel red first[ij] = Color.RED; @@ -122,7 +129,7 @@ protected static boolean isDifferentComparingHSL(int[] first, int width, int hei } } if (totDifferentPixels <= 0) totDifferentPixels = 1; - boolean different = totDifferentPixels > mThreshold; + boolean different = totDifferentPixels > mRgbThreshold; int percent = 100/(size/totDifferentPixels); String output = "Number of different pixels: " + totDifferentPixels + "> " + percent + "%"; @@ -134,10 +141,10 @@ protected static boolean isDifferentComparingHSL(int[] first, int width, int hei return different; } - - public static boolean detect(int[] data, int width, int height) { - if (data==null) return false; - int[] original = data.clone(); + + public static boolean detect(int[] rgb, int width, int height) { + if (rgb==null) return false; + int[] original = rgb.clone(); // Create the "mPrevious" picture, the one that will be used to check the next frame against. if(mPrevious == null) { @@ -148,11 +155,14 @@ public static boolean detect(int[] data, int width, int height) { return false; } + long bDetection = System.currentTimeMillis(); boolean motionDetected = false; - if (USE_RGB) motionDetected = isDifferentComparingRGB(data, width, height); - if (USE_HSL) motionDetected = isDifferentComparingHSL(data, width, height); - if (USE_STATE) motionDetected = isDifferentComparingState(data, width, height); - + if (USE_RGB) motionDetected = isDifferentComparingRGB(rgb, width, height); + if (USE_HSL) motionDetected = isDifferentComparingHSL(rgb, width, height); + if (USE_STATE) motionDetected = isDifferentComparingState(rgb, width, height); + long aDetection = System.currentTimeMillis(); + Log.d(TAG, "Detection "+(aDetection-bDetection)); + // Replace the current image with the previous. mPrevious = original; mPreviousWidth = width; diff --git a/src/com/jwetherell/motion_detection/detection/State.java b/src/com/jwetherell/motion_detection/detection/State.java index 7117778..6aacd76 100644 --- a/src/com/jwetherell/motion_detection/detection/State.java +++ b/src/com/jwetherell/motion_detection/detection/State.java @@ -11,11 +11,18 @@ * @author Justin Wetherell */ public class State { - private int[][] map; + private int[][] map = null; private int width; private int height; private int average; + public State(State other) { + this.map = other.map.clone(); + this.width = other.width; + this.height = other.height; + this.average = other.average; + } + public State(int[] data, int width, int height) { if (data==null) return; @@ -27,15 +34,14 @@ public State(int[] data, int width, int height) { // build map and stats average = 0; - int ta = 0; - for (int y = 0; y < height; y++) { - for (int x = 0, xy = 0; x < width; x++, xy++) { - ta = (int)(100*ImageProcessing.getBrightnessAtPoint(data[xy])); + for (int y = 0, xy=0; y < this.height; y++) { + for (int x = 0; x < this.width; x++, xy++) { + int ta = ImageProcessing.getBrightnessAtPoint(data[xy]); map[y][x] = ta; average += ta; } } - average = (int)(average / (width * height)); + average = (average / (this.width * this.height)); } public int[][] getMap() { @@ -49,4 +55,25 @@ public int getWidth() { public int getHeight() { return height; } + + @Override + public String toString() { + StringBuilder output = new StringBuilder(); + output.append("h="+height+" w="+width+"\n"); + for (int y = 0; y < height; y++) { + output.append('|'); + for (int x = 0;x < width; x++) { + output.append(map[y][x]); + output.append('|'); + } + output.append("\n"); + } + return output.toString(); + } + + @Override + public State clone() { + State newState = new State(this); + return newState; + } } diff --git a/src/com/jwetherell/motion_detection/image/ImageProcessing.java b/src/com/jwetherell/motion_detection/image/ImageProcessing.java index 1e5da6e..167ed94 100644 --- a/src/com/jwetherell/motion_detection/image/ImageProcessing.java +++ b/src/com/jwetherell/motion_detection/image/ImageProcessing.java @@ -5,6 +5,7 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; +import android.util.Log; /** * This abstract class is used to process images. @@ -33,7 +34,7 @@ public static float[] getARGB(int pixel) { //Get HSL from RGB //H is 0-360 (degrees) //H and S are 0-100 (percent) - public static float[] convertToHSL(int r, int g, int b) { + public static int[] convertToHSL(int r, int g, int b) { float red = r / 255; float green = g / 255; float blue = b / 255; @@ -41,80 +42,124 @@ public static float[] convertToHSL(int r, int g, int b) { float minComponent = Math.min(red, Math.min(green, blue)); float maxComponent = Math.max(red, Math.max(green, blue)); float range = maxComponent - minComponent; - float[] HSL = new float[3]; + float h=0,s=0,l=0; - HSL[L] = (maxComponent + minComponent) / 2; + l = (maxComponent + minComponent) / 2; if(range == 0) { // Monochrome image - HSL[H] = HSL[S] = 0; + h = s = 0; } else { - HSL[S] = (HSL[L] > 0.5) ? - range / (2 - range) - : - range / (maxComponent + minComponent); + s = (l > 0.5) ? + range / (2 - range) + : + range / (maxComponent + minComponent); if(red == maxComponent) { - HSL[H] = (blue - green) / range; + h = (blue - green) / range; } else if(green == maxComponent) { - HSL[H] = 2 + (blue - red) / range; + h = 2 + (blue - red) / range; } else if(blue == maxComponent) { - HSL[H] = 4 +(red - green) / range; + h = 4 +(red - green) / range; } } //convert to 0-360 (degrees) - HSL[H] *= 60; - if (HSL[H]<0) HSL[H] += 360; + h *= 60; + if (h<0) h += 360; //convert to 0-100 (percent) - HSL[S] *= 100; - HSL[L] *= 100; + s *= 100; + l *= 100; - return HSL; + //Since they were converted from float to int + return (new int[]{(int)h,(int)s,(int)l}); } - public static float getBrightnessAtPoint(int pixel) { + public static int getBrightnessAtPoint(int pixel) { //Get RGB from Integer - int r = (pixel >> 16) & 0xff; + int r = (pixel >> 16) & 0xff; int g = (pixel >> 8) & 0xff; int b = (pixel) & 0xff; - + //Convert RGB to HSL (not using method above because I don't want to create //an extra float[] for every pixel. - float red = r / 255; - float green = g / 255; - float blue = b / 255; - float h=0,l=0; - - float minComponent = Math.min(red, Math.min(green, blue)); + float red = r; + float green = g; + float blue = b; + red = red / 255; + green = green / 255; + blue = blue / 255; + + float h=0,s=0,l=0; + float minComponent = Math.min(red, Math.min(green, blue)); float maxComponent = Math.max(red, Math.max(green, blue)); float range = maxComponent - minComponent; l = (maxComponent + minComponent) / 2; - if(range > 0) { - if(red == maxComponent) { + if(range == 0) { // Monochrome image + h = s = 0; + } else { + s = (l > 0.5) ? + range / (2 - range) + : + range / (maxComponent + minComponent); + if (Float.compare(red,maxComponent)==0) { h = (blue - green) / range; - } else if(green == maxComponent) { + } else if(Float.compare(green,maxComponent)==0) { h = 2 + (blue - red) / range; - } else if(blue == maxComponent) { + } else if(Float.compare(blue,maxComponent)==0) { h = 4 +(red - green) / range; + } else { + Log.e("TAG", "Should not get here!"); } } - + //convert to 0-360 - h *= 60; - if (h<0) h += 360; + h = h * 60; + if (h<0) h = h + 360; //convert to 0-100 - l *= 100; + s = s * 100; + l = l * 100; //Convert the HSL into a single "brightness" representation //brightness is between 0-100 using 50% lightness and 50% hue - return (float)((l * 0.5) + ((h / 360) * 50)); + int brightness = (int)((l * 0.5) + ((h / 360) * 50)); + return brightness; } + + public static int[][] decodeYUV420SPtoHSL(byte[] yuv420sp, int width, int height) { + if (yuv420sp==null) return null; + + final int frameSize = width * height; + int[][] hsl = new int[frameSize][3]; - public static int[] decodeYUV420SP(byte[] yuv420sp, int width, int height) { + for (int j = 0, yp = 0; j < height; j++) { + int uvp = frameSize + (j >> 1) * width, u = 0, v = 0; + for (int i = 0; i < width; i++, yp++) { + int y = (0xff & ((int) yuv420sp[yp])) - 16; + if (y < 0) y = 0; + if ((i & 1) == 0) { + v = (0xff & yuv420sp[uvp++]) - 128; + u = (0xff & yuv420sp[uvp++]) - 128; + } + int y1192 = 1192 * y; + int r = (y1192 + 1634 * v); + int g = (y1192 - 833 * v - 400 * u); + int b = (y1192 + 2066 * u); + + if (r < 0) r = 0; else if (r > 262143) r = 262143; + if (g < 0) g = 0; else if (g > 262143) g = 262143; + if (b < 0) b = 0; else if (b > 262143) b = 262143; + + hsl[yp] = convertToHSL(r,g,b); + } + } + return hsl; + } + + public static int[] decodeYUV420SPtoRGB(byte[] yuv420sp, int width, int height) { if (yuv420sp==null) return null; final int frameSize = width * height;