Compare commits

..
48 Commits
Author SHA1 Message Date
kohlerba68671 10e5cb3e61 Added Audio Recorder. Its working 2023-06-18 12:57:50 +02:00
kohlerba68671 f329fab62d Microphone Detector and Video Detector and Recorder working simultaniously 2023-06-18 11:50:19 +02:00
kohlerba68671 4baf3db1bb Merge branch 'tw' into bk_video_test
# Conflicts:
#	app/build.gradle
#	app/src/main/AndroidManifest.xml
#	app/src/main/java/com/example/ueberwachungssystem/MainActivity.java
#	app/src/main/res/layout/activity_main.xml
2023-06-18 11:02:20 +02:00
kohlerba68671 29c193b716 Removed Preview Use Case, Audio still not working 2023-06-18 10:55:21 +02:00
kohlerba68671 76442888f7 Working Video Detector + Recorder WIP: Use Case logic 2023-06-17 19:44:08 +02:00
kohlerba68671 d32e0a11f5 Fixed bug in Detector and VideoDetector 2023-06-17 11:20:56 +02:00
wolzto94552 b94a1d98b7 Working detection with calibration and dB signals. FFT also implemented, but does not work properly at the moment. (Added Complex class, FFT class and imported jjoe64 Graphview (in gradle and gradle.properties) for visualising FFT results) 2023-06-16 16:49:20 +02:00
kohlerba68671 fb8bdcd895 Fixed up Video detector 2023-06-15 12:40:54 +02:00
kohlerba68671 7ef0df48dd Refactored Detector 2023-06-14 20:14:06 +02:00
kohlerba68671 50b579705e Detector based on OpenCV working 2023-06-14 19:43:18 +02:00
kohlerba68671 8551abfc5d Changde Detector class 2023-06-14 16:38:33 +02:00
kohlerba68671 bbd333426d unfinished changes in Detector 2023-06-13 20:55:30 +02:00
kohlerba68671 e98ae10267 refactor 2023-06-12 19:03:52 +02:00
kohlerba68671 7d29ed1ca9 Rebuild to use openCV. Alarm not yet implemented 2023-06-09 20:56:14 +02:00
kohlerba68671 f98079d431 Readded main and main xml 2023-06-09 09:09:50 +02:00
kohlerba68671 047d45f1cb Added comments to VideoDetector class 2023-06-02 17:48:41 +02:00
kohlerba68671 95919a6602 preparing branch for merge 2023-06-02 14:56:57 +02:00
kohlerba68671 19d1bfe1e3 Refactor 2023-05-29 10:43:51 +02:00
kohlerba68671 e9da712882 Merge remote-tracking branch 'origin/bk_video' into bk_video 2023-05-28 22:30:58 +02:00
kohlerba68671 804c5aba36 Removed old Luminosity detection 2023-05-28 22:30:22 +02:00
kohlerba68671 c5577b125f Changed VideoDetector.java to detect based on changed pixel count instead of average luminosity 2023-05-28 22:30:22 +02:00
kohlerba68671 6100d17bd2 Refactored in Main 2023-05-28 22:30:22 +02:00
kohlerba68671 74a291bdd6 Removed olf Luminosity detection 2023-05-28 22:28:23 +02:00
kohlerba68671 ba6765b1fb Changed VideoDetector.java to detect based on changed pixel count instead of average luminosity 2023-05-28 22:20:56 +02:00
kohlerba68671 9fce3be6a8 Refactored some stuff in Main 2023-05-28 22:19:30 +02:00
kohlerba68671 9892fc35bc Refactor of Detector.java 2023-05-28 22:19:05 +02:00
kohlerba68671 62a528d33b Implemented VideoDetector as a child class of abstract class Detector 2023-05-28 21:16:17 +02:00
wolzto94552 7c8610facb Move from Mikrofon to MicrophoneDetector class and add of abstract Detector class 2023-05-25 16:58:08 +02:00
kohlerba68671 37617dbd54 Fixed a bug in Detector.java 2023-05-25 10:54:08 +02:00
kohlerba68671 569bd43163 Adjusted some values in xml and video detector for showcase 2023-05-25 10:20:11 +02:00
kohlerba68671 fdb0036ea6 Added comments to abstract class "Detector" 2023-05-25 10:19:21 +02:00
kohlerba68671 f9797f2dca Added Abstract class Detector 2023-05-24 23:51:49 +02:00
kohlerba68671 ba75c58a23 Refactor 2023-05-24 22:16:54 +02:00
kohlerba68671 3d799ae461 Rebuild Demo in Main Activity 2023-05-18 15:05:09 +02:00
kohlerba68671 2501662737 Fixed a bug in the video decoder 2023-05-18 12:02:18 +02:00
kohlerba68671 d04d166e14 Added log function to detection report 2023-05-18 12:01:45 +02:00
kohlerba68671 1576719d10 Added onDetectionListener to VideoDecoder 2023-05-16 22:35:50 +02:00
kohlerba68671 362af75b9a Added DetectionReportCalss 2023-05-16 22:34:32 +02:00
kohlerba68671 bef5d03494 Added functions for checking luminosity violation 2023-05-16 19:15:57 +02:00
kohlerba68671 80ebf77eb6 Removed empty Text File 2023-05-16 17:48:33 +02:00
kohlerba68671 c4ed247a05 added Functions to VideoDetector:
startDetection(), stopDetection(), isRunning()
2023-05-16 17:42:10 +02:00
kohlerba68671 323786859e added Video Decoder Class 2023-05-12 18:45:13 +02:00
kohlerba68671 354bf937b2 added camerax libraries to build.gradle 2023-05-12 09:37:58 +02:00
kohlerba68671 39bb37f5c3 removed VideoDetection.java 2023-05-11 21:46:15 +02:00
kohlerba68671 b198dc2e06 small refactor 2023-05-11 21:33:40 +02:00
BepsiKohler d83cd3dc32 Added camera access permission 2023-05-11 20:58:01 +02:00
unknown 90a2aad491 new class Mikrofon 2023-05-11 16:21:59 +02:00
kohlerba68671 065acefeb9 Merge branch 'ms' of siebenhaarmi76624/MDT5_1_Ueberwachungssystem_G1 into master 2023-05-11 13:29:40 +00:00
16 changed files with 1751 additions and 7 deletions
View File
+18
View File
@@ -36,4 +36,22 @@ dependencies {
testImplementation 'junit:junit:4.13.2'
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}"
// 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}"
implementation 'com.jjoe64:graphview:4.2.2'
}
+9
View File
@@ -2,6 +2,15 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-feature android:name="android.hardware.camera"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!--<uses-permission android:name="android.permission.BLUETOOTH"/>-->
<!--<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>-->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
@@ -0,0 +1,83 @@
package com.example.ueberwachungssystem;
import android.content.Context;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
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;
}
}
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 void playAudio() {
MediaPlayer mp = new MediaPlayer();
try {
mp.setDataSource(context.getFilesDir() + "/audio.3gp");
mp.prepare();
mp.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
@@ -0,0 +1,38 @@
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 boolean detectionState;
public DetectionReport(boolean detectionState, String detectionType, float detectedAmplitude) {
this.timeStamp = String.valueOf(Calendar.getInstance().getTime());
this.detectionType = detectionType;
this.detectedValue = detectedAmplitude;
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 + "]";
return String.join("\t", state, time, type, value);
}
/** Debug Report */
public void log(String tag) {
Log.d(tag, this.toString());
}
}
@@ -0,0 +1,75 @@
package com.example.ueberwachungssystem.Detection;
import android.os.CountDownTimer;
import androidx.annotation.NonNull;
abstract public class Detector {
private OnDetectionListener listener;
private boolean isDetecting = false;
private boolean extendViolation = false;
// Countdown parameters
private final int COUNTDOWN_TIME = 5000; // milliseconds
private final int COUNTDOWN_POLLING_TIME = 100; // milliseconds
/** 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 detectionType, float amplitude) {
if (listener != null) {
if (!isDetecting) {
isDetecting = true;
DetectionReport detectionReport = new DetectionReport(true, detectionType, amplitude);
listener.onDetection(detectionReport);
startDetectionTimer(detectionType, amplitude);
} else {
extendViolation = true;
}
} else {
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();
/** Stops Detection (abstract method: needs to be overridden in child class) */
public abstract void stopDetection();
}
@@ -0,0 +1,441 @@
package com.example.ueberwachungssystem.Detection;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.ImageFormat;
import android.media.Image;
import android.os.CountDownTimer;
import android.util.Log;
import android.view.Surface;
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.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.io.File;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
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;
// Permission handling
private static final int PERMISSION_REQUEST_CODE = 3691;
// Camera Provider
private ProcessCameraProvider cameraProvider;
private final ImageAnalysis imageAnalysis;
private final VideoCapture videoCapture;
private final 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;
// Recording
private final String outputName = "video.mp4";
// Parameters
private static final float ALARM_THRESHOLD = 0.5f; // Percent of pixels changed
private static final long START_DELAY = 20000; // milliseconds
private static final android.util.Size IMAGE_RES = new android.util.Size(640, 480);
private enum UseCase {
ImageAnalysis,
Preview,
VideoCapture
};
/** Constructor */
public VideoDetector(Context context) {
super();
this.context = context;
this.imageAnalysis = setupImageAnalysis();
this.videoCapture = setupVideoCapture();
this.preview = new Preview.Builder().build();
}
/** Get State of the Detector */
public boolean isRunning() {
return isDetecting;
}
/** Starts the Video Detection */
@Override
public void startDetection() {
// Check States
if (isDetecting)
return;
// Return On Request Permissions
if (!hasPermissions()) {
getPermissions();
return;
}
// 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);
cameraProviderFuture.addListener(() -> {
try {
cameraProvider = cameraProviderFuture.get();
isDetecting = true;
bindCameraProvider(UseCase.ImageAnalysis);
} catch (ExecutionException | InterruptedException e) {}
}, ContextCompat.getMainExecutor(context));
startViolationTimer();
}
/** Stops the Video Detection */
@Override
public void stopDetection() {
if (!isDetecting || imageAnalysis == null)
return;
cameraProvider.unbind(imageAnalysis);
isDetecting = false;
allowReportViolation = false;
}
/** Permission handling */
private boolean hasPermissions() {
return ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED;
}
private void getPermissions() {
if (!hasPermissions())
ActivityCompat.requestPermissions((Activity) context, new String[]{android.Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO}, PERMISSION_REQUEST_CODE);
}
/** Binds the Luminosity Analyzer (configure and run Analysis) */
private void bindCameraProvider(UseCase useCase) {
// 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);
}
/** Start delay until Violation Report is allowed */
private void startViolationTimer() {
new CountDownTimer((long) (START_DELAY), 100) {
@Override
public void onTick(long millisUntilFinished) {
}
@Override
public void onFinish() {
allowReportViolation = true;
}
}.start();
}
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);
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", n);
}
}
imageProxy.close();
});
return imageAnalysis;
}
@SuppressLint("RestrictedApi")
private VideoCapture setupVideoCapture() {
return new VideoCapture.Builder()
.setTargetRotation(Surface.ROTATION_0)
.build();
}
@SuppressLint("RestrictedApi")
public void startRecording() {
// Check States
if (isRecording){
extendViolation();
return;
}
// Return On Request Permissions
if (!hasPermissions()) {
getPermissions();
return;
}
final ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(context);
cameraProviderFuture.addListener(() -> {
try {
cameraProvider = cameraProviderFuture.get();
isRecording = true;
bindCameraProvider(UseCase.VideoCapture);
File vidFile = new File(context.getFilesDir() + "/" + outputName);
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, "recording saved", Toast.LENGTH_SHORT).show();
}
@Override
public void onError(int videoCaptureError, @NonNull String message, @Nullable Throwable cause) {
isRecording = false;
Toast.makeText(context, "recording failed", Toast.LENGTH_SHORT).show();
}
}
);
} catch (ExecutionException | InterruptedException e) {}
}, ContextCompat.getMainExecutor(context));
}
@SuppressLint("RestrictedApi")
public void stopRecording(){
videoCapture.stopRecording();
cameraProvider.unbind(videoCapture);
isRecording = false;
}
/** 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));
if (previousImage == null) {
previousImage = preprocessed;
return null;
}
// Process Image
Mat processed = preprocessed.clone();
processed = OpenCVHelper.thresholdPixels(processed, previousImage, 25);
processed = OpenCVHelper.dilateBinaryMat(processed, new Size(3,3));
processed = OpenCVHelper.dilateBinaryMat(processed, new Size(3,3));
processed = OpenCVHelper.thresholdContourArea(processed, 500);
// Output
previousImage = preprocessed.clone();
// Show Output Image
if (outputImageView != null)
OpenCVHelper.debugMat(processed, outputImageView);
return processed;
}
public void debugProcessing(@NonNull ImageView inputImageView, @NonNull ImageView outputImageView){
this.inputImageView = inputImageView;
this.outputImageView = outputImageView;
}
public void setPreviewView(@NonNull PreviewView previewView) {
// Create Preview
if (this.preview != null)
this.preview.setSurfaceProvider(previewView.getSurfaceProvider());
}
private static class OpenCVHelper{
private OpenCVHelper() {}
/** OpenCV helper methods **/
private static Mat addGaussianBlur(Mat inputMat, Size kernelSize){
Mat outputMat = new Mat();
Imgproc.GaussianBlur(inputMat, outputMat, kernelSize, 0);
return outputMat;
}
private static Mat addBlur(Mat inputMat, Size kernelSize){
Mat outputMat = new Mat();
Imgproc.blur(inputMat, outputMat, kernelSize);
return outputMat;
}
private 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;
}
private 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;
}
private static Mat imageProxyToGrayscaleMat(ImageProxy imageProxy) {
// Step 1: Extract the image data from ImageProxy
ImageProxy.PlaneProxy[] planes = imageProxy.getPlanes();
ByteBuffer yBuffer = planes[0].getBuffer();
byte[] yData = new byte[yBuffer.remaining()];
yBuffer.get(yData);
// Step 2: Convert the image data to NV21 format
int width = imageProxy.getWidth();
int height = imageProxy.getHeight();
byte[] nv21Data = new byte[width * height * 3 / 2];
// Assuming the image format is YUV_420_888
System.arraycopy(yData, 0, nv21Data, 0, yData.length);
for (int i = yData.length; i < nv21Data.length; i += 2) {
nv21Data[i] = yData[i + 1];
nv21Data[i + 1] = yData[i];
}
// Step 3: Create a grayscale Mat from the NV21 data
Mat grayscaleMat = new Mat(height, width, CvType.CV_8UC1);
grayscaleMat.put(0, 0, nv21Data);
return grayscaleMat;
}
private 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;
}
private 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;
}
private static int countNonZeroPixels(Mat inputImage) {
if (inputImage != null)
return Core.countNonZero(inputImage);
else
return 0;
}
private 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);
}
}
}
@@ -0,0 +1,36 @@
package com.example.ueberwachungssystem;
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());
}
}
@@ -0,0 +1,40 @@
package com.example.ueberwachungssystem;
import android.app.Activity;
import android.content.Context;
import androidx.annotation.NonNull;
abstract public class Detector {
private OnDetectionListener listener;
/** Constructor - takes context of current activity */
public Detector(Context context) {};
/** 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 */
private 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();
}
@@ -1,15 +1,78 @@
package com.example.ueberwachungssystem;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.camera.core.ExperimentalGetImage;
import androidx.camera.view.PreviewView;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.ToggleButton;
import com.example.ueberwachungssystem.Detection.DetectionReport;
import com.example.ueberwachungssystem.Detection.Detector;
import com.example.ueberwachungssystem.Detection.VideoDetector;
import com.example.ueberwachungssystem.logger.Logger;
import com.jjoe64.graphview.GraphView;
@ExperimentalGetImage
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//
ImageView inputImageView = findViewById(R.id.inputImageView);
ImageView outputImageView = findViewById(R.id.outputImageView);
PreviewView previewView = findViewById(R.id.previewView);
// Mic stuff
TextView textView= (TextView) findViewById(R.id.textView); //Set textview for showing logged content
Logger logger = new Logger(this.getClass().getSimpleName(), textView, "");
GraphView graph = (GraphView) findViewById(R.id.graph);
MicrophoneDetector mic = new MicrophoneDetector(this, logger, graph);
mic.startDetection();
AudioRecorder audioRecorder = new AudioRecorder(this);
// video Stuff
VideoDetector vd = new VideoDetector(this);
//vd.setPreviewView(previewView);
vd.debugProcessing(inputImageView, outputImageView);
vd.setOnDetectionListener(new Detector.OnDetectionListener() {
@Override
public void onDetection(@NonNull DetectionReport detectionReport) {
Log.d("onDetection", detectionReport.toString());
}
});
vd.startDetection();
ToggleButton toggleButton = findViewById(R.id.toggleButton);
toggleButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (toggleButton.isChecked())
{
//vd.startDetection();
vd.startRecording();
audioRecorder.startRecording();
}
else {
//vd.stopDetection();
vd.stopRecording();
audioRecorder.stopRecording();
}
}
});
}
}
@@ -0,0 +1,445 @@
package com.example.ueberwachungssystem;
import static java.lang.Math.*;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.AsyncTask;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.example.ueberwachungssystem.Signalverarbeitung.Complex;
import com.example.ueberwachungssystem.Signalverarbeitung.FFT;
import com.example.ueberwachungssystem.logger.Logger;
import com.jjoe64.graphview.GraphView;
import com.jjoe64.graphview.series.DataPoint;
import com.jjoe64.graphview.series.LineGraphSeries;
public class MicrophoneDetector extends Detector {
/**
* Constructor - takes context of current activity
*
* @param context
*/
private static final int RECHTEANFORDERUNG_MIKROFON = 1;
private AufnahmeTask aufnahmeTask;
public boolean armed = false;
public int Schwellwert_Alarm = 100;
GraphView graph;
Logger logger;
private Activity MainActivityForClass;
public MicrophoneDetector(Context context, Logger MainLogger, GraphView MainGraph) {
super(context);
MainActivityForClass = (Activity) context;
logger = MainLogger; //Class uses the same logger as the MainActivity
logger.log(this.getClass().getSimpleName() + ".onCreate");
graph = MainGraph;
if (!istZugriffAufMikrofonErlaubt()) {
zugriffAufMikrofonAnfordern();
}
}
@Override
public void startDetection() {
logger.log(this.getClass().getSimpleName() + ".startDetection");
if (!istZugriffAufMikrofonErlaubt()) {
zugriffAufMikrofonAnfordern();
}
if (istZugriffAufMikrofonErlaubt()) {
aufnahmeTask = new AufnahmeTask();
aufnahmeTask.execute();
}
}
@Override
public void stopDetection() {
logger.log(this.getClass().getSimpleName() + ".stopDetection");
if (aufnahmeTask != null) {
aufnahmeTask.cancel(true);
// aufnahmeTask = null; // if aufnahmeTask = null, break in for loop would not work (Nullpointer Exception)
}
}
private boolean istZugriffAufMikrofonErlaubt() {
if (ContextCompat.checkSelfPermission(MainActivityForClass, android.Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
logger.log("Zugriff auf Mikrofon ist verboten.");
return false;
} else {
logger.log("Zugriff auf Mikrofon ist erlaubt.");
return true;
}
}
private void zugriffAufMikrofonAnfordern() {
ActivityCompat.requestPermissions(MainActivityForClass, new String[]{Manifest.permission.RECORD_AUDIO}, RECHTEANFORDERUNG_MIKROFON);
}
class AufnahmeTask extends AsyncTask<Long, Verarbeitungsergebnis, Void> {
private AudioRecord recorder;
private final int sampleRateInHz = 44100;
private final int channelConfig = AudioFormat.CHANNEL_IN_MONO;
private final int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
private int minPufferGroesseInBytes;
private int pufferGroesseInBytes;
private RingPuffer ringPuffer = new RingPuffer(10);
private float kalibierWert;
private DetectionReport detectionReport;
AufnahmeTask() {
minPufferGroesseInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
pufferGroesseInBytes = minPufferGroesseInBytes * 2;
if (ActivityCompat.checkSelfPermission(MainActivityForClass, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
ActivityCompat.requestPermissions(MainActivityForClass, new String[]{Manifest.permission.RECORD_AUDIO}, RECHTEANFORDERUNG_MIKROFON);
}
recorder = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRateInHz, channelConfig, audioFormat, pufferGroesseInBytes);
// textViewMinPufferGroesseInBytes.setText("" + minPufferGroesseInBytes);
// textViewPufferGroesseInBytes.setText("" + pufferGroesseInBytes);
// textViewAbtastrate.setText("" + recorder.getSampleRate());
// textViewAnzahlKanaele.setText("" + recorder.getChannelCount());
logger.log("Puffergroeße: "+ minPufferGroesseInBytes + " " + pufferGroesseInBytes);
logger.log("Recorder (SR, CH): "+ recorder.getSampleRate() + " " + recorder.getChannelCount());
int anzahlBytesProAbtastwert;
String s;
switch (recorder.getAudioFormat()) {
case AudioFormat.ENCODING_PCM_8BIT:
s = "8 Bit PCM ";
anzahlBytesProAbtastwert = 1;
break;
case AudioFormat.ENCODING_PCM_16BIT:
s = "16 Bit PCM";
anzahlBytesProAbtastwert = 2;
break;
case AudioFormat.ENCODING_PCM_FLOAT:
s = "Float PCM";
anzahlBytesProAbtastwert = 4;
break;
default:
throw new IllegalArgumentException();
}
// textViewAudioFormat.setText(s);
switch (recorder.getChannelConfiguration()) {
case AudioFormat.CHANNEL_IN_MONO:
s = "Mono";
break;
case AudioFormat.CHANNEL_IN_STEREO:
s = "Stereo";
anzahlBytesProAbtastwert *= 2;
break;
case AudioFormat.CHANNEL_INVALID:
s = "ungültig";
break;
default:
throw new IllegalArgumentException();
}
// textViewKanalKonfiguration.setText(s);
logger.log("Konfiguration: "+ s);
int pufferGroesseInAnzahlAbtastwerten = pufferGroesseInBytes / anzahlBytesProAbtastwert;
int pufferGroesseInMillisekunden = 1000 * pufferGroesseInAnzahlAbtastwerten / recorder.getSampleRate();
// textViewPufferGroesseInAnzahlAbtastwerte.setText("" + pufferGroesseInAnzahlAbtastwerten);
// textViewPufferGroesseInMillisekunden.setText("" + pufferGroesseInMillisekunden);
}
@Override
protected Void doInBackground(Long... params) {
recorder.startRecording();
short[] puffer = new short[pufferGroesseInBytes / 2];
long lastTime = System.currentTimeMillis();
float verarbeitungsrate = 0;
final int maxZaehlerZeitMessung = 10;
int zaehlerZeitMessung = 0;
int anzahlVerarbeitet = 0;
GleitenderMittelwert gleitenderMittelwert = new GleitenderMittelwert(0.3f);
//Kalibrierung
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int i = 0;
for (i = 0; i < 20; i++) {
int n = recorder.read(puffer, 0, puffer.length);
Verarbeitungsergebnis kalibrierErgebnis = verarbeiten(puffer, n);
kalibierWert += kalibrierErgebnis.maxAmp;
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
kalibierWert = kalibierWert/i;
// Complex[] zeitSignal = new Complex[puffer.length];
// for (int j = 0; j < puffer.length; j++) {
// zeitSignal[j] = new Complex(puffer[j], 0);
// }
// Complex[] spektrum = FFT.fft(zeitSignal);
double[] spektrum = calculateFFT(puffer);
DataPoint AddPoint;
// LineGraphSeries<DataPoint> series = new LineGraphSeries<DataPoint>(new DataPoint[]{});
// for (i = 0; i < spektrum.length; i++) {
// AddPoint = new DataPoint(i, spektrum[i]);
// series.appendData(AddPoint, true, spektrum.length);
// }
// graph.addSeries(series);
// logger.log(spektrum.toString());
for (; ; ) {
if (aufnahmeTask.isCancelled()) {
break;
} else {
int n = recorder.read(puffer, 0, puffer.length);
Verarbeitungsergebnis ergebnis = verarbeiten(puffer, n);
anzahlVerarbeitet += n;
spektrum = calculateFFT(puffer);
LineGraphSeries<DataPoint> newseries = new LineGraphSeries<DataPoint>(new DataPoint[]{});
for (i = 0; i < spektrum.length; i++) {
AddPoint = new DataPoint(i, spektrum[i]);
newseries.appendData(AddPoint, true, spektrum.length);
}
graph.removeAllSeries();
graph.addSeries(newseries);
zaehlerZeitMessung++;
if (zaehlerZeitMessung == maxZaehlerZeitMessung) {
long time = System.currentTimeMillis();
long deltaTime = time - lastTime;
verarbeitungsrate = 1000.0f * anzahlVerarbeitet / deltaTime;
verarbeitungsrate = gleitenderMittelwert.mittel(verarbeitungsrate);
zaehlerZeitMessung = 0;
anzahlVerarbeitet = 0;
lastTime = time;
}
ergebnis.verarbeitungsrate = (int) verarbeitungsrate;
publishProgress(ergebnis);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
recorder.release();
return null;
}
private Verarbeitungsergebnis verarbeiten(short[] daten, int n) {
String status;
short maxAmp = -1;
if (n == AudioRecord.ERROR_INVALID_OPERATION) {
status = "ERROR_INVALID_OPERATION";
} else if (n == AudioRecord.ERROR_BAD_VALUE) {
status = "ERROR_BAD_VALUE";
} else {
status = "OK";
short max = 0;
for (int i = 0; i < n; i++) {
if (daten[i] > max) {
max = daten[i];
//max = 20 * log10(abs(daten[i]) / 32768);
}
}
ringPuffer.hinzufuegen(max);
maxAmp = ringPuffer.maximum();
if (maxAmp <= Schwellwert_Alarm+kalibierWert) {
armed = true;
}
}
return new Verarbeitungsergebnis(status, maxAmp, 0);
}
@Override
protected void onProgressUpdate(Verarbeitungsergebnis... progress) {
super.onProgressUpdate(progress);
// textViewMaxAmp.setText("" + progress[0].maxAmp);
// textViewVerarbeitungsrate.setText("" + progress[0].verarbeitungsrate);
float maxAmpPrint = round(20*log10(abs(progress[0].maxAmp/1.0)));
float kalibierWertPrint = round(20*log10(abs(kalibierWert)));
logger.overwriteLastlog("VR, Max, Kal:" + progress[0].verarbeitungsrate + ", " + maxAmpPrint
+ " dB, " + kalibierWertPrint + " dB");
if (progress[0].maxAmp >= Schwellwert_Alarm+kalibierWert && armed == true) {
armed = false;
detectionReport = new DetectionReport("Mic1", "Audio", maxAmpPrint);
logger.log("");
logger.log("Alarm!");
logger.log(detectionReport.toString());
logger.log("");
}
}
}
private double[] calculateFFT(short[] zeitsignal)
{
byte signal[] = new byte[zeitsignal.length];
// loops through all the values of a Short
for (int i = 0; i < zeitsignal.length-1; i++) {
signal[i] = (byte) (zeitsignal[i]);
signal[i+1] = (byte) (zeitsignal[i] >> 8);
}
final int mNumberOfFFTPoints =1024;
double mMaxFFTSample;
double temp;
Complex[] y;
Complex[] complexSignal = new Complex[mNumberOfFFTPoints];
double[] absSignal = new double[mNumberOfFFTPoints/2];
for(int i = 0; i < mNumberOfFFTPoints; i++){
temp = (double)((signal[2*i] & 0xFF) | (signal[2*i+1] << 8)) / 32768.0F;
complexSignal[i] = new Complex(temp,0.0);
}
y = FFT.fft(complexSignal);
mMaxFFTSample = 0.0;
// mPeakPos = 0;
for(int i = 0; i < (mNumberOfFFTPoints/2); i++)
{
absSignal[i] = y[i].abs();
// absSignal[i] = Math.sqrt(Math.pow(y[i].re(), 2) + Math.pow(y[i].im(), 2));
// if(absSignal[i] > mMaxFFTSample)
// {
// mMaxFFTSample = absSignal[i];
// // mPeakPos = i;
// }
}
return absSignal;
}
class Verarbeitungsergebnis {
String status;
short maxAmp;
int verarbeitungsrate;
Verarbeitungsergebnis(String status, short maxAmp, int verarbeitungsrate) {
this.status = status;
this.maxAmp = maxAmp;
this.verarbeitungsrate = verarbeitungsrate;
}
}
class RingPuffer {
private short[] puffer;
private final int laenge;
private int anzahlEnthaltenerDaten;
private int position;
public RingPuffer(int n) {
laenge = n;
anzahlEnthaltenerDaten = 0;
position = 0;
puffer = new short[laenge];
}
public void hinzufuegen(short wert) {
puffer[position] = wert;
position++;
if (position >= laenge) {
position = 0;
}
if (anzahlEnthaltenerDaten < laenge) {
anzahlEnthaltenerDaten++;
}
}
public void hinzufuegen(short[] daten) {
for (short d : daten) {
puffer[position] = d;
position++;
if (position >= laenge) {
position = 0;
}
}
if (anzahlEnthaltenerDaten < laenge) {
anzahlEnthaltenerDaten += daten.length;
if (anzahlEnthaltenerDaten >= laenge) {
anzahlEnthaltenerDaten = laenge;
}
}
}
public short maximum() {
short max = 0;
for (int i = 0; i < anzahlEnthaltenerDaten; i++) {
if (puffer[i] > max) {
max = puffer[i];
}
}
return max;
}
public float mittelwert() {
float summe = 0;
for (int i = 0; i < anzahlEnthaltenerDaten; i++) {
summe += puffer[i];
}
return summe / anzahlEnthaltenerDaten;
}
}
class GleitenderMittelwert {
private final float wichtungNeuerWert;
private final float wichtungAlterWert;
private float mittelwert = 0;
private boolean istMittelwertGesetzt = false;
GleitenderMittelwert(float wichtungNeuerWert) {
this.wichtungNeuerWert = wichtungNeuerWert;
this.wichtungAlterWert = 1 - this.wichtungNeuerWert;
}
float MittelwertPuffer(short[] puffer) {
for (int i = 0; i < puffer.length; i++) {
mittelwert = Math.abs(puffer[i]);
}
mittelwert = mittelwert/puffer.length;
return mittelwert;
}
float mittel(float wert) {
if (istMittelwertGesetzt) {
mittelwert = wert * wichtungNeuerWert + mittelwert * wichtungAlterWert;
} else {
mittelwert = wert;
istMittelwertGesetzt = true;
}
return mittelwert;
}
}
}
@@ -0,0 +1,148 @@
package com.example.ueberwachungssystem.Signalverarbeitung;
import java.util.Objects;
public class Complex {
private final double re; // the real part
private final double im; // the imaginary part
// create a new object with the given real and imaginary parts
public Complex(double real, double imag) {
re = real;
im = imag;
}
// return a string representation of the invoking com.example.ueberwachungssystem.Signalverarbeitung.Complex object
public String toString() {
if (im == 0) return re + "";
if (re == 0) return im + "i";
if (im < 0) return re + " - " + (-im) + "i";
return re + " + " + im + "i";
}
// return abs/modulus/magnitude
public double abs() {
return Math.hypot(re, im);
}
// return angle/phase/argument, normalized to be between -pi and pi
public double phase() {
return Math.atan2(im, re);
}
// return a new com.example.ueberwachungssystem.Signalverarbeitung.Complex object whose value is (this + b)
public Complex plus(Complex b) {
Complex a = this; // invoking object
double real = a.re + b.re;
double imag = a.im + b.im;
return new Complex(real, imag);
}
// return a new com.example.ueberwachungssystem.Signalverarbeitung.Complex object whose value is (this - b)
public Complex minus(Complex b) {
Complex a = this;
double real = a.re - b.re;
double imag = a.im - b.im;
return new Complex(real, imag);
}
// return a new com.example.ueberwachungssystem.Signalverarbeitung.Complex object whose value is (this * b)
public Complex times(Complex b) {
Complex a = this;
double real = a.re * b.re - a.im * b.im;
double imag = a.re * b.im + a.im * b.re;
return new Complex(real, imag);
}
// return a new object whose value is (this * alpha)
public Complex scale(double alpha) {
return new Complex(alpha * re, alpha * im);
}
// return a new com.example.ueberwachungssystem.Signalverarbeitung.Complex object whose value is the conjugate of this
public Complex conjugate() {
return new Complex(re, -im);
}
// return a new com.example.ueberwachungssystem.Signalverarbeitung.Complex object whose value is the reciprocal of this
public Complex reciprocal() {
double scale = re * re + im * im;
return new Complex(re / scale, -im / scale);
}
// return the real or imaginary part
public double re() {
return re;
}
public double im() {
return im;
}
// return a / b
public Complex divides(Complex b) {
Complex a = this;
return a.times(b.reciprocal());
}
// return a new com.example.ueberwachungssystem.Signalverarbeitung.Complex object whose value is the complex exponential of this
public Complex exp() {
return new Complex(Math.exp(re) * Math.cos(im), Math.exp(re) * Math.sin(im));
}
// return a new com.example.ueberwachungssystem.Signalverarbeitung.Complex object whose value is the complex sine of this
public Complex sin() {
return new Complex(Math.sin(re) * Math.cosh(im), Math.cos(re) * Math.sinh(im));
}
// return a new com.example.ueberwachungssystem.Signalverarbeitung.Complex object whose value is the complex cosine of this
public Complex cos() {
return new Complex(Math.cos(re) * Math.cosh(im), -Math.sin(re) * Math.sinh(im));
}
// return a new com.example.ueberwachungssystem.Signalverarbeitung.Complex object whose value is the complex tangent of this
public Complex tan() {
return sin().divides(cos());
}
// a static version of plus
public static Complex plus(Complex a, Complex b) {
double real = a.re + b.re;
double imag = a.im + b.im;
Complex sum = new Complex(real, imag);
return sum;
}
// See Section 3.3.
public boolean equals(Object x) {
if (x == null) return false;
if (this.getClass() != x.getClass()) return false;
Complex that = (Complex) x;
return (this.re == that.re) && (this.im == that.im);
}
// See Section 3.3.
public int hashCode() {
return Objects.hash(re, im);
}
// sample client for testing
public static void main(String[] args) {
Complex a = new Complex(5.0, 6.0);
Complex b = new Complex(-3.0, 4.0);
System.out.println("a = " + a);
System.out.println("b = " + b);
System.out.println("Re(a) = " + a.re());
System.out.println("Im(a) = " + a.im());
System.out.println("b + a = " + b.plus(a));
System.out.println("a - b = " + a.minus(b));
System.out.println("a * b = " + a.times(b));
System.out.println("b * a = " + b.times(a));
System.out.println("a / b = " + a.divides(b));
System.out.println("(a / b) * b = " + a.divides(b).times(b));
System.out.println("conj(a) = " + a.conjugate());
System.out.println("|a| = " + a.abs());
System.out.println("tan(a) = " + a.tan());
}
}
@@ -0,0 +1,248 @@
package com.example.ueberwachungssystem.Signalverarbeitung;
// Source: https://introcs.cs.princeton.edu/java/97data/FFT.java.html
import android.util.Log;
/******************************************************************************
* Compilation: javac FFT.java
* Execution: java FFT n
* Dependencies: com.example.ueberwachungssystem.Signalverarbeitung.Complex.java
*
* Compute the FFT and inverse FFT of a length n complex sequence
* using the radix 2 Cooley-Tukey algorithm.
* Bare bones implementation that runs in O(n log n) time and O(n)
* space. Our goal is to optimize the clarity of the code, rather
* than performance.
*
* This implementation uses the primitive root of unity w = e^(-2 pi i / n).
* Some resources use w = e^(2 pi i / n).
*
* Reference: https://www.cs.princeton.edu/~wayne/kleinberg-tardos/pdf/05DivideAndConquerII.pdf
*
* Limitations
* -----------
* - assumes n is a power of 2
*
* - not the most memory efficient algorithm (because it uses
* an object type for representing complex numbers and because
* it re-allocates memory for the subarray, instead of doing
* in-place or reusing a single temporary array)
*
* For an in-place radix 2 Cooley-Tukey FFT, see
* https://introcs.cs.princeton.edu/java/97data/InplaceFFT.java.html
*
******************************************************************************/
public class FFT {
// compute the FFT of x[], assuming its length n is a power of 2
public static Complex[] fft(Complex[] x) {
int n = x.length;
// base case
if (n == 1) return new Complex[]{x[0]};
// radix 2 Cooley-Tukey FFT
if (n % 2 != 0) {
throw new IllegalArgumentException("n is not a power of 2");
}
// compute FFT of even terms
Complex[] even = new Complex[n / 2];
for (int k = 0; k < n / 2; k++) {
even[k] = x[2 * k];
}
Complex[] evenFFT = fft(even);
// compute FFT of odd terms
Complex[] odd = even; // reuse the array (to avoid n log n space)
for (int k = 0; k < n / 2; k++) {
odd[k] = x[2 * k + 1];
}
Complex[] oddFFT = fft(odd);
// combine
Complex[] y = new Complex[n];
for (int k = 0; k < n / 2; k++) {
double kth = -2 * k * Math.PI / n;
Complex wk = new Complex(Math.cos(kth), Math.sin(kth));
y[k] = evenFFT[k].plus(wk.times(oddFFT[k]));
y[k + n / 2] = evenFFT[k].minus(wk.times(oddFFT[k]));
}
return y;
}
// compute the inverse FFT of x[], assuming its length n is a power of 2
public static Complex[] ifft(Complex[] x) {
int n = x.length;
Complex[] y = new Complex[n];
// take conjugate
for (int i = 0; i < n; i++) {
y[i] = x[i].conjugate();
}
// compute forward FFT
y = fft(y);
// take conjugate again
for (int i = 0; i < n; i++) {
y[i] = y[i].conjugate();
}
// divide by n
for (int i = 0; i < n; i++) {
y[i] = y[i].scale(1.0 / n);
}
return y;
}
// compute the circular convolution of x and y
public static Complex[] cconvolve(Complex[] x, Complex[] y) {
// should probably pad x and y with 0s so that they have same length
// and are powers of 2
if (x.length != y.length) {
throw new IllegalArgumentException("Dimensions don't agree");
}
int n = x.length;
// compute FFT of each sequence
Complex[] a = fft(x);
Complex[] b = fft(y);
// point-wise multiply
Complex[] c = new Complex[n];
for (int i = 0; i < n; i++) {
c[i] = a[i].times(b[i]);
}
// compute inverse FFT
return ifft(c);
}
// compute the linear convolution of x and y
public static Complex[] convolve(Complex[] x, Complex[] y) {
Complex ZERO = new Complex(0, 0);
Complex[] a = new Complex[2 * x.length];
for (int i = 0; i < x.length; i++) a[i] = x[i];
for (int i = x.length; i < 2 * x.length; i++) a[i] = ZERO;
Complex[] b = new Complex[2 * y.length];
for (int i = 0; i < y.length; i++) b[i] = y[i];
for (int i = y.length; i < 2 * y.length; i++) b[i] = ZERO;
return cconvolve(a, b);
}
// compute the DFT of x[] via brute force (n^2 time)
public static Complex[] dft(Complex[] x) {
int n = x.length;
Complex ZERO = new Complex(0, 0);
Complex[] y = new Complex[n];
for (int k = 0; k < n; k++) {
y[k] = ZERO;
for (int j = 0; j < n; j++) {
int power = (k * j) % n;
double kth = -2 * power * Math.PI / n;
Complex wkj = new Complex(Math.cos(kth), Math.sin(kth));
y[k] = y[k].plus(x[j].times(wkj));
}
}
return y;
}
// display an array of com.example.ueberwachungssystem.Signalverarbeitung.Complex numbers to standard output
public static void show(Complex[] x, String title) {
System.out.println(title);
System.out.println("-------------------");
for (int i = 0; i < x.length; i++) {
System.out.println(x[i]);
}
System.out.println();
}
/***************************************************************************
* Test client and sample execution
*
* % java FFT 4
* x
* -------------------
* -0.03480425839330703
* 0.07910192950176387
* 0.7233322451735928
* 0.1659819820667019
*
* y = fft(x)
* -------------------
* 0.9336118983487516
* -0.7581365035668999 + 0.08688005256493803i
* 0.44344407521182005
* -0.7581365035668999 - 0.08688005256493803i
*
* z = ifft(y)
* -------------------
* -0.03480425839330703
* 0.07910192950176387 + 2.6599344570851287E-18i
* 0.7233322451735928
* 0.1659819820667019 - 2.6599344570851287E-18i
*
* c = cconvolve(x, x)
* -------------------
* 0.5506798633981853
* 0.23461407150576394 - 4.033186818023279E-18i
* -0.016542951108772352
* 0.10288019294318276 + 4.033186818023279E-18i
*
* d = convolve(x, x)
* -------------------
* 0.001211336402308083 - 3.122502256758253E-17i
* -0.005506167987577068 - 5.058885073636224E-17i
* -0.044092969479563274 + 2.1934338938072244E-18i
* 0.10288019294318276 - 3.6147323062478115E-17i
* 0.5494685269958772 + 3.122502256758253E-17i
* 0.240120239493341 + 4.655566391833896E-17i
* 0.02755001837079092 - 2.1934338938072244E-18i
* 4.01805098805014E-17i
*
***************************************************************************/
public static void main(String[] args) {
int n = Integer.parseInt(args[0]);
Complex[] x = new Complex[n];
// original data
for (int i = 0; i < n; i++) {
x[i] = new Complex(i, 0);
}
show(x, "x");
// FFT of original data
Complex[] y = fft(x);
show(y, "y = fft(x)");
// FFT of original data
Complex[] y2 = dft(x);
show(y2, "y2 = dft(x)");
// take inverse FFT
Complex[] z = ifft(y);
show(z, "z = ifft(y)");
// circular convolution of x with itself
Complex[] c = cconvolve(x, x);
show(c, "c = cconvolve(x, x)");
// linear convolution of x with itself
Complex[] d = convolve(x, x);
show(d, "d = convolve(x, x)");
}
}
@@ -0,0 +1,61 @@
package com.example.ueberwachungssystem.logger;
import android.util.Log;
import android.widget.TextView;
import java.io.PrintWriter;
import java.io.StringWriter;
public class Logger {
private TextView textView;
private StringBuffer sb = new StringBuffer();
private String tag;
private int lengthOfLastLog = 0;
private boolean overwrite = false;
public Logger(String tag, TextView textView, String logInitText) {
this.tag = tag;
this.textView = textView;
sb.append(logInitText);
}
public void log(String s) {
overwrite = false;
Log.d(tag, s);
sb.append(s).append("\n");
if (textView != null) {
textView.setText(sb.toString());
}
}
public void overwriteLastlog(String s) {
Log.d(tag, s);
lengthOfLastLog = s.length();
if (overwrite)
{
sb.setLength(sb.length() - (lengthOfLastLog + 1));
}
sb.append(s).append("\n");
overwrite = true;
if (textView != null) {
textView.setText(sb.toString());
}
}
public void log(Exception e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
log(sw.toString());
}
public void clearLog() {
sb.setLength(0);
if (textView != null) {
textView.setText("");
}
}
public String getLoggedText() {
return sb.toString();
}
}
+43 -5
View File
@@ -1,18 +1,56 @@
<?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:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="top"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="100dp"
android:text="TextView" />
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello World!"
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="match_parent"
android:layout_height="wrap_content"
tools:srcCompat="@tools:sample/avatars" />
<ImageView
android:id="@+id/outputImageView"
android:layout_width="match_parent"
android:layout_height="1dp"
tools:srcCompat="@tools:sample/avatars" />
<com.jjoe64.graphview.GraphView
android:id="@+id/graph"
android:layout_width="match_parent"
android:layout_height="200dip"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.499" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
+2 -1
View File
@@ -18,4 +18,5 @@ android.useAndroidX=true
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
android.nonTransitiveRClass=true
android.enableJetifier=true