openCV-Python鼠标操控实现案例

☞ ░ 前往老猿Python博文目录 ░

一、引言

OpenCV实现了鼠标回调事件可以比较方便的处理鼠标操作,通过鼠标回调函数捕获鼠标动作,通过事件类型及事件信息获取鼠标操作内容。开发者可以通过将两次鼠标动作关联实现鼠标的拖拽等操作。

二、鼠标回调函数

鼠标回调函数获取指定窗口的鼠标事件,使用前通过setMouseCallback设置鼠标回调函数。一个鼠标回调函数包含5个参数,分别是事件类型、鼠标位置x和y,标记和特定参数。下面为一个笔者实现的鼠标回调函数:

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)

三、实现一个鼠标操作类

本次实现的类 CImgMouseEvent用于OpenCV显示图像的窗口的鼠标事件处理,会记录下前一次鼠标左键按下或释放的位置以及操作类型,并记录下当前鼠标移动、左键按下或释放事件的信息。

1、CImgMouseEvent关键属性

  • mouseIsPressed:表示鼠标左键当前是否为按下状态
  • playPaused:表示当前窗口播放视频(就是连续显示视频的帧)是否暂停状态,当鼠标左键按下时,播放暂停,通过鼠标左键双击或右键点击继续播放
  • previousePos、pos:上次和本次鼠标事件的位置
  • previousEvent、event:上次和本次鼠标事件的类型
  • parent:为创建CImgMouseEvent对象的调用者,该对象必须定义一个processMouseEvent方法,用于当鼠标事件执行时的具体操
  • winName:CImgMouseEvent处理鼠标事件所属的窗口名
  • img:在窗口中当前显示的图像对象,可以通过showImg显示图像并改变winName、img

以上鼠标事件属性的记录处理都在CImgMouseEvent的方法processMouseEvent中,但processMouseEvent方法仅记录鼠标事件属性,记录后调用父对象的 parent的processMouseEvent方法实现真正的操作

2、CImgMouseEvent主要方法

  • processMouseEvent:鼠标事件回调函数调用该方法记录鼠标事件数据,并由该方法调用父对象的processMouseEvent方法实现真正的操作
  • showImg:在窗口winName中显示img图像,并设置鼠标回调函数为OnMouseEvent
  • getMouseSelectRange:获取鼠标左键按下位置到当前鼠标移动位置或左键释放位置的对应的矩形以及矩形最后位置的鼠标事件类型,如果无都有操作则返回None
  • drawRect:画下当前鼠标事件选择的矩形或参数指定的矩形,一般供父对象调用
  • drawEllipse:画下当前鼠标事件选择的矩形或参数指定的矩形的内接椭圆,一般供父对象调用

3、 CImgMouseEvent类实现代码

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()

小结

本文详细介绍了一个支持openCV-Python鼠标操控的鼠标回调函数及对应鼠标事件操作处理类,通过这个鼠标回调函数及该操作类,可以方便的在图像窗口指定位置画几何图形。

关于老猿的付费专栏

老猿的付费专栏《使用PyQt开发图形界面Python应用》专门介绍基于Python的PyQt图形界面开发基础教程,付费专栏《moviepy音视频开发专栏》详细介绍moviepy音视频剪辑合成处理的类相关方法及使用相关方法进行相关剪辑合成场景的处理,两个专栏加起来只需要19.9元,都适合有一定Python基础但无相关专利知识的小白读者学习。这2个收费专栏都有对应免费专栏,只是收费专栏的文章介绍更具体、内容更深入、案例更多。

付费专栏文章目录:《moviepy音视频开发专栏文章目录》、《使用PyQt开发图形界面Python应用专栏目录》。

关于Moviepy音视频开发的内容,请大家参考《Python音视频剪辑库MoviePy1.0.3中文教程导览及可执行工具下载》的导览式介绍。

对于缺乏Python基础的同仁,可以通过老猿的免费专栏《专栏:Python基础教程目录》从零开始学习Python。

如果有兴趣也愿意支持老猿的读者,欢迎购买付费专栏。

跟老猿学Python!

☞ ░ 前往老猿Python博文目录 ░

你可能感兴趣的:(老猿Python,python,opencv,编程语言,图形图像处理,鼠标操控)