# Conflicts: # app/src/main/java/com/example/ueberwachungssystem/MainActivity.javaKommunikationcopie
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' | ||||
// 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 | // Required for CameraX | ||||
def camerax_version = "1.2.2" | def camerax_version = "1.2.2" | ||||
implementation "androidx.camera:camera-core:${camerax_version}" | implementation "androidx.camera:camera-core:${camerax_version}" |
<uses-feature android:name="android.hardware.camera"/> | <uses-feature android:name="android.hardware.camera"/> | ||||
<uses-permission android:name="android.permission.CAMERA"/> | <uses-permission android:name="android.permission.CAMERA"/> | ||||
<uses-permission android:name="android.permission.RECORD_AUDIO" /> | |||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> | <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> | ||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> | <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> | ||||
<category android:name="android.intent.category.LAUNCHER" /> | <category android:name="android.intent.category.LAUNCHER" /> | ||||
</intent-filter> | </intent-filter> | ||||
</activity> | </activity> | ||||
<service android:name=".Detection.DetectorService"/> | |||||
</application> | </application> | ||||
</manifest> | </manifest> |
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); | |||||
} | |||||
} |
public String timeStamp; | public String timeStamp; | ||||
public String detectionType; | public String detectionType; | ||||
public float detectedValue; | 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.timeStamp = String.valueOf(Calendar.getInstance().getTime()); | ||||
this.detectionType = detectionType; | this.detectionType = detectionType; | ||||
this.detectedValue = detectedAmplitude; | this.detectedValue = detectedAmplitude; | ||||
this.detectorID = detectorID; | |||||
this.detectionState = detectionState; | |||||
//this.detectorID = detectorID; | |||||
} | } | ||||
/** Get Detection Report in String format */ | /** Get Detection Report in String format */ | ||||
public String toString() { | public String toString() { | ||||
String state = "State: " + "[" + this.detectionState + "]"; | |||||
String time = "Time: " + "[" + this.timeStamp + "]"; | String time = "Time: " + "[" + this.timeStamp + "]"; | ||||
String type = "Type: " + "[" + this.detectionType + "]"; | String type = "Type: " + "[" + this.detectionType + "]"; | ||||
String value = "Value: " + "[" + this.detectedValue + "]"; | 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 */ | /** Debug Report */ |
package com.example.ueberwachungssystem.Detection; | package com.example.ueberwachungssystem.Detection; | ||||
import android.os.CountDownTimer; | |||||
import androidx.annotation.NonNull; | import androidx.annotation.NonNull; | ||||
import androidx.camera.core.ExperimentalGetImage; | |||||
abstract public class Detector { | abstract public class Detector { | ||||
private OnDetectionListener listener; | 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 */ | /** Constructor - takes context of current activity */ | ||||
public Detector() {} | public Detector() {} | ||||
this.listener = listener; | this.listener = listener; | ||||
} | } | ||||
/** Triggers onDetectionListener - call this to trigger violation/alarm */ | /** 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) { | 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 { | } 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) */ | /** Starts Detection (abstract method: needs to be overridden in child class) */ | ||||
public abstract void startDetection(); | public abstract void startDetection(); |
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; | |||||
} | |||||
} |
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<MatOfPoint> 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); | |||||
} | |||||
} |
package com.example.ueberwachungssystem.Detection; | package com.example.ueberwachungssystem.Detection; | ||||
import android.Manifest; | |||||
import android.annotation.SuppressLint; | |||||
import android.content.Context; | import android.content.Context; | ||||
import android.content.pm.PackageManager; | |||||
import android.graphics.ImageFormat; | import android.graphics.ImageFormat; | ||||
import android.media.Image; | 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.NonNull; | ||||
import androidx.annotation.Nullable; | |||||
import androidx.camera.core.CameraSelector; | import androidx.camera.core.CameraSelector; | ||||
import androidx.camera.core.ExperimentalGetImage; | import androidx.camera.core.ExperimentalGetImage; | ||||
import androidx.camera.core.ImageAnalysis; | import androidx.camera.core.ImageAnalysis; | ||||
import androidx.camera.core.ImageProxy; | |||||
import androidx.camera.core.Preview; | import androidx.camera.core.Preview; | ||||
import androidx.camera.core.VideoCapture; | |||||
import androidx.camera.lifecycle.ProcessCameraProvider; | import androidx.camera.lifecycle.ProcessCameraProvider; | ||||
import androidx.camera.view.PreviewView; | import androidx.camera.view.PreviewView; | ||||
import androidx.core.app.ActivityCompat; | |||||
import androidx.core.content.ContextCompat; | import androidx.core.content.ContextCompat; | ||||
import androidx.lifecycle.LifecycleOwner; | import androidx.lifecycle.LifecycleOwner; | ||||
import com.google.common.util.concurrent.ListenableFuture; | 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; | import java.util.concurrent.ExecutionException; | ||||
/** | /** | ||||
* Video Detector inherits some methods from abstract Detector class (more info there) | * Video Detector inherits some methods from abstract Detector class (more info there) | ||||
* | |||||
* | |||||
* USE FROM MAIN ACTIVITY: | * USE FROM MAIN ACTIVITY: | ||||
* | |||||
* VideoDetector vd = new VideoDetector(this); | * VideoDetector vd = new VideoDetector(this); | ||||
* vd.setPreview(previewView); //THIS IS OPTIONAL! | |||||
* vd.setOnDetectionListener(new Detector.OnDetectionListener{...}); | |||||
* vd.startDetection(); | |||||
* vd.stopDetection | |||||
* | |||||
* */ | * */ | ||||
@ExperimentalGetImage | @ExperimentalGetImage | ||||
public class VideoDetector extends Detector { | public class VideoDetector extends Detector { | ||||
// Calling Activity | // Calling Activity | ||||
private final Context context; | private final Context context; | ||||
// Camera Provider | // Camera Provider | ||||
private ProcessCameraProvider cameraProvider; | 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; | |||||
/** | |||||
* Constructor | |||||
* @param context: the context of calling activity (usually "this") | |||||
* */ | |||||
// 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) { | public VideoDetector(Context context) { | ||||
super(); | super(); | ||||
this.context = context; | 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 | @Override | ||||
public void startDetection() { | public void startDetection() { | ||||
if (isDetectionRunning) | |||||
// Check States | |||||
if (isDetecting) | |||||
return; | 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<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(context); | final ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(context); | ||||
//Check for Camera availability | |||||
cameraProviderFuture.addListener(() -> { | cameraProviderFuture.addListener(() -> { | ||||
try { | try { | ||||
cameraProvider = cameraProviderFuture.get(); | 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<ProcessCameraProvider> 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 | @Override | ||||
public void stopDetection() { | public void stopDetection() { | ||||
if (!isDetectionRunning) | |||||
if (!isDetecting || imageAnalysis == null) | |||||
return; | return; | ||||
cameraProvider.unbindAll(); | |||||
isDetectionRunning = false; | |||||
cameraProvider.unbind(imageAnalysis); | |||||
isDetecting = false; | |||||
allowReportViolation = false; | |||||
} | } | ||||
/** Stops the Recording */ | |||||
@SuppressLint("RestrictedApi") | |||||
public void stopRecording(){ | |||||
if(!isRecording) | |||||
return; | |||||
/** | |||||
* 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; | |||||
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); | |||||
} | |||||
/** | |||||
* 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 | // Configure and create Image Analysis | ||||
ImageAnalysis.Builder builder = new ImageAnalysis.Builder(); | ImageAnalysis.Builder builder = new ImageAnalysis.Builder(); | ||||
builder.setTargetResolution(new Size(640, 480)); | |||||
builder.setTargetResolution(IMAGE_RES); | |||||
builder.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST); | builder.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST); | ||||
builder.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888); | builder.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888); | ||||
builder.setTargetRotation(Surface.ROTATION_90); | |||||
ImageAnalysis imageAnalysis = builder.build(); | ImageAnalysis imageAnalysis = builder.build(); | ||||
// Set Analyzer | // Set Analyzer | ||||
imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(context), imageProxy -> { | imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(context), imageProxy -> { | ||||
if (imageProxy.getFormat() == ImageFormat.YUV_420_888) { | if (imageProxy.getFormat() == ImageFormat.YUV_420_888) { | ||||
Image image = imageProxy.getImage(); | Image image = imageProxy.getImage(); | ||||
assert image != null; | 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(); | 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; | |||||
} | |||||
@SuppressLint("RestrictedApi") | |||||
private VideoCapture setupVideoCapture() { | |||||
int rotation = getDisplayRotation(); | |||||
return new VideoCapture.Builder() | |||||
.setTargetRotation(rotation) | |||||
.build(); | |||||
} | |||||
cameraProvider.bindToLifecycle((LifecycleOwner) context, cameraSelector, imageAnalysis, preview); | |||||
/** 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; | |||||
} | |||||
private void setPreviewView(@NonNull PreviewView previewView) { | |||||
// Create Preview | |||||
if (this.preview != null) | |||||
this.preview.setSurfaceProvider(previewView.getSurfaceProvider()); | |||||
} | |||||
*/ | |||||
int width = image.getWidth(); | |||||
int height = image.getHeight(); | |||||
int yRowStride = image.getPlanes()[0].getRowStride(); | |||||
int yPixelStride = image.getPlanes()[0].getPixelStride(); | |||||
/** 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); | |||||
} | |||||
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++; | |||||
} | |||||
} | |||||
/** Get current Display Rotation */ | |||||
private int getDisplayRotation() { | |||||
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); | |||||
Display display = windowManager.getDefaultDisplay(); | |||||
return display.getRotation(); | |||||
} | |||||
// Reset and copy Byte Buffer | |||||
buffer.rewind(); | |||||
previousBuffer.rewind(); | |||||
previousBuffer.put(buffer); | |||||
/** 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(); | |||||
} | |||||
return changedPixelCount; | |||||
public void setOutputDir(File outputDir) { | |||||
this.outputDir = outputDir; | |||||
} | } | ||||
} | |||||
} |
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(); | |||||
} | |||||
} |
<?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | |||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |||||
xmlns:app="http://schemas.android.com/apk/res-auto" | xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
xmlns:tools="http://schemas.android.com/tools" | xmlns:tools="http://schemas.android.com/tools" | ||||
android:layout_width="match_parent" | android:layout_width="match_parent" | ||||
android:layout_height="match_parent" | android:layout_height="match_parent" | ||||
android:layout_gravity="center" | |||||
android:gravity="top" | |||||
android:orientation="vertical" | |||||
tools:context=".MainActivity"> | tools:context=".MainActivity"> | ||||
<TextView | |||||
<androidx.camera.view.PreviewView | |||||
android:id="@+id/previewView" | |||||
android:layout_width="match_parent" | |||||
android:layout_height="wrap_content" | |||||
android:backgroundTint="@android:color/black"/> | |||||
<ToggleButton | |||||
android:id="@+id/toggleButton" | |||||
android:layout_width="match_parent" | |||||
android:layout_height="wrap_content" | |||||
android:text="ToggleButton" /> | |||||
<ImageView | |||||
android:id="@+id/inputImageView" | |||||
android:layout_width="wrap_content" | android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | android:layout_height="wrap_content" | ||||
android:text="Hello World!" | |||||
app:layout_constraintBottom_toBottomOf="parent" | |||||
app:layout_constraintEnd_toEndOf="parent" | |||||
app:layout_constraintStart_toStartOf="parent" | |||||
app:layout_constraintTop_toTopOf="parent" /> | |||||
tools:srcCompat="@tools:sample/avatars" /> | |||||
<ImageView | |||||
android:id="@+id/outputImageView" | |||||
android:layout_width="wrap_content" | |||||
android:layout_height="wrap_content" | |||||
tools:srcCompat="@tools:sample/avatars" /> | |||||
<androidx.camera.view.PreviewView | |||||
android:layout_width="wrap_content" | |||||
android:layout_height="wrap_content" /> | |||||
</androidx.constraintlayout.widget.ConstraintLayout> | |||||
</LinearLayout> |