# Conflicts: # app/build.gradle # app/src/main/AndroidManifest.xml # app/src/main/res/layout/activity_main.xml # gradle/wrapper/gradle-wrapper.propertiesko^2
} | } | ||||
android { | android { | ||||
namespace 'de.oklein.android.ueberwachungssystem' | |||||
namespace 'com.example.ueberwachungssystem' | |||||
compileSdk 33 | compileSdk 33 | ||||
defaultConfig { | defaultConfig { | ||||
applicationId "de.oklein.android.ueberwachungssystem" | |||||
applicationId "com.example.ueberwachungssystem" | |||||
minSdk 28 | minSdk 28 | ||||
targetSdk 33 | targetSdk 33 | ||||
versionCode 1 | versionCode 1 | ||||
dependencies { | dependencies { | ||||
implementation 'androidx.appcompat:appcompat:1.6.1' | 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' | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' | ||||
testImplementation 'junit:junit:4.13.2' | testImplementation 'junit:junit:4.13.2' | ||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5' | androidTestImplementation 'androidx.test.ext:junit:1.1.5' | ||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' | 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}" | |||||
} | } |
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 <a href="http://d.android.com/tools/testing">Testing documentation</a> | |||||
*/ | |||||
@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()); | |||||
} | |||||
} |
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()); | |||||
} | |||||
} |
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(); | |||||
} |
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<ProcessCameraProvider> 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; | |||||
} | |||||
} |
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); | |||||
} | |||||
} |
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 <a href="http://d.android.com/tools/testing">Testing documentation</a> | |||||
*/ | |||||
public class ExampleUnitTest { | |||||
@Test | |||||
public void addition_isCorrect() { | |||||
assertEquals(4, 2 + 2); | |||||
} | |||||
} |