I have something working but something is still off.
I am able to detect motion on cam1 by comparing frame by frame by calculating the % of pixels that changed within my ROI.
Then I trigger a capture on cam2 which saves to a file.
Here’s my problem
When I create a named window with a slider to view the generated capture,
somehow I’m getting 2 windows with a working slider on the wrong window?
Here’s the main section of my code
import cv2
import os
import sys
import time
import datetime
import logging
from contextlib import redirect_stderr
from light import Light
from camera import Camera
from motion_detection import MotionDetector
import threading
title_window = None
frames = []
framenum = 0
framecnt = 0
def load_frames(video_file):
"""Loads a video file as an array of frames."""
cap = cv2.VideoCapture(video_file)
global frames, framecnt
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
frames.append(frame)
cap.release()
framecnt = len(frames)
def on_change(value):
global framenum, frames, framecnt
if value < framecnt:
cv2.imshow(title_window, frames[value])
def main():
global framenum, frames, framecnt
logging.basicConfig(filename='swing.log', format='%(asctime)s [%(threadName)s:%(thread)d] %(message)s', filemode='a', level=logging.INFO)
logger = logging.getLogger(__name__)
# Initialize lights
light = Light(port="/dev/cu.usbmodem2401", baudrate=115200)
light.status("ready")
# Initialize cameras
motion_trigger_camera = Camera(camera_index=1,fps=60.0, color_mode=cv2.COLOR_BGR2RGB, width=800, height=600, logger=logger)
motion_response_camera = Camera(camera_index=0, fps=120.0, color_mode=cv2.COLOR_BGR2GRAY, width=800, height=600, logger=logger)
# Start video streams
motion_trigger_camera.start()
motion_response_camera.start()
# Initialize motion detector
motion_detector = MotionDetector(roi=(420, 420, 300, 40), min_threshold=2.0, max_threshold=5.0, logger=logger)
def record_swing(duration,outfile):
try:
logging.info(f"Recording swing to {swing_file}")
motion_response_camera.start_recording(duration=duration, outfile=outfile)
except Exception as e:
logging.exception(f"Exception in recording thread: {e}")
def view_swing(title_window,infile):
global frames, framenum, framecnt
try:
load_frames(infile)
on_change(0)
logging.info(f"Loaded {framecnt} frames from {infile}")
cv2.namedWindow(title_window)
cv2.createTrackbar("Frame:", title_window , 0, framecnt, on_change)
while True:
cv2.imshow(title_window,frames[framenum])
key = cv2.waitKey(1)
if key == 99: # c key
break
elif key == 2: # Left arrow key
if framenum > 1:
framenum -= 1
cv2.setTrackbarPos("Frame:",title_window,framenum)
elif key == 3: # right arrow key
if framenum < (framecnt-1):
framenum += 1
cv2.setTrackbarPos("Frame:",title_window,framenum)
cv2.destroyWindow(title_window)
except Exception as e:
logging.exception(f"Exception in viewing thread: {e}")
try:
while True:
# Capture frames from both cameras
tigger_frame = motion_trigger_camera.get_frame()
swing_frame = motion_response_camera.get_frame()
# Draw ROI rectangle on the frame
x, y, w, h = motion_detector.roi
cv2.rectangle(tigger_frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
# Process the first camera frame for motion detection
if motion_detector.detect_motion(tigger_frame):
light.status("recording")
# Start recording on a different thread for 2 seconds
swing_file = "swing-" + datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + ".mp4"
recorder = threading.Thread(target=record_swing(duration=2,outfile=swing_file), daemon=False, name='Recorder')
recorder.start()
time.sleep(1)
# block the main thread a lil bit
#motion_response_camera.start_recording(duration=2,outfile=swing_file)
light.status("busy")
# show the recording
if os.path.exists(swing_file):
viewer = threading.Thread(target=view_swing(title_window=f"swing: {swing_file}",infile=swing_file), daemon=True, name='Viewer')
viewer.start()
light.status("ready")
# Display the frames (optional)
cv2.imshow('Trigger Camera', tigger_frame)
#cv2.imshow('Swing Camera', swing_frame)
# Break the loop on 'q' key press
key = cv2.waitKey(1)
if key == 113:
break
finally:
# Release cameras and close windows
motion_trigger_camera.release()
motion_response_camera.release()
light.close()
cv2.destroyAllWindows()
def redirect_to_null(error_code, error_message, func_name, file_name, line_number):
pass
if __name__ == "__main__":
with open("error.log", "w") as f:
sys.stderr = f
cv2.redirectError(redirect_to_null)
with redirect_stderr(f):
main()
And the motion_detection part to calculate the movement_percentage is:
def detect_motion(self, frame):
# Convert frame to grayscale
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# Crop the region of interest
x, y, w, h = self.roi
roi_gray = gray[y:y+h, x:x+w]
# Initialize previous_frame if it's None
if self.previous_frame is None:
self.previous_frame = roi_gray
return False
# Compute the absolute difference between the current frame and the previous frame
frame_diff = cv2.absdiff(self.previous_frame, roi_gray)
self.previous_frame = roi_gray
# Threshold the difference
_, thresh = cv2.threshold(frame_diff, 25, 255, cv2.THRESH_BINARY)
# Calculate the percentage of changed pixels
non_zero_count = cv2.countNonZero(thresh)
total_pixels = thresh.size
movement_percentage = (non_zero_count / total_pixels) * 100
if (self.min_threshold < movement_percentage < self.max_threshold):
if self.logger:
self.logger.info(f"Movement percentage: {movement_percentage:.2f}")```