kopia lustrzana https://github.com/alanesq/esp32cam-demo
Update motionDetect.pde
rodzic
5211f88ce1
commit
cccc2f4488
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
----------------------------------------------------
|
----------------------------------------------------
|
||||||
|
|
||||||
ESP32cam motion detection using Processing - 02Jan25
|
ESP32cam motion detection using Processing - 02Apr25
|
||||||
|
|
||||||
uses libraries OpenCV and Minim
|
uses libraries OpenCV and Minim
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
Convert jpg files to amovie: convert -delay 10 *.jpg output.mp4
|
Convert jpg files to amovie: convert -delay 10 *.jpg output.mp4
|
||||||
|
Set up individual camera settings in 'setCam()'
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -17,20 +18,28 @@
|
||||||
// ----------------------------------------------------
|
// ----------------------------------------------------
|
||||||
|
|
||||||
String myParam = "ESPCam"; // camera title
|
String myParam = "ESPCam"; // camera title
|
||||||
String imgUrl = "http://192.168.1.2/jpg" + "?image.jpg"; // url of camera
|
String imgUrl = "http://192.168.1.2/jpg" + "?image.jpg"; // url of the esp32cam
|
||||||
String imageFolder = sketchPath() + "/images/"; // folder to store images
|
String imageFolder = sketchPath() + "/images/"; // folder to store images
|
||||||
|
|
||||||
boolean soundEnabled = false; // if sound when motion detected
|
boolean showDiags = true; // show extra diagnostic information on screen
|
||||||
int triggerLevel = 20; // movement trigger level above baseline level
|
int lineSpace = 20; // line spacing for text/buttons on screen
|
||||||
int enableAdaptiveTriggerLevel = 1;// if trigger level adapts to previous movement levels (1 or 0)
|
boolean soundEnabled = false; // if sound when motion detected
|
||||||
int windowSize = 50; // Sliding window size for average calculation
|
boolean UDPenabled = false; // if sending UDP broadcasts is enabled
|
||||||
long imageAgeLimit = 7; // days to keep stored images
|
int minTriggerTime = 15; // Minimum time between repeat triggers of sound or UDP (seconds)
|
||||||
int resizeX = 160; // size of image created to motion detect
|
int maxLineTriggers = 40; // If trigger level of a horizontal line excedes this it is ignored (percentage 0-100)
|
||||||
|
int triggerLevel = 65; // movement trigger level above baseline level
|
||||||
|
int enableAdaptiveTriggerLevel = 1; // if trigger level adapts to previous movement levels (1 or 0)
|
||||||
|
int windowSize = 50; // Sliding window size for average calculation
|
||||||
|
long imageAgeLimit = 7; // days to keep stored images
|
||||||
|
int resizeX = 160; // size of image created to motion detect
|
||||||
int resizeY = 120;
|
int resizeY = 120;
|
||||||
int changeThreshold = 25; // Pixel intensity change threshold
|
int changeThreshold = 20; // Pixel intensity change threshold
|
||||||
int delay = 800; // Delay in milliseconds between draw calls (ms)
|
int delay = 800; // Delay in milliseconds between draw calls (ms)
|
||||||
float alpha = 0.1; // Smoothing factor (0.0 < alpha <= 1.0)
|
float alpha = 0.1; // Smoothing factor (0.0 < alpha <= 1.0)
|
||||||
int graphHeight = 80; // Height of the graph area
|
int graphHeight = 80; // Height of the graph area
|
||||||
|
int attemptsToTry = 5; // Max retries when capturing image
|
||||||
|
int overlayTint = 80; // how srong the overlaed movement/mask is on the image (0-255)
|
||||||
|
|
||||||
|
|
||||||
// A 4x4 movement detection mask (true = ignore area) - masked area is shown as yellow
|
// A 4x4 movement detection mask (true = ignore area) - masked area is shown as yellow
|
||||||
boolean[][] mask = {
|
boolean[][] mask = {
|
||||||
|
@ -43,17 +52,64 @@
|
||||||
|
|
||||||
// ----------------------------------------------------
|
// ----------------------------------------------------
|
||||||
|
|
||||||
|
// required to broadcast UDP
|
||||||
|
import java.net.*;
|
||||||
|
import java.io.*;
|
||||||
|
DatagramSocket socket;
|
||||||
|
InetAddress broadcastAddress;
|
||||||
|
int port = 12345;
|
||||||
|
|
||||||
// required to delete old image files
|
// required to delete old image files
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
|
// diag variables
|
||||||
|
int lineErrorCounter = 0; // for monitoring how many horizontal line rejections are occuring
|
||||||
|
int maxHLTriggers = 0; // max triggers on a horizontal line
|
||||||
|
long fameCompareTime = 0; // timing the frame compare procedure
|
||||||
|
long fameCaptureTime = 0; // timing the frame capture from URL
|
||||||
|
int TriggerCounter = 0; // trigger counter
|
||||||
|
|
||||||
// sound toggle button
|
// control buttons
|
||||||
int buttonX = 5; // X position of the button
|
int buttonWidth = 70;
|
||||||
int buttonY = 55; // Y position of the button
|
int buttonHeight = 17;
|
||||||
int buttonWidth = 75;
|
int buttonSpacing = 80; // horizontal spacing of the buttons
|
||||||
int buttonHeight = 25;
|
int buttonY = 3 * lineSpace - buttonHeight + 4; // Y position of the buttons
|
||||||
|
int soundButtonX = 5; // X position of the sound button
|
||||||
|
int UDPbuttonX = soundButtonX + buttonSpacing; // X position of the sound button
|
||||||
|
int imageButtonX = soundButtonX + buttonSpacing * 2; // X position of the save image button
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------
|
||||||
|
// camera settings
|
||||||
|
// ----------------------------------------------------
|
||||||
|
// camera selected from command line parameter
|
||||||
|
|
||||||
|
void setCam(String param) {
|
||||||
|
|
||||||
|
if (param.equals("front")) {
|
||||||
|
imgUrl = "http://192.168.1.144/jpg" + "?image.jpg";
|
||||||
|
imageFolder = sketchPath() + "/../images/front/";
|
||||||
|
// change mask
|
||||||
|
mask[3][0] = true; mask[3][1] = true; mask[3][2] = true; mask[3][3] = true;
|
||||||
|
mask[0][3] = true; mask[1][3] = true; mask[2][3] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (param.equals("side")) {
|
||||||
|
imgUrl = "http://192.168.1.192/jpg" + "?image.jpg";
|
||||||
|
imageFolder = sketchPath() + "/../images/side/";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (param.equals("back")) {
|
||||||
|
imgUrl = "http://192.168.1.222/jpg" + "?image.jpg";
|
||||||
|
imageFolder = sketchPath() + "/../images/back/";
|
||||||
|
// change mask
|
||||||
|
//mask[0][0] = true; mask[0][1] = true; mask[0][2] = true; mask[0][3] = true;
|
||||||
|
//mask[1][3] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteOldFiles(imageFolder); // delete any image files older than 2 weeks
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
PImage currentImg, previousImg, motionOverlay;
|
PImage currentImg, previousImg, motionOverlay;
|
||||||
|
@ -65,6 +121,8 @@ int movementLevel = 0; // Accumulated movement level
|
||||||
ArrayList<Integer> recentReadings = new ArrayList<Integer>(); // Store past readings
|
ArrayList<Integer> recentReadings = new ArrayList<Integer>(); // Store past readings
|
||||||
int maxReading = 0; // Maximum reading for bar graph normalization
|
int maxReading = 0; // Maximum reading for bar graph normalization
|
||||||
String lastDownloadTime = ""; // Store last image fetch time
|
String lastDownloadTime = ""; // Store last image fetch time
|
||||||
|
long lastUDPtrigger = 0; // Last time a UDP broadcast was sent
|
||||||
|
long lastSoundtrigger = 0; // Last time a sound was triggered
|
||||||
|
|
||||||
// sound
|
// sound
|
||||||
import ddf.minim.*;
|
import ddf.minim.*;
|
||||||
|
@ -77,29 +135,45 @@ AudioPlayer beep;
|
||||||
// ----------------------------------------------------
|
// ----------------------------------------------------
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
frameRate(refreshRate);
|
|
||||||
size(640, 480);
|
|
||||||
surface.setResizable(true);
|
|
||||||
|
|
||||||
// audio
|
// audio
|
||||||
minim = new Minim(this);
|
minim = new Minim(this);
|
||||||
beep = minim.loadFile("beep.wav");
|
beep = minim.loadFile("../beep.wav");
|
||||||
|
|
||||||
surface.setTitle("Motion: " + myParam);
|
// get camera from parameter
|
||||||
|
myParam = System.getenv("motionCAM");
|
||||||
|
if (myParam == null) myParam = "side"; // default camera
|
||||||
|
println(getCurrentDateTime() + "[camera] motionCAM parameter: " + myParam);
|
||||||
|
setCam(myParam); // set camera parameters
|
||||||
|
|
||||||
|
// setup display
|
||||||
|
frameRate(refreshRate);
|
||||||
|
size(640, 480);
|
||||||
|
surface.setResizable(true);
|
||||||
|
surface.setTitle("Motion: " + myParam);
|
||||||
|
|
||||||
|
println(getCurrentDateTime() + "[camera] '" + myParam + "' starting");
|
||||||
|
|
||||||
// request image from camera
|
// request image from camera
|
||||||
currentImg = requestImg(); // load first image
|
currentImg = requestImg(); // load first image
|
||||||
normalizeBrightness(currentImg);
|
normalizeBrightness(currentImg); // adjust brigtness to compensate for sudden changes in sunlight
|
||||||
if (currentImg != null) previousImg = currentImg.get();
|
if (currentImg != null) previousImg = currentImg.get(); // store this image as the reference for comparison
|
||||||
|
|
||||||
if (soundEnabled == true) {
|
if (soundEnabled == true) makeSound();
|
||||||
beep.rewind();
|
|
||||||
beep.play();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enableAdaptiveTriggerLevel == 0) saveThreshold = 0; // if adaptive trigger is disabled
|
if (enableAdaptiveTriggerLevel == 0) saveThreshold = 0; // if adaptive trigger is disabled
|
||||||
|
|
||||||
println(getCurrentDateTime() + " camera '" + myParam + "' starting");
|
// setup for UDP broadcasting
|
||||||
|
try {
|
||||||
|
socket = new DatagramSocket(null);
|
||||||
|
socket.setReuseAddress(true); // Enable reuse
|
||||||
|
socket.setBroadcast(true); // Enable broadcast
|
||||||
|
socket.bind(new InetSocketAddress(port));
|
||||||
|
broadcastAddress = InetAddress.getByName("192.168.1.255");
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
sendUDPmessage("GM:Camera " + myParam + " starting"); // send a UDP broadcast
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -116,12 +190,11 @@ void draw() {
|
||||||
// Refresh image
|
// Refresh image
|
||||||
if (currentImg != null) previousImg = currentImg.get();
|
if (currentImg != null) previousImg = currentImg.get();
|
||||||
currentImg = requestImg();
|
currentImg = requestImg();
|
||||||
normalizeBrightness(currentImg); // compensate for changes in brightness
|
normalizeBrightness(currentImg); // adjust brigtness to compensate for sudden changes in sunlight
|
||||||
|
|
||||||
if (previousImg != null && currentImg != null) {
|
if (previousImg != null && currentImg != null) {
|
||||||
|
|
||||||
MovementResult diff = compareImages(previousImg, currentImg, mask);
|
int movementLevel = compareImages(previousImg, currentImg, mask);
|
||||||
movementLevel = diff.movementLevel;
|
|
||||||
|
|
||||||
// Update graph data
|
// Update graph data
|
||||||
recentReadings.add(movementLevel);
|
recentReadings.add(movementLevel);
|
||||||
|
@ -141,27 +214,36 @@ void draw() {
|
||||||
// display text
|
// display text
|
||||||
textSize(18);
|
textSize(18);
|
||||||
textAlign(LEFT);
|
textAlign(LEFT);
|
||||||
text("Movement:" + movementLevel + " Trigger:" + (saveThreshold + triggerLevel), 10, 40);
|
text(myParam + ": " + lastDownloadTime, 10, 1 * lineSpace);
|
||||||
text(myParam + ": " + lastDownloadTime, 10, 20);
|
text("Movement: " + movementLevel + " Trigger: " + (saveThreshold + triggerLevel), 10, 2 * lineSpace);
|
||||||
|
|
||||||
|
// if extra diagnostic info display is enabled
|
||||||
|
if (showDiags == true) {
|
||||||
|
text("Line rejections: " + lineErrorCounter + " (" + maxHLTriggers + "/" + resizeX + ")", width / 2, 1 * lineSpace);
|
||||||
|
text("Triggers: " + TriggerCounter, width / 2, 2 * lineSpace);
|
||||||
|
text("Time to capture image: " + fameCaptureTime + "ms", width / 2, 3 * lineSpace);
|
||||||
|
text("Time to compare images: " + fameCompareTime + "ms", width / 2, 4 * lineSpace);
|
||||||
|
}
|
||||||
|
|
||||||
// if movement threshold exceeded
|
// if movement threshold exceeded
|
||||||
if (enableAdaptiveTriggerLevel == 1) updateThreshold(); // adapt threshold level
|
if (enableAdaptiveTriggerLevel == 1) updateThreshold(); // adapt threshold level
|
||||||
if (movementLevel > saveThreshold + triggerLevel) {
|
if (movementLevel > saveThreshold + triggerLevel) {
|
||||||
println(getCurrentDateTime() + " Movement detected (" + myParam +")");
|
println(getCurrentDateTime() + "[camera] Movement detected (" + myParam +")");
|
||||||
//currentImg.save(imageFolder + lastDownloadTime + ".jpg");
|
//currentImg.save(imageFolder + lastDownloadTime + ".jpg");
|
||||||
save(imageFolder + lastDownloadTime + ".jpg");
|
save(imageFolder + lastDownloadTime + ".jpg");
|
||||||
if (soundEnabled == true) {
|
if (soundEnabled == true) makeSound();
|
||||||
beep.rewind();
|
if (UDPenabled == true) {
|
||||||
beep.play();
|
sendUDPmessage("IN:Movement detected"); // send UDP broadcast
|
||||||
}
|
}
|
||||||
|
TriggerCounter++; // trigger counter for extra diag display
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw movement graph
|
// Draw movement graph
|
||||||
tint(255, 96);
|
tint(255, 80);
|
||||||
drawGraph();
|
drawGraph();
|
||||||
|
|
||||||
// display movement detection image on top of camera image
|
// display movement detection image on top of camera image
|
||||||
tint(255, 96);
|
tint(255, 255);
|
||||||
image(motionOverlay, 0, 0, width, height);
|
image(motionOverlay, 0, 0, width, height);
|
||||||
|
|
||||||
// delete older images once per day
|
// delete older images once per day
|
||||||
|
@ -178,24 +260,41 @@ void draw() {
|
||||||
|
|
||||||
|
|
||||||
// ----------------------------------------------------
|
// ----------------------------------------------------
|
||||||
// sound on/off toggle button
|
// sound and UDP on/off toggle buttons
|
||||||
// ----------------------------------------------------
|
// ----------------------------------------------------
|
||||||
|
|
||||||
void togButton() {
|
void togButton() {
|
||||||
// Draw the toggle button
|
// Draw the sound toggle button
|
||||||
if (soundEnabled) {
|
if (soundEnabled) {
|
||||||
fill(0, 255, 0); // Green when ON
|
fill(0, 255, 0); // Green when ON
|
||||||
} else {
|
} else {
|
||||||
fill(255, 0, 0); // Red when OFF
|
fill(255, 0, 0); // Red when OFF
|
||||||
}
|
}
|
||||||
tint(255, 64);
|
tint(255, 64);
|
||||||
rect(buttonX, buttonY, buttonWidth, buttonHeight);
|
rect(soundButtonX, buttonY, buttonWidth, buttonHeight);
|
||||||
// Draw button label
|
|
||||||
|
// Draw the UDP toggle button
|
||||||
|
if (UDPenabled) {
|
||||||
|
fill(0, 255, 0); // Green when ON
|
||||||
|
} else {
|
||||||
|
fill(255, 0, 0); // Red when OFF
|
||||||
|
}
|
||||||
|
tint(255, 64);
|
||||||
|
rect(UDPbuttonX, buttonY, buttonWidth, buttonHeight);
|
||||||
|
|
||||||
|
// Draw sound button label
|
||||||
fill(0, 0, 255); // blue
|
fill(0, 0, 255); // blue
|
||||||
tint(255, 64);
|
tint(255, 64);
|
||||||
textSize(12);
|
textSize(12);
|
||||||
textAlign(CENTER, CENTER);
|
textAlign(CENTER, CENTER);
|
||||||
text(soundEnabled ? "Sound ON" : "Sound OFF", buttonX + buttonWidth / 2, buttonY + buttonHeight / 2);
|
text(soundEnabled ? "Sound ON" : "Sound OFF", soundButtonX + buttonWidth / 2, buttonY + buttonHeight / 2);
|
||||||
|
|
||||||
|
// Draw UDP button label
|
||||||
|
fill(0, 0, 255); // blue
|
||||||
|
tint(255, 64);
|
||||||
|
textSize(12);
|
||||||
|
textAlign(CENTER, CENTER);
|
||||||
|
text(UDPenabled ? "UDP ON" : "UDP OFF", UDPbuttonX + buttonWidth / 2, buttonY + buttonHeight / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -207,20 +306,29 @@ void saveButton() {
|
||||||
// Draw the save button
|
// Draw the save button
|
||||||
fill(0, 255, 0); // Green when ON
|
fill(0, 255, 0); // Green when ON
|
||||||
tint(255, 64);
|
tint(255, 64);
|
||||||
rect(buttonX + 5 + buttonWidth, buttonY, buttonWidth, buttonHeight);
|
rect(imageButtonX, buttonY, buttonWidth, buttonHeight);
|
||||||
// Draw button label
|
// Draw button label
|
||||||
fill(0, 0, 255); // blue
|
fill(0, 0, 255); // blue
|
||||||
tint(255, 64);
|
tint(255, 64);
|
||||||
textSize(12);
|
textSize(12);
|
||||||
textAlign(CENTER, CENTER);
|
textAlign(CENTER, CENTER);
|
||||||
text("Save", 5 + buttonWidth + buttonWidth / 2 , buttonY + buttonHeight / 2);
|
text("Save", imageButtonX + buttonWidth / 2 , buttonY + buttonHeight / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------
|
||||||
|
// if mouse was clicked action buttons
|
||||||
|
// ----------------------------------------------------
|
||||||
|
|
||||||
void mousePressed() {
|
void mousePressed() {
|
||||||
if (mouseX > buttonX && mouseX < buttonX + buttonWidth && mouseY > buttonY && mouseY < buttonY + buttonHeight) {
|
if (mouseX > soundButtonX && mouseX < soundButtonX + buttonWidth && mouseY > buttonY && mouseY < buttonY + buttonHeight) {
|
||||||
soundEnabled = !soundEnabled; // Toggle the flag
|
soundEnabled = !soundEnabled; // Toggle the flag
|
||||||
}
|
}
|
||||||
if (mouseX > buttonX + buttonWidth + 5 && mouseX < buttonX + (2 * buttonWidth) + 5 && mouseY > buttonY && mouseY < buttonY + buttonHeight) {
|
if (mouseX > UDPbuttonX && mouseX < UDPbuttonX + buttonWidth && mouseY > buttonY && mouseY < buttonY + buttonHeight) {
|
||||||
|
UDPenabled = !UDPenabled; // Toggle the UDP broadcasts flag
|
||||||
|
}
|
||||||
|
if (mouseX > imageButtonX && mouseX < imageButtonX + buttonWidth && mouseY > buttonY && mouseY < buttonY + buttonHeight) {
|
||||||
save(imageFolder + lastDownloadTime + ".jpg"); // save image
|
save(imageFolder + lastDownloadTime + ".jpg"); // save image
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -231,46 +339,64 @@ void mousePressed() {
|
||||||
// ----------------------------------------------------
|
// ----------------------------------------------------
|
||||||
|
|
||||||
PImage requestImg() {
|
PImage requestImg() {
|
||||||
int attemptsToTry = 3;
|
int startTime3 = millis(); // used to time this procedure
|
||||||
int retries = attemptsToTry;
|
|
||||||
|
|
||||||
while (retries-- > 0) {
|
for (int attempt = 1; attempt <= attemptsToTry; attempt++) {
|
||||||
PImage img = null; // Always start fresh
|
PImage img = requestImage(imgUrl); // Start loading the image asynchronously
|
||||||
try {
|
int startTime2 = millis();
|
||||||
img = requestImage(imgUrl); // Attempt to load the image
|
boolean loaded = false;
|
||||||
int startTime = millis();
|
|
||||||
while (img != null && img.width < 1) { // Wait for a valid image
|
// Wait (up to 5 seconds) for the image to load without blocking the main thread
|
||||||
if (millis() - startTime >= 5000) { // Timeout
|
while (millis() - startTime2 < 5000) {
|
||||||
println(getCurrentDateTime() + " Image load timed out (" + myParam + ")");
|
if (img.width > 1 && img.height > 1) {
|
||||||
img = null; // Clear invalid image
|
loaded = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
delay(50);
|
try {
|
||||||
}
|
Thread.sleep(50);
|
||||||
if (img != null && img.width > 1 && img.height > 1) { // Successfully loaded
|
} catch (InterruptedException e) {
|
||||||
lastDownloadTime = day() + "-" + month() + "-" + year() + "--" + hour() + ":" + nf(minute(), 2) + ":" + nf(second(), 2);
|
println(getCurrentDateTime() + "[camera] '" + myParam + "' interrupted: " + e.getMessage());
|
||||||
if (retries != attemptsToTry - 1) println(getCurrentDateTime() + " camera '" + myParam + "' image captured ok");
|
return null;
|
||||||
return img;
|
}
|
||||||
} else {
|
|
||||||
println(getCurrentDateTime() + " camera '" + myParam + "' image capture failed");
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
println(getCurrentDateTime() + " camera '" + myParam + "' Exception during image request: " + e.getMessage());
|
|
||||||
}
|
|
||||||
println(myParam + " Retrying... (" + retries + " attempts left)");
|
|
||||||
delay(800); // Allow cooldown between retries
|
|
||||||
}
|
}
|
||||||
println(getCurrentDateTime() + myParam + " - Failed to fetch image after multiple attempts (" + myParam + ")");
|
|
||||||
return null;
|
if (loaded) {
|
||||||
|
lastDownloadTime = day() + "-" + month() + "-" + year() + "--" +
|
||||||
|
hour() + ":" + nf(minute(), 2) + ":" + nf(second(), 2);
|
||||||
|
if (attempt > 1)
|
||||||
|
println(getCurrentDateTime() + "[camera] '" + myParam + "' image captured ok on attempt " + attempt);
|
||||||
|
fameCaptureTime = millis() - startTime3; // store time to compare images
|
||||||
|
return img;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
println(getCurrentDateTime() + "[camera] '" + myParam + "' image capture timed out on attempt " + attempt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cooldown before retrying
|
||||||
|
try {
|
||||||
|
Thread.sleep(800);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
println(getCurrentDateTime() + "[camera] '" + myParam + "' interrupted during cooldown: " + e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println(getCurrentDateTime() + "[camera '" + myParam + "' - Failed to fetch image after " + attemptsToTry + " attempts");
|
||||||
|
exit(); // close app
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ----------------------------------------------------
|
// ----------------------------------------------------
|
||||||
// compare two images
|
// compare two images
|
||||||
// ----------------------------------------------------
|
// ----------------------------------------------------
|
||||||
// a mask in the form of a 4x4 grid can be supplied (true = ignore area)
|
// a mask in the form of a 4x4 grid can be supplied (true = ignore area)
|
||||||
|
|
||||||
MovementResult compareImages(PImage img1, PImage img2, boolean[][] mask) {
|
int compareImages(PImage img1, PImage img2, boolean[][] mask) {
|
||||||
|
long startTime4 = millis(); // used to time this procedure
|
||||||
|
maxHLTriggers = 0; // maximum triggers on a horizontal line
|
||||||
|
|
||||||
// Resize images for faster computation
|
// Resize images for faster computation
|
||||||
PImage smallImg1 = img1.get(); // Make a copy of img1
|
PImage smallImg1 = img1.get(); // Make a copy of img1
|
||||||
PImage smallImg2 = img2.get(); // Make a copy of img2
|
PImage smallImg2 = img2.get(); // Make a copy of img2
|
||||||
|
@ -280,15 +406,17 @@ MovementResult compareImages(PImage img1, PImage img2, boolean[][] mask) {
|
||||||
smallImg1.loadPixels();
|
smallImg1.loadPixels();
|
||||||
smallImg2.loadPixels();
|
smallImg2.loadPixels();
|
||||||
|
|
||||||
motionOverlay = createImage(smallImg1.width, smallImg1.height, RGB);
|
motionOverlay = createImage(smallImg1.width, smallImg1.height, ARGB);
|
||||||
motionOverlay.loadPixels();
|
motionOverlay.loadPixels();
|
||||||
|
|
||||||
int movementLevel = 0;
|
int movementLevel = 0;
|
||||||
|
|
||||||
int gridWidth = smallImg1.width / 4;
|
int gridWidth = smallImg1.width / 4;
|
||||||
int gridHeight = smallImg1.height / 4;
|
int gridHeight = smallImg1.height / 4;
|
||||||
|
int startOfLineTriggers = 0;
|
||||||
|
|
||||||
for (int y = 0; y < smallImg1.height; y++) {
|
for (int y = 0; y < smallImg1.height; y++) {
|
||||||
|
startOfLineTriggers = movementLevel; // store how many triggers at start of this horizontal line
|
||||||
for (int x = 0; x < smallImg1.width; x++) {
|
for (int x = 0; x < smallImg1.width; x++) {
|
||||||
int i = x + y * smallImg1.width;
|
int i = x + y * smallImg1.width;
|
||||||
|
|
||||||
|
@ -298,31 +426,40 @@ MovementResult compareImages(PImage img1, PImage img2, boolean[][] mask) {
|
||||||
|
|
||||||
// Check if the mask excludes this cell
|
// Check if the mask excludes this cell
|
||||||
if (mask != null && mask[gridY][gridX]) {
|
if (mask != null && mask[gridY][gridX]) {
|
||||||
//motionOverlay.pixels[i] = smallImg1.pixels[i]; // Keep original pixel
|
motionOverlay.pixels[i] = color(128, 128, 0, overlayTint); // highlight mask in yellow
|
||||||
motionOverlay.pixels[i] = color(255, 255, 0); // yellow
|
continue; // skip detection for this pixel
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float diff = brightness(smallImg1.pixels[i]) - brightness(smallImg2.pixels[i]);
|
float diff = brightness(smallImg1.pixels[i]) - brightness(smallImg2.pixels[i]);
|
||||||
if (abs(diff) > changeThreshold) {
|
if (abs(diff) > changeThreshold) { // if motion detected
|
||||||
movementLevel++;
|
movementLevel++; // increment movement level detected value
|
||||||
// Highlight motion in green
|
motionOverlay.pixels[i] = color(0, 128, 0, overlayTint); // Highlight motion in green
|
||||||
motionOverlay.pixels[i] = color(0, 255, 0);
|
|
||||||
} else {
|
} else {
|
||||||
// Keep original pixel
|
motionOverlay.pixels[i] = color(0, 0, 0, 0); // clear pixel
|
||||||
motionOverlay.pixels[i] = smallImg1.pixels[i];
|
//motionOverlay.pixels[i] = smallImg1.pixels[i]; // Keep original pixel
|
||||||
}
|
}
|
||||||
}
|
} // x
|
||||||
}
|
|
||||||
|
// if whole line is triggered then assune it is error (interference in image or whole image has changed)
|
||||||
|
int Triggers = movementLevel - startOfLineTriggers; // triggers on this line
|
||||||
|
if (Triggers > (smallImg1.width * maxLineTriggers) / 100) {
|
||||||
|
movementLevel = startOfLineTriggers; // discard this line as error
|
||||||
|
lineErrorCounter++; // diag variable for monitoring line rejections
|
||||||
|
}
|
||||||
|
if (Triggers > maxHLTriggers) maxHLTriggers = Triggers; // update diag variable maximum triggers per line
|
||||||
|
|
||||||
|
} // y
|
||||||
|
|
||||||
motionOverlay.updatePixels();
|
motionOverlay.updatePixels();
|
||||||
|
|
||||||
// Display the image with motion highlighted
|
// Display the image with motion highlighted
|
||||||
image(motionOverlay, 0, 0, width, height);
|
image(motionOverlay, 0, 0, width, height);
|
||||||
|
|
||||||
return new MovementResult(movementLevel);
|
// store time to compare images
|
||||||
}
|
fameCompareTime = millis() - startTime4;
|
||||||
|
|
||||||
|
return movementLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ----------------------------------------------------
|
// ----------------------------------------------------
|
||||||
|
@ -332,7 +469,6 @@ MovementResult compareImages(PImage img1, PImage img2, boolean[][] mask) {
|
||||||
void normalizeBrightness(PImage img) {
|
void normalizeBrightness(PImage img) {
|
||||||
|
|
||||||
if (img == null) return;
|
if (img == null) return;
|
||||||
|
|
||||||
img.loadPixels();
|
img.loadPixels();
|
||||||
float totalBrightness = 0;
|
float totalBrightness = 0;
|
||||||
int numPixels = img.pixels.length;
|
int numPixels = img.pixels.length;
|
||||||
|
@ -441,12 +577,13 @@ void updateThreshold() {
|
||||||
// ----------------------------------------------------
|
// ----------------------------------------------------
|
||||||
// delete older image files
|
// delete older image files
|
||||||
// ----------------------------------------------------
|
// ----------------------------------------------------
|
||||||
|
|
||||||
void deleteOldFiles(String folderPath) {
|
void deleteOldFiles(String folderPath) {
|
||||||
println(getCurrentDateTime() + " Deleting older image files (" + myParam + ")");
|
println(getCurrentDateTime() + "[camera] Deleting older image files (" + myParam + ")");
|
||||||
File folder = new File(folderPath);
|
File folder = new File(folderPath);
|
||||||
|
|
||||||
if (!folder.exists() || !folder.isDirectory()) {
|
if (!folder.exists() || !folder.isDirectory()) {
|
||||||
println(myParam + " - Invalid folder path: " + folderPath);
|
println("[camera]" + myParam + " - Invalid folder path: " + folderPath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -454,7 +591,7 @@ void deleteOldFiles(String folderPath) {
|
||||||
File[] files = folder.listFiles();
|
File[] files = folder.listFiles();
|
||||||
|
|
||||||
if (files == null || files.length == 0) {
|
if (files == null || files.length == 0) {
|
||||||
println(myParam + " - No files to check in: " + folderPath);
|
println("[camera]" + myParam + " - No files to check in: " + folderPath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -471,12 +608,12 @@ void deleteOldFiles(String folderPath) {
|
||||||
// If the file is older than 2 weeks, delete it
|
// If the file is older than 2 weeks, delete it
|
||||||
if (lastModified < cutoffDate) {
|
if (lastModified < cutoffDate) {
|
||||||
if (file.delete()) {
|
if (file.delete()) {
|
||||||
println(myParam + " - Deleted: " + file.getName());
|
println("[camera]" + myParam + " - Deleted: " + file.getName());
|
||||||
} else {
|
} else {
|
||||||
println(myParam + " - Failed to delete: " + file.getName());
|
println("[camera]" + myParam + " - Failed to delete: " + file.getName());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//println(myParam +" - Retained: " + file.getName());
|
//println("[camera]" + myParam +" - Retained: " + file.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -497,20 +634,44 @@ String getCurrentDateTime() {
|
||||||
int second = second();
|
int second = second();
|
||||||
|
|
||||||
// Construct the date and time string
|
// Construct the date and time string
|
||||||
String dateTime = nf(month, 2) + "/" + nf(day, 2) + "/" + year + " " + nf(hour, 2) + ":" + nf(minute, 2) + ":" + nf(second, 2);
|
String dateTime = nf(day, 2) + "/" + nf(month, 2) + "/" + year + " " + nf(hour, 2) + ":" + nf(minute, 2) + ":" + nf(second, 2);
|
||||||
|
|
||||||
return dateTime;
|
return dateTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ----------------------------------------------------
|
// ----------------------------------------------------
|
||||||
// used for comparing images
|
// send a UDP broadcast
|
||||||
// ----------------------------------------------------
|
// ----------------------------------------------------
|
||||||
class MovementResult {
|
|
||||||
int movementLevel;
|
void sendUDPmessage(String message) {
|
||||||
MovementResult(int movementLevel) {
|
if (UDPenabled == false) return;
|
||||||
this.movementLevel = movementLevel;
|
long currentTime = millis();
|
||||||
}
|
if (currentTime - lastUDPtrigger >= (minTriggerTime * 1000) || lastUDPtrigger == 0) {
|
||||||
|
lastUDPtrigger = currentTime;
|
||||||
|
byte[] buffer = message.getBytes();
|
||||||
|
DatagramPacket packet = new DatagramPacket(buffer, buffer.length, broadcastAddress, port);
|
||||||
|
try {
|
||||||
|
socket.send(packet);
|
||||||
|
println("Message sent!");
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------
|
||||||
|
// Make a sound
|
||||||
|
// ----------------------------------------------------
|
||||||
|
|
||||||
|
void makeSound() {
|
||||||
|
long currentTime = millis();
|
||||||
|
if (currentTime - lastSoundtrigger >= (minTriggerTime * 1000) || lastSoundtrigger == 0) {
|
||||||
|
beep.rewind();
|
||||||
|
beep.play();
|
||||||
|
lastSoundtrigger = currentTime;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Ładowanie…
Reference in New Issue