I need help with this code (color detector on screen)

I’m trying to make windows program that keeps monitoring an area of choice on one of my monitors, I used deepseek to help me because I don’t no anything about programming, so it gave a code but sometime it trigger the alert and sometimes not. the idea is to look for the color when it appears it trigger the alert and then keeps monitoring for the same color again.

import cv2
import numpy as np
import pyautogui
import time
import tkinter as tk
from tkinter import Tk, Canvas, Button, messagebox, colorchooser, filedialog
import threading
import sys
import os
import pygame
from PIL import Image, ImageTk
import mss

# Initialize pygame for sound
pygame.mixer.init()
alert_sound = None

# Standard colors + custom color picker
STANDARD_COLORS = [
    "#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#FF00FF", "#00FFFF",
    "#FFA500", "#A52A2A", "#800080", "#008000", "#000080", "#800000",
    "#FF6347", "#FFD700", "#ADFF2F", "#FF4500", "#DA70D6", "#1E90FF"
]

class ColorDetectorApp:
    def __init__(self):
        self.root = tk.Tk()
        self.root.title("Advanced Color Detector")
        self.setup_ui()
        
        self.monitor = None
        self.monitor_region = None
        self.target_color = (255, 255, 255)
        self.is_monitoring = False
        self.tolerance = 30
        self.stop_monitoring = threading.Event()
        self.sound_file = None

    def setup_ui(self):
        # Main frame
        main_frame = tk.Frame(self.root, padx=10, pady=10)
        main_frame.pack(fill=tk.BOTH, expand=True)

        # Monitor selection
        tk.Button(main_frame, text="1. Select Monitor", command=self.select_monitor).grid(row=0, column=0, sticky="ew", pady=5)
        self.monitor_label = tk.Label(main_frame, text="No monitor selected")
        self.monitor_label.grid(row=0, column=1, sticky="w")

        # Area selection
        tk.Button(main_frame, text="2. Select Area", command=self.select_area).grid(row=1, column=0, sticky="ew", pady=5)
        self.area_label = tk.Label(main_frame, text="No area selected")
        self.area_label.grid(row=1, column=1, sticky="w")

        # Color selection frame
        color_frame = tk.LabelFrame(main_frame, text="3. Select Color", padx=5, pady=5)
        color_frame.grid(row=2, column=0, columnspan=2, sticky="ew", pady=5)

        # Color palette
        for i, color in enumerate(STANDARD_COLORS):
            tk.Button(color_frame, bg=color, width=3, height=1,
                     command=lambda c=color: self.set_color(c)).grid(row=i//6, column=i%6, padx=2, pady=2)

        # Custom color button
        tk.Button(color_frame, text="Custom", command=self.choose_custom_color).grid(row=3, column=4, columnspan=2, sticky="ew")

        # Color display
        self.color_display = tk.Canvas(color_frame, width=30, height=30, bg="white")
        self.color_display.grid(row=3, column=2, columnspan=2)

        # Sound selection
        tk.Button(main_frame, text="4. Select Alert Sound", command=self.select_sound).grid(row=3, column=0, sticky="ew", pady=5)
        self.sound_label = tk.Label(main_frame, text="No sound selected")
        self.sound_label.grid(row=3, column=1, sticky="w")

        # Tolerance control
        tk.Label(main_frame, text="Color Tolerance:").grid(row=4, column=0, sticky="e")
        self.tolerance_slider = tk.Scale(main_frame, from_=0, to=100, orient=tk.HORIZONTAL)
        self.tolerance_slider.set(30)
        self.tolerance_slider.grid(row=4, column=1, sticky="ew")

        # Monitor button
        self.monitor_btn = tk.Button(main_frame, text="Start Monitoring", command=self.toggle_monitoring)
        self.monitor_btn.grid(row=5, column=0, columnspan=2, pady=10)

        # Preview area
        self.preview_label = tk.Label(main_frame)
        self.preview_label.grid(row=6, column=0, columnspan=2)

        # Status bar
        self.status_var = tk.StringVar(value="Ready")
        tk.Label(main_frame, textvariable=self.status_var, relief=tk.SUNKEN).grid(row=7, column=0, columnspan=2, sticky="ew")

    def select_monitor(self):
        self.status_var.set("Select a monitor from the list")
        
        selector = tk.Toplevel(self.root)
        selector.title("Select Monitor")
        selector.attributes("-topmost", True)
        
        with mss.mss() as sct:
            monitors = sct.monitors[1:]  # Skip default monitor
            
        for i, monitor in enumerate(monitors, 1):
            frame = tk.Frame(selector)
            frame.pack(pady=5)
            
            # Capture monitor preview
            with mss.mss() as sct:
                screenshot = sct.grab(monitor)
                img = Image.frombytes("RGB", screenshot.size, screenshot.rgb)
                img.thumbnail((200, 150))
                photo = ImageTk.PhotoImage(img)
                
                label = tk.Label(frame, image=photo)
                label.image = photo
                label.pack(side=tk.LEFT)
                
                tk.Button(frame, text=f"Monitor {i}", 
                         command=lambda m=monitor: self.set_monitor(m, selector)).pack(side=tk.LEFT, padx=10)
        
        selector.protocol("WM_DELETE_WINDOW", lambda: self.on_selector_close(selector))

    def set_monitor(self, monitor, selector):
        self.monitor = monitor
        selector.destroy()
        self.monitor_label.config(text=f"Selected: {monitor['width']}x{monitor['height']}")
        self.status_var.set("Monitor selected")

    def on_selector_close(self, selector):
        selector.destroy()
        self.status_var.set("Monitor selection cancelled")

    def select_area(self):
        if not self.monitor:
            messagebox.showerror("Error", "Please select a monitor first")
            return
            
        self.status_var.set("Click and drag to select area")
        self.root.withdraw()
        
        selector = tk.Toplevel()
        selector.attributes("-fullscreen", True)
        selector.attributes("-alpha", 0.3)
        selector.attributes("-topmost", True)
        
        canvas = tk.Canvas(selector)
        canvas.pack(fill=tk.BOTH, expand=True)
        
        # Display monitor preview
        with mss.mss() as sct:
            screenshot = sct.grab(self.monitor)
            img = Image.frombytes("RGB", screenshot.size, screenshot.rgb)
            img = img.resize((self.root.winfo_screenwidth(), self.root.winfo_screenheight()))
            self.bg_img = ImageTk.PhotoImage(img)
            canvas.create_image(0, 0, anchor=tk.NW, image=self.bg_img)
        
        # Selection variables
        self.selection_start = None
        self.selection_rect = None
        
        def on_press(event):
            self.selection_start = (event.x, event.y)
            
        def on_drag(event):
            if self.selection_start:
                if self.selection_rect:
                    canvas.delete(self.selection_rect)
                x1, y1 = self.selection_start
                x2, y2 = event.x, event.y
                self.selection_rect = canvas.create_rectangle(
                    x1, y1, x2, y2, outline="red", width=2
                )
                
        def on_release(event):
            if self.selection_start:
                x1, y1 = self.selection_start
                x2, y2 = event.x, event.y
                
                # Convert to absolute coordinates
                left = min(x1, x2) + self.monitor["left"]
                top = min(y1, y2) + self.monitor["top"]
                width = abs(x2 - x1)
                height = abs(y2 - y1)
                
                self.monitor_region = {
                    "left": left,
                    "top": top,
                    "width": width,
                    "height": height
                }
                
                selector.destroy()
                self.root.deiconify()
                self.area_label.config(text=f"Selected: {width}x{height} at ({left},{top})")
                self.status_var.set("Area selected")
                self.update_preview()
        
        canvas.bind("<Button-1>", on_press)
        canvas.bind("<B1-Motion>", on_drag)
        canvas.bind("<ButtonRelease-1>", on_release)
        
        selector.protocol("WM_DELETE_WINDOW", lambda: self.on_area_selector_close(selector))

    def on_area_selector_close(self, selector):
        selector.destroy()
        self.root.deiconify()
        self.status_var.set("Area selection cancelled")

    def set_color(self, hex_color):
        rgb = tuple(int(hex_color.lstrip('#')[i:i+2], 16) for i in (0, 2, 4))
        self.target_color = rgb
        self.color_display.config(bg=hex_color)
        self.status_var.set(f"Color selected: RGB{rgb}")

    def choose_custom_color(self):
        color = colorchooser.askcolor(title="Choose Color")
        if color[0]:
            rgb = tuple(int(c) for c in color[0])
            self.target_color = rgb
            hex_color = color[1]
            self.color_display.config(bg=hex_color)
            self.status_var.set(f"Color selected: RGB{rgb}")

    def select_sound(self):
        file_path = filedialog.askopenfilename(
            title="Select Alert Sound",
            filetypes=[("Sound Files", "*.wav *.mp3 *.ogg"), ("All Files", "*.*")]
        )
        if file_path:
            try:
                global alert_sound
                alert_sound = pygame.mixer.Sound(file_path)
                self.sound_label.config(text=os.path.basename(file_path))
                self.status_var.set("Sound selected")
            except:
                messagebox.showerror("Error", "Could not load sound file")

    def update_preview(self):
        if self.monitor_region:
            with mss.mss() as sct:
                screenshot = sct.grab(self.monitor_region)
                img = Image.frombytes("RGB", screenshot.size, screenshot.rgb)
                img.thumbnail((400, 300))
                photo = ImageTk.PhotoImage(img)
                self.preview_label.config(image=photo)
                self.preview_label.image = photo

    def toggle_monitoring(self):
        if not all([self.monitor, self.monitor_region, self.target_color]):
            messagebox.showerror("Error", "Please complete all steps first")
            return
            
        self.is_monitoring = not self.is_monitoring
        
        if self.is_monitoring:
            self.stop_monitoring.clear()
            self.monitor_btn.config(text="Stop Monitoring")
            self.status_var.set("Monitoring...")
            threading.Thread(target=self.monitor_color, daemon=True).start()
        else:
            self.stop_monitoring.set()
            self.monitor_btn.config(text="Start Monitoring")
            self.status_var.set("Monitoring stopped")

    def monitor_color(self):
        while not self.stop_monitoring.is_set():
            start_time = time.time()
            
            try:
                with mss.mss() as sct:
                    screenshot = sct.grab(self.monitor_region)
                    img = np.array(screenshot, dtype=np.uint8)
                    
                    # Convert target color to BGR (OpenCV format) and ensure proper shape
                    target_bgr = np.array([self.target_color[2], self.target_color[1], self.target_color[0]], dtype=np.uint8)
                    
                    # Calculate bounds with tolerance (safe from overflow)
                    tolerance = np.clip(self.tolerance_slider.get(), 0, 255)
                    lower = np.clip(target_bgr - tolerance, 0, 255)
                    upper = np.clip(target_bgr + tolerance, 0, 255)
                    
                    # Reshape to proper dimensions for OpenCV
                    lower = lower.reshape(1, 1, 3)
                    upper = upper.reshape(1, 1, 3)
                    
                    # Create mask and check for matches
                    mask = cv2.inRange(img, lower, upper)
                    
                    if cv2.countNonZero(mask) > 0:
                        self.root.after(0, self.color_detected)
                        break
                    
                    # Calculate time to wait for 1 second interval
                    elapsed = time.time() - start_time
                    wait_time = max(0, 1.0 - elapsed)
                    time.sleep(wait_time)
                    
            except Exception as e:
                print(f"Monitoring error: {e}")
                self.root.after(0, lambda: self.status_var.set(f"Error: {str(e)}"))
                break
                
    def color_detected(self):
        if not self.is_monitoring:
            return
            
        self.is_monitoring = False
        self.stop_monitoring.set()
        self.monitor_btn.config(text="Start Monitoring")
        self.status_var.set("Color detected!")
        
        if alert_sound:
            alert_sound.play()
            
        messagebox.showinfo("Color Detected", "The target color was found in the selected area!")
        
        # Flash the preview
        self.preview_label.config(bg="red")
        self.root.after(200, lambda: self.preview_label.config(bg="SystemButtonFace"))

if __name__ == "__main__":
    app = ColorDetectorApp()
    app.root.mainloop()

This is something that the PyAutoGUI package has: the pixel(x, y)function returns the RGB color tuple of a point on the screen.

However, the bigger message I have to tell you is that you need to learn to program. I can already see a number of mistakes with this code that it’s not even worth debugging. Despite the hype, AI cannot produce working software for anything but the most trivial of examples.

3 Likes

Sorry to highjack the thread, but this makes me wonder how the news can be breathlessly reporting the demise entry level programming jobs due to AI. Here’s one example:

I’m retired from a career as a software engineer, so this doesn’t affect me directly, but I wonder about the difference between Al’s “most trivial of examples” and the reportage about the gutting of entry level coding positions. They can’t both be right. Are the news outlets just regurgitating stuff they saw on the wire?

The difference is that AI is absolute trash, but enough people THINK that it’s awesome that they’re firing humans in favour of it. Some of those entry-level jobs will have been so bland that AI really can do them, but otherwise, the companies end up paying the price elsewhere. “Hey look how fast the AI built this app! Now we just need someone to debug it.”

Unfortunately, they can. People are definitely losing their jobs (or not getting them in the first place) because of technologies that aren’t actually as good as a competent programmer.

2 Likes

I’m retired from a career as a software engineer, so this doesn’t affect me directly, but I wonder about the difference between Al’s “most trivial of examples” and the reportage about the gutting of entry level coding positions. They can’t both be right. Are the news outlets just regurgitating stuff they saw on the wire?

Any more, tech reporters are that guy on the street corner wearing a “the end is nigh!” sign around his neck, because doomsday predictions get the most eyeballs.

A discussion today with sysadmin and dev friends about “AI” agents and assisted coding reached the conclusion that it’s currently about as reliable as an unpaid college intern, just more expensive.

Also there’s the paradox that if you stop hiring entry-level programmers, how do you ever make new senior developers?

I think entry level coders are also “complete trash” (to quote @Rosuav ).

I want to be clear I blame the education system and not the people.[1]

My interpretation is that tasks that someone fresh out of university can do can also be done by chatGPT. That these were never very valuable tasks, and people were arguable being paid more than they were worth, but that the existence of chatGPT made this more explicit to the management.

It’s still worth investing in fresh programmers for society, but I can see the perspective that it’s not worth it for a lot of companies.


  1. Also I was in that position not too long ago, and I’m still not as good as I want to be. But I was fortunate to get a first job where I learned quickly. ↩︎

I’m not so sure about that; some might be, but they are much more capable of learning and improving than ChatGPT is.

2 Likes

Obviously different graduates do different courses and have different capability/experience etc. Many graduates who have covered programming in their course are capable of doing much better than ChatGPT given some time for a particular task and some supervision and feedback after which they will have learnt and improved in a way that ChatGPT will not.

The difference with ChatGPT is that it has already consumed all of the tutorials, documentation, StackOverflow etc so it can immediately produce the basic code that can get things sort of working for particularly common tasks. This is useful in various ways but you still need humans to be able to do anything with it and new graduates are likely to be experienced at precisely that although still needing supervision and feedback.

2 Likes