1c2e865bafca1392f653f7457b2d1805118b8157
[iot2.git] / benchmarks / IrrigationController / MotionDetection.java
1 package IrrigationController;
2 // IoT packages
3 import iotcode.interfaces.*;
4
5 // BoofCv packages
6 import boofcv.alg.background.BackgroundModelStationary;
7 import boofcv.factory.background.ConfigBackgroundGaussian;
8 import boofcv.factory.background.FactoryBackgroundModel;
9 import boofcv.gui.binary.VisualizeBinaryData;
10 import boofcv.gui.image.ImageGridPanel;
11 import boofcv.gui.image.ShowImages;
12 import boofcv.io.MediaManager;
13 import boofcv.io.UtilIO;
14 import boofcv.io.image.SimpleImageSequence;
15 import boofcv.io.image.ConvertBufferedImage;
16 import boofcv.io.wrapper.DefaultMediaManager;
17 import boofcv.struct.image.ImageBase;
18 import boofcv.struct.image.ImageFloat32;
19 import boofcv.struct.image.ImageType;
20 import boofcv.struct.image.ImageUInt8;
21 import boofcv.alg.filter.blur.BlurImageOps;
22
23
24 // Standard Java Packages
25 import java.awt.image.BufferedImage;
26 import java.util.Date;
27 import java.util.concurrent.locks.Lock;
28 import java.util.concurrent.locks.ReadWriteLock;
29 import java.util.concurrent.locks.ReentrantReadWriteLock;
30 import java.util.concurrent.atomic.AtomicBoolean;
31 import java.awt.image.ColorModel;
32 import java.awt.image.WritableRaster;
33 import java.util.List;
34 import java.util.ArrayList;
35 import java.io.ByteArrayInputStream;
36 import java.io.ByteArrayOutputStream;
37 import java.io.File;
38 import java.io.IOException;
39 import java.io.InputStream;
40 import javax.imageio.ImageIO;
41
42 // RMI Packages
43 import java.rmi.RemoteException;
44 import java.rmi.server.UnicastRemoteObject;
45
46 // Checker annotations
47 import iotchecker.qual.*;
48
49
50
51 /** Class MotionDetection to do motion detection using images
52  *
53  *
54  * @author      Ali Younis <ayounis @ uci.edu>
55  * @version     1.0
56  * @since       2016-03-21
57  */
58 class MotionDetection extends UnicastRemoteObject implements CameraCallback {
59
60         // Define Like variables
61         private static boolean DO_GRAPHICAL_USER_INTERFACE = false;
62
63         /*******************************************************************************************************************************************
64         **
65         **  Constants
66         **
67         *******************************************************************************************************************************************/
68         private final float MOTION_DETECTED_THRESHOLD_PERCENTAGE = 5;
69
70
71         /*******************************************************************************************************************************************
72         **
73         **  Variables
74         **
75         *******************************************************************************************************************************************/
76
77         // Timestamp buffer and locks needed for that safety on that buffer
78         // This is the buffer for post-detection algorithm use
79         private Date timestampOfLastMotion = null;
80         private ReadWriteLock timestampReadWriteLock = new ReentrantReadWriteLock();
81         private Lock timestampReadLock = timestampReadWriteLock.readLock();
82         private Lock timestampWriteLock = timestampReadWriteLock.writeLock();
83
84         // Flag for when new data is available and ready for processing
85         private AtomicBoolean newFrameAvailable = new AtomicBoolean(false);
86
87         // Flag for determining if motion has been detected and therefore
88         // the callbacks should be issued
89         private AtomicBoolean motionDetected = new AtomicBoolean(false);
90
91         // Image and timestamp buffers and  locks needed for that safety on those buffers
92         // Timestamp buffer for pre-detection algorithm use
93         private BufferedImage latestImage = null;
94         private Date possibleDate = null;
95         private ReadWriteLock imageReadWriteLock = new ReentrantReadWriteLock();
96         private Lock imageReadLock = imageReadWriteLock.readLock();
97         private Lock imageWriteLock = imageReadWriteLock.writeLock();
98
99         // List of objects wishing to receive callbacks from this class.
100         private List<@NonLocalRemote MotionDetectionCallback> callbackList = new ArrayList<@NonLocalRemote MotionDetectionCallback>();
101
102         // Variables to help with motion detection
103         private ConfigBackgroundGaussian configGaussian = null;
104         private BackgroundModelStationary backgroundDetector = null;
105         private ImageUInt8 segmented = null;
106         private ImageFloat32 newFrameFloat = null;
107
108         // counts the number of frames since a background image is added to algorithm
109         private int frameCounter = 0;
110
111
112
113         /*******************************************************************************************************************************************
114         **
115         **  Threads
116         **
117         *******************************************************************************************************************************************/
118         private Thread workThread = null;
119         private Thread callBackThread = null;
120
121         /*******************************************************************************************************************************************
122         **
123         **  GUI Stuff (Used Only for Testing)
124         **
125         *******************************************************************************************************************************************/
126         ImageGridPanel gui;
127
128
129
130         /** Constructor
131          *
132          *   @param _threshold       [float], Variable for gaussian background detector.
133          *   @param _learnSpeed      [float], Variable for gaussian background detector.
134          *   @param _initialVariance [float], Variable for gaussian background detector.
135          *   @param _minDifference   [float], Variable for gaussian background detector.
136          *
137          */
138         public MotionDetection(float _threshold, float _learnSpeed, float _initialVariance, float _minDifference) throws RemoteException {
139
140                 // Configure the Gaussian model used for background detection
141                 configGaussian = new ConfigBackgroundGaussian(_threshold, _learnSpeed);
142                 configGaussian.initialVariance = _initialVariance;
143                 configGaussian.minimumDifference = _minDifference;
144
145                 // setup the background detector
146                 ImageType imageType = ImageType.single(ImageFloat32.class);
147                 backgroundDetector = FactoryBackgroundModel.stationaryGaussian(configGaussian, imageType);
148
149                 // setup the gui if we are going to use it
150                 if (DO_GRAPHICAL_USER_INTERFACE) {
151
152                         // create an image grid for images to place on, tile fashion
153                         gui = new ImageGridPanel(1, 2);
154
155                         // make the window large so we dont have to manually resize with the mouse
156                         gui.setSize(1920, 1080);
157
158                         // Make the window visible and set the title
159                         ShowImages.showWindow(gui, "Static Scene: Background Segmentation", true);
160                 }
161
162                 // Launch the worker thread
163                 workThread = new Thread(new Runnable() {
164                         public void run() {
165
166                                 while (true) {
167                                         runMotionDetection();
168                                 }
169                         }
170                 });
171                 workThread.start();
172
173
174                 // Launch the callback thread
175                 callBackThread = new Thread(new Runnable() {
176                         public void run() {
177
178                                 while (true) {
179                                         doCallbacks();
180                                 }
181                         }
182                 });
183                 callBackThread.start();
184         }
185
186
187         /*******************************************************************************************************************************************
188         **
189         **  Public Methods
190         **
191         *******************************************************************************************************************************************/
192
193         /** Method to add a new frame to the motion detector.
194          *
195          *   @param _newFrame  [byte[]], Frame data of new frame.
196          *   @param _timestamp [Date]  , Timestamp of new frame.
197          *
198          *   @return [void] None.
199          */
200         public void addFrame(byte[]  _newFrame, Date _timestamp) {
201                 BufferedImage img = null;
202
203                 try {
204                         // Parse the byte array into a Buffered Image
205                         InputStream in = new ByteArrayInputStream(_newFrame);
206                         img = ImageIO.read(in);
207
208                 } catch (Exception e) {
209                         e.printStackTrace();
210                         return;
211                 }
212
213                 // Save the image and timestamp for use later
214                 imageWriteLock.lock();                                          // lock the image and timestamp buffers since multithread
215                 latestImage = img;                                                              // image into image buffer
216                 possibleDate = _timestamp;                              // timestamp into timestamp buffer
217                 imageWriteLock.unlock();                                        // Never forget to unlock
218
219                 // flag the worker thread that there is new data ready for processing
220                 newFrameAvailable.set(true);
221         }
222
223         /** Method to get the timestamp of the last time motion was detected
224          *
225          *   @return [Date] timestamp of last motion or null if no motion was ever detected.
226          */
227         public Date getTimestampOfLastMotion() {
228                 Date ret = null;
229
230                 // Be safe because multithread
231                 timestampReadLock.lock();
232
233                 // Checks if there was ever motion, if not then timestampOfLastMotion
234                 // will be null
235                 if (timestampOfLastMotion != null) {
236                         // Clone since we don't know what the other person is going to do
237                         // with the timestamp
238                         ret = (Date)timestampOfLastMotion.clone();
239                 }
240
241                 timestampReadLock.unlock();
242
243                 return ret;
244         }
245
246
247         /** Method to add a new frame to the motion detector from a camera
248          *
249          *   @param _camera  [Camera], Camera that has the new data.
250          *
251          *   @return [void] None.
252          */
253         //public void newCameraFrameAvailable(@NonLocalRemote Camera _camera) {
254         public void newCameraFrameAvailable(byte[] latestFrame, long timeStamp) {
255                 BufferedImage img = null;
256
257                 try {
258                         // Parse the byte array into a Buffered Image
259                         //InputStream in = new ByteArrayInputStream(_camera.getLatestFrame());
260                         InputStream in = new ByteArrayInputStream(latestFrame);
261                         img = ImageIO.read(in);
262
263                 } catch (RemoteException e) {
264                         e.printStackTrace();
265                         return;
266
267                 } catch (Exception e) {
268                         e.printStackTrace();
269                         return;
270
271                 }
272
273                 // Save the image and timestamp for use later
274                 imageWriteLock.lock();                  // lock the image and timestamp buffers since multithread
275                 latestImage = img;                                      // image into image buffer
276
277                 // timestamp from camera into timestamo buffer
278                 //try {
279                         //long dateLong = _camera.getTimestamp();
280                         long dateLong = timeStamp;
281                         possibleDate = new Date(dateLong);
282                 //} catch (RemoteException e) {
283                 //      e.printStackTrace();
284                 //}
285
286                 imageWriteLock.unlock();                // Never forget to unlock
287
288                 // flag the worker thread that there is new data ready for processing
289                 newFrameAvailable.set(true);
290         }
291
292         /** Method to register an object to recieve callbacks from this motion detector
293          *
294          *   @param _mdc  [MotionDetectionCallback], object to recieve callbacks.
295          *
296          *   @return [void] None.
297          */
298         public void registerCallback(@NonLocalRemote MotionDetectionCallback _mdc) {
299                 callbackList.add(_mdc);
300         }
301
302         /*******************************************************************************************************************************************
303         **
304         **  Helper Methods
305         **
306         *******************************************************************************************************************************************/
307
308         /** Method that constantly loops checking if new data is available.  If there is
309          *   new data, it is processed.
310          *   This method should be run on a separate thread.
311          *
312          *   @return [void] None.
313          */
314         private void runMotionDetection() {
315
316                 // check if there is a new frame availble, only runs detection if there is new data to save
317                 // computation time
318                 if (!newFrameAvailable.get()) {
319                         return;
320                 }
321
322                 // Lock since we are accessing the data buffers
323                 imageReadLock.lock();
324
325                 // processing data so now the buffered data is old
326                 newFrameAvailable.set(false);
327
328                 // copy from buffer to local for processing
329                 Date tmpDate = possibleDate;
330
331                 // Allocate space for the segmented image based on the first image we received
332                 // cannot pre-allocate this since we do not know what the size of the images is
333                 // before the first image arrives
334                 if (segmented == null) {
335                         segmented = new ImageUInt8(latestImage.getWidth(), latestImage.getHeight());
336                 }
337
338                 // copy from data buffers and convert into correct data type for BoofCv libraries
339                 newFrameFloat = ConvertBufferedImage.convertFrom(latestImage, newFrameFloat);
340
341                 // All done accessing the data buffers
342                 imageReadLock.unlock();
343
344                 // Run background detection
345                 backgroundDetector.segment(newFrameFloat, segmented);
346
347                 // Update the background baseline every 10 frames, helps the algorithm
348                 frameCounter++;
349                 if (frameCounter > 10) {
350                         backgroundDetector.updateBackground(newFrameFloat);
351                         frameCounter = 0;
352                 }
353
354                 // get the raw pixel data, gray-scale image
355                 byte[] frameData = segmented.getData();
356
357                 // count the number of pixels of the image that was deemed as "motion"
358                 double count = 0;
359                 double countMotion = 0;
360                 for (byte b : frameData) {
361                         count++;
362                         if (b > 0) {
363                                 countMotion++;
364                         }
365                 }
366
367                 // calculate the percentage of the image that was in motion
368                 double percentMotion = (countMotion / count) * 100.0;
369
370                 // Check if a high enough percentage of the image was in motion to say that there was motion in this frame of data
371                 if (percentMotion > MOTION_DETECTED_THRESHOLD_PERCENTAGE) {
372
373                         // Motion detected so save timestamp of this frame to another buffer
374                         timestampWriteLock.lock();
375                         timestampOfLastMotion = (Date)tmpDate.clone();                  // clone to a different buffer
376                         timestampWriteLock.unlock();
377
378                         System.out.println("Motion Detected (with percentage: " + Double.toString(percentMotion) + "%)");
379                 }
380
381                 // Do output to the screen if we are using gui mode
382                 if (DO_GRAPHICAL_USER_INTERFACE) {
383
384                         // change image data unto correct type for rendering
385                         BufferedImage visualized1 = new BufferedImage(segmented.width, segmented.height, BufferedImage.TYPE_INT_RGB);
386                         VisualizeBinaryData.renderBinary(segmented, false, visualized1);
387
388                         // change image data unto correct type for rendering
389                         BufferedImage visualized2 = null;
390                         visualized2 = ConvertBufferedImage.convertTo(newFrameFloat, visualized2, true);
391
392                         // place the images into the image grid
393                         gui.setImage(0, 1, visualized1);
394                         gui.setImage(0, 2, visualized2);
395
396                         // trigger rendering
397                         gui.repaint();
398                 }
399         }
400
401         /** Method that constantly loops checking if the callbacks should be issues and
402          *   issues the callbacks if they should be issues.
403          *   This method should be run on a separate thread.
404          *
405          *   @return [void] None.
406          */
407         private void doCallbacks() {
408
409                 // Keep looping forever for callback
410                 while (true) {
411
412                         // If motion detected
413                         if (motionDetected.compareAndSet(true, false)) {
414
415                                 // Motion was detected so issue callbacks to all objects that registered
416                                 // to receive callback from this class.
417                                 for (@NonLocalRemote MotionDetectionCallback c : callbackList) {
418                                         try {
419                                                 c.motionDetected(this);
420                                         } catch (RemoteException re) {
421                                         }
422                                 }
423
424                         } else {
425
426                                 // Sleep for 15 millisec to give time for new frame to arrive
427                                 try {
428                                         Thread.sleep(15);
429                                 } catch (InterruptedException ie) {
430                                 }
431                         }
432                 }
433         }
434 }
435