diff --git a/app/build.gradle b/app/build.gradle index 4d7dcc8..083ba82 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -37,6 +37,14 @@ dependencies { androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + + // OpenCV (from: https://github.com/QuickBirdEng/opencv-android) + def opencv_version = "4.5.3.0" + implementation "com.quickbirdstudios:opencv:${opencv_version}" + + implementation "androidx.lifecycle:lifecycle-extensions:2.0.0" + + // Required for CameraX def camerax_version = "1.2.2" implementation "androidx.camera:camera-core:${camerax_version}" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1e65373..45d2346 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,6 +4,7 @@ + @@ -27,6 +28,7 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/example/ueberwachungssystem/Detection/AudioRecorder.java b/app/src/main/java/com/example/ueberwachungssystem/Detection/AudioRecorder.java new file mode 100644 index 0000000..aa16846 --- /dev/null +++ b/app/src/main/java/com/example/ueberwachungssystem/Detection/AudioRecorder.java @@ -0,0 +1,74 @@ +package com.example.ueberwachungssystem.Detection; + +import android.content.Context; +import android.media.MediaPlayer; +import android.media.MediaRecorder; +import android.widget.Toast; + +import java.io.File; +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +public class AudioRecorder { + private final Context context; + private MediaRecorder mediaRecorder = null; + private boolean isRecording = false; + private File outputDir; // Default: in app files directory + + + public AudioRecorder (Context context) { + this.context = context; + this.outputDir = context.getFilesDir(); + } + + public void startRecording() { + // Handle logic + if (outputDir == null) + return; + if (isRecording) + return; + isRecording = true; + + // Setup Audio Recorder for output Format: 3GP + mediaRecorder = new MediaRecorder(); + mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); + mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); + mediaRecorder.setOutputFile(outputDir + "/" + generateFileName() + ".3gp"); + mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); + try { + mediaRecorder.prepare(); + } catch (IOException e) { + e.printStackTrace(); + } + mediaRecorder.start(); + } + + public void stopRecording() { + if (mediaRecorder != null) { + mediaRecorder.stop(); + mediaRecorder.reset(); + mediaRecorder.release(); + mediaRecorder = null; + isRecording = false; + Toast.makeText(context, "audio recording saved", Toast.LENGTH_SHORT).show(); + } + } + + public boolean isRecording(){ + return isRecording; + } + + public void setOutputDir(File outputDir) { + this.outputDir = outputDir; + } + + private String generateFileName(){ + // Get the current timestamp + LocalDateTime currentTime = LocalDateTime.now(); + // Define the format for the timestamp + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"); + // Return the timestamp as a string + return currentTime.format(formatter); + } +} diff --git a/app/src/main/java/com/example/ueberwachungssystem/Detection/DetectionReport.java b/app/src/main/java/com/example/ueberwachungssystem/Detection/DetectionReport.java index 2dc8cba..96f9dbf 100644 --- a/app/src/main/java/com/example/ueberwachungssystem/Detection/DetectionReport.java +++ b/app/src/main/java/com/example/ueberwachungssystem/Detection/DetectionReport.java @@ -9,24 +9,26 @@ public class DetectionReport { public String timeStamp; public String detectionType; public float detectedValue; - public String detectorID; + public boolean detectionState; - public DetectionReport(String detectorID, String detectionType, float detectedAmplitude) { + public DetectionReport(boolean detectionState, String detectionType, float detectedAmplitude) { this.timeStamp = String.valueOf(Calendar.getInstance().getTime()); this.detectionType = detectionType; this.detectedValue = detectedAmplitude; - this.detectorID = detectorID; + this.detectionState = detectionState; + + //this.detectorID = detectorID; } /** Get Detection Report in String format */ public String toString() { + String state = "State: " + "[" + this.detectionState + "]"; String time = "Time: " + "[" + this.timeStamp + "]"; String type = "Type: " + "[" + this.detectionType + "]"; String value = "Value: " + "[" + this.detectedValue + "]"; - String id = "ID: " + "[" + this.detectorID + "]"; - return String.join("\t", time, type, value, id); + return String.join("\t", state, time, type, value); } /** Debug Report */ diff --git a/app/src/main/java/com/example/ueberwachungssystem/Detection/Detector.java b/app/src/main/java/com/example/ueberwachungssystem/Detection/Detector.java index 0b726a7..826878b 100644 --- a/app/src/main/java/com/example/ueberwachungssystem/Detection/Detector.java +++ b/app/src/main/java/com/example/ueberwachungssystem/Detection/Detector.java @@ -1,10 +1,19 @@ package com.example.ueberwachungssystem.Detection; +import android.os.CountDownTimer; + import androidx.annotation.NonNull; +import androidx.camera.core.ExperimentalGetImage; abstract public class Detector { private OnDetectionListener listener; + private boolean isDetecting = false; + private boolean extendViolation = false; + + // Countdown parameters + private final int COUNTDOWN_TIME = 10000; // milliseconds + private final int COUNTDOWN_POLLING_TIME = 100; // milliseconds /** Constructor - takes context of current activity */ public Detector() {} @@ -18,17 +27,46 @@ abstract public class Detector { this.listener = listener; } - /** Triggers onDetectionListener - call this to trigger violation/alarm */ - public void reportViolation(String detectorID, String detectionType, float amplitude) { + public void reportViolation(String detectionType, float amplitude) { if (listener != null) { - DetectionReport detectionReport = new DetectionReport(detectorID, detectionType, amplitude); - listener.onDetection(detectionReport); + if (!isDetecting) { + isDetecting = true; + DetectionReport detectionReport = new DetectionReport(true, detectionType, amplitude); + listener.onDetection(detectionReport); + startDetectionTimer(detectionType, amplitude); + } else { + extendViolation = true; + } } else { - throw new IllegalStateException("No listener set for violation reporting"); + isDetecting = false; + extendViolation = false; } } + private void startDetectionTimer(String detectionType, float amplitude) { + isDetecting = true; + new CountDownTimer((long) COUNTDOWN_TIME, COUNTDOWN_POLLING_TIME) { + @Override + public void onTick(long millisUntilFinished) { + if (extendViolation) { + extendViolation = false; + startDetectionTimer(detectionType, amplitude); + this.cancel(); + } + } + @Override + public void onFinish() { + isDetecting = false; + DetectionReport detectionReport = new DetectionReport(false, detectionType, amplitude); + listener.onDetection(detectionReport); + } + }.start(); + } + + public void extendViolation(){ + this.extendViolation = true; + } /** Starts Detection (abstract method: needs to be overridden in child class) */ public abstract void startDetection(); diff --git a/app/src/main/java/com/example/ueberwachungssystem/Detection/DetectorService.java b/app/src/main/java/com/example/ueberwachungssystem/Detection/DetectorService.java new file mode 100644 index 0000000..af2d440 --- /dev/null +++ b/app/src/main/java/com/example/ueberwachungssystem/Detection/DetectorService.java @@ -0,0 +1,160 @@ +package com.example.ueberwachungssystem.Detection; + +import android.content.Intent; +import android.os.Binder; +import android.os.IBinder; +import android.widget.ImageView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.camera.core.ExperimentalGetImage; +import androidx.lifecycle.LifecycleService; + +import java.io.File; + +@ExperimentalGetImage +public class DetectorService extends LifecycleService { + public ServiceBinder serviceBinder = new ServiceBinder(); + private DetectorService.OnDetectionListener listener; + private boolean isServiceRunning = false; + + VideoDetector videoDetector = null; + AudioRecorder audioRecorder = null; + + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (isServiceRunning) + return START_NOT_STICKY; + + + // Setup Service classes: + videoDetector = new VideoDetector(this); + videoDetector.setOnDetectionListener(new Detector.OnDetectionListener() { + @Override + public void onDetection(@NonNull DetectionReport detectionReport) { + passToServiceListener(detectionReport); + } + }); + + audioRecorder = new AudioRecorder(this); + + + + isServiceRunning = true; + return super.onStartCommand(intent, flags, startId); + } + + @Override + public void onDestroy() { + super.onDestroy(); + isServiceRunning = false; + } + + /** Service methods */ + public class ServiceBinder extends Binder { + public DetectorService getBoundService() { + // Return an instance of the TestService + return DetectorService.this; + } + } + @Nullable + @Override + public IBinder onBind(Intent intent) { + super.onBind(intent); + return serviceBinder; + } + + + /** Video Detection */ + public void startVideoDetection() { + if(videoDetector != null) + videoDetector.startDetection(); + } + public void stopVideoDetection() { + if(videoDetector != null) + videoDetector.stopDetection(); + } + public boolean isVideoDetectionRunning() { + if(videoDetector != null) + return videoDetector.isDetecting(); + return false; + } + public void debugVideoProcessing(ImageView input, ImageView output) { + if(videoDetector != null) + videoDetector.debugProcessing(input, output); + } + + /** Audio Detection */ + public void startAudioDetection() { + + } + public void stopAudioDetection() { + + } + + /** Motion Detection */ + public void startMotionDetection() { + + } + public void stopMotionDetection() { + + } + + /** Video Recording */ + public void startVideoRecording() { + if(videoDetector != null) + videoDetector.startRecording(); + } + public void stopVideoRecording() { + if(videoDetector != null) + videoDetector.stopRecording(); + } + public boolean isVideoRecordingRunning() { + if(videoDetector != null) + return videoDetector.isRecording(); + return false; + } + public void setVideoRecordingDir(File outputDir) { + if (videoDetector != null) + videoDetector.setOutputDir(outputDir); + } + + /** Audio Recording */ + public void startAudioRecording() { + if(audioRecorder != null) + audioRecorder.startRecording(); + } + public void stopAudioRecording() { + if(audioRecorder != null) + audioRecorder.stopRecording(); + } + public boolean isAudioRecordingRunning() { + if(videoDetector != null) + return audioRecorder.isRecording(); + return false; + } + public void setAudioRecordingDir(File outputDir) { + if (audioRecorder != null) + audioRecorder.setOutputDir(outputDir); + } + + + + + /** pass Detection Report to Service Detection Listener and trigger it */ + public void passToServiceListener(DetectionReport detectionReport) { + if (listener != null) { + listener.onDetection(detectionReport); + } + } + + + /** On Detection Listener - runs when violation is reported */ + public interface OnDetectionListener { + void onDetection(@NonNull DetectionReport detectionReport); + } + public void setOnDetectionListener(@NonNull DetectorService.OnDetectionListener listener) { + this.listener = listener; + } +} diff --git a/app/src/main/java/com/example/ueberwachungssystem/Detection/OpenCVHelper.java b/app/src/main/java/com/example/ueberwachungssystem/Detection/OpenCVHelper.java new file mode 100644 index 0000000..7a6cb28 --- /dev/null +++ b/app/src/main/java/com/example/ueberwachungssystem/Detection/OpenCVHelper.java @@ -0,0 +1,109 @@ +package com.example.ueberwachungssystem.Detection; + +import android.graphics.Bitmap; +import android.media.Image; +import android.widget.ImageView; + +import androidx.annotation.NonNull; +import androidx.camera.core.ExperimentalGetImage; +import androidx.camera.core.ImageProxy; + +import org.opencv.android.Utils; +import org.opencv.core.Core; +import org.opencv.core.CvType; +import org.opencv.core.Mat; +import org.opencv.core.MatOfPoint; +import org.opencv.core.Scalar; +import org.opencv.core.Size; +import org.opencv.imgproc.Imgproc; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + + +@ExperimentalGetImage +public class OpenCVHelper { + + /** OpenCV helper methods **/ + public static Mat addGaussianBlur(Mat inputMat, Size kernelSize){ + Mat outputMat = new Mat(); + Imgproc.GaussianBlur(inputMat, outputMat, kernelSize, 0); + return outputMat; + } + + public static Mat addBlur(Mat inputMat, Size kernelSize){ + Mat outputMat = new Mat(); + Imgproc.blur(inputMat, outputMat, kernelSize); + return outputMat; + } + + public static Mat extractYChannel(@NonNull ImageProxy imgProxy) { + Image img = imgProxy.getImage(); + + assert img != null; + ByteBuffer yBuffer = img.getPlanes()[0].getBuffer(); + byte[] yData = new byte[yBuffer.remaining()]; + yBuffer.get(yData); + + Mat yMat = new Mat(img.getHeight(), img.getWidth(), CvType.CV_8UC1); + yMat.put(0, 0, yData); + + return yMat; + } + + public static Mat thresholdPixels(Mat inputMat, Mat previousImage, int threshold){ + Mat diffImage = new Mat(); + Core.absdiff(inputMat, previousImage, diffImage); + Mat binaryMat = new Mat(); + Imgproc.threshold(diffImage, binaryMat, threshold, 255, Imgproc.THRESH_BINARY); + return binaryMat; + } + + + public static Mat thresholdContourArea(Mat inputMat, float areaThreshold){ + List contours = new ArrayList<>(); + Mat hierarchy = new Mat(); + Imgproc.findContours(inputMat, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE); + + Mat outputMat = new Mat(inputMat.size(), inputMat.type(), new Scalar(0)); + // Iterate over the contours and draw only the larger contours on the outputMat + for (MatOfPoint contour : contours) { + double contourArea = Imgproc.contourArea(contour); + if (contourArea > areaThreshold) { + Imgproc.drawContours(outputMat, Collections.singletonList(contour), 0, new Scalar(255), -1); + } + } + // Apply the outputMat as a mask to the dilatedImage + Mat maskedImage = new Mat(); + inputMat.copyTo(maskedImage, outputMat); + return outputMat; + } + + public static Mat dilateBinaryMat(Mat inputMat, Size kernelSize){ + Mat dilatedMat = new Mat(); + Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, kernelSize); + Imgproc.dilate(inputMat, dilatedMat, kernel); + return dilatedMat; + } + + public static int countNonZeroPixels(Mat inputImage) { + if (inputImage != null) + return Core.countNonZero(inputImage); + else + return 0; + } + + + public static void debugMat(Mat mat, ImageView imageView) { + if (imageView == null || mat == null) + return; + + Bitmap bitmap = Bitmap.createBitmap(mat.cols(), mat.rows(), Bitmap.Config.ARGB_8888); + Utils.matToBitmap(mat, bitmap); + + // Display the bitmap in an ImageView + imageView.setImageBitmap(bitmap); + } +} diff --git a/app/src/main/java/com/example/ueberwachungssystem/Detection/VideoDetector.java b/app/src/main/java/com/example/ueberwachungssystem/Detection/VideoDetector.java index bfec02c..9018ddf 100644 --- a/app/src/main/java/com/example/ueberwachungssystem/Detection/VideoDetector.java +++ b/app/src/main/java/com/example/ueberwachungssystem/Detection/VideoDetector.java @@ -1,196 +1,327 @@ package com.example.ueberwachungssystem.Detection; +import android.Manifest; +import android.annotation.SuppressLint; import android.content.Context; +import android.content.pm.PackageManager; import android.graphics.ImageFormat; import android.media.Image; -import android.util.Size; +import android.os.CountDownTimer; +import android.util.Log; +import android.view.Display; +import android.view.Surface; +import android.view.WindowManager; +import android.widget.ImageView; +import android.widget.Toast; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.camera.core.CameraSelector; import androidx.camera.core.ExperimentalGetImage; import androidx.camera.core.ImageAnalysis; +import androidx.camera.core.ImageProxy; import androidx.camera.core.Preview; +import androidx.camera.core.VideoCapture; import androidx.camera.lifecycle.ProcessCameraProvider; import androidx.camera.view.PreviewView; +import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.lifecycle.LifecycleOwner; import com.google.common.util.concurrent.ListenableFuture; -import java.nio.ByteBuffer; +import org.opencv.android.OpenCVLoader; +import org.opencv.core.Mat; +import org.opencv.core.Size; + +import java.io.File; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.concurrent.ExecutionException; /** * Video Detector inherits some methods from abstract Detector class (more info there) - * - * * USE FROM MAIN ACTIVITY: - * * VideoDetector vd = new VideoDetector(this); - * vd.setPreview(previewView); //THIS IS OPTIONAL! - * vd.setOnDetectionListener(new Detector.OnDetectionListener{...}); - * vd.startDetection(); - * vd.stopDetection - * * */ @ExperimentalGetImage public class VideoDetector extends Detector { - // Calling Activity private final Context context; + // Camera Provider private ProcessCameraProvider cameraProvider; - private Boolean isDetectionRunning = false; - // Preview Camera Image - private PreviewView previewView = null; - // Detect Violation - private static final float PIXEL_THRESHOLD = 30f; // Luminosity (brightness channel of YUV_420_888) - private static final float ALARM_THRESHOLD = 0.2f; // Percent of pixels changed - private ByteBuffer previousBuffer = null; + private ImageAnalysis imageAnalysis; + private VideoCapture videoCapture; + //private Preview preview; + + // Logic + private boolean isDetecting = false; + private boolean isRecording = false; + private boolean allowReportViolation = false; + + // Image Processing + private Mat previousImage = null; + + // Debugging + private ImageView inputImageView = null; + private ImageView outputImageView = null; + + // Recorder + private File outputDir; // Default: in app files directory + + + // Parameters + private static final float ALARM_THRESHOLD = 0f; // Percent of pixels changed + private static final float AREA_THRESHOLD = 10f; + private static final int DILATE_ITERATIONS = 2; + private static final float START_DELAY = 20000; // milliseconds + private static final android.util.Size IMAGE_RES = new android.util.Size(640, 480); - /** - * Constructor - * @param context: the context of calling activity (usually "this") - * */ + /** Constructor */ public VideoDetector(Context context) { super(); this.context = context; + this.imageAnalysis = setupImageAnalysis(); + this.videoCapture = setupVideoCapture(); + this.outputDir = context.getFilesDir(); + //this.preview = new Preview.Builder().build(); + } + + /** Get States */ + public boolean isDetecting() { + return isDetecting; + } + public boolean isRecording(){ + return isRecording; } - /** - * Starts the Video Detection - * */ + /** Starts the Video Detection */ @Override public void startDetection() { - if (isDetectionRunning) + // Check States + if (isDetecting) return; - // Request Camera Provider + // Configure Image Analysis + imageAnalysis = setupImageAnalysis(); + // Open CV startup check + if (!OpenCVLoader.initDebug()) { + Log.e("OpenCV", "Unable to load OpenCV!"); + return; + } else + Log.d("OpenCV", "OpenCV loaded Successfully!"); + // Get Process Camera Provider and start final ListenableFuture cameraProviderFuture = ProcessCameraProvider.getInstance(context); - //Check for Camera availability cameraProviderFuture.addListener(() -> { try { cameraProvider = cameraProviderFuture.get(); - bindAnalysis(cameraProvider); - isDetectionRunning = true; - previousBuffer = null; - } catch (ExecutionException | InterruptedException e) { - // No errors need to be handled for this Future. This should never be reached. - } - },ContextCompat.getMainExecutor(context)); + isDetecting = true; + bindCameraProvider(); + } catch (ExecutionException | InterruptedException e) {} + }, ContextCompat.getMainExecutor(context)); + // Disable Violation Calling for Setup Time + startViolationTimer(START_DELAY); } + /** Starts the Recorder */ + @SuppressLint("RestrictedApi") + public void startRecording() { + // Check States + if (isRecording){ + return; + } - /** - * Stops the Video Detection - * */ + videoCapture = setupVideoCapture(); + + final ListenableFuture cameraProviderFuture = ProcessCameraProvider.getInstance(context); + cameraProviderFuture.addListener(() -> { + try { + cameraProvider = cameraProviderFuture.get(); + isRecording = true; + bindCameraProvider(); + + File vidFile = new File(context.getFilesDir() + "/" + generateFileName() + ".mp4"); + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { + return; + } + videoCapture.startRecording( + new VideoCapture.OutputFileOptions.Builder(vidFile).build(), + context.getMainExecutor(), + new VideoCapture.OnVideoSavedCallback() { + @Override + public void onVideoSaved(@NonNull VideoCapture.OutputFileResults outputFileResults) { + isRecording = false; + Toast.makeText(context, "video recording saved", Toast.LENGTH_SHORT).show(); + } + @Override + public void onError(int videoCaptureError, @NonNull String message, @Nullable Throwable cause) { + isRecording = false; + Toast.makeText(context, "video recording failed", Toast.LENGTH_SHORT).show(); + } + } + ); + } catch (ExecutionException | InterruptedException ignored) {} + }, ContextCompat.getMainExecutor(context)); + } + + /** Stops the Video Detection */ @Override public void stopDetection() { - if (!isDetectionRunning) + if (!isDetecting || imageAnalysis == null) return; + cameraProvider.unbind(imageAnalysis); + isDetecting = false; + allowReportViolation = false; + } + + /** Stops the Recording */ + @SuppressLint("RestrictedApi") + public void stopRecording(){ + if(!isRecording) + return; + + videoCapture.stopRecording(); + cameraProvider.unbind(videoCapture); + isRecording = false; + } + + /** Bind Camera Provider */ + private void bindCameraProvider() { + // Specify which Camera to use + CameraSelector cameraSelector = new CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build(); cameraProvider.unbindAll(); - isDetectionRunning = false; + cameraProvider.bindToLifecycle((LifecycleOwner) context, cameraSelector, imageAnalysis, videoCapture); } - - /** - * Set PreviewView to show video feed while detecting - * this is optional and does not need to be called - * */ - public void setPreviewView(PreviewView previewView) { - this.previewView = previewView; - } - - - /** - * Binds the Luminosity Analyzer (configure and run Analysis) - * @param cameraProvider: CameraProvider of Context passed by Constructor - * */ - private void bindAnalysis(@NonNull ProcessCameraProvider cameraProvider) { + /** Setup Use Cases */ + private ImageAnalysis setupImageAnalysis() { // Configure and create Image Analysis ImageAnalysis.Builder builder = new ImageAnalysis.Builder(); - builder.setTargetResolution(new Size(640, 480)); + builder.setTargetResolution(IMAGE_RES); builder.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST); builder.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888); + builder.setTargetRotation(Surface.ROTATION_90); ImageAnalysis imageAnalysis = builder.build(); - // Set Analyzer imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(context), imageProxy -> { if (imageProxy.getFormat() == ImageFormat.YUV_420_888) { Image image = imageProxy.getImage(); assert image != null; - // Changed Pixel Detection - int pixelChanged = getChangedPixelCount(image); - int width = image.getWidth(); - int height = image.getHeight(); + // Violation Handling + Mat processed = processImage(imageProxy); - float percentPixelChanged = (float) 100f * pixelChanged / (width * height); + int n = OpenCVHelper.countNonZeroPixels(processed); + int pixelCount = image.getWidth() * image.getHeight(); + float percentChanged = (float) n / pixelCount; - if (percentPixelChanged > ALARM_THRESHOLD) - reportViolation("0", "Video", percentPixelChanged); + // Violation Condition + if (percentChanged * 100 > ALARM_THRESHOLD) { + if (allowReportViolation) + reportViolation("Video", percentChanged); + } } imageProxy.close(); }); - // Create Preview - Preview preview = new Preview.Builder().build(); - // Specify which Camera to use - CameraSelector cameraSelector = new CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build(); - // Update PreviewView if set - if (previewView != null) - preview.setSurfaceProvider(previewView.getSurfaceProvider()); + return imageAnalysis; + } - cameraProvider.bindToLifecycle((LifecycleOwner) context, cameraSelector, imageAnalysis, preview); + @SuppressLint("RestrictedApi") + private VideoCapture setupVideoCapture() { + int rotation = getDisplayRotation(); + return new VideoCapture.Builder() + .setTargetRotation(rotation) + .build(); + } + + /** Process Image to be used for Motion Detection */ + private Mat processImage(ImageProxy imageProxy){ + if (imageProxy == null) + return null; + // Image Transformation + Mat imageMat = OpenCVHelper.extractYChannel(imageProxy); + // Show Input Image + if (inputImageView != null) + OpenCVHelper.debugMat(imageMat, inputImageView); + // Preprocess Image + Mat preprocessed = imageMat; + preprocessed = OpenCVHelper.addGaussianBlur(preprocessed, new Size(21, 21)); + preprocessed = OpenCVHelper.addBlur(preprocessed, new Size(3, 3)); + // Set Previous Image + if (previousImage == null) { + previousImage = preprocessed; + return null; + } + // Process Image + Mat processed = preprocessed.clone(); + processed = OpenCVHelper.thresholdPixels(processed, previousImage, 25); + + for(int i = 0; i < DILATE_ITERATIONS; i++) + processed = OpenCVHelper.dilateBinaryMat(processed, new Size(3,3)); + + processed = OpenCVHelper.thresholdContourArea(processed, AREA_THRESHOLD); + // Output + previousImage = preprocessed.clone(); + // Show Output Image + if (outputImageView != null) + OpenCVHelper.debugMat(processed, outputImageView); + return processed; } + /** Debug input and result of processing */ + public void debugProcessing(@NonNull ImageView inputImageView, @NonNull ImageView outputImageView){ + this.inputImageView = inputImageView; + this.outputImageView = outputImageView; + } /** - * Calculate Amount of Pixels changed using a threshold - * @param image - * */ - private int getChangedPixelCount(Image image) { - Image.Plane[] planes = image.getPlanes(); - ByteBuffer buffer = planes[0].getBuffer(); - - if (previousBuffer == null) { - previousBuffer = ByteBuffer.allocate(buffer.capacity()); - buffer.rewind(); - previousBuffer.put(buffer); - previousBuffer.rewind(); - return 0; - } - - int width = image.getWidth(); - int height = image.getHeight(); - - int yRowStride = image.getPlanes()[0].getRowStride(); - int yPixelStride = image.getPlanes()[0].getPixelStride(); - - int changedPixelCount = 0; - - // Count changed pixels - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; x++) { - int index = (y * yRowStride) + (x * yPixelStride); - int luminosity = (buffer.get(index) & 0xff); - int previousLuminosity = (previousBuffer.get(index) & 0xff); - int diff = Math.abs(luminosity - previousLuminosity); - if (diff > PIXEL_THRESHOLD) - changedPixelCount++; - } - } - - // Reset and copy Byte Buffer - buffer.rewind(); - previousBuffer.rewind(); - previousBuffer.put(buffer); - - return changedPixelCount; + private void setPreviewView(@NonNull PreviewView previewView) { + // Create Preview + if (this.preview != null) + this.preview.setSurfaceProvider(previewView.getSurfaceProvider()); } -} + */ + + + /** Generate File Name */ + private String generateFileName(){ + // Get the current timestamp + LocalDateTime currentTime = LocalDateTime.now(); + // Define the format for the timestamp + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"); + // Return the timestamp as a string + return currentTime.format(formatter); + } + + + /** Get current Display Rotation */ + private int getDisplayRotation() { + WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + Display display = windowManager.getDefaultDisplay(); + return display.getRotation(); + } + + /** Start delay until Violation Report is allowed */ + private void startViolationTimer(float setupTime) { + new CountDownTimer((long) (START_DELAY), 100) { + @Override + public void onTick(long millisUntilFinished) { + } + @Override + public void onFinish() { + allowReportViolation = true; + } + }.start(); + } + + public void setOutputDir(File outputDir) { + this.outputDir = outputDir; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/ueberwachungssystem/PermissionHandler.java b/app/src/main/java/com/example/ueberwachungssystem/PermissionHandler.java new file mode 100644 index 0000000..2ffcc72 --- /dev/null +++ b/app/src/main/java/com/example/ueberwachungssystem/PermissionHandler.java @@ -0,0 +1,43 @@ +package com.example.ueberwachungssystem; + + +import android.app.Activity; +import android.content.Context; +import android.content.pm.PackageManager; +import android.widget.Toast; + +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + +public class PermissionHandler { + private final Context context; + private static final int PERMISSION_REQUEST_CODE = 23409; + private static final String[] permissions = new String[]{ + android.Manifest.permission.CAMERA, + android.Manifest.permission.RECORD_AUDIO + }; + + public PermissionHandler(Context context) { + this.context = context; + } + + public boolean hasPermissions() { + boolean permissionState = true; + for (String permission: permissions) { + permissionState = permissionState && ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED; + } + return permissionState; + } + + public void getPermissions() { + if (!hasPermissions()) + ActivityCompat.requestPermissions((Activity) context, permissions, PERMISSION_REQUEST_CODE); + } + + public void showPermissionToast() { + if (hasPermissions()) + Toast.makeText(context, "permissions available", Toast.LENGTH_SHORT).show(); + else + Toast.makeText(context, "permissions missing", Toast.LENGTH_SHORT).show(); + } +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 17eab17..c5abe55 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,18 +1,41 @@ - - + + + + + tools:srcCompat="@tools:sample/avatars" /> - \ No newline at end of file + + + + + \ No newline at end of file