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.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 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); * */ @ExperimentalGetImage public class VideoDetector extends Detector { // Calling Activity private final Context context; // Camera Provider private ProcessCameraProvider cameraProvider; 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 */ 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 */ @Override public void startDetection() { // Check States if (isDetecting) return; // 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); cameraProviderFuture.addListener(() -> { try { cameraProvider = cameraProviderFuture.get(); 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; } 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 (!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(); cameraProvider.bindToLifecycle((LifecycleOwner) context, cameraSelector, imageAnalysis, videoCapture); } /** Setup Use Cases */ private ImageAnalysis setupImageAnalysis() { // Configure and create Image Analysis ImageAnalysis.Builder builder = new ImageAnalysis.Builder(); 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; // Violation Handling Mat processed = processImage(imageProxy); int n = OpenCVHelper.countNonZeroPixels(processed); int pixelCount = image.getWidth() * image.getHeight(); float percentChanged = (float) n / pixelCount; // Violation Condition if (percentChanged * 100 > ALARM_THRESHOLD) { if (allowReportViolation) reportViolation("Video", percentChanged); } } imageProxy.close(); }); return imageAnalysis; } @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; } /** 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; } }