diff --git a/src/com/jwetherell/motion_detection/MotionDetectionActivity.java b/src/com/jwetherell/motion_detection/MotionDetectionActivity.java index 704c091..162edaf 100644 --- a/src/com/jwetherell/motion_detection/MotionDetectionActivity.java +++ b/src/com/jwetherell/motion_detection/MotionDetectionActivity.java @@ -26,53 +26,54 @@ /** - * This class extends Activity to handle a picture preview, process the frame for motion, and then - * save the file to the SD card. + * This class extends Activity to handle a picture preview, process the frame + * for motion, and then save the file to the SD card. * * @author Justin Wetherell */ public class MotionDetectionActivity extends SensorsActivity { - private static final String TAG = "MotionDetectionActivity"; - - private static SurfaceView preview = null; - private static SurfaceHolder previewHolder = null; - private static Camera camera = null; - private static boolean inPreview = false; - private static long mReferenceTime = 0; - private static IMotionDetection detector = null; - - private static volatile AtomicBoolean processing = new AtomicBoolean(false); - - /** - * {@inheritDoc} - */ - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.main); - - preview = (SurfaceView)findViewById(R.id.preview); - previewHolder = preview.getHolder(); - previewHolder.addCallback(surfaceCallback); - previewHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); - - if (Preferences.USE_RGB) { - detector = new RgbMotionDetection(); - } else if (Preferences.USE_LUMA) { - detector = new LumaMotionDetection(); - } else { - //Using State based (aggregate map) - detector = new AggregateLumaMotionDetection(); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - } + + private static final String TAG = "MotionDetectionActivity"; + + private static SurfaceView preview = null; + private static SurfaceHolder previewHolder = null; + private static Camera camera = null; + private static boolean inPreview = false; + private static long mReferenceTime = 0; + private static IMotionDetection detector = null; + + private static volatile AtomicBoolean processing = new AtomicBoolean(false); + + /** + * {@inheritDoc} + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + + preview = (SurfaceView) findViewById(R.id.preview); + previewHolder = preview.getHolder(); + previewHolder.addCallback(surfaceCallback); + previewHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); + + if (Preferences.USE_RGB) { + detector = new RgbMotionDetection(); + } else if (Preferences.USE_LUMA) { + detector = new LumaMotionDetection(); + } else { + // Using State based (aggregate map) + detector = new AggregateLumaMotionDetection(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + } /** * {@inheritDoc} @@ -88,199 +89,205 @@ public void onPause() { camera = null; } - /** - * {@inheritDoc} - */ - @Override - public void onResume() { - super.onResume(); - - camera = Camera.open(); - } - - private PreviewCallback previewCallback = new PreviewCallback() { - /** - * {@inheritDoc} - */ - @Override - public void onPreviewFrame(byte[] data, Camera cam) { - if (data == null) return; - Camera.Size size = cam.getParameters().getPreviewSize(); - if (size == null) return; - - if (!GlobalData.isPhoneInMotion()) { - DetectionThread thread = new DetectionThread(data,size.width,size.height); - thread.start(); - } - } - }; - - private SurfaceHolder.Callback surfaceCallback=new SurfaceHolder.Callback() { - /** - * {@inheritDoc} - */ - @Override - public void surfaceCreated(SurfaceHolder holder) { - try { - camera.setPreviewDisplay(previewHolder); - camera.setPreviewCallback(previewCallback); - } catch (Throwable t) { - Log.e("PreviewDemo-surfaceCallback", "Exception in setPreviewDisplay()", t); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { - Camera.Parameters parameters = camera.getParameters(); - Camera.Size size = getBestPreviewSize(width, height, parameters); - if (size!=null) { - parameters.setPreviewSize(size.width, size.height); - Log.d(TAG, "Using width="+size.width+" height="+size.height); - } - camera.setParameters(parameters); - camera.startPreview(); - inPreview=true; - } - - /** - * {@inheritDoc} - */ - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - // Ignore - } - }; - - private static Camera.Size getBestPreviewSize(int width, int height, Camera.Parameters parameters) { - Camera.Size result=null; - - for (Camera.Size size : parameters.getSupportedPreviewSizes()) { - if (size.width<=width && size.height<=height) { - if (result==null) { - result=size; - } else { - int resultArea=result.width*result.height; - int newArea=size.width*size.height; - - if (newArea>resultArea) result=size; - } - } - } - - return result; - } - - private static final class DetectionThread extends Thread { - private byte[] data; - private int width; - private int height; - - public DetectionThread(byte[] data, int width, int height) { - this.data = data; - this.width = width; - this.height = height; - } - - /** - * {@inheritDoc} - */ - @Override - public void run() { - if (!processing.compareAndSet(false, true)) return; - - //Log.d(TAG, "BEGIN PROCESSING..."); - try { - //Previous frame - int[] pre = null; - if (Preferences.SAVE_PREVIOUS) pre = detector.getPrevious(); - - //Current frame (with changes) - //long bConversion = System.currentTimeMillis(); - int[] img = null; - if (Preferences.USE_RGB) { - img = ImageProcessing.decodeYUV420SPtoRGB(data, width, height); - } else { - img = ImageProcessing.decodeYUV420SPtoLuma(data, width, height); - } - //long aConversion = System.currentTimeMillis(); - //Log.d(TAG, "Converstion="+(aConversion-bConversion)); - - //Current frame (without changes) - int[] org = null; - if (Preferences.SAVE_ORIGINAL && img!=null) org = img.clone(); - - if (img!=null && detector.detect(img, width, height)) { - // The delay is necessary to avoid taking a picture while in the - // middle of taking another. This problem can causes some phones - // to reboot. - long now = System.currentTimeMillis(); - if (now > (mReferenceTime + Preferences.PICTURE_DELAY)) { - mReferenceTime = now; - - Bitmap previous = null; - if (Preferences.SAVE_PREVIOUS && pre!=null) { - if (Preferences.USE_RGB) previous = ImageProcessing.rgbToBitmap(pre, width, height); - else previous = ImageProcessing.lumaToGreyscale(pre, width, height); - } - - Bitmap original = null; - if (Preferences.SAVE_ORIGINAL && org!=null) { - if (Preferences.USE_RGB) original = ImageProcessing.rgbToBitmap(org, width, height); - else original = ImageProcessing.lumaToGreyscale(org, width, height); - } - - Bitmap bitmap = null; - if (Preferences.SAVE_CHANGES && img!=null) { - if (Preferences.USE_RGB) bitmap = ImageProcessing.rgbToBitmap(img, width, height); - else bitmap = ImageProcessing.lumaToGreyscale(img, width, height); - } - - Log.i(TAG,"Saving.. previous="+previous+" original="+original+" bitmap="+bitmap); - Looper.prepare(); - new SavePhotoTask().execute(previous,original,bitmap); - } else { - Log.i(TAG, "Not taking picture because not enough time has passed since the creation of the Surface"); - } - } - } catch (Exception e) { - e.printStackTrace(); - } finally { - processing.set(false); - } - //Log.d(TAG, "END PROCESSING..."); - - processing.set(false); - } - }; - - private static final class SavePhotoTask extends AsyncTask { - /** - * {@inheritDoc} - */ - @Override - protected Integer doInBackground(Bitmap... data) { - for (int i=0; i { + + /** + * {@inheritDoc} + */ + @Override + protected Integer doInBackground(Bitmap... data) { + for (int i = 0; i < data.length; i++) { + Bitmap bitmap = data[i]; + String name = String.valueOf(System.currentTimeMillis()); + if (bitmap != null) save(name, bitmap); + } + return 1; + } + + private void save(String name, Bitmap bitmap) { + File photo = new File(Environment.getExternalStorageDirectory(), name + ".jpg"); + if (photo.exists()) photo.delete(); + + try { + FileOutputStream fos = new FileOutputStream(photo.getPath()); + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos); + fos.close(); + } catch (java.io.IOException e) { + Log.e("PictureDemo", "Exception in photoCallback", e); + } + } + } } \ No newline at end of file diff --git a/src/com/jwetherell/motion_detection/SensorsActivity.java b/src/com/jwetherell/motion_detection/SensorsActivity.java index 480057d..6a2a3c4 100644 --- a/src/com/jwetherell/motion_detection/SensorsActivity.java +++ b/src/com/jwetherell/motion_detection/SensorsActivity.java @@ -5,7 +5,6 @@ import com.jwetherell.motion_detection.data.GlobalData; - import android.app.Activity; import android.hardware.Sensor; import android.hardware.SensorEvent; @@ -16,33 +15,36 @@ /** - * This class extends Activity and processes sensor data and location data. It is used to - * detect when the phone is in motion, so we do not try to detect motion. + * This class extends Activity and processes sensor data and location data. It + * is used to detect when the phone is in motion, so we do not try to detect + * motion. * * @author Justin Wetherell */ public class SensorsActivity extends Activity implements SensorEventListener { + private static final String TAG = "SensorsActivity"; - private static final AtomicBoolean computing = new AtomicBoolean(false); + private static final AtomicBoolean computing = new AtomicBoolean(false); - private static final float grav[] = new float[3]; //Gravity (a.k.a accelerometer data) - private static final float mag[] = new float[3]; //Magnetic + private static final float grav[] = new float[3]; // Gravity (a.k.a + // accelerometer data) + private static final float mag[] = new float[3]; // Magnetic private static final float gravThreshold = 0.5f; private static final float magThreshold = 1.0f; - + private static SensorManager sensorMgr = null; private static List sensors = null; private static Sensor sensorGrav = null; private static Sensor sensorMag = null; - + private static float prevGrav = 0.0f; private static float prevMag = 0.0f; - /** - * {@inheritDoc} - */ - @Override + /** + * {@inheritDoc} + */ + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @@ -55,13 +57,13 @@ public void onDestroy() { super.onDestroy(); } - /** - * {@inheritDoc} - */ - @Override + /** + * {@inheritDoc} + */ + @Override public void onStart() { super.onStart(); - + try { sensorMgr = (SensorManager) getSystemService(SENSOR_SERVICE); @@ -81,15 +83,15 @@ public void onStart() { sensorMgr = null; } } catch (Exception ex2) { - ex2.printStackTrace(); + ex2.printStackTrace(); } } } - /** - * {@inheritDoc} - */ - @Override + /** + * {@inheritDoc} + */ + @Override public void onStop() { super.onStop(); @@ -97,26 +99,26 @@ public void onStop() { try { sensorMgr.unregisterListener(this, sensorGrav); } catch (Exception ex) { - ex.printStackTrace(); + ex.printStackTrace(); } try { sensorMgr.unregisterListener(this, sensorMag); } catch (Exception ex) { - ex.printStackTrace(); + ex.printStackTrace(); } sensorMgr = null; } catch (Exception ex) { - ex.printStackTrace(); + ex.printStackTrace(); } } - /** - * {@inheritDoc} - */ - @Override + /** + * {@inheritDoc} + */ + @Override public void onSensorChanged(SensorEvent evt) { - if (!computing.compareAndSet(false, true)) return; - + if (!computing.compareAndSet(false, true)) return; + if (evt.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { grav[0] = evt.values[0]; grav[1] = evt.values[1]; @@ -127,35 +129,33 @@ public void onSensorChanged(SensorEvent evt) { mag[2] = evt.values[2]; } - float gravity = grav[0]+grav[1]+grav[2]; - float magnetic = mag[0]+mag[1]+mag[2]; - - float gravDiff = Math.abs(gravity-prevGrav); - float magDiff = Math.abs(magnetic-prevMag); - //Log.i(TAG, "gravDiff="+gravDiff+" magDiff="+magDiff); + float gravity = grav[0] + grav[1] + grav[2]; + float magnetic = mag[0] + mag[1] + mag[2]; + + float gravDiff = Math.abs(gravity - prevGrav); + float magDiff = Math.abs(magnetic - prevMag); + // Log.i(TAG, "gravDiff="+gravDiff+" magDiff="+magDiff); - if ( (Float.compare(prevGrav,0.0f)!=0 && Float.compare(prevMag,0.0f)!=0) && - (gravDiff>gravThreshold || magDiff>magThreshold) ) - { + if ((Float.compare(prevGrav, 0.0f) != 0 && Float.compare(prevMag, 0.0f) != 0) && (gravDiff > gravThreshold || magDiff > magThreshold)) { GlobalData.setPhoneInMotion(true); } else { GlobalData.setPhoneInMotion(false); } - + prevGrav = gravity; prevMag = magnetic; - + computing.set(false); } - /** - * {@inheritDoc} - */ - @Override + /** + * {@inheritDoc} + */ + @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { - if (sensor==null) throw new NullPointerException(); - - if(sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD && accuracy==SensorManager.SENSOR_STATUS_UNRELIABLE) { + if (sensor == null) throw new NullPointerException(); + + if (sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD && accuracy == SensorManager.SENSOR_STATUS_UNRELIABLE) { Log.e(TAG, "Compass data unreliable"); } } diff --git a/src/com/jwetherell/motion_detection/data/GlobalData.java b/src/com/jwetherell/motion_detection/data/GlobalData.java index 75c727f..da91c25 100644 --- a/src/com/jwetherell/motion_detection/data/GlobalData.java +++ b/src/com/jwetherell/motion_detection/data/GlobalData.java @@ -2,19 +2,23 @@ import java.util.concurrent.atomic.AtomicBoolean; + /** * This class is used to store global data. * * @author Justin Wetherell */ public abstract class GlobalData { - private GlobalData() { }; - + + private GlobalData() { + }; + private static final AtomicBoolean phoneInMotion = new AtomicBoolean(false); public static boolean isPhoneInMotion() { return phoneInMotion.get(); } + public static void setPhoneInMotion(boolean bool) { phoneInMotion.set(bool); } diff --git a/src/com/jwetherell/motion_detection/data/Preferences.java b/src/com/jwetherell/motion_detection/data/Preferences.java index 0459cc5..9882437 100644 --- a/src/com/jwetherell/motion_detection/data/Preferences.java +++ b/src/com/jwetherell/motion_detection/data/Preferences.java @@ -1,24 +1,26 @@ package com.jwetherell.motion_detection.data; - /** - * This class is used to store preferences on how to decode images and what to save. + * This class is used to store preferences on how to decode images and what to + * save. * * @author Justin Wetherell */ -public abstract class Preferences { - private Preferences() { } - - //Which motion detection to use - public static boolean USE_RGB = true; - public static boolean USE_LUMA = false; - public static boolean USE_STATE = false; - - //Which photos to save - public static boolean SAVE_PREVIOUS = false; - public static boolean SAVE_ORIGINAL = true; - public static boolean SAVE_CHANGES = true; - - //Time between saving photos - public static int PICTURE_DELAY = 10000; +public abstract class Preferences { + + private Preferences() { + } + + // Which motion detection to use + public static boolean USE_RGB = true; + public static boolean USE_LUMA = false; + public static boolean USE_STATE = false; + + // Which photos to save + public static boolean SAVE_PREVIOUS = false; + public static boolean SAVE_ORIGINAL = true; + public static boolean SAVE_CHANGES = true; + + // Time between saving photos + public static int PICTURE_DELAY = 10000; } diff --git a/src/com/jwetherell/motion_detection/detection/AggregateLumaMotionDetection.java b/src/com/jwetherell/motion_detection/detection/AggregateLumaMotionDetection.java index 400d42c..4d30a83 100644 --- a/src/com/jwetherell/motion_detection/detection/AggregateLumaMotionDetection.java +++ b/src/com/jwetherell/motion_detection/detection/AggregateLumaMotionDetection.java @@ -2,91 +2,93 @@ //import android.util.Log; - /** - * This class is used to process integer arrays containing luma data and detects motion using an aggregate map. + * This class is used to process integer arrays containing luma data and detects + * motion using an aggregate map. * * @author Justin Wetherell */ public class AggregateLumaMotionDetection implements IMotionDetection { - //private static final String TAG = "AggregateLumaMotionDetection"; - - //Specific settings - private static final int mLeniency = 10; //Difference of aggregate map of luma values - private static final int mDebugMode = 2; //State based debug - private static final int mXBoxes = 10; //State based debug - private static final int mYBoxes = 10; //State based debug - - private static int[] mPrevious = null; - private static int mPreviousWidth; - private static int mPreviousHeight; - private static State mPreviousState = null; - - /** - * {@inheritDoc} - */ - @Override - public int[] getPrevious() { - return ((mPrevious!=null)?mPrevious.clone():null); - } - - protected static boolean isDifferent(int[] first, int width, int height) { - if (first==null) throw new NullPointerException(); - - if (mPrevious==null) return false; - if (first.length != mPrevious.length) return true; - if (mPreviousWidth != width || mPreviousHeight != height) return true; - - if (mPreviousState==null) { - mPreviousState = new State(mPrevious, mPreviousWidth, mPreviousHeight); - return false; - } - - State state = new State(first, width, height); - Comparer comparer = new Comparer(state, mPreviousState, mXBoxes, mYBoxes, mLeniency, mDebugMode); - - boolean different = comparer.isDifferent(); - //String output = "isDifferent="+different; - if (different) { - //Log.e(TAG, output); - comparer.paintDifferences(first); - //} else { - //Log.d(TAG, output); - } - - mPreviousState = state; - - return different; - } - - /** - * Detect motion using aggregate luma values. - * {@inheritDoc} - */ - @Override - public boolean detect(int[] luma, int width, int height) { - if (luma==null) throw new NullPointerException(); - - int[] original = luma.clone(); - - // Create the "mPrevious" picture, the one that will be used to check the next frame against. - if(mPrevious == null) { - mPrevious = original; - mPreviousWidth = width; - mPreviousHeight = height; - //Log.i(TAG, "Creating background image"); - } - - //long bDetection = System.currentTimeMillis(); - boolean motionDetected = isDifferent(luma, width, height); - //long aDetection = System.currentTimeMillis(); - //Log.d(TAG, "Detection "+(aDetection-bDetection)); - - // Replace the current image with the previous. - mPrevious = original; - mPreviousWidth = width; - mPreviousHeight = height; - - return motionDetected; - } + + // private static final String TAG = "AggregateLumaMotionDetection"; + + // Specific settings + private static final int mLeniency = 10; // Difference of aggregate map of + // luma values + private static final int mDebugMode = 2; // State based debug + private static final int mXBoxes = 10; // State based debug + private static final int mYBoxes = 10; // State based debug + + private static int[] mPrevious = null; + private static int mPreviousWidth; + private static int mPreviousHeight; + private static State mPreviousState = null; + + /** + * {@inheritDoc} + */ + @Override + public int[] getPrevious() { + return ((mPrevious != null) ? mPrevious.clone() : null); + } + + protected static boolean isDifferent(int[] first, int width, int height) { + if (first == null) throw new NullPointerException(); + + if (mPrevious == null) return false; + if (first.length != mPrevious.length) return true; + if (mPreviousWidth != width || mPreviousHeight != height) return true; + + if (mPreviousState == null) { + mPreviousState = new State(mPrevious, mPreviousWidth, mPreviousHeight); + return false; + } + + State state = new State(first, width, height); + Comparer comparer = new Comparer(state, mPreviousState, mXBoxes, mYBoxes, mLeniency, mDebugMode); + + boolean different = comparer.isDifferent(); + // String output = "isDifferent="+different; + if (different) { + // Log.e(TAG, output); + comparer.paintDifferences(first); + // } else { + // Log.d(TAG, output); + } + + mPreviousState = state; + + return different; + } + + /** + * Detect motion using aggregate luma values. {@inheritDoc} + */ + @Override + public boolean detect(int[] luma, int width, int height) { + if (luma == null) throw new NullPointerException(); + + int[] original = luma.clone(); + + // Create the "mPrevious" picture, the one that will be used to check + // the next frame against. + if (mPrevious == null) { + mPrevious = original; + mPreviousWidth = width; + mPreviousHeight = height; + // Log.i(TAG, "Creating background image"); + } + + // long bDetection = System.currentTimeMillis(); + boolean motionDetected = isDifferent(luma, width, height); + // long aDetection = System.currentTimeMillis(); + // Log.d(TAG, "Detection "+(aDetection-bDetection)); + + // Replace the current image with the previous. + mPrevious = original; + mPreviousWidth = width; + mPreviousHeight = height; + + return motionDetected; + } } diff --git a/src/com/jwetherell/motion_detection/detection/Comparer.java b/src/com/jwetherell/motion_detection/detection/Comparer.java index 09ec255..ef6c6be 100644 --- a/src/com/jwetherell/motion_detection/detection/Comparer.java +++ b/src/com/jwetherell/motion_detection/detection/Comparer.java @@ -4,210 +4,225 @@ /** - * This class is adapted from the web site below. It is used to compare two State objects. + * This class is adapted from the web site below. It is used to compare two + * State objects. * http://mindmeat.blogspot.com/2008/11/java-image-comparison.html * * @author Pat Cullen * @author Justin Wetherell */ public class Comparer { - private State state1 = null; - private State state2 = null; - private int xBoxes; - private int yBoxes; - private int xPixelsPerBox; - private int yPixelsPerBox; - private int xLeftOver; - private int yLeftOver; - private int rowWidthInPix; - private int colWidthInPix; - private int leniency; - private int debugMode; // 1: textual indication of change, 2: difference of factors - - private int[][] variance = null; - private boolean different = false; - - public Comparer(State s1, State s2, int xBoxes, int yBoxes, int leniency, int debug) { - this.state1 = s1; - this.state2 = s2; - - this.xBoxes = xBoxes; - if (this.xBoxes > s1.getWidth()) this.xBoxes = s1.getWidth(); - - this.yBoxes = yBoxes; - if (this.yBoxes > s1.getHeight()) this.yBoxes = s1.getHeight(); - - this.leniency = leniency; - this.debugMode = 0; - - // how many points per box - this.xPixelsPerBox = (int)(Math.floor(s1.getWidth() / this.xBoxes)); - if (xPixelsPerBox <= 0) xPixelsPerBox = 1; - this.yPixelsPerBox = (int)(Math.floor(s1.getHeight() / this.yBoxes)); - if (yPixelsPerBox <= 0) yPixelsPerBox = 1; - - this.xLeftOver = s1.getWidth() - (this.xBoxes*this.xPixelsPerBox); - if (xLeftOver>0) this.xBoxes++; - yLeftOver = s1.getHeight() - (this.yBoxes*this.yPixelsPerBox); - if (yLeftOver>0) this.yBoxes++; - - this.rowWidthInPix = (this.xBoxes*xPixelsPerBox)-(xPixelsPerBox-xLeftOver); - this.colWidthInPix = this.xPixelsPerBox; - - this.debugMode = debug; - - this.different = isDifferent(this.state1,this.state2); - } - - /** - * Compare two images and populate the variance variable - * @param s1 State for image one. - * @param s2 State for image two. - * @return True is the two images are different. - * @throws NullPointerException if s1 or s2 is NULL. - */ - public boolean isDifferent(State s1, State s2) { - if (s1==null || s2==null) - throw new NullPointerException(); - if (s1.getWidth()!=s2.getWidth() || s1.getHeight()!=s2.getHeight()) - return true; - - // Boxes - this.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 b1 = 0; - int b2 = 0; - int diff = 0; - for (int y = 0; y < yBoxes; y++) { - for (int x = 0; x < xBoxes; x++) { - b1 = aggregateMapArea(state1.getMap(), x, y); - b2 = aggregateMapArea(state2.getMap(), x, y); - 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; - } - } - return different; - } - - private int aggregateMapArea(int[] map, int xBox, int yBox) { - if (map==null) throw new NullPointerException(); - - int yPix = yPixelsPerBox; - int xPix = xPixelsPerBox; - if (yBox==(yBoxes-1) && yLeftOver>0) yPix = yLeftOver; - if (xBox==(xBoxes-1) && xLeftOver>0) xPix = xLeftOver; - - int rowOffset = (yBox*yPixelsPerBox*rowWidthInPix); - int columnOffset = (xBox*colWidthInPix); - - int i = 0; - int iy = 0; - for (int y = 0; y < yPix; y++) { - iy = (y*(xBoxes*xPixelsPerBox))-(y*(xPixelsPerBox-xLeftOver)); - for (int x = 0; x < xPix; x++) { - i += map[rowOffset+columnOffset+iy+x]; - } - } - - return (i/(xPix*yPix)); - } - - /** - * Given the int array of an image, paint the pixels that are different. - * @param data int array of an image. - * @throws NullPointerException if data int array is NULL. - */ - public void paintDifferences(int[] data) { - if (data==null) throw new NullPointerException(); - - for (int y = 0; y < yBoxes; y++) { - for (int x = 0; x < xBoxes; x++) { - if (variance[y][x] > leniency) paint(data, x, y, Color.RED); - } - } - } - - private void paint(int[] data, int xBox, int yBox, int color) { - if (data==null) throw new NullPointerException(); - - int yPix = yPixelsPerBox; - int xPix = xPixelsPerBox; - if (yBox==(yBoxes-1) && yLeftOver>0) yPix = yLeftOver; - if (xBox==(xBoxes-1) && xLeftOver>0) xPix = xLeftOver; - - int rowOffset = (yBox*yPixelsPerBox*rowWidthInPix); - int columnOffset = (xBox*colWidthInPix); - - int iy = 0; - for (int y = 0; y < yPix; y++) { - iy = y*(xBoxes*xPixelsPerBox)-(y*(xPixelsPerBox-xLeftOver)); - for (int x = 0; x < xPix; x++) { - if (y==0 || y==(yPix-1) || x==0 || x==(xPix-1)) data[rowOffset+columnOffset+iy+x] = color; - } - } - } - - /** - * Number of X Boxes. - * @return int representing the number of X boxes. - */ - public int getCompareX() { - return xBoxes; - } - - /** - * Number of Y Boxes. - * @return int representing the number of Y boxes. - */ - public int getCompareY() { - return yBoxes; - } - - /** - * Leniency of the pixel comparison. - * @return int representing the leniency. - */ - public int getLeniency() { - return leniency; - } - - /** - * Debug mode of the comparer. - * @return int representing the debug mode. - */ - public int getDebugMode() { - return debugMode; - } - - /** - * Are the two States different. - * @return True is the States are different. - */ - public boolean isDifferent() { - return different; - } - - /** - * {@inheritDoc} - */ - @Override - public String toString() { - int diff = 0; - StringBuilder output = new StringBuilder(); - for (int y = 0; y < yBoxes; y++) { - output.append('|'); - for (int x = 0; x < xBoxes; x++) { - diff = variance[y][x]; - if (debugMode == 1) output.append((diff > leniency)?'X':' '); - if (debugMode == 2) output.append(diff + ((x<(xBoxes-1))? "," : "")); - } - output.append("|\n"); - } - return output.toString(); - } + + private State state1 = null; + private State state2 = null; + private int xBoxes; + private int yBoxes; + private int xPixelsPerBox; + private int yPixelsPerBox; + private int xLeftOver; + private int yLeftOver; + private int rowWidthInPix; + private int colWidthInPix; + private int leniency; + private int debugMode; // 1: textual indication of change, 2: difference of + // factors + + private int[][] variance = null; + private boolean different = false; + + public Comparer(State s1, State s2, int xBoxes, int yBoxes, int leniency, int debug) { + this.state1 = s1; + this.state2 = s2; + + this.xBoxes = xBoxes; + if (this.xBoxes > s1.getWidth()) this.xBoxes = s1.getWidth(); + + this.yBoxes = yBoxes; + if (this.yBoxes > s1.getHeight()) this.yBoxes = s1.getHeight(); + + this.leniency = leniency; + this.debugMode = 0; + + // how many points per box + this.xPixelsPerBox = (int) (Math.floor(s1.getWidth() / this.xBoxes)); + if (xPixelsPerBox <= 0) xPixelsPerBox = 1; + this.yPixelsPerBox = (int) (Math.floor(s1.getHeight() / this.yBoxes)); + if (yPixelsPerBox <= 0) yPixelsPerBox = 1; + + this.xLeftOver = s1.getWidth() - (this.xBoxes * this.xPixelsPerBox); + if (xLeftOver > 0) this.xBoxes++; + yLeftOver = s1.getHeight() - (this.yBoxes * this.yPixelsPerBox); + if (yLeftOver > 0) this.yBoxes++; + + this.rowWidthInPix = (this.xBoxes * xPixelsPerBox) - (xPixelsPerBox - xLeftOver); + this.colWidthInPix = this.xPixelsPerBox; + + this.debugMode = debug; + + this.different = isDifferent(this.state1, this.state2); + } + + /** + * Compare two images and populate the variance variable + * + * @param s1 + * State for image one. + * @param s2 + * State for image two. + * @return True is the two images are different. + * @throws NullPointerException + * if s1 or s2 is NULL. + */ + public boolean isDifferent(State s1, State s2) { + if (s1 == null || s2 == null) throw new NullPointerException(); + if (s1.getWidth() != s2.getWidth() || s1.getHeight() != s2.getHeight()) return true; + + // Boxes + this.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 b1 = 0; + int b2 = 0; + int diff = 0; + for (int y = 0; y < yBoxes; y++) { + for (int x = 0; x < xBoxes; x++) { + b1 = aggregateMapArea(state1.getMap(), x, y); + b2 = aggregateMapArea(state2.getMap(), x, y); + 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; + } + } + return different; + } + + private int aggregateMapArea(int[] map, int xBox, int yBox) { + if (map == null) throw new NullPointerException(); + + int yPix = yPixelsPerBox; + int xPix = xPixelsPerBox; + if (yBox == (yBoxes - 1) && yLeftOver > 0) yPix = yLeftOver; + if (xBox == (xBoxes - 1) && xLeftOver > 0) xPix = xLeftOver; + + int rowOffset = (yBox * yPixelsPerBox * rowWidthInPix); + int columnOffset = (xBox * colWidthInPix); + + int i = 0; + int iy = 0; + for (int y = 0; y < yPix; y++) { + iy = (y * (xBoxes * xPixelsPerBox)) - (y * (xPixelsPerBox - xLeftOver)); + for (int x = 0; x < xPix; x++) { + i += map[rowOffset + columnOffset + iy + x]; + } + } + + return (i / (xPix * yPix)); + } + + /** + * Given the int array of an image, paint the pixels that are different. + * + * @param data + * int array of an image. + * @throws NullPointerException + * if data int array is NULL. + */ + public void paintDifferences(int[] data) { + if (data == null) throw new NullPointerException(); + + for (int y = 0; y < yBoxes; y++) { + for (int x = 0; x < xBoxes; x++) { + if (variance[y][x] > leniency) paint(data, x, y, Color.RED); + } + } + } + + private void paint(int[] data, int xBox, int yBox, int color) { + if (data == null) throw new NullPointerException(); + + int yPix = yPixelsPerBox; + int xPix = xPixelsPerBox; + if (yBox == (yBoxes - 1) && yLeftOver > 0) yPix = yLeftOver; + if (xBox == (xBoxes - 1) && xLeftOver > 0) xPix = xLeftOver; + + int rowOffset = (yBox * yPixelsPerBox * rowWidthInPix); + int columnOffset = (xBox * colWidthInPix); + + int iy = 0; + for (int y = 0; y < yPix; y++) { + iy = y * (xBoxes * xPixelsPerBox) - (y * (xPixelsPerBox - xLeftOver)); + for (int x = 0; x < xPix; x++) { + if (y == 0 || y == (yPix - 1) || x == 0 || x == (xPix - 1)) data[rowOffset + columnOffset + iy + x] = color; + } + } + } + + /** + * Number of X Boxes. + * + * @return int representing the number of X boxes. + */ + public int getCompareX() { + return xBoxes; + } + + /** + * Number of Y Boxes. + * + * @return int representing the number of Y boxes. + */ + public int getCompareY() { + return yBoxes; + } + + /** + * Leniency of the pixel comparison. + * + * @return int representing the leniency. + */ + public int getLeniency() { + return leniency; + } + + /** + * Debug mode of the comparer. + * + * @return int representing the debug mode. + */ + public int getDebugMode() { + return debugMode; + } + + /** + * Are the two States different. + * + * @return True is the States are different. + */ + public boolean isDifferent() { + return different; + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + int diff = 0; + StringBuilder output = new StringBuilder(); + for (int y = 0; y < yBoxes; y++) { + output.append('|'); + for (int x = 0; x < xBoxes; x++) { + diff = variance[y][x]; + if (debugMode == 1) output.append((diff > leniency) ? 'X' : ' '); + if (debugMode == 2) output.append(diff + ((x < (xBoxes - 1)) ? "," : "")); + } + output.append("|\n"); + } + return output.toString(); + } } diff --git a/src/com/jwetherell/motion_detection/detection/IMotionDetection.java b/src/com/jwetherell/motion_detection/detection/IMotionDetection.java index f64a16c..afad9f8 100644 --- a/src/com/jwetherell/motion_detection/detection/IMotionDetection.java +++ b/src/com/jwetherell/motion_detection/detection/IMotionDetection.java @@ -1,6 +1,5 @@ package com.jwetherell.motion_detection.detection; - /** * This interface is used to represent a class that can detect motion * @@ -8,19 +7,25 @@ */ public interface IMotionDetection { - /** - * Get the previous image in integer array format - * @return int array of previous image. - */ - public int[] getPrevious(); - - /** - * Detect motion. - * @param data integer array representing an image. - * @param width Width of the image. - * @param height Height of the image. - * @return boolean True is there is motion. - * @throws NullPointerException if data integer array is NULL. - */ - public boolean detect(int[] data, int width, int height); + /** + * Get the previous image in integer array format + * + * @return int array of previous image. + */ + public int[] getPrevious(); + + /** + * Detect motion. + * + * @param data + * integer array representing an image. + * @param width + * Width of the image. + * @param height + * Height of the image. + * @return boolean True is there is motion. + * @throws NullPointerException + * if data integer array is NULL. + */ + public boolean detect(int[] data, int width, int height); } diff --git a/src/com/jwetherell/motion_detection/detection/LumaMotionDetection.java b/src/com/jwetherell/motion_detection/detection/LumaMotionDetection.java index bb6c1d9..8f9a085 100644 --- a/src/com/jwetherell/motion_detection/detection/LumaMotionDetection.java +++ b/src/com/jwetherell/motion_detection/detection/LumaMotionDetection.java @@ -1,103 +1,103 @@ package com.jwetherell.motion_detection.detection; import android.graphics.Color; -//import android.util.Log; +//import android.util.Log; + /** - * This class is used to process integer arrays containing Luma data and detects motion. + * This class is used to process integer arrays containing Luma data and detects + * motion. * * @author Justin Wetherell */ public class LumaMotionDetection implements IMotionDetection { - //private static final String TAG = "LumaMotionDetection"; - - //Specific settings - private static final int mPixelThreshold = 50; //Difference in luma value - private static final int mThreshold = 10000; //Number of different pixels - - private static int[] mPrevious = null; - private static int mPreviousWidth = 0; - private static int mPreviousHeight = 0; - - /** - * {@inheritDoc} - */ - @Override - public int[] getPrevious() { - return ((mPrevious!=null)?mPrevious.clone():null); - } - - protected static boolean isDifferent(int[] first, int width, int height) { - if (first==null) throw new NullPointerException(); - - if (mPrevious==null) return false; - if (first.length != mPrevious.length) return true; - if (mPreviousWidth != width || mPreviousHeight != height) return true; - - int totDifferentPixels = 0; - for (int i = 0, ij=0; i < height; i++) { - for (int j = 0; j < width; j++, ij++) { - int pix = (0xff & ((int)first[ij])); - int otherPix = (0xff & ((int)mPrevious[ij])); - - //Catch any pixels that are out of range - if (pix < 0) pix = 0; - if (pix > 255) pix = 255; - if (otherPix < 0) otherPix = 0; - if (otherPix > 255) otherPix = 255; - - if (Math.abs(pix - otherPix) >= mPixelThreshold) { - totDifferentPixels++; - //Paint different pixel red - first[ij] = Color.RED; - } - } - } - if (totDifferentPixels <= 0) totDifferentPixels = 1; - boolean different = totDifferentPixels > mThreshold; -/* - int size = height * width; - int percent = 100/(size/totDifferentPixels); - String output = "Number of different pixels: " + totDifferentPixels + "> " + percent + "%"; - if (different) { - Log.e(TAG, output); - } else { - Log.d(TAG, output); - } -*/ - return different; - } - - /** - * Detect motion using comparing luma values. - * {@inheritDoc} - */ - @Override - public boolean detect(int[] luma, int width, int height) { - if (luma==null) throw new NullPointerException(); - - int[] original = luma.clone(); - - // Create the "mPrevious" picture, the one that will be used to check the next frame against. - if(mPrevious == null) { - mPrevious = original; - mPreviousWidth = width; - mPreviousHeight = height; - //Log.i(TAG, "Creating background image"); - return false; - } - - //long bDetection = System.currentTimeMillis(); - boolean motionDetected = isDifferent(luma, width, height); - //long aDetection = System.currentTimeMillis(); - //Log.d(TAG, "Detection "+(aDetection-bDetection)); - - // Replace the current image with the previous. - mPrevious = original; - mPreviousWidth = width; - mPreviousHeight = height; - - return motionDetected; - } + + // private static final String TAG = "LumaMotionDetection"; + + // Specific settings + private static final int mPixelThreshold = 50; // Difference in luma value + private static final int mThreshold = 10000; // Number of different pixels + + private static int[] mPrevious = null; + private static int mPreviousWidth = 0; + private static int mPreviousHeight = 0; + + /** + * {@inheritDoc} + */ + @Override + public int[] getPrevious() { + return ((mPrevious != null) ? mPrevious.clone() : null); + } + + protected static boolean isDifferent(int[] first, int width, int height) { + if (first == null) throw new NullPointerException(); + + if (mPrevious == null) return false; + if (first.length != mPrevious.length) return true; + if (mPreviousWidth != width || mPreviousHeight != height) return true; + + int totDifferentPixels = 0; + for (int i = 0, ij = 0; i < height; i++) { + for (int j = 0; j < width; j++, ij++) { + int pix = (0xff & ((int) first[ij])); + int otherPix = (0xff & ((int) mPrevious[ij])); + + // Catch any pixels that are out of range + if (pix < 0) pix = 0; + if (pix > 255) pix = 255; + if (otherPix < 0) otherPix = 0; + if (otherPix > 255) otherPix = 255; + + if (Math.abs(pix - otherPix) >= mPixelThreshold) { + totDifferentPixels++; + // Paint different pixel red + first[ij] = Color.RED; + } + } + } + if (totDifferentPixels <= 0) totDifferentPixels = 1; + boolean different = totDifferentPixels > mThreshold; + /* + * int size = height * width; int percent = + * 100/(size/totDifferentPixels); String output = + * "Number of different pixels: " + totDifferentPixels + "> " + percent + * + "%"; if (different) { Log.e(TAG, output); } else { Log.d(TAG, + * output); } + */ + return different; + } + + /** + * Detect motion using comparing luma values. {@inheritDoc} + */ + @Override + public boolean detect(int[] luma, int width, int height) { + if (luma == null) throw new NullPointerException(); + + int[] original = luma.clone(); + + // Create the "mPrevious" picture, the one that will be used to check + // the next frame against. + if (mPrevious == null) { + mPrevious = original; + mPreviousWidth = width; + mPreviousHeight = height; + // Log.i(TAG, "Creating background image"); + return false; + } + + // long bDetection = System.currentTimeMillis(); + boolean motionDetected = isDifferent(luma, width, height); + // long aDetection = System.currentTimeMillis(); + // Log.d(TAG, "Detection "+(aDetection-bDetection)); + + // Replace the current image with the previous. + mPrevious = original; + mPreviousWidth = width; + mPreviousHeight = height; + + return motionDetected; + } } diff --git a/src/com/jwetherell/motion_detection/detection/RgbMotionDetection.java b/src/com/jwetherell/motion_detection/detection/RgbMotionDetection.java index 5fce271..3a29212 100644 --- a/src/com/jwetherell/motion_detection/detection/RgbMotionDetection.java +++ b/src/com/jwetherell/motion_detection/detection/RgbMotionDetection.java @@ -1,103 +1,104 @@ package com.jwetherell.motion_detection.detection; import android.graphics.Color; -//import android.util.Log; +//import android.util.Log; + /** - * This class is used to process integer arrays containing RGB data and detects motion. + * This class is used to process integer arrays containing RGB data and detects + * motion. * * @author Justin Wetherell */ public class RgbMotionDetection implements IMotionDetection { - //private static final String TAG = "RgbMotionDetection"; - - //Specific settings - private static final int mPixelThreshold = 50; //Difference in pixel (RGB) - private static final int mThreshold = 10000; //Number of different pixels (RGB) - - private static int[] mPrevious = null; - private static int mPreviousWidth = 0; - private static int mPreviousHeight = 0; - - /** - * {@inheritDoc} - */ - @Override - public int[] getPrevious() { - return ((mPrevious!=null)?mPrevious.clone():null); - } - - protected static boolean isDifferent(int[] first, int width, int height) { - if (first==null) throw new NullPointerException(); - - if (mPrevious==null) return false; - if (first.length != mPrevious.length) return true; - if (mPreviousWidth != width || mPreviousHeight != height) return true; - - int totDifferentPixels = 0; - for (int i = 0, ij=0; i < height; i++) { - for (int j = 0; j < width; j++, ij++) { - int pix = (0xff & ((int)first[ij])); - int otherPix = (0xff & ((int)mPrevious[ij])); - - //Catch any pixels that are out of range - if (pix < 0) pix = 0; - if (pix > 255) pix = 255; - if (otherPix < 0) otherPix = 0; - if (otherPix > 255) otherPix = 255; - - if (Math.abs(pix - otherPix) >= mPixelThreshold) { - totDifferentPixels++; - //Paint different pixel red - first[ij] = Color.RED; - } - } - } - if (totDifferentPixels <= 0) totDifferentPixels = 1; - boolean different = totDifferentPixels > mThreshold; -/* - int size = height * width; - int percent = 100/(size/totDifferentPixels); - String output = "Number of different pixels: " + totDifferentPixels + "> " + percent + "%"; - if (different) { - Log.e(TAG, output); - } else { - Log.d(TAG, output); - } -*/ - return different; - } - - /** - * Detect motion comparing RGB pixel values. - * {@inheritDoc} - */ - @Override - public boolean detect(int[] rgb, int width, int height) { - if (rgb==null) throw new NullPointerException(); - - int[] original = rgb.clone(); - - // Create the "mPrevious" picture, the one that will be used to check the next frame against. - if(mPrevious == null) { - mPrevious = original; - mPreviousWidth = width; - mPreviousHeight = height; - //Log.i(TAG, "Creating background image"); - return false; - } - - //long bDetection = System.currentTimeMillis(); - boolean motionDetected = isDifferent(rgb, width, height); - //long aDetection = System.currentTimeMillis(); - //Log.d(TAG, "Detection "+(aDetection-bDetection)); - - // Replace the current image with the previous. - mPrevious = original; - mPreviousWidth = width; - mPreviousHeight = height; - - return motionDetected; - } + + // private static final String TAG = "RgbMotionDetection"; + + // Specific settings + private static final int mPixelThreshold = 50; // Difference in pixel (RGB) + private static final int mThreshold = 10000; // Number of different pixels + // (RGB) + + private static int[] mPrevious = null; + private static int mPreviousWidth = 0; + private static int mPreviousHeight = 0; + + /** + * {@inheritDoc} + */ + @Override + public int[] getPrevious() { + return ((mPrevious != null) ? mPrevious.clone() : null); + } + + protected static boolean isDifferent(int[] first, int width, int height) { + if (first == null) throw new NullPointerException(); + + if (mPrevious == null) return false; + if (first.length != mPrevious.length) return true; + if (mPreviousWidth != width || mPreviousHeight != height) return true; + + int totDifferentPixels = 0; + for (int i = 0, ij = 0; i < height; i++) { + for (int j = 0; j < width; j++, ij++) { + int pix = (0xff & ((int) first[ij])); + int otherPix = (0xff & ((int) mPrevious[ij])); + + // Catch any pixels that are out of range + if (pix < 0) pix = 0; + if (pix > 255) pix = 255; + if (otherPix < 0) otherPix = 0; + if (otherPix > 255) otherPix = 255; + + if (Math.abs(pix - otherPix) >= mPixelThreshold) { + totDifferentPixels++; + // Paint different pixel red + first[ij] = Color.RED; + } + } + } + if (totDifferentPixels <= 0) totDifferentPixels = 1; + boolean different = totDifferentPixels > mThreshold; + /* + * int size = height * width; int percent = + * 100/(size/totDifferentPixels); String output = + * "Number of different pixels: " + totDifferentPixels + "> " + percent + * + "%"; if (different) { Log.e(TAG, output); } else { Log.d(TAG, + * output); } + */ + return different; + } + + /** + * Detect motion comparing RGB pixel values. {@inheritDoc} + */ + @Override + public boolean detect(int[] rgb, int width, int height) { + if (rgb == null) throw new NullPointerException(); + + int[] original = rgb.clone(); + + // Create the "mPrevious" picture, the one that will be used to check + // the next frame against. + if (mPrevious == null) { + mPrevious = original; + mPreviousWidth = width; + mPreviousHeight = height; + // Log.i(TAG, "Creating background image"); + return false; + } + + // long bDetection = System.currentTimeMillis(); + boolean motionDetected = isDifferent(rgb, width, height); + // long aDetection = System.currentTimeMillis(); + // Log.d(TAG, "Detection "+(aDetection-bDetection)); + + // Replace the current image with the previous. + mPrevious = original; + mPreviousWidth = width; + mPreviousHeight = height; + + return motionDetected; + } } diff --git a/src/com/jwetherell/motion_detection/detection/State.java b/src/com/jwetherell/motion_detection/detection/State.java index 69b9156..622b791 100644 --- a/src/com/jwetherell/motion_detection/detection/State.java +++ b/src/com/jwetherell/motion_detection/detection/State.java @@ -1,76 +1,79 @@ package com.jwetherell.motion_detection.detection; - /** - * This class is adapted from the web site below. It creates a state object based on the brightness of a RGB image - * represented by an integer array. + * This class is adapted from the web site below. It creates a state object + * based on the brightness of a RGB image represented by an integer array. * http://mindmeat.blogspot.com/2008/11/java-image-comparison.html * * @author Pat Cullen * @author Justin Wetherell */ public class State { - private int[] map = null; - private int width; - private int height; - private int average; - public State(int[] data, int width, int height) { - if (data==null) throw new NullPointerException(); - - this.map = data.clone(); - this.width = width; - this.height = height; + private int[] map = null; + private int width; + private int height; + private int average; + + public State(int[] data, int width, int height) { + if (data == null) throw new NullPointerException(); + + this.map = data.clone(); + this.width = width; + this.height = height; + + // build map and stats + this.average = 0; + for (int y = 0, xy = 0; y < this.height; y++) { + for (int x = 0; x < this.width; x++, xy++) { + this.average += data[xy]; + } + } + this.average = (this.average / (this.width * this.height)); + } - // build map and stats - this.average = 0; - for (int y = 0, xy=0; y < this.height; y++) { - for (int x = 0; x < this.width; x++, xy++) { - this.average += data[xy]; - } - } - this.average = (this.average / (this.width * this.height)); - } + /** + * Get Map of the State. + * + * @return integer array of the State. + */ + public int[] getMap() { + return map; + } - /** - * Get Map of the State. - * @return integer array of the State. - */ - public int[] getMap() { - return map; - } + /** + * Get the width of the State. + * + * @return integer representing the width of the state. + */ + public int getWidth() { + return width; + } - /** - * Get the width of the State. - * @return integer representing the width of the state. - */ - public int getWidth() { - return width; - } + /** + * Get the height of the State. + * + * @return integer representing the height of the state. + */ + public int getHeight() { + return height; + } - /** - * Get the height of the State. - * @return integer representing the height of the state. - */ - public int getHeight() { - return height; - } - - /** - * {@inheritDoc} - */ - @Override - public String toString() { - StringBuilder output = new StringBuilder(); - output.append("h="+height+" w="+width+"\n"); - for (int y = 0, xy = 0; y < height; y++) { - output.append('|'); - for (int x = 0;x < width; x++, xy++) { - output.append(map[xy]); - output.append('|'); - } - output.append("\n"); - } - return output.toString(); - } + /** + * {@inheritDoc} + */ + @Override + public String toString() { + StringBuilder output = new StringBuilder(); + output.append("h=" + height + " w=" + width + "\n"); + for (int y = 0, xy = 0; y < height; y++) { + output.append('|'); + for (int x = 0; x < width; x++, xy++) { + output.append(map[xy]); + output.append('|'); + } + output.append("\n"); + } + return output.toString(); + } } diff --git a/src/com/jwetherell/motion_detection/image/ImageProcessing.java b/src/com/jwetherell/motion_detection/image/ImageProcessing.java index 16d6bcb..7c375d5 100644 --- a/src/com/jwetherell/motion_detection/image/ImageProcessing.java +++ b/src/com/jwetherell/motion_detection/image/ImageProcessing.java @@ -13,221 +13,254 @@ * @author Justin Wetherell */ public abstract class ImageProcessing { - public static final int A = 0; - public static final int R = 1; - public static final int G = 2; - public static final int B = 3; - - public static final int H = 0; - public static final int S = 1; - public static final int L = 2; - - private ImageProcessing() { } - - /** - * Get RGB values from pixel. - * @param pixel Integer representation of a pixel. - * @return float array of a,r,g,b values. - */ - public static float[] getARGB(int pixel) { - int a = (pixel >> 24) & 0xff; - int r = (pixel >> 16) & 0xff; - int g = (pixel >> 8) & 0xff; - int b = (pixel) & 0xff; - return (new float[]{a,r,g,b}); - } - - /** - * Get HSL (Hue, Saturation, Luma) from RGB. - * Note1: H is 0-360 (degrees) - * Note2: S and L are 0-100 (percent) - * @param r Red value. - * @param g Green value. - * @param b Blue value. - * @return Integer array representing an HSL pixel. - */ - public static int[] convertToHSL(int r, int g, int b) { + + public static final int A = 0; + public static final int R = 1; + public static final int G = 2; + public static final int B = 3; + + public static final int H = 0; + public static final int S = 1; + public static final int L = 2; + + private ImageProcessing() { + } + + /** + * Get RGB values from pixel. + * + * @param pixel + * Integer representation of a pixel. + * @return float array of a,r,g,b values. + */ + public static float[] getARGB(int pixel) { + int a = (pixel >> 24) & 0xff; + int r = (pixel >> 16) & 0xff; + int g = (pixel >> 8) & 0xff; + int b = (pixel) & 0xff; + return (new float[] { a, r, g, b }); + } + + /** + * Get HSL (Hue, Saturation, Luma) from RGB. Note1: H is 0-360 (degrees) + * Note2: S and L are 0-100 (percent) + * + * @param r + * Red value. + * @param g + * Green value. + * @param b + * Blue value. + * @return Integer array representing an HSL pixel. + */ + public static int[] convertToHSL(int r, int g, int b) { float red = r / 255; float green = g / 255; float blue = b / 255; - - float minComponent = Math.min(red, Math.min(green, blue)); + + float minComponent = Math.min(red, Math.min(green, blue)); float maxComponent = Math.max(red, Math.max(green, blue)); float range = maxComponent - minComponent; - float h=0,s=0,l=0; - + float h = 0, s = 0, l = 0; + l = (maxComponent + minComponent) / 2; - if(range == 0) { // Monochrome image - h = s = 0; + if (range == 0) { // Monochrome image + h = s = 0; } else { - s = (l > 0.5) ? - range / (2 - range) - : - range / (maxComponent + minComponent); - - if(red == maxComponent) { - h = (blue - green) / range; - } else if(green == maxComponent) { - h = 2 + (blue - red) / range; - } else if(blue == maxComponent) { - h = 4 +(red - green) / range; + s = (l > 0.5) ? range / (2 - range) : range / (maxComponent + minComponent); + + if (red == maxComponent) { + h = (blue - green) / range; + } else if (green == maxComponent) { + h = 2 + (blue - red) / range; + } else if (blue == maxComponent) { + h = 4 + (red - green) / range; } } - - //convert to 0-360 (degrees) + + // convert to 0-360 (degrees) h *= 60; - if (h<0) h += 360; - - //convert to 0-100 (percent) + if (h < 0) h += 360; + + // convert to 0-100 (percent) s *= 100; l *= 100; - - //Since they were converted from float to int - return (new int[]{(int)h,(int)s,(int)l}); - } - - /** - * Decode a YUV420SP image to Luma. - * @param yuv420sp Byte array representing a YUV420SP image. - * @param width Width of the image. - * @param height Height of the image. - * @return Integer array representing the Luma image. - * @throws NullPointerException if yuv420sp byte array is NULL. - */ - public static int[] decodeYUV420SPtoLuma(byte[] yuv420sp, int width, int height) { - if (yuv420sp==null) throw new NullPointerException(); - - final int frameSize = width * height; - int[] hsl = new int[frameSize]; - - for (int j = 0, yp = 0; j < height; j++) { - for (int i = 0; i < width; i++, yp++) { - int y = (0xff & ((int) yuv420sp[yp])) - 16; - if (y < 0) y = 0; - hsl[yp] = y; - } - } - return hsl; - } - - /** - * Decode a YUV420SP image to RGB. - * @param yuv420sp Byte array representing a YUV420SP image. - * @param width Width of the image. - * @param height Height of the image. - * @return Integer array representing the RGB image. - * @throws NullPointerException if yuv420sp byte array is NULL. - */ - public static int[] decodeYUV420SPtoRGB(byte[] yuv420sp, int width, int height) { - if (yuv420sp==null) throw new NullPointerException(); - - final int frameSize = width * height; - int[] rgb = new int[frameSize]; - - 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; - - rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff); - } - } - return rgb; - } - - /** - * Convert an RGB image into a Bitmap. - * @param rgb Integer array representing an RGB image. - * @param width Width of the image. - * @param height Height of the image. - * @return Bitmap of the RGB image. - * @throws NullPointerException if RGB integer array is NULL. - */ - public static Bitmap rgbToBitmap(int[] rgb, int width, int height) { - if (rgb==null) throw new NullPointerException(); - - Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); - bitmap.setPixels(rgb, 0, width, 0, 0, width, height); - return bitmap; - } - - /** - * Convert an Luma image into Greyscale. - * @param lum Integer array representing an Luma image. - * @param width Width of the image. - * @param height Height of the image. - * @return Bitmap of the Luma image. - * @throws NullPointerException if RGB integer array is NULL. - */ - public static Bitmap lumaToGreyscale(int[] lum, int width, int height) { - if (lum==null) throw new NullPointerException(); - - Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); - for(int y=0, xy=0; y> 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; + + rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff); + } + } + return rgb; + } + + /** + * Convert an RGB image into a Bitmap. + * + * @param rgb + * Integer array representing an RGB image. + * @param width + * Width of the image. + * @param height + * Height of the image. + * @return Bitmap of the RGB image. + * @throws NullPointerException + * if RGB integer array is NULL. + */ + public static Bitmap rgbToBitmap(int[] rgb, int width, int height) { + if (rgb == null) throw new NullPointerException(); + + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); + bitmap.setPixels(rgb, 0, width, 0, 0, width, height); + return bitmap; + } + + /** + * Convert an Luma image into Greyscale. + * + * @param lum + * Integer array representing an Luma image. + * @param width + * Width of the image. + * @param height + * Height of the image. + * @return Bitmap of the Luma image. + * @throws NullPointerException + * if RGB integer array is NULL. + */ + public static Bitmap lumaToGreyscale(int[] lum, int width, int height) { + if (lum == null) throw new NullPointerException(); + + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); + for (int y = 0, xy = 0; y < bitmap.getHeight(); y++) { + for (int x = 0; x < bitmap.getWidth(); x++, xy++) { + int luma = lum[xy]; + bitmap.setPixel(x, y, Color.argb(1, luma, luma, luma)); + } + } + return bitmap; + } + + /** + * Rotate the given Bitmap by the given degrees. + * + * @param bmp + * Bitmap to rotate. + * @param degrees + * Degrees to rotate. + * @return Bitmap which was rotated. + */ + public static Bitmap rotate(Bitmap bmp, int degrees) { + if (bmp == null) throw new NullPointerException(); + + // getting scales of the image + int width = bmp.getWidth(); + int height = bmp.getHeight(); + + // Creating a Matrix and rotating it to 90 degrees + Matrix matrix = new Matrix(); + matrix.postRotate(degrees); + + // Getting the rotated Bitmap Bitmap rotatedBmp = Bitmap.createBitmap(bmp, 0, 0, width, height, matrix, true); ByteArrayOutputStream stream = new ByteArrayOutputStream(); - rotatedBmp.compress(Bitmap.CompressFormat.JPEG, 100, stream); + rotatedBmp.compress(Bitmap.CompressFormat.JPEG, 100, stream); return rotatedBmp; - } - - /** - * Rotate the given image in byte array format by the given degrees. - * @param data Bitmap to rotate in byte array form. - * @param degrees Degrees to rotate. - * @return Byte array format of an image which was rotated. - */ - public static byte[] rotate(byte[] data, int degrees) { - if (data==null) throw new NullPointerException(); - - //Convert the byte data into a Bitmap - Bitmap bmp = BitmapFactory.decodeByteArray(data, 0, data.length); - - //Getting the rotated Bitmap - Bitmap rotatedBmp = rotate(bmp,degrees); - - //Get the byte array from the Bitmap + } + + /** + * Rotate the given image in byte array format by the given degrees. + * + * @param data + * Bitmap to rotate in byte array form. + * @param degrees + * Degrees to rotate. + * @return Byte array format of an image which was rotated. + */ + public static byte[] rotate(byte[] data, int degrees) { + if (data == null) throw new NullPointerException(); + + // Convert the byte data into a Bitmap + Bitmap bmp = BitmapFactory.decodeByteArray(data, 0, data.length); + + // Getting the rotated Bitmap + Bitmap rotatedBmp = rotate(bmp, degrees); + + // Get the byte array from the Bitmap ByteArrayOutputStream stream = new ByteArrayOutputStream(); - rotatedBmp.compress(Bitmap.CompressFormat.JPEG, 100, stream); + rotatedBmp.compress(Bitmap.CompressFormat.JPEG, 100, stream); return stream.toByteArray(); - } + } }