OpenCV-Python mouse control implementation case

☞ ░ to the old ape Python Bowen directory

I. Introduction

OpenCV implements mouse callback events, which can handle mouse operations more conveniently, capture mouse actions through mouse callback functions, and obtain mouse operation content through event types and event information. Developers can realize mouse dragging and other operations by associating two mouse actions.

2. Mouse callback function

The mouse callback function obtains the mouse events of the specified window, and sets the mouse callback function through setMouseCallback before use. A mouse callback function contains 5 parameters, which are event type, mouse position x and y, mark and specific parameters. The following is a mouse callback function implemented by the author:

def OnMouseEvent( event, x, y, flags, param):
    try:
        mouseEvent = param
        mouseEvent.processMouseEvent(event, x, y, flags)

    except Exception as e:
        print("使用回调函数OnMouseEvent的方法错误,所有使用该回调函数处理鼠标事件的对象,必须满足如下条件:")
        print("    1、必须将自身通过param传入")
        print("    2、必须定义一个processMouseEvent(self)方法来处理鼠标事件")
        print(e)

Three, implement a mouse operation class

The CImgMouseEvent class implemented this time is used for the mouse event processing of the window where OpenCV displays the image. It will record the position and operation type of the previous left mouse button press or release, and record the current mouse movement, left button press or release event Information.

1. CImgMouseEvent key attributes

  • mouseIsPressed: Indicates whether the left mouse button is currently pressed
  • playPaused: Indicates whether the current window playing video (that is, the frame of the video is continuously displayed) is paused. When the left mouse button is pressed, the playback is paused. Double-click the left mouse button or click the right mouse button to continue playing
  • previousePos, pos: the position of the last and current mouse events
  • previousEvent, event: the type of last and current mouse events
  • parent: To create the caller of the CImgMouseEvent object, the object must define a processMouseEvent method for specific operations when the mouse event is executed
  • winName: The name of the window to which CImgMouseEvent handles mouse events
  • img: The image object currently displayed in the window, you can display the image through showImg and change winName, img

The recording processing of the above mouse event attributes is in the method processMouseEvent of CImgMouseEvent, but the processMouseEvent method only records the mouse event attributes. After recording, the processMouseEvent method of the parent of the parent object is called to realize the real operation.

2. The main method of CImgMouseEvent

  • processMouseEvent: The mouse event callback function calls this method to record mouse event data, and this method calls the processMouseEvent method of the parent object to implement the real operation
  • showImg: Display the img image in the window winName, and set the mouse callback function to OnMouseEvent
  • getMouseSelectRange: Get the mouse event type corresponding to the rectangle from the left mouse button pressed position to the current mouse move position or the left button released position and the last position of the rectangle. If there is no operation, it returns None
  • drawRect: draw the rectangle selected by the current mouse event or the rectangle specified by the parameter, generally for the parent object to call
  • drawEllipse: draw the rectangle selected by the current mouse event or the inscribed ellipse of the rectangle specified by the parameter, which is generally called by the parent object

3. CImgMouseEvent class implementation code

class CImgMouseEvent():
    def __init__(self,parent,img=None,winName=None):
        self.img = img
        self.winName = winName
        self.parent = parent
        self.ignoreEvent = [cv2.EVENT_MBUTTONDOWN,cv2.EVENT_MBUTTONUP,cv2.EVENT_MBUTTONDBLCLK,cv2.EVENT_MOUSEWHEEL,cv2.EVENT_MOUSEHWHEEL] #需要忽略的鼠标事件
        self.needRecordEvent = [cv2.EVENT_MOUSEMOVE,cv2.EVENT_LBUTTONDOWN,cv2.EVENT_LBUTTONUP] #需要记录当前信息的鼠标事件
        self.windowCreated = False #窗口是否创建标记
        if img is not None:self.showImg(img,winName)
        self.open(winName)

    def open(self, winName=None):
    #初始化窗口相关属性,一般情况下此时窗口还未创建,因此鼠标回调函数设置不会执行
        if winName:
            if self.winName != winName:
                if self.winName:
                    cv2.destroyWindow(self.winName)
                    self.windowCreated = False
                self.WinName = winName

        self.mouseIsPressed = self.playPaused = False
        self.previousePos = self.pos = self.previousEvent = self.event = self.flags = self.previouseFlags = None
        if self.winName and self.windowCreated : cv2.setMouseCallback(self.winName, OnMouseEvent, self)

    def showImg(self,img,winName=None):
        """
        在窗口winName中显示img图像,并设置鼠标回调函数为OnMouseEvent
        """
        if not winName:winName = self.winName
        self.img = img
        if winName != self.winName:
            self.winName = winName
            self.open()

        if not self.windowCreated:
            self.windowCreated = True
            cv2.namedWindow(winName)#cv2.WINDOW_NORMAL| cv2.WINDOW_KEEPRATIO | cv2.WINDOW_GUI_EXPANDED
            cv2.setMouseCallback(winName, OnMouseEvent, self)
        cv2.imshow(winName, img)

    def processMouseEvent(self,event, x, y, flags):
        #鼠标回调函数调用该函数处理鼠标事件,包括记录当前事件信息、判断是否记录上次鼠标事件信息、是否暂停视频播放,调用parent.processMouseEvent() 执行响应操作
        #mouseventDict = {cv2.EVENT_MOUSEMOVE:"鼠标移动中",cv2.EVENT_LBUTTONDOWN:"鼠标左键按下",cv2.EVENT_RBUTTONDOWN:"鼠标右键按下",cv2.EVENT_MBUTTONDOWN:"鼠标中键按下",cv2.EVENT_LBUTTONUP:"鼠标左键释放",cv2.EVENT_RBUTTONUP:"鼠标右键释放",cv2.EVENT_MBUTTONUP:"鼠标中键释放",cv2.EVENT_LBUTTONDBLCLK:"鼠标左键双击",cv2.EVENT_RBUTTONDBLCLK:"鼠标右键双击",cv2.EVENT_MBUTTONDBLCLK:"鼠标中键双击",cv2.EVENT_MOUSEWHEEL:"鼠标轮上下滚动",cv2.EVENT_MOUSEHWHEEL:"鼠标轮左右滚动"}
        #print(f"processMouseEvent {mouseventDict[event]} ")

        if event in self.ignoreEvent:return
        if self.event in [cv2.EVENT_LBUTTONDOWN,cv2.EVENT_LBUTTONUP]:#当上次鼠标事件左键按下或释放时,上次信息保存
            self.previousEvent,self.previousePos,self.previouseFlags  = self.event,self.pos,self.flags

        if  event==cv2.EVENT_LBUTTONUP:
            self.mouseIsPressed = False
        elif event == cv2.EVENT_LBUTTONDOWN:
            self.mouseIsPressed = True
            self.playPaused = True
        elif  event in [cv2.EVENT_LBUTTONDBLCLK,cv2.EVENT_RBUTTONDBLCLK,cv2.EVENT_RBUTTONDOWN,cv2.EVENT_RBUTTONUP]:#鼠标右键动作、鼠标双击动作恢复视频播放
            self.playPaused = False 
        if event in self.needRecordEvent:
            self.event,self.flags,self.pos = event,flags,(x,y)

        self.parent.processMouseEvent()  #调用者对象的鼠标处理方法执行

    def getMouseSelectRange(self):
        """
        获取鼠标左键按下位置到当前鼠标移动位置或左键释放位置的对应的矩形以及矩形最后位置的鼠标事件类型
        :return: 由鼠标左键按下开始到鼠标左键释放或鼠标当前移动位置的矩形,为None表示当前没有这样的操作
        """
        if self.previousEvent is None or self.event is None:
            return None
        if (self.event!=cv2.EVENT_LBUTTONUP)  and (self.event!=cv2.EVENT_MOUSEMOVE): #最近的事件不是鼠标左键释放或鼠标移动
            return None
        if self.pos == self.previousePos:#与上次比位置没有变化
            return None
        if (self.previousEvent== cv2.EVENT_LBUTTONDOWN ) and (self.event==cv2.EVENT_LBUTTONUP): #鼠标左键按下位置到鼠标左键释放位置
            return [self.previousePos,self.pos,cv2.EVENT_LBUTTONUP]
        elif (self.previousEvent== cv2.EVENT_LBUTTONDOWN ) and (self.event==cv2.EVENT_MOUSEMOVE):#鼠标左键按下位置到鼠标当前移动位置
            return [self.previousePos, self.pos, cv2.EVENT_MOUSEMOVE]
        return None

    def drawRect(self,color,specRect=None,filled=False):
        """
        :param color: 矩形颜色
        :param specRect: 不为None画specRect指定矩形,否则根据鼠标操作来判断
        :param filled: 是画实心还是空心矩形,缺省为空心矩形
        :return: 画下的矩形,specRect不为None时是specRect指定矩形,否则根据鼠标操作来判断
        """
        if specRect:
            rect = specRect
        else:
            rect = self.getMouseSelectRange()

        if rect:
            img = self.img
            img = self.img.copy()
            if not filled:
                cv2.rectangle(img, rect[0], rect[1], color,1)
            else:
                cv2.rectangle(img, rect[0], rect[1], color,-1)
            cv2.imshow(self.winName, img)
            return rect
        else:
            return None

    def drawEllipse(self, color,specRect=None, filled=False):
        """
        :param color: 椭圆颜色
        :param specRect: 不为None画specRect指定椭圆,否则根据鼠标操作来判断
        :param filled: 是画实心还是空心椭圆,缺省为空心椭圆
        :return: 画下的椭圆对应的外接矩形,specRect不为None时是specRect指定矩形,否则根据鼠标操作来判断
        """
        if specRect:
            rect = specRect
        else:
            rect = self.getMouseSelectRange()
        if rect:
            x0, y0 = rect[0]
            x1, y1 = rect[1]
            x = int((x0+x1)/2)
            y = int((y0+y1)/2)
            axes = (int(abs(x1-x0)/2),int(abs(y1-y0)/2))
            img = self.img.copy()
            if not filled:
                cv2.ellipse(img, (x, y),axes, 0,0,360,  color,1)
            else:
                cv2.ellipse(img, (x, y),axes, 0,0,360,  color,-1)
            cv2.imshow(self.winName, img)
            return rect
        else:
            return None

    def close(self):
        cv2.destroyWindow(self.winName)
        self.windowCreated = False

    def __del__(self):
        self.close()

summary

This article introduces in detail a mouse callback function that supports openCV-Python mouse control and the corresponding mouse event operation processing class. Through this mouse callback function and this operation class, you can easily draw geometric figures at a specified position in the image window.

Paid column about the old ape

Lao Yuan’s paid column " Developing Graphical Interface Python Applications Using PyQt " specifically introduces the basic tutorials of PyQt graphical interface development based on Python, and the paid column " Moviepy Audio and Video Development Column " details the related methods and usage of moviepy audio and video editing and synthesis processing The method is used to process related editing and synthesis scenes. The two columns add up to only 19.9 yuan, which is suitable for novice readers who have a certain Python foundation but no relevant patent knowledge. These two paid columns have corresponding free columns, but the articles in the paid column are more specific, more in-depth, and more cases.

Paid column article catalog : " Moviepy audio and video development column article directory ", " Use PyQt to develop graphical interface Python application column directory ".

Regarding the content of Moviepy audio and video development, please refer to the guided introduction of " Python Audio and Video Clip Library MoviePy1.0.3 Chinese Tutorial Guide and Executable Tool Download ".

For those who lack Python foundation, you can learn Python from scratch through Lao Yuan’s free column " Column: Python Basic Tutorial Directory ".

If you are interested and willing to support the readers of Old Ape, welcome to buy paid columns.

Learn Python from the old ape!

☞ ░ to the old ape Python Bowen directory

Guess you like

Origin blog.csdn.net/LaoYuanPython/article/details/108228898