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显示图像的窗口的鼠标事件处理,会记录下前一次鼠标左键按下或释放的位置以及操作类型,并记录下当前鼠标移动、左键按下或释放事件的信息。
以上鼠标事件属性的记录处理都在CImgMouseEvent的方法processMouseEvent中,但processMouseEvent方法仅记录鼠标事件属性,记录后调用父对象的 parent的processMouseEvent方法实现真正的操作
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。
如果有兴趣也愿意支持老猿的读者,欢迎购买付费专栏。