diff --git a/Neues Textdokument.txt b/Neues Textdokument.txt deleted file mode 100644 index e69de29..0000000 diff --git a/app/build.gradle b/app/build.gradle index 1c98d76..4d7dcc8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,11 +3,11 @@ plugins { } android { - namespace 'de.oklein.android.ueberwachungssystem' + namespace 'com.example.ueberwachungssystem' compileSdk 33 defaultConfig { - applicationId "de.oklein.android.ueberwachungssystem" + applicationId "com.example.ueberwachungssystem" minSdk 28 targetSdk 33 versionCode 1 @@ -31,10 +31,18 @@ android { dependencies { implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'com.google.android.material:material:1.9.0' + implementation 'com.google.android.material:material:1.8.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' - implementation "androidx.startup:startup-runtime:1.1.1" + + // Required for CameraX + def camerax_version = "1.2.2" + implementation "androidx.camera:camera-core:${camerax_version}" + implementation "androidx.camera:camera-camera2:${camerax_version}" + implementation "androidx.camera:camera-lifecycle:${camerax_version}" + implementation "androidx.camera:camera-video:${camerax_version}" + implementation "androidx.camera:camera-view:${camerax_version}" + implementation "androidx.camera:camera-extensions:${camerax_version}" } \ No newline at end of file diff --git a/app/src/androidTest/java/com/example/ueberwachungssystem/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/example/ueberwachungssystem/ExampleInstrumentedTest.java new file mode 100644 index 0000000..9b83717 --- /dev/null +++ b/app/src/androidTest/java/com/example/ueberwachungssystem/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.example.ueberwachungssystem; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.example.ueberwachungssystem", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/ueberwachungssystem/Detection/DetectionReport.java b/app/src/main/java/com/example/ueberwachungssystem/Detection/DetectionReport.java new file mode 100644 index 0000000..2dc8cba --- /dev/null +++ b/app/src/main/java/com/example/ueberwachungssystem/Detection/DetectionReport.java @@ -0,0 +1,36 @@ +package com.example.ueberwachungssystem.Detection; + +import android.util.Log; + +import java.util.Calendar; + +/** Detection Report Class */ +public class DetectionReport { + public String timeStamp; + public String detectionType; + public float detectedValue; + public String detectorID; + + public DetectionReport(String detectorID, String detectionType, float detectedAmplitude) { + this.timeStamp = String.valueOf(Calendar.getInstance().getTime()); + this.detectionType = detectionType; + this.detectedValue = detectedAmplitude; + this.detectorID = detectorID; + } + + + /** Get Detection Report in String format */ + public String toString() { + 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); + } + + /** Debug Report */ + public void log(String tag) { + Log.d(tag, this.toString()); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/ueberwachungssystem/Detection/Detector.java b/app/src/main/java/com/example/ueberwachungssystem/Detection/Detector.java new file mode 100644 index 0000000..0b726a7 --- /dev/null +++ b/app/src/main/java/com/example/ueberwachungssystem/Detection/Detector.java @@ -0,0 +1,38 @@ +package com.example.ueberwachungssystem.Detection; + +import androidx.annotation.NonNull; + + +abstract public class Detector { + private OnDetectionListener listener; + + /** Constructor - takes context of current activity */ + public Detector() {} + + + /** On Detection Listener - runs when violation is reported */ + public interface OnDetectionListener { + void onDetection(@NonNull DetectionReport detectionReport); + } + public void setOnDetectionListener(@NonNull OnDetectionListener listener) { + this.listener = listener; + } + + + /** Triggers onDetectionListener - call this to trigger violation/alarm */ + public void reportViolation(String detectorID, String detectionType, float amplitude) { + if (listener != null) { + DetectionReport detectionReport = new DetectionReport(detectorID, detectionType, amplitude); + listener.onDetection(detectionReport); + } else { + throw new IllegalStateException("No listener set for violation reporting"); + } + } + + + /** Starts Detection (abstract method: needs to be overridden in child class) */ + public abstract void startDetection(); + + /** Stops Detection (abstract method: needs to be overridden in child class) */ + public abstract void stopDetection(); +} \ No newline at end of file diff --git a/app/src/main/java/com/example/ueberwachungssystem/Detection/VideoDetector.java b/app/src/main/java/com/example/ueberwachungssystem/Detection/VideoDetector.java new file mode 100644 index 0000000..bfec02c --- /dev/null +++ b/app/src/main/java/com/example/ueberwachungssystem/Detection/VideoDetector.java @@ -0,0 +1,196 @@ +package com.example.ueberwachungssystem.Detection; + +import android.content.Context; +import android.graphics.ImageFormat; +import android.media.Image; +import android.util.Size; + +import androidx.annotation.NonNull; +import androidx.camera.core.CameraSelector; +import androidx.camera.core.ExperimentalGetImage; +import androidx.camera.core.ImageAnalysis; +import androidx.camera.core.Preview; +import androidx.camera.lifecycle.ProcessCameraProvider; +import androidx.camera.view.PreviewView; +import androidx.core.content.ContextCompat; +import androidx.lifecycle.LifecycleOwner; + +import com.google.common.util.concurrent.ListenableFuture; + +import java.nio.ByteBuffer; +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; + + + + /** + * Constructor + * @param context: the context of calling activity (usually "this") + * */ + public VideoDetector(Context context) { + super(); + this.context = context; + } + + + /** + * Starts the Video Detection + * */ + @Override + public void startDetection() { + if (isDetectionRunning) + return; + // Request Camera Provider + 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)); + } + + + /** + * Stops the Video Detection + * */ + @Override + public void stopDetection() { + if (!isDetectionRunning) + return; + cameraProvider.unbindAll(); + isDetectionRunning = false; + } + + + /** + * 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) { + // Configure and create Image Analysis + ImageAnalysis.Builder builder = new ImageAnalysis.Builder(); + builder.setTargetResolution(new Size(640, 480)); + builder.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST); + builder.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888); + 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(); + + float percentPixelChanged = (float) 100f * pixelChanged / (width * height); + + if (percentPixelChanged > ALARM_THRESHOLD) + reportViolation("0", "Video", percentPixelChanged); + } + 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()); + + cameraProvider.bindToLifecycle((LifecycleOwner) context, cameraSelector, imageAnalysis, preview); + } + + + + /** + * 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; + } +} diff --git a/app/src/main/java/com/example/ueberwachungssystem/MainActivity.java b/app/src/main/java/com/example/ueberwachungssystem/MainActivity.java new file mode 100644 index 0000000..f4fdae7 --- /dev/null +++ b/app/src/main/java/com/example/ueberwachungssystem/MainActivity.java @@ -0,0 +1,14 @@ +package com.example.ueberwachungssystem; + +import androidx.appcompat.app.AppCompatActivity; + +import android.os.Bundle; + +public class MainActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + } +} \ No newline at end of file diff --git a/app/src/test/java/com/example/ueberwachungssystem/ExampleUnitTest.java b/app/src/test/java/com/example/ueberwachungssystem/ExampleUnitTest.java new file mode 100644 index 0000000..fb36b56 --- /dev/null +++ b/app/src/test/java/com/example/ueberwachungssystem/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.example.ueberwachungssystem; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file