From e668b79dc4dc75849b2e7617dc0a0ebc50ff8b9a Mon Sep 17 00:00:00 2001
From: lauriebose <lauriebose@gmail.com>
Date: Mon, 2 Dec 2024 19:18:04 +0000
Subject: [PATCH] Added background subtraction example

---
 EXAMPLES/EX_BACKGROUND_SUBTRACTION.hpp | 248 +++++++++++++++++++++++++
 scamp5_main.cpp                        |   3 +-
 2 files changed, 250 insertions(+), 1 deletion(-)
 create mode 100644 EXAMPLES/EX_BACKGROUND_SUBTRACTION.hpp

diff --git a/EXAMPLES/EX_BACKGROUND_SUBTRACTION.hpp b/EXAMPLES/EX_BACKGROUND_SUBTRACTION.hpp
new file mode 100644
index 0000000..3abb3ac
--- /dev/null
+++ b/EXAMPLES/EX_BACKGROUND_SUBTRACTION.hpp
@@ -0,0 +1,248 @@
+#include <scamp5.hpp>
+#include "MISC/MISC_FUNCS.hpp"
+using namespace SCAMP5_PE;
+
+vs_stopwatch frame_timer;
+vs_stopwatch output_timer;
+vs_stopwatch areg_quantization_refresh_timer;
+
+const areg_t AREG_captured_image = A;
+const areg_t AREG_stored_background = B;
+const areg_t AREG_background_update_mask = C;
+const dreg_t DREG_detected_motion = S0;
+
+int main()
+{
+    vs_init();
+
+    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    //SETUP IMAGE DISPLAYS
+
+		int disp_size = 2;
+		auto display_00 = vs_gui_add_display("Captured Image",0,0,disp_size);
+		auto display_01 = vs_gui_add_display("Stored Background",0,disp_size,disp_size);
+		auto display_02 = vs_gui_add_display("Background Subtraction Result",0,disp_size*2,disp_size);
+		auto display_10 = vs_gui_add_display("Background Update Mask",disp_size,0,disp_size);
+		auto display_11 = vs_gui_add_display("Motion",disp_size,disp_size,disp_size);
+
+    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    //SETUP GUI ELEMENTS & CONTROLLABLE VARIABLES
+
+		int quantization_levels = 16;
+		vs_gui_add_slider("quantization_levels",2,32,quantization_levels,&quantization_levels);
+
+		//Each frame the background update mask is steadily increased in value according to the following variables
+		//The stored background is updated wherever this mask is high in value
+		int bg_mask_increase_value = 10;//value added to mask
+		vs_gui_add_slider("bg_mask_inc_value",0,127,bg_mask_increase_value,&bg_mask_increase_value);
+		int bg_mask_increase_interval = 250;//time in ms between each addition
+		vs_gui_add_slider("bg_mask_inc_interval",0,1000,bg_mask_increase_interval,&bg_mask_increase_interval);
+		vs_stopwatch bg_mask_update_timer;
+
+		//Threshold used to detect motion, using frame differencing (i.e. abs(current_frame-previous_frame)
+		//The background update mask is reset wherever motion is detected, stopping the background from being updated in those PEs
+		int motion_threshold = 16;
+		vs_gui_add_slider("motion_threshold",0,127,motion_threshold,&motion_threshold);
+
+		int motion_expansion = 5;
+		vs_gui_add_slider("motion_expansion",0,20,motion_expansion,&motion_expansion);
+
+		//The threshold used to detect differences between the stored background and the current frame
+		int background_sub_threshold = 32;
+		vs_gui_add_slider("background_sub_threshold",0,127,background_sub_threshold,&background_sub_threshold);
+
+		auto gui_btn_store_images = vs_gui_add_button("Set Background");
+		vs_on_gui_update(gui_btn_store_images,[&](int32_t new_value)
+		{
+			scamp5_in(AREG_background_update_mask,127); //Reset Background Mask
+	   });
+
+		int output_binary_result = 1;
+		vs_gui_add_switch("binary_result",output_binary_result == 1,&output_binary_result);
+
+		//Reset background update mask
+		scamp5_in(AREG_background_update_mask,127);
+
+    //CONTINOUS FRAME LOOP
+    while(true)
+    {
+        frame_timer.reset();//reset frame_timer
+
+    	vs_disable_frame_trigger();
+        vs_frame_loop_control();
+
+        //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+        //CAPTURE FRAME
+
+			scamp5_kernel_begin();
+				mov(E, AREG_captured_image);//store a copy previous frame
+				get_image(AREG_captured_image, F);	//get new frame
+			scamp5_kernel_end();
+
+		//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+		//DETECT MOTION BY FRAME DIFFERENCING
+
+		 	scamp5_in(D,motion_threshold);
+			scamp5_kernel_begin();
+				//Compute absolute difference between latest frame and previous frame
+				sub(F,AREG_captured_image,E);// E = difference between latest and previous frame
+				abs(E,F);// F == absolute difference between current and last frame
+
+				//Detect PEs where "motion" occurred due to absolute difference between current and last frame being above threshold
+				sub(E,E,D);
+				where(E);
+					MOV(DREG_detected_motion,FLAG);//Store mask of PEs where there was "motion"
+				all();
+			scamp5_kernel_end();
+
+			//Expand motion mask to ensure background is not updated in any PEs even near where motion is occuring
+			scamp5_kernel_begin();
+				SET(RN,RS,RE,RW);
+			scamp5_kernel_end();
+			for(int n = 0 ; n < motion_expansion; n++)
+			{
+				scamp5_kernel_begin();
+					DNEWS0(S6,DREG_detected_motion);//S6 will contain 1s at locations that S0 will expand into this step
+					OR(DREG_detected_motion,S6);//Combine expanded locations with S0 itself
+				scamp5_kernel_end();
+			}
+
+
+		//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+		//UPDATE BACKGROUND MASK
+
+			//Where motion occurred reset background update mask to minimum value
+			scamp5_in(D,-127);
+			scamp5_kernel_begin();
+				WHERE(DREG_detected_motion);
+					mov(AREG_background_update_mask,D);
+				all();
+			scamp5_kernel_end();
+
+			//If sufficient time has passed, increase the value of mask
+			if(bg_mask_update_timer.get_usec() >= bg_mask_increase_interval*1000)
+			{
+				//Reset Counter
+				bg_mask_update_timer.reset();
+
+				//Update Background Update Mask
+				scamp5_in(F,bg_mask_increase_value);
+				scamp5_kernel_begin();
+					add(AREG_background_update_mask,AREG_background_update_mask,F);
+				scamp5_kernel_end();
+			}
+
+		//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+		//UPDATE BACKGROUND, BUT ONLY WHERE BACKGROUND UPDATE MASK IS HIGH VALUE
+
+			int background_update_mask_threshold = 100;
+			scamp5_in(F,background_update_mask_threshold);//Threshold at which to perform background update
+			scamp5_kernel_begin();
+				sub(E,AREG_background_update_mask,F);
+				where(E);//Where Background Update Mask > Threshold
+					mov(AREG_stored_background,AREG_captured_image);//Update Background
+				all();
+			scamp5_kernel_end();
+
+	    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+		//REFRESH QUANTIZED AREG STORAGE, SNAPS EACH PE'S STORED VALUE TO THE NEAREST QUANTIZED VALUE TO PREVENT VALUE DECAY/DRIFT
+
+			areg_quantization_refresh_timer.reset();
+			{
+				scamp5_kernel_begin();
+					SET(S6);//Use the S6 DREG to track which PEs have been snapped to a quantization level
+				scamp5_kernel_end();
+
+				int quantization_interval = 256/(quantization_levels-1);
+				int current_value = -127;//Value of the current quantization level
+				int decision_value = current_value + quantization_interval/2;//Value to compare with data to determine if it should snap downwards to current quantization level
+
+				//Iterate through each value of quantization, from lowest to highest
+				for(int n = 0 ; n < quantization_levels-1 ;n++)
+				{
+					scamp5_in(F,decision_value);
+					scamp5_in(E,current_value);
+
+					scamp5_kernel_begin();
+						sub(F,F,AREG_stored_background);
+						where(F);//Compare stored data with decision value, FLAG == 1 in PEs with data below the decision value
+
+							AND(S5,S6,FLAG);//Get PEs whose is below the decision value, AND has not been snapped yet, these PEs will be snapped to a value now
+							NOT(S6,FLAG);//Update S6, copy the FLAG over now before the next WHERE instruction, all PEs where FLAG = 1 will have had their data snapped to a value
+
+							WHERE(S5);
+								mov(AREG_stored_background,E);//Snap the data of these PEs to the current quantization value
+						all();
+					scamp5_kernel_end();
+
+					//update current and decision values
+					current_value += quantization_interval;
+					decision_value = current_value + quantization_interval/2;
+				}
+
+				//snap all remaining PEs that have not been quantized to the max quantization value
+				scamp5_in(E,127);
+				scamp5_kernel_begin();
+					WHERE(S6);
+						mov(AREG_stored_background,E);
+					all();
+				scamp5_kernel_end();
+			}
+			int time_spent_on_areg_quantization_refresh = areg_quantization_refresh_timer.get_usec();
+
+        //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+        //COMPUTE BACKGROUND SUBTRACTION MASK
+
+			scamp5_in(F,background_sub_threshold);
+			scamp5_kernel_begin();
+				//Compute absolute difference between background and current frame
+				sub(D,AREG_captured_image,AREG_stored_background);
+				abs(E,D);
+
+				//Find where absolute difference is above threshold, and copy the current frames data into these PEs
+				sub(E,E,F);
+				where(E);
+					MOV(S5,FLAG);//Store binary background subtraction mask/result
+				all();
+			scamp5_kernel_end();
+
+		//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+		//OUTPUT IMAGES
+
+			output_timer.reset();
+
+			//display current captured frame and thresholded image
+			output_4bit_image_via_DNEWS(AREG_captured_image,display_00);
+			output_4bit_image_via_DNEWS(AREG_stored_background,display_01);
+			output_4bit_image_via_DNEWS(AREG_background_update_mask,display_10);
+			scamp5_output_image(DREG_detected_motion,display_11);
+			if(output_binary_result)
+			{
+				scamp5_output_image(S5,display_02);
+			}
+			else
+			{
+				//Generate analogue background subtraction image using the binary background subtraction mask
+				scamp5_in(E,-127);
+				scamp5_kernel_begin();
+					WHERE(S5);
+						mov(E,AREG_captured_image);
+					ALL();
+				scamp5_kernel_end();
+				output_4bit_image_via_DNEWS(E,display_02);
+			}
+
+			int output_time_microseconds = output_timer.get_usec();//get the time taken for image output
+
+	    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+		//OUTPUT TEXT INFO
+
+			int frame_time_microseconds = frame_timer.get_usec(); //get the time taken this frame
+			int max_possible_frame_rate = 1000000/frame_time_microseconds; //calculate the possible max FPS
+			int image_output_time_percentage = (output_time_microseconds*100)/frame_time_microseconds; //calculate the % of frame time which is used for image output
+			vs_post_text("time_spent_on_areg_quantization_refresh %d\n",time_spent_on_areg_quantization_refresh);
+			vs_post_text("frame time %d microseconds(%%%d image output), potential FPS ~%d \n",frame_time_microseconds,image_output_time_percentage,max_possible_frame_rate); //display this values on host
+    }
+    return 0;
+}
+
diff --git a/scamp5_main.cpp b/scamp5_main.cpp
index ca1d244..4bb5a54 100644
--- a/scamp5_main.cpp
+++ b/scamp5_main.cpp
@@ -24,7 +24,8 @@
 //#include "EXAMPLES/EX04_IMAGE_THRESHOLDING.hpp"
 //#include "EXAMPLES/EX05_AREG_NEWS.hpp"
 //#include "EXAMPLES/EX06_SIMPLE_EDGE_DETECTION.hpp"
-#include "EXAMPLES/EX07_DNEWS.hpp"
+//#include "EXAMPLES/EX07_DNEWS.hpp"
 //#include "EXAMPLES/EX08_DREG_SHIFTING.hpp"
 //#include "EXAMPLES/EX09_DREG_EXPAND_AND_ERODE.hpp"
 //#include "EXAMPLES/EX10_HALF_SCALING.hpp"
+#include "EXAMPLES/EX_BACKGROUND_SUBTRACTION.hpp"