#!/usr/bin/env -S uv run --script # /// script # requires-python = ">=3.11" # dependencies = [ # "requests", # "opencv-python", # "numpy", # "ntfy", # ] # /// import requests import cv2 import numpy as np import time # URL for the video stream stream_url = 'http://loge.local:8083/stream' public_url = "https://mail.see.unbl.ink/stream" # Load a template image of a school bus (you need to have this template) template = cv2.imread('/home/powellc/var/inbox/school_bus_template.jpg', 0) w, h = template.shape[::-1] # Open the stream using OpenCV cap = cv2.VideoCapture(stream_url) # Check if the stream opened successfully if not cap.isOpened(): print("Error: Could not open video stream.") exit() # Configure ntfy server and topic ntfy_server = "https://ntfy.unbl.ink" # Replace with your ntfy server topic = "school-bus-alert" # Replace with your desired topic # Send notification function with custom server and topic def send_ntfy_notification(title, message): # Construct the full URL with topic (this is how ntfy expects the URL) url = f"{ntfy_server}" # Payload for the notification headers = {"Title": title} payload = { "topic": topic, "message": message, } # Send the POST request to the ntfy server response = requests.post(url, json=payload, headers=headers) if response.status_code == 200: print("Notification sent successfully.") else: print(f"Failed to send notification: {response.status_code} - {response.text}") while True: # Read a frame from the stream ret, frame = cap.read() if not ret: print("Failed to capture frame.") continue # Convert the frame to grayscale for template matching gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # Perform template matching on the current frame res = cv2.matchTemplate(gray_frame, template, cv2.TM_CCOEFF_NORMED) threshold = 0.80 # Set your threshold for matching loc = np.where(res >= threshold) # If a match is found, send a notification if len(loc[0]) > 0: print("Bus detected!") # Save the frame with bounding boxes to a file in /tmp timestamp = time.strftime("%Y%m%d_%H%M") filename = f"school_bus_detection_{timestamp}.jpg" output_path = f"/media/photos/misc/school_bus/{filename}" cv2.imwrite(output_path, frame) print(f"Frame with match locations saved to {output_path}") # Send notification using ntfy (with both title and message) title = "School Bus Detected!" message = f"Check the video feed at: {public_url}\nMatched image at https://files.lab.unbl.ink/school_bus/{filename}" # Send the notification with both title and message send_ntfy_notification(title, message) # Draw bounding boxes on the detected matches for pt in zip(*loc[::-1]): # Switch x and y for rectangle drawing cv2.rectangle(frame, pt, (pt[0] + w, pt[1] + h), (0, 0, 255), 2) print("Pausing for 3 minutes to let the bus leave...") time.sleep(180) # Sleep for 3 minutes (180 seconds) else: print("No bus found, sleeping for 2 seconds") # Optional: Display the frame with the match location (for debugging) #for pt in zip(*loc[::-1]): # cv2.rectangle(frame, pt, (pt[0] + w, pt[1] + h), (0, 0, 255), 2) ## Display the frame (for debugging) #cv2.imshow('Frame', frame) time.sleep(2) # two seconds so we don't spam the camera # Break the loop on key press (e.g., 'q' to quit) if cv2.waitKey(1) & 0xFF == ord('q'): break