Search code examples
pythonopencvpynput

How can I get this program to trigger cv.waitKey() on its own in order to exit this loop?


Problem: I want the code to exit the loop once a rectangle is drawn but it's not working! I have a loop with cv.waitKey() waiting to be pressed. How can I exit the loop from some external initiator? I am building off of someone else's code btw.

I tried using pynput but for some reason it isn't working... Here is the code and I would like to exit the while loop once the rectangle is drawn on the image.

import numpy as np
import cv2 as cv
from pynput.keyboard import Key, Controller
from time import sleep



class Interface:

    def __init__(self, image):

        self.ix = 0
        self.iy = 0
        self.ix2 = 0
        self.iy2 = 0
        self.drawing = False
        self.before_rect = image.copy()
        self.image = image.copy()
        self.result_img = image.copy()

    # Adding Function Attached To Mouse Callback
    def draw(self,event,x,y,flags,params):

        # Left Mouse Button Down Pressed
        if(event==1):
            self.drawing = True
            self.ix = x
            self.iy = y
        # Moving mouse
        if(event==0):
            self.ix2 = x
            self.iy2 = y
            if self.drawing == True:
                self.image = (self.before_rect).copy()
                cv.rectangle(self.image, pt1=(self.ix, self.iy), pt2=(x, y), color=(255, 255, 255), thickness=3)
        # Left Mouse Button Up
        if(event==4):
            if self.drawing == True:
                self.ix2 = x
                self.iy2 = y
                # For Drawing Rectangle
                cv.rectangle(self.result_img, pt1=(self.ix, self.iy), pt2=(x, y), color=(255, 255, 255), thickness=3)
                self.image = (self.result_img).copy()


                '''Here is the issue!!! How do I solve it?'''

                # Doesn't do anything to exit the loop like I wanted :(
                print('pressed') # (output to terminal) -> pressed 
                keyboard = Controller()
                keyboard.press(Key.esc)
                sleep(.01)
                keyboard.release(Key.esc)

            self.drawing = False

    def get_boxes(self):

        cv.namedWindow("Window")

        # Adding Mouse CallBack Event
        cv.setMouseCallback("Window", self.draw)

        # Starting The Loop So Image Can Be Shown
        while (True):
            cv.imshow("Window", self.image)
            if cv.waitKey(5) & 0xFF == 27:
                break
        cv.destroyAllWindows()

        return


# Making The Blank Image
orig = np.zeros((512,512,3))

# Start drawing boxes!
obj = Interface(orig)
obj.get_boxes()

-it is executing the pynput keypress (I know since it prints 'pressed'), but it isn't producing any effect on the code. sad.

Thank you for the helppppp! ~(^ \_`_/ ^)~


Solution

  • Use boolean flags

    Instead of implicitly breaking the loop through key commands, I think it is better and more Pythonic to use flags, i.e. boolean variables that tell you when to run or to break a loop. This is especially easy for you since you are working with an instantiated object, that is an instance that shares the same scope among its methods (through the self parameter).

    For example, in your __init__() function, you can initialize a variable self.loop_running = True, which is defined upon instantiation.

    Subsequently, in your draw() method, set this flag to False whenever you want to trigger a loop break, example:

    def drawing(*args):
        # some code
        if self.drawing:
            self.ix2 = x
            self.iy2 = y
            # For Drawing Rectangle
            cv.rectangle(self.result_img, pt1=(self.ix, self.iy), pt2=(x, y), color=(255, 255, 255), thickness=3)
            self.image = (self.result_img).copy()
            # set flag to false ->
            self.loop_running = False
    

    Then in your final function where you call the main loop, change while True to while self.loop_running instead!:

    def get_boxes(self):
    
        cv.namedWindow("Window")
    
        # Adding Mouse CallBack Event
        cv.setMouseCallback("Window", self.draw)
    
        # Starting The Loop So Image Can Be Shown
        while self.loop_running:
            cv.imshow("Window", self.image)
        cv.destroyAllWindows()
    
        return